Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@
*.so
/Makefile.dep
.vscode
*.gresource
*~
test-slack-emoji
25 changes: 24 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,25 @@ C_SRCS = slack.c \
slack-api.c \
slack-object.c \
slack-json.c \
slack-emoji.c \
purple-websocket.c \
json.c

RESOURCES_XMLS = slack-emoji.gresource.xml
RESOURCES_C_SRCS = $(RESOURCES_XMLS:.gresource.xml=-resource.c)
RESOURCES_C_HDRS = $(RESOURCES_XMLS:.gresource.xml=-resource.h)

C_SRCS += $(RESOURCES_C_SRCS)

# Object file names using 'Substitution Reference'
C_OBJS = $(C_SRCS:.c=.o)

%-resource.c: %.gresource.xml %-resource.h
glib-compile-resources --internal --generate $< --target $@

%-resource.h: %.gresource.xml
glib-compile-resources --internal --generate $< --target $@

PURPLE_MOD=purple

ifeq ($(OS),Windows_NT)
Expand Down Expand Up @@ -53,7 +66,7 @@ LIBNAME=libslack.so

PLUGIN_DIR_PURPLE:=$(DESTDIR)$(shell pkg-config --variable=plugindir $(PURPLE_MOD))
DATA_ROOT_DIR_PURPLE:=$(DESTDIR)$(shell pkg-config --variable=datarootdir $(PURPLE_MOD))
PKGS=$(PURPLE_MOD) glib-2.0 gobject-2.0
PKGS=$(PURPLE_MOD) glib-2.0 gobject-2.0 gio-2.0

CFLAGS = \
-g \
Expand Down Expand Up @@ -119,4 +132,14 @@ modversion:
Makefile.dep: $(C_SRCS)
$(CC) -MM $(CFLAGS) $^ > Makefile.dep


test-slack-emoji: test-slack-emoji.c slack-emoji.o json.o slack-json.o $(RESOURCES_C_SRCS)
$(CC) -o $@ \
$(shell pkg-config --cflags --libs $(PKGS)) \
-lm $^

.PHONY: test
test: test-slack-emoji
for i in $< ; do ./$$i ; done

include Makefile.dep
213 changes: 213 additions & 0 deletions slack-emoji.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// SPDX-License-Identifier: GPL-2.0-or-later

#include <gio/gio.h>
#include <glib.h>
#include <stdio.h>

#include "slack-emoji.h"
#include "slack-json.h"

static void slack_emoji_load_json(SlackEmojiContext *ctx, json_value *emoji, gboolean skins, GString *unicode_re);

static void slack_emoji_load_json_one(SlackEmojiContext *ctx, json_value *emoji, gboolean skins, GString *unicode_re)
{
gchar *slackcode;
gchar *unicode;

if (emoji->type != json_object) {
return;
}

slackcode = json_get_prop_strptr(emoji, "name");
unicode = json_get_prop_strptr(emoji, "unicode");

g_hash_table_insert(ctx->slackcode_to_unicode,
g_strdup(slackcode),
g_strdup(unicode));
g_hash_table_insert(ctx->unicode_to_slackcode,
g_strdup(unicode),
g_strdup(slackcode));

if (unicode_re->len != 1) {
g_string_append(unicode_re, "|");
}
while (*unicode) {
gunichar c = g_utf8_get_char(unicode);
g_string_append_printf(unicode_re, "\\N{U+%04x}", c);
unicode = g_utf8_next_char(unicode);
}

if (!skins) {
json_value *skins = json_get_prop(emoji, "skinVariations");
if (skins) {
slack_emoji_load_json(ctx, skins, TRUE, unicode_re);
}
}
}

static void slack_emoji_load_json(SlackEmojiContext *ctx, json_value *emoji, gboolean skins, GString *unicode_re)
{
gsize i;
if (emoji->type != json_object) {
return;
}

for (i = 0; i < emoji->u.object.length; i++) {
slack_emoji_load_json_one(ctx,
emoji->u.object.values[i].value,
skins,
unicode_re);
}
}

