diff options
| author | 2025-12-18 07:58:24 +0100 | |
|---|---|---|
| committer | 2025-12-18 07:58:24 +0100 | |
| commit | 85ed0eb0d535700a5df837c37f51848811e461a0 (patch) | |
| tree | 818c80cc48c445b2950796230946c63eca3a3242 /activitypub.c | |
| parent | Bumped version. (diff) | |
| download | snac2-85ed0eb0d535700a5df837c37f51848811e461a0.tar.gz snac2-85ed0eb0d535700a5df837c37f51848811e461a0.tar.xz snac2-85ed0eb0d535700a5df837c37f51848811e461a0.zip | |
Added emoji reactions (contributed by violette).
Diffstat (limited to 'activitypub.c')
| -rw-r--r-- | activitypub.c | 161 |
1 files changed, 156 insertions, 5 deletions
diff --git a/activitypub.c b/activitypub.c index 90230d8..19f0cc6 100644 --- a/activitypub.c +++ b/activitypub.c | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | #include "xs.h" | 4 | #include "xs.h" |
| 5 | #include "xs_json.h" | 5 | #include "xs_json.h" |
| 6 | #include "xs_curl.h" | 6 | #include "xs_curl.h" |
| 7 | #include "xs_url.h" | ||
| 7 | #include "xs_mime.h" | 8 | #include "xs_mime.h" |
| 8 | #include "xs_openssl.h" | 9 | #include "xs_openssl.h" |
| 9 | #include "xs_regex.h" | 10 | #include "xs_regex.h" |
| @@ -1530,11 +1531,24 @@ xs_dict *msg_update(snac *snac, const xs_dict *object) | |||
| 1530 | 1531 | ||
| 1531 | 1532 | ||
| 1532 | xs_dict *msg_admiration(snac *snac, const char *object, const char *type) | 1533 | xs_dict *msg_admiration(snac *snac, const char *object, const char *type) |
| 1533 | /* creates a Like or Announce message */ | 1534 | /* creates a Like, Announce or EmojiReact message */ |
| 1534 | { | 1535 | { |
| 1535 | xs *a_msg = NULL; | 1536 | xs *a_msg = NULL; |
| 1536 | xs_dict *msg = NULL; | 1537 | xs_dict *msg = NULL; |
| 1537 | xs *wrk = NULL; | 1538 | xs *wrk = NULL; |
| 1539 | char t = 0; | ||
| 1540 | |||
| 1541 | switch (*type) { | ||
| 1542 | case 'L': | ||
| 1543 | t = 'l'; | ||
| 1544 | break; | ||
| 1545 | case 'A': | ||
| 1546 | t = 'a'; | ||
| 1547 | break; | ||
| 1548 | case 'E': | ||
| 1549 | t = 'e'; | ||
| 1550 | break; | ||
| 1551 | } | ||
| 1538 | 1552 | ||
| 1539 | /* call the object */ | 1553 | /* call the object */ |
| 1540 | timeline_request(snac, &object, &wrk, 0); | 1554 | timeline_request(snac, &object, &wrk, 0); |
| @@ -1542,7 +1556,7 @@ xs_dict *msg_admiration(snac *snac, const char *object, const char *type) | |||
| 1542 | if (valid_status(object_get(object, &a_msg))) { | 1556 | if (valid_status(object_get(object, &a_msg))) { |
| 1543 | xs *rcpts = xs_list_new(); | 1557 | xs *rcpts = xs_list_new(); |
| 1544 | xs *o_md5 = xs_md5_hex(object, strlen(object)); | 1558 | xs *o_md5 = xs_md5_hex(object, strlen(object)); |
| 1545 | xs *id = xs_fmt("%s/%s/%s", snac->actor, *type == 'L' ? "l" : "a", o_md5); | 1559 | xs *id = xs_fmt("%s/%c/%s", snac->actor, t, o_md5); |
| 1546 | 1560 | ||
| 1547 | msg = msg_base(snac, type, id, snac->actor, "@now", object); | 1561 | msg = msg_base(snac, type, id, snac->actor, "@now", object); |
| 1548 | 1562 | ||
| @@ -1586,6 +1600,113 @@ xs_dict *msg_repulsion(snac *user, const char *id, const char *type) | |||
| 1586 | return msg; | 1600 | return msg; |
| 1587 | } | 1601 | } |
| 1588 | 1602 | ||
| 1603 | xs_dict *msg_emoji_init(snac *snac, const char *mid, const char *eid) | ||
| 1604 | /* creates an emoji reaction from a local user */ | ||
| 1605 | { | ||
| 1606 | xs_dict *n_msg = msg_admiration(snac, mid, "EmojiReact"); | ||
| 1607 | |||
| 1608 | eid = xs_strip_chars_i(xs_dup(eid), ":"); | ||
| 1609 | xs *content = NULL; | ||
| 1610 | xs *tag = xs_list_new(); | ||
| 1611 | xs *dict = xs_dict_new(); | ||
| 1612 | xs *icon = xs_dict_new(); | ||
| 1613 | xs *accounts = xs_list_new(); | ||
| 1614 | |||
| 1615 | /* may be a default emoji */ | ||
| 1616 | xs *eidd = xs_dup(eid); | ||
| 1617 | const char *eidda = eid; | ||
| 1618 | |||
| 1619 | if (xs_is_emoji(xs_utf8_dec(&eidda))) | ||
| 1620 | content = xs_dup(eid); | ||
| 1621 | |||
| 1622 | else if (*eid == '%') { | ||
| 1623 | content = xs_url_dec_emoji(xs_dup(eid)); | ||
| 1624 | if (content == NULL) { | ||
| 1625 | return NULL; | ||
| 1626 | } | ||
| 1627 | } | ||
| 1628 | else if (xs_dict_get(emojis(), xs_fmt(":%s:", eid)) == NULL) | ||
| 1629 | return NULL; | ||
| 1630 | else { | ||
| 1631 | content = xs_fmt(":%s:", eid); | ||
| 1632 | icon = xs_dict_set(icon, "type", "Image"); | ||
| 1633 | icon = xs_dict_set(icon, "url", xs_fmt("%s/s/%s.png", snac->actor, eid)); | ||
| 1634 | dict = xs_dict_set(dict, "icon", icon); | ||
| 1635 | |||
| 1636 | dict = xs_dict_set(dict, "id", xs_fmt("%s/s/%s.png", snac->actor, eid)); | ||
| 1637 | dict = xs_dict_set(dict, "name", content); | ||
| 1638 | dict = xs_dict_set(dict, "type", "Emoji"); | ||
| 1639 | tag = xs_list_append(tag, dict); | ||
| 1640 | } | ||
| 1641 | |||
| 1642 | accounts = xs_list_append(accounts, snac->actor); | ||
| 1643 | |||
| 1644 | n_msg = xs_dict_set(n_msg, "content", content); | ||
| 1645 | n_msg = xs_dict_set(n_msg, "accounts", accounts); | ||
| 1646 | n_msg = xs_dict_set(n_msg, "attributedTo", xs_list_get(xs_dup(xs_dict_get(n_msg, "to")), 1)); | ||
| 1647 | n_msg = xs_dict_set(n_msg, "accountId", snac->uid); | ||
| 1648 | n_msg = xs_dict_set(n_msg, "tag", tag); | ||
| 1649 | |||
| 1650 | int ret = timeline_admire(snac, xs_dict_get(n_msg, "object"), snac->actor, 1, n_msg); | ||
| 1651 | if (ret == 200 || ret == 201) { | ||
| 1652 | enqueue_message(snac, n_msg); | ||
| 1653 | return n_msg; | ||
| 1654 | } | ||
| 1655 | |||
| 1656 | return NULL; | ||
| 1657 | } | ||
| 1658 | |||
| 1659 | xs_dict *msg_emoji_unreact(snac *user, const char *mid, const char *eid) | ||
| 1660 | /* creates an Undo + emoji reaction message */ | ||
| 1661 | { | ||
| 1662 | xs *a_msg = NULL; | ||
| 1663 | xs_dict *msg = NULL; | ||
| 1664 | |||
| 1665 | if (valid_status(object_get(mid, &a_msg))) { | ||
| 1666 | /* create a clone of the original admiration message */ | ||
| 1667 | xs *object = msg_admiration(user, mid, "EmojiReact"); | ||
| 1668 | |||
| 1669 | /* delete the published date */ | ||
| 1670 | object = xs_dict_del(object, "published"); | ||
| 1671 | |||
| 1672 | /* create an undo message for this object */ | ||
| 1673 | msg = msg_undo(user, object); | ||
| 1674 | |||
| 1675 | /* copy the 'to' field */ | ||
| 1676 | msg = xs_dict_set(msg, "to", xs_dict_get(object, "to")); | ||
| 1677 | } | ||
| 1678 | |||
| 1679 | xs *emotes = object_get_emoji_reacts(mid); | ||
| 1680 | const char *v; | ||
| 1681 | int c = 0; | ||
| 1682 | |||
| 1683 | /* may be a default emoji */ | ||
| 1684 | if (strlen(eid) == 12 && *eid == '%') { | ||
| 1685 | eid = xs_url_dec(eid); | ||
| 1686 | if (eid == NULL) { | ||
| 1687 | return NULL; | ||
| 1688 | } | ||
| 1689 | } | ||
| 1690 | |||
| 1691 | /* lets get all emotes for this msg, and compare it to our content */ | ||
| 1692 | while (xs_list_next(emotes, &v, &c)) { | ||
| 1693 | xs_dict *e = NULL; | ||
| 1694 | if (valid_status(object_get_by_md5(v, &e))) { | ||
| 1695 | const char *content = xs_dict_get(e, "content"); | ||
| 1696 | const char *id = xs_dict_get(e, "id"); | ||
| 1697 | const char *actor = xs_dict_get(e, "actor"); | ||
| 1698 | /* maybe formated as :{emoteName}: too */ | ||
| 1699 | if (xs_str_in(eid, content) != -1) | ||
| 1700 | if (strcmp(user->actor, actor) == 0) { | ||
| 1701 | object_rm_emoji_react(mid, id); | ||
| 1702 | return msg; | ||
| 1703 | } | ||
| 1704 | } | ||
| 1705 | } | ||
| 1706 | |||
| 1707 | return NULL; | ||
| 1708 | } | ||
| 1709 | |||
| 1589 | 1710 | ||
| 1590 | xs_dict *msg_actor_place(snac *user, const char *label) | 1711 | xs_dict *msg_actor_place(snac *user, const char *label) |
| 1591 | /* creates a Place object, if the user has a location defined */ | 1712 | /* 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) | |||
| 2605 | else | 2726 | else |
| 2606 | if (strcmp(type, "Undo") == 0) { /** **/ | 2727 | if (strcmp(type, "Undo") == 0) { /** **/ |
| 2607 | const char *id = xs_dict_get(object, "object"); | 2728 | const char *id = xs_dict_get(object, "object"); |
| 2729 | const char *content = xs_dict_get(object, "content"); | ||
| 2730 | /* misskey sends emojis as like + tag */ | ||
| 2731 | xs *cd = xs_dup(content); | ||
| 2732 | const char *sna = cd; | ||
| 2733 | const xs_dict *tag = xs_dict_get(object, "tag"); | ||
| 2734 | unsigned int utf = xs_utf8_dec((const char **)&sna); | ||
| 2735 | |||
| 2736 | int isEmoji = 0; | ||
| 2737 | if (xs_is_emoji(utf) || (tag && xs_list_len(tag) > 0)) | ||
| 2738 | isEmoji = 1; | ||
| 2608 | 2739 | ||
| 2609 | if (xs_type(object) != XSTYPE_DICT) { | 2740 | if (xs_type(object) != XSTYPE_DICT) { |
| 2610 | snac_debug(snac, 1, xs_fmt("undo: overriding utype %s | %s | %s", | 2741 | 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) | |||
| 2633 | else | 2764 | else |
| 2634 | snac_log(snac, xs_fmt("error deleting follower %s", actor)); | 2765 | snac_log(snac, xs_fmt("error deleting follower %s", actor)); |
| 2635 | } | 2766 | } |
| 2767 | /* *key emojis are like w/ Emoji tag */ | ||
| 2768 | else | ||
| 2769 | if ((isEmoji || strcmp(utype, "EmojiReact") == 0) && | ||
| 2770 | (content && strcmp(content, "♥") != 0)) { | ||
| 2771 | const xs_val *mid = xs_dict_get(object, "id"); | ||
| 2772 | int status = object_rm_emoji_react((char *)id, mid); | ||
| 2773 | /* ensure *key notifications type */ | ||
| 2774 | utype = "EmojiReact"; | ||
| 2775 | |||
| 2776 | snac_log(snac, xs_fmt("Undo 'EmojiReact' for %s %d", id, status)); | ||
| 2777 | } | ||
| 2636 | else | 2778 | else |
| 2637 | if (strcmp(utype, "Like") == 0 || strcmp(utype, "EmojiReact") == 0) { /** **/ | 2779 | if (strcmp(utype, "Like") == 0) { /** **/ |
| 2638 | int status = object_unadmire(id, actor, 1); | 2780 | int status = object_unadmire(id, actor, 1); |
| 2639 | 2781 | ||
| 2640 | snac_log(snac, xs_fmt("Undo '%s' for %s %d", utype, id, status)); | 2782 | 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) | |||
| 2771 | } | 2913 | } |
| 2772 | else | 2914 | else |
| 2773 | if (strcmp(type, "Like") == 0 || strcmp(type, "EmojiReact") == 0) { /** **/ | 2915 | if (strcmp(type, "Like") == 0 || strcmp(type, "EmojiReact") == 0) { /** **/ |
| 2916 | /* misskey sends emojis as Like + tag. | ||
| 2917 | * It is easier to handle them both at the same time. */ | ||
| 2918 | const char *sna = xs_dict_get(msg, "content"); | ||
| 2919 | const xs_dict *tag = xs_dict_get(msg, "tag"); | ||
| 2920 | unsigned int utf = xs_utf8_dec((const char **)&sna); | ||
| 2921 | |||
| 2922 | if (xs_is_emoji(utf) || (tag && xs_list_len(tag) > 0)) | ||
| 2923 | type = "EmojiReact"; | ||
| 2924 | |||
| 2774 | if (xs_type(object) == XSTYPE_DICT) | 2925 | if (xs_type(object) == XSTYPE_DICT) |
| 2775 | object = xs_dict_get(object, "id"); | 2926 | object = xs_dict_get(object, "id"); |
| 2776 | 2927 | ||
| 2777 | if (xs_is_null(object)) | 2928 | if (xs_is_null(object)) |
| 2778 | snac_log(snac, xs_fmt("malformed message: no 'id' field")); | 2929 | snac_log(snac, xs_fmt("malformed message: no 'id' field")); |
| 2779 | else | 2930 | else |
| 2780 | if (timeline_admire(snac, object, actor, 1) == HTTP_STATUS_CREATED) | 2931 | if (timeline_admire(snac, object, actor, 1, xs_dup(msg)) == HTTP_STATUS_CREATED) |
| 2781 | snac_log(snac, xs_fmt("new '%s' %s %s", type, actor, object)); | 2932 | snac_log(snac, xs_fmt("new '%s' %s %s", type, actor, object)); |
| 2782 | else | 2933 | else |
| 2783 | snac_log(snac, xs_fmt("repeated '%s' from %s to %s", type, actor, object)); | 2934 | 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) | |||
| 2818 | xs *this_relay = xs_fmt("%s/relay", srv_baseurl); | 2969 | xs *this_relay = xs_fmt("%s/relay", srv_baseurl); |
| 2819 | 2970 | ||
| 2820 | if (strcmp(actor, this_relay) != 0) { | 2971 | if (strcmp(actor, this_relay) != 0) { |
| 2821 | if (valid_status(timeline_admire(snac, object, actor, 0))) | 2972 | if (valid_status(timeline_admire(snac, object, actor, 0, a_msg))) |
| 2822 | snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object)); | 2973 | snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object)); |
| 2823 | else | 2974 | else |
| 2824 | snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s", | 2975 | snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s", |