From 85ed0eb0d535700a5df837c37f51848811e461a0 Mon Sep 17 00:00:00 2001 From: violette Date: Thu, 18 Dec 2025 07:58:24 +0100 Subject: Added emoji reactions (contributed by violette). --- activitypub.c | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 156 insertions(+), 5 deletions(-) (limited to 'activitypub.c') diff --git a/activitypub.c b/activitypub.c index 90230d8..19f0cc6 100644 --- a/activitypub.c +++ b/activitypub.c @@ -4,6 +4,7 @@ #include "xs.h" #include "xs_json.h" #include "xs_curl.h" +#include "xs_url.h" #include "xs_mime.h" #include "xs_openssl.h" #include "xs_regex.h" @@ -1530,11 +1531,24 @@ xs_dict *msg_update(snac *snac, const xs_dict *object) xs_dict *msg_admiration(snac *snac, const char *object, const char *type) -/* creates a Like or Announce message */ +/* creates a Like, Announce or EmojiReact message */ { xs *a_msg = NULL; xs_dict *msg = NULL; xs *wrk = NULL; + char t = 0; + + switch (*type) { + case 'L': + t = 'l'; + break; + case 'A': + t = 'a'; + break; + case 'E': + t = 'e'; + break; + } /* call the object */ timeline_request(snac, &object, &wrk, 0); @@ -1542,7 +1556,7 @@ xs_dict *msg_admiration(snac *snac, const char *object, const char *type) if (valid_status(object_get(object, &a_msg))) { xs *rcpts = xs_list_new(); xs *o_md5 = xs_md5_hex(object, strlen(object)); - xs *id = xs_fmt("%s/%s/%s", snac->actor, *type == 'L' ? "l" : "a", o_md5); + xs *id = xs_fmt("%s/%c/%s", snac->actor, t, o_md5); msg = msg_base(snac, type, id, snac->actor, "@now", object); @@ -1586,6 +1600,113 @@ xs_dict *msg_repulsion(snac *user, const char *id, const char *type) return msg; } +xs_dict *msg_emoji_init(snac *snac, const char *mid, const char *eid) +/* creates an emoji reaction from a local user */ +{ + xs_dict *n_msg = msg_admiration(snac, mid, "EmojiReact"); + + eid = xs_strip_chars_i(xs_dup(eid), ":"); + xs *content = NULL; + xs *tag = xs_list_new(); + xs *dict = xs_dict_new(); + xs *icon = xs_dict_new(); + xs *accounts = xs_list_new(); + + /* may be a default emoji */ + xs *eidd = xs_dup(eid); + const char *eidda = eid; + + if (xs_is_emoji(xs_utf8_dec(&eidda))) + content = xs_dup(eid); + + else if (*eid == '%') { + content = xs_url_dec_emoji(xs_dup(eid)); + if (content == NULL) { + return NULL; + } + } + else if (xs_dict_get(emojis(), xs_fmt(":%s:", eid)) == NULL) + return NULL; + else { + content = xs_fmt(":%s:", eid); + icon = xs_dict_set(icon, "type", "Image"); + icon = xs_dict_set(icon, "url", xs_fmt("%s/s/%s.png", snac->actor, eid)); + dict = xs_dict_set(dict, "icon", icon); + + dict = xs_dict_set(dict, "id", xs_fmt("%s/s/%s.png", snac->actor, eid)); + dict = xs_dict_set(dict, "name", content); + dict = xs_dict_set(dict, "type", "Emoji"); + tag = xs_list_append(tag, dict); + } + + accounts = xs_list_append(accounts, snac->actor); + + n_msg = xs_dict_set(n_msg, "content", content); + n_msg = xs_dict_set(n_msg, "accounts", accounts); + n_msg = xs_dict_set(n_msg, "attributedTo", xs_list_get(xs_dup(xs_dict_get(n_msg, "to")), 1)); + n_msg = xs_dict_set(n_msg, "accountId", snac->uid); + n_msg = xs_dict_set(n_msg, "tag", tag); + + int ret = timeline_admire(snac, xs_dict_get(n_msg, "object"), snac->actor, 1, n_msg); + if (ret == 200 || ret == 201) { + enqueue_message(snac, n_msg); + return n_msg; + } + + return NULL; +} + +xs_dict *msg_emoji_unreact(snac *user, const char *mid, const char *eid) +/* creates an Undo + emoji reaction message */ +{ + xs *a_msg = NULL; + xs_dict *msg = NULL; + + if (valid_status(object_get(mid, &a_msg))) { + /* create a clone of the original admiration message */ + xs *object = msg_admiration(user, mid, "EmojiReact"); + + /* delete the published date */ + object = xs_dict_del(object, "published"); + + /* create an undo message for this object */ + msg = msg_undo(user, object); + + /* copy the 'to' field */ + msg = xs_dict_set(msg, "to", xs_dict_get(object, "to")); + } + + xs *emotes = object_get_emoji_reacts(mid); + const char *v; + int c = 0; + + /* may be a default emoji */ + if (strlen(eid) == 12 && *eid == '%') { + eid = xs_url_dec(eid); + if (eid == NULL) { + return NULL; + } + } + + /* lets get all emotes for this msg, and compare it to our content */ + while (xs_list_next(emotes, &v, &c)) { + xs_dict *e = NULL; + if (valid_status(object_get_by_md5(v, &e))) { + const char *content = xs_dict_get(e, "content"); + const char *id = xs_dict_get(e, "id"); + const char *actor = xs_dict_get(e, "actor"); + /* maybe formated as :{emoteName}: too */ + if (xs_str_in(eid, content) != -1) + if (strcmp(user->actor, actor) == 0) { + object_rm_emoji_react(mid, id); + return msg; + } + } + } + + return NULL; +} + xs_dict *msg_actor_place(snac *user, const char *label) /* creates a Place object, if the user has a location defined */ @@ -2605,6 +2726,16 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) else if (strcmp(type, "Undo") == 0) { /** **/ const char *id = xs_dict_get(object, "object"); + const char *content = xs_dict_get(object, "content"); + /* misskey sends emojis as like + tag */ + xs *cd = xs_dup(content); + const char *sna = cd; + const xs_dict *tag = xs_dict_get(object, "tag"); + unsigned int utf = xs_utf8_dec((const char **)&sna); + + int isEmoji = 0; + if (xs_is_emoji(utf) || (tag && xs_list_len(tag) > 0)) + isEmoji = 1; if (xs_type(object) != XSTYPE_DICT) { snac_debug(snac, 1, xs_fmt("undo: overriding utype %s | %s | %s", @@ -2633,8 +2764,19 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) else snac_log(snac, xs_fmt("error deleting follower %s", actor)); } + /* *key emojis are like w/ Emoji tag */ + else + if ((isEmoji || strcmp(utype, "EmojiReact") == 0) && + (content && strcmp(content, "♥") != 0)) { + const xs_val *mid = xs_dict_get(object, "id"); + int status = object_rm_emoji_react((char *)id, mid); + /* ensure *key notifications type */ + utype = "EmojiReact"; + + snac_log(snac, xs_fmt("Undo 'EmojiReact' for %s %d", id, status)); + } else - if (strcmp(utype, "Like") == 0 || strcmp(utype, "EmojiReact") == 0) { /** **/ + if (strcmp(utype, "Like") == 0) { /** **/ int status = object_unadmire(id, actor, 1); snac_log(snac, xs_fmt("Undo '%s' for %s %d", utype, id, status)); @@ -2771,13 +2913,22 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) } else if (strcmp(type, "Like") == 0 || strcmp(type, "EmojiReact") == 0) { /** **/ + /* misskey sends emojis as Like + tag. + * It is easier to handle them both at the same time. */ + const char *sna = xs_dict_get(msg, "content"); + const xs_dict *tag = xs_dict_get(msg, "tag"); + unsigned int utf = xs_utf8_dec((const char **)&sna); + + if (xs_is_emoji(utf) || (tag && xs_list_len(tag) > 0)) + type = "EmojiReact"; + if (xs_type(object) == XSTYPE_DICT) object = xs_dict_get(object, "id"); if (xs_is_null(object)) snac_log(snac, xs_fmt("malformed message: no 'id' field")); else - if (timeline_admire(snac, object, actor, 1) == HTTP_STATUS_CREATED) + if (timeline_admire(snac, object, actor, 1, xs_dup(msg)) == HTTP_STATUS_CREATED) snac_log(snac, xs_fmt("new '%s' %s %s", type, actor, object)); else snac_log(snac, xs_fmt("repeated '%s' from %s to %s", type, actor, object)); @@ -2818,7 +2969,7 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) xs *this_relay = xs_fmt("%s/relay", srv_baseurl); if (strcmp(actor, this_relay) != 0) { - if (valid_status(timeline_admire(snac, object, actor, 0))) + if (valid_status(timeline_admire(snac, object, actor, 0, a_msg))) snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object)); else snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s", -- cgit v1.2.3