SlackEmojiContext *slack_emoji_new(void)
{
GError *err = NULL;
GBytes *emojidata;
gsize emojilen;
gconstpointer emojibytes;
json_value *emoji;
SlackEmojiContext *ctx;
GString *unicode_re_match = NULL;

ctx = g_new0(SlackEmojiContext, 1);
ctx->slackcode_to_unicode = g_hash_table_new_full(g_str_hash,
g_str_equal,
g_free,
g_free);
ctx->unicode_to_slackcode = g_hash_table_new_full(g_str_hash,
g_str_equal,
g_free,
g_free);

emojidata = g_resources_lookup_data("/im/pidgin/plugins/slack-libpurple/weemoji.json",
G_RESOURCE_LOOKUP_FLAGS_NONE,
&err);
if (err) {
g_warning("Unable to load weemoji.json resource: %s", err->message);
g_error_free(err);
goto error;
}

emojibytes = g_bytes_get_data(emojidata, &emojilen);
emoji = json_parse(emojibytes, emojilen);
g_bytes_unref(emojidata);


unicode_re_match = g_string_new("(");
slack_emoji_load_json(ctx, emoji, FALSE, unicode_re_match);
g_string_append(unicode_re_match, ")");


ctx->slackcode_re = g_regex_new(":([a-z0-9_+-]+):",
G_REGEX_DEFAULT,
G_REGEX_MATCH_DEFAULT,
&err);
if (err) {
g_warning("Unable to compile emoji unicode regex: %s", err->message);
g_error_free(err);
goto error;
}

ctx->unicode_re = g_regex_new(unicode_re_match->str,
G_REGEX_DEFAULT,
G_REGEX_MATCH_DEFAULT,
&err);
if (err) {
fprintf(stderr, "Unable to compile emoji unicode regex: %s", err->message);
g_error_free(err);
goto error;
}

json_value_free(emoji);
g_string_free(unicode_re_match, TRUE);

return ctx;

error:
g_string_free(unicode_re_match, TRUE);
slack_emoji_free(ctx);
return NULL;
}

void slack_emoji_free(SlackEmojiContext *ctx)
{
if (!ctx)
return;
if (ctx->slackcode_to_unicode)
g_hash_table_destroy(ctx->slackcode_to_unicode);
if (ctx->unicode_to_slackcode)
g_hash_table_destroy(ctx->unicode_to_slackcode);
if (ctx->slackcode_re)
g_regex_unref(ctx->slackcode_re);
if (ctx->unicode_re)
g_regex_unref(ctx->unicode_re);
g_free(ctx);
}

static gboolean slack_emoji_replace(const GMatchInfo *info,
GString *res,
GHashTable *mapping,
gboolean slackcode)
{
gchar *match;
gchar *r;

match = g_match_info_fetch(info, 1);
r = g_hash_table_lookup(mapping, match);

if (r != NULL) {
if (!slackcode)
g_string_append(res, ":");
g_string_append(res, r);
if (!slackcode)
g_string_append(res, ":");
} else {
if (slackcode)
g_string_append(res, ":");
g_string_append(res, match);
if (slackcode)
g_string_append(res, ":");
}
g_free(match);

return FALSE;
}

static gboolean slack_emoji_replace_slackcode(const GMatchInfo *info,
GString *res,
gpointer data)
{
return slack_emoji_replace(info, res, data, TRUE);
}

static gboolean slack_emoji_replace_unicode(const GMatchInfo *info,
GString *res,
gpointer data)
{
return slack_emoji_replace(info, res, data, FALSE);
}

char *slack_emoji_slackcode_to_unicode(SlackEmojiContext *ctx, const char *msg)
{
return g_regex_replace_eval(ctx->slackcode_re,
msg,
-1,
0,
0,
slack_emoji_replace_slackcode,
ctx->slackcode_to_unicode,
NULL);
}

char *slack_emoji_unicode_to_slackcode(SlackEmojiContext *ctx, const char *msg)
{
return g_regex_replace_eval(ctx->unicode_re,
msg,
-1,
0,
0,
slack_emoji_replace_unicode,
ctx->unicode_to_slackcode,
NULL);
}
6 changes: 6 additions & 0 deletions slack-emoji.gresource.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<gresources>
<gresource prefix="/im/pidgin/plugins/slack-libpurple">
<file>weemoji.json</file>
</gresource>
</gresources>
18 changes: 18 additions & 0 deletions slack-emoji.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#ifndef _PURPLE_SLACK_EMOJI_H
#define _PURPLE_SLACK_EMOJI_H

#include <glib.h>

typedef struct SlackEmojiContext {
GHashTable *slackcode_to_unicode; /* char *slackcode -> char *unicode */
GHashTable *unicode_to_slackcode; /* char *unicode -> char *slackcode */
GRegex *slackcode_re;
GRegex *unicode_re;
} SlackEmojiContext;

SlackEmojiContext *slack_emoji_new();
void slack_emoji_free(SlackEmojiContext *ctx);
char *slack_emoji_slackcode_to_unicode(SlackEmojiContext *ctx, const char *msg);
char *slack_emoji_unicode_to_slackcode(SlackEmojiContext *ctx, const char *msg);

#endif
9 changes: 8 additions & 1 deletion slack-message.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "slack-conversation.h"
#include "slack-message.h"
#include "slack-thread.h"
#include "slack-emoji.h"

gchar *slack_html_to_message(SlackAccount *sa, const char *s, PurpleMessageFlags flags) {

Expand Down Expand Up @@ -71,7 +72,9 @@ gchar *slack_html_to_message(SlackAccount *sa, const char *s, PurpleMessageFlags
g_string_append_c(msg, *s++);
}

return g_string_free(msg, FALSE);
gchar *converted = slack_emoji_unicode_to_slackcode(sa->emoji_ctx, msg->str);
g_string_free(msg, TRUE);
return converted;
}

void slack_message_to_html(GString *html, SlackAccount *sa, gchar *s, PurpleMessageFlags *flags, gchar *prepend_newline_str) {
Expand All @@ -81,6 +84,8 @@ void slack_message_to_html(GString *html, SlackAccount *sa, gchar *s, PurpleMess
if (flags)
*flags |= PURPLE_MESSAGE_NO_LINKIFY;

gchar *converted = slack_emoji_slackcode_to_unicode(sa->emoji_ctx, s);
s = converted;
size_t l = strlen(s);
char *end = &s[l];

Expand Down Expand Up @@ -176,6 +181,8 @@ void slack_message_to_html(GString *html, SlackAccount *sa, gchar *s, PurpleMess
g_free(replace);
g_free(newHtml);
}

g_free(converted);
}

/*
Expand Down
4 changes: 4 additions & 0 deletions slack.c
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,8 @@ static void slack_login(PurpleAccount *account) {

sa->buddies = g_hash_table_new_full(/* slack_object_id_hash, slack_object_id_equal, */ g_str_hash, g_str_equal, NULL, NULL);

sa->emoji_ctx = slack_emoji_new();

sa->mark_list = MARK_LIST_END;

purple_connection_set_display_name(gc, account->alias ?: account->username);
Expand Down Expand Up @@ -397,6 +399,8 @@ static void slack_close(PurpleConnection *gc) {
g_hash_table_destroy(sa->user_names);
g_hash_table_destroy(sa->users);

slack_emoji_free(sa->emoji_ctx);

#if GLIB_CHECK_VERSION(2,60,0)
g_queue_clear_full(&sa->avatar_queue, g_object_unref);
#else
Expand Down
3 changes: 3 additions & 0 deletions slack.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "glibcompat.h"
#include "purple-websocket.h"
#include "slack-object.h"
#include "slack-emoji.h"

#define SLACK_PLUGIN_ID "prpl-slack"

Expand Down Expand Up @@ -48,6 +49,8 @@ typedef struct _SlackAccount {
GHashTable *buddies; /* char *slack_id -> PurpleBListNode */
gboolean roomlist_stop;

SlackEmojiContext *emoji_ctx;

guint mark_timer;
SlackObject *mark_list;

Expand Down
Loading