summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar violette2025-12-18 07:58:24 +0100
committerGravatar grunfink2025-12-18 07:58:24 +0100
commit85ed0eb0d535700a5df837c37f51848811e461a0 (patch)
tree818c80cc48c445b2950796230946c63eca3a3242
parentBumped version. (diff)
downloadsnac2-85ed0eb0d535700a5df837c37f51848811e461a0.tar.gz
snac2-85ed0eb0d535700a5df837c37f51848811e461a0.tar.xz
snac2-85ed0eb0d535700a5df837c37f51848811e461a0.zip
Added emoji reactions (contributed by violette).
-rw-r--r--activitypub.c161
-rw-r--r--data.c124
-rw-r--r--format.c3
-rw-r--r--html.c317
-rw-r--r--main.c2
-rw-r--r--mastoapi.c160
-rw-r--r--snac.h10
-rw-r--r--xs_unicode.h7
-rw-r--r--xs_url.h33
9 files changed, 786 insertions, 31 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
1532xs_dict *msg_admiration(snac *snac, const char *object, const char *type) 1533xs_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
1603xs_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
1659xs_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
1590xs_dict *msg_actor_place(snac *user, const char *label) 1711xs_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",
diff --git a/data.c b/data.c
index 688d2e3..07eea3c 100644
--- a/data.c
+++ b/data.c
@@ -1043,6 +1043,14 @@ xs_list *object_children(const char *id)
1043} 1043}
1044 1044
1045 1045
1046xs_list *object_get_emoji_reacts(const char *id)
1047/* returns the list of an object's emoji reactions */
1048{
1049 xs *fn = _object_index_fn(id, "_e.idx");
1050 return index_list(fn, XS_ALL);
1051}
1052
1053
1046xs_list *object_likes(const char *id) 1054xs_list *object_likes(const char *id)
1047{ 1055{
1048 xs *fn = _object_index_fn(id, "_l.idx"); 1056 xs *fn = _object_index_fn(id, "_l.idx");
@@ -1086,12 +1094,26 @@ int object_admire(const char *id, const char *actor, int like)
1086 1094
1087 1095
1088int object_unadmire(const char *id, const char *actor, int like) 1096int object_unadmire(const char *id, const char *actor, int like)
1089/* actor no longer likes or announces this object */ 1097/* actor retrives their likes, announces or emojis this object */
1090{ 1098{
1099 switch (like) {
1100 case 0:
1101 like = 'a';
1102 break;
1103 case 1:
1104 like = 'l';
1105 break;
1106 case 2:
1107 like = 'e';
1108 break;
1109 }
1091 int status; 1110 int status;
1092 xs *fn = _object_fn(id); 1111 xs *fn = _object_fn(id);
1093 1112
1094 fn = xs_replace_i(fn, ".json", like ? "_l.idx" : "_a.idx"); 1113 char sfx[7] = "_x.idx";
1114 sfx[1] = like;
1115
1116 fn = xs_replace_i(fn, ".json", sfx);
1095 1117
1096 status = index_del(fn, actor); 1118 status = index_del(fn, actor);
1097 1119
@@ -1099,7 +1121,46 @@ int object_unadmire(const char *id, const char *actor, int like)
1099 index_gc(fn); 1121 index_gc(fn);
1100 1122
1101 srv_debug(0, 1123 srv_debug(0,
1102 xs_fmt("object_unadmire (%s) %s %s %d", like ? "Like" : "Announce", actor, fn, status)); 1124 xs_fmt("object_unadmire (%s) %s %s %d", like >= 'e' ?
1125 (like == 'l' ? "Like" : "EmojiReact") : "Announce" , actor, fn, status));
1126
1127 return status;
1128}
1129
1130int object_emoji_react(const char *mid, const char *eid)
1131/* actor reacts w/ an emoji */
1132{
1133 int status = HTTP_STATUS_OK;
1134 xs *fn = _object_fn(mid);
1135
1136 fn = xs_replace_i(fn, ".json", "_e.idx");
1137
1138 if (!index_in(fn, eid)) {
1139 status = index_add(fn, eid);
1140
1141 srv_debug(1, xs_fmt("object_emoji_react (%s) added %s to %s", "EmojiReact", eid, fn));
1142 }
1143
1144 return status;
1145}
1146
1147
1148int object_rm_emoji_react(const char *mid, const char *eid)
1149/* actor retrives their emoji reaction */
1150{
1151 int status;
1152 xs *fn = _object_fn(mid);
1153
1154 fn = xs_replace_i(fn, ".json", "_e.idx");
1155
1156 status = index_del(fn, eid);
1157 object_del(eid);
1158
1159 if (valid_status(status))
1160 index_gc(fn);
1161
1162 srv_debug(0,
1163 xs_fmt("object_unadmire (EmojiReact) %s %s %d", eid, fn, status));
1103 1164
1104 return status; 1165 return status;
1105} 1166}
@@ -1506,19 +1567,47 @@ int timeline_add(snac *snac, const char *id, const xs_dict *o_msg)
1506} 1567}
1507 1568
1508 1569
1509int timeline_admire(snac *snac, const char *id, const char *admirer, int like) 1570int timeline_emoji_react(const char *act, const char *id, xs_dict *msg)
1510/* updates a timeline entry with a new admiration */ 1571/* adds an emoji reaction to a message */
1572{
1573 msg = xs_dict_append(msg, "attributedTo", act);
1574 msg = xs_dict_set(msg, "type", "EmojiReact");
1575 const char *emote_id = xs_dict_get(msg, "id");
1576
1577 int ret = object_add(emote_id, msg);
1578 if (ret == HTTP_STATUS_OK || ret == HTTP_STATUS_CREATED)
1579 ret = object_emoji_react(id, emote_id);
1580
1581 return ret;
1582}
1583
1584
1585int timeline_admire(snac *snac, const char *id,
1586 const char *admirer, int like, xs_dict *msg)
1587/* updates a timeline entry with a new admiration or emoji reaction */
1511{ 1588{
1589 int ret;
1590 const char *content = xs_dict_get_path(msg, "content");
1591 const char *type = xs_dict_get_path(msg, "type");
1592
1512 /* if we are admiring this, add to both timelines */ 1593 /* if we are admiring this, add to both timelines */
1513 if (!like && strcmp(admirer, snac->actor) == 0) { 1594 if (!like && strcmp(admirer, snac->actor) == 0) {
1514 object_user_cache_add(snac, id, "public"); 1595 object_user_cache_add(snac, id, "public");
1515 object_user_cache_add(snac, id, "private"); 1596 object_user_cache_add(snac, id, "private");
1516 } 1597 }
1517 1598
1518 int ret = object_admire(id, admirer, like); 1599 /* use utf <3 as a like, as it is ugly */
1600 if (type && xs_match(type, "Like|EmojiReact|Emoji") &&
1601 content && strcmp(content, "❤") != 0) {
1602 ret = timeline_emoji_react(xs_dup(snac->actor), id, xs_dup(msg));
1603 snac_debug(snac, 1, xs_fmt("timeline_emoji_react %s", id));
1604 }
1519 1605
1520 snac_debug(snac, 1, xs_fmt("timeline_admire (%s) %s %s", 1606 else {
1521 like ? "Like" : "Announce", id, admirer)); 1607 ret = object_admire(id, admirer, like);
1608 snac_debug(snac, 1, xs_fmt("timeline_admire (%s) %s %s",
1609 like ? "Like" : "Announce", id, admirer));
1610 }
1522 1611
1523 return ret; 1612 return ret;
1524} 1613}
@@ -1867,6 +1956,25 @@ xs_list *muted_list(snac *user)
1867 return l; 1956 return l;
1868} 1957}
1869 1958
1959/** emojis react **/
1960
1961const xs_str *emoji_reacted(snac *user, const char *id)
1962/* returns the emoji an user reacted to a message */
1963{
1964 xs *emojis = object_get_emoji_reacts(id);
1965 int c = 0;
1966 const char *v;
1967 xs_dict *msg;
1968
1969 while (xs_list_next(emojis, &v, &c)) {
1970 if (object_get_by_md5(v, &msg)) {
1971 const xs_val *act = xs_dict_get(msg, "actor");
1972 if (act && strcmp(act, user->actor) == 0)
1973 return xs_dict_get(msg, "content");
1974 }
1975 }
1976 return NULL;
1977}
1870 1978
1871/** bookmarking **/ 1979/** bookmarking **/
1872 1980
diff --git a/format.c b/format.c
index 84c634d..4f93b7b 100644
--- a/format.c
+++ b/format.c
@@ -459,6 +459,9 @@ xs_str *sanitize(const char *content)
459 char *p; 459 char *p;
460 const char *v; 460 const char *v;
461 461
462 if (!content)
463 return NULL;
464
462 sl = xs_regex_split(content, "</?[^>]+>"); 465 sl = xs_regex_split(content, "</?[^>]+>");
463 466
464 p = sl; 467 p = sl;
diff --git a/html.c b/html.c
index 8f7c4a9..8cc0067 100644
--- a/html.c
+++ b/html.c
@@ -54,9 +54,10 @@ int login(snac *user, const xs_dict *headers)
54 return logged_in; 54 return logged_in;
55} 55}
56 56
57 57xs_str *_replace_shortnames(xs_str *s, const xs_list *tag, int ems,
58xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *proxy) 58 const char *proxy, const xs_list *cl, const char *act)
59/* replaces all the :shortnames: with the emojis in tag */ 59/* replace but also adds a class list and an actor in its alt text.
60 * Used for emoji reactions */
60{ 61{
61 if (!xs_is_null(tag)) { 62 if (!xs_is_null(tag)) {
62 xs *tag_list = NULL; 63 xs *tag_list = NULL;
@@ -69,11 +70,15 @@ xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *p
69 tag_list = xs_dup(tag); 70 tag_list = xs_dup(tag);
70 } 71 }
71 72
72 xs *style = xs_fmt("height: %dem; width: %dem; vertical-align: middle;", ems, ems); 73 xs *style = xs_fmt("max-height: %dem; max-width: %dem;", ems, ems);
73 xs *class = xs_fmt("snac-emoji snac-emoji-%d-em", ems); 74 xs *class = xs_fmt("snac-emoji snac-emoji-%d-em", ems);
75 if (cl)
76 class = xs_str_cat(class, " ", xs_join(cl, " "));
74 77
75 const xs_dict *v;
76 int c = 0; 78 int c = 0;
79 const xs_val *v;
80
81 c = 0;
77 82
78 xs_set rep_emoji; 83 xs_set rep_emoji;
79 xs_set_init(&rep_emoji); 84 xs_set_init(&rep_emoji);
@@ -100,6 +105,8 @@ xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *p
100 if (!xs_is_string(mt)) 105 if (!xs_is_string(mt))
101 mt = xs_mime_by_ext(u); 106 mt = xs_mime_by_ext(u);
102 107
108 act = act ? xs_fmt("%s\n%s", n, act) : xs_fmt("%s", n);
109
103 if (strcmp(mt, "image/svg+xml") == 0 && !xs_is_true(xs_dict_get(srv_config, "enable_svg"))) 110 if (strcmp(mt, "image/svg+xml") == 0 && !xs_is_true(xs_dict_get(srv_config, "enable_svg")))
104 s = xs_replace_i(s, n, ""); 111 s = xs_replace_i(s, n, "");
105 else { 112 else {
@@ -108,8 +115,8 @@ xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *p
108 xs_html *img = xs_html_sctag("img", 115 xs_html *img = xs_html_sctag("img",
109 xs_html_attr("loading", "lazy"), 116 xs_html_attr("loading", "lazy"),
110 xs_html_attr("src", url), 117 xs_html_attr("src", url),
111 xs_html_attr("alt", n), 118 xs_html_attr("alt", act),
112 xs_html_attr("title", n), 119 xs_html_attr("title", act),
113 xs_html_attr("class", class), 120 xs_html_attr("class", class),
114 xs_html_attr("style", style)); 121 xs_html_attr("style", style));
115 122
@@ -130,6 +137,13 @@ xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *p
130} 137}
131 138
132 139
140xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *proxy)
141/* replaces all the :shortnames: with the emojis in tag */
142{
143 return _replace_shortnames(s, tag, ems, proxy, NULL, NULL);
144}
145
146
133xs_str *actor_name(xs_dict *actor, const char *proxy) 147xs_str *actor_name(xs_dict *actor, const char *proxy)
134/* gets the actor name */ 148/* gets the actor name */
135{ 149{
@@ -430,6 +444,52 @@ void html_note_render_visibility(snac* user, xs_html *form, const int scope)
430 xs_html_add(form, paragraph); 444 xs_html_add(form, paragraph);
431} 445}
432 446
447/* html_note but moddled for emoji's needs. here and not bellow, since the
448 * other one is already so complex. */
449xs_html *html_emoji(snac *user, const char *summary,
450 const char *div_id, const char *form_id,
451 const char* placeholder, const char *post_id,
452 const char* eid)
453{
454 xs *action = xs_fmt("%s/admin/action", user->actor);
455
456 xs_html *form;
457 const int react = eid == NULL ? 0 : 1;
458
459 xs_html *note = xs_html_tag("div",
460 xs_html_tag("details",
461 xs_html_tag("summary",
462 xs_html_text(summary)),
463 xs_html_tag("p", NULL),
464 xs_html_tag("div",
465 xs_html_attr("class", "snac-note"),
466 xs_html_attr("id", div_id),
467 form = xs_html_tag("form",
468 xs_html_attr("autocomplete", "off"),
469 xs_html_attr("method", "post"),
470 xs_html_attr("action", action),
471 xs_html_attr("enctype", "multipart/form-data"),
472 xs_html_attr("id", form_id),
473 xs_html_sctag("input",
474 xs_html_attr("type", "hidden"),
475 xs_html_attr("name", "id"),
476 xs_html_attr("value", post_id)),
477 xs_html_sctag("input",
478 xs_html_attr("type", react ? "hidden" : "text"),
479 xs_html_attr("name", "eid"),
480 xs_html_attr(react ? "value" : "placeholder", react ? eid : placeholder)),
481 xs_html_text(" "),
482 xs_html_sctag("input",
483 xs_html_attr("type", "submit"),
484 xs_html_attr("name", "action"),
485 xs_html_attr("eid", "action"),
486 xs_html_attr("value", react ? L("EmojiUnreact") : L("EmojiReact"))),
487 xs_html_text(" "),
488 xs_html_tag("p", NULL)))));
489
490 return note;
491}
492
433xs_html *html_note(snac *user, const char *summary, 493xs_html *html_note(snac *user, const char *summary,
434 const char *div_id, const char *form_id, 494 const char *div_id, const char *form_id,
435 const char *ta_plh, const char *ta_content, 495 const char *ta_plh, const char *ta_content,
@@ -1356,6 +1416,28 @@ xs_html *html_top_controls(snac *user)
1356 xs_html_attr("value", L("Like"))), 1416 xs_html_attr("value", L("Like"))),
1357 xs_html_text(" "), 1417 xs_html_text(" "),
1358 xs_html_text(L("(by URL)"))), 1418 xs_html_text(L("(by URL)"))),
1419 xs_html_tag("form",
1420 xs_html_attr("autocomplete", "off"),
1421 xs_html_attr("method", "post"),
1422 xs_html_attr("action", ops_action),
1423 xs_html_sctag("input",
1424 xs_html_attr("type", "text"),
1425 xs_html_attr("name", "eid"),
1426 xs_html_attr("required", "required"),
1427 xs_html_attr("placeholder", ":neocat:")),
1428 xs_html_text(" "),
1429 xs_html_sctag("input",
1430 xs_html_attr("type", "text"),
1431 xs_html_attr("name", "id"),
1432 xs_html_attr("required", "required"),
1433 xs_html_attr("placeholder", "https:/" "/fedi.example.com/bob/...")),
1434 xs_html_text(" "),
1435 xs_html_sctag("input",
1436 xs_html_attr("type", "submit"),
1437 xs_html_attr("name", "action"),
1438 xs_html_attr("value", L("EmojiReact"))),
1439 xs_html_text(" "),
1440 xs_html_text(L("(by URL)"))),
1359 xs_html_tag("p", NULL))); 1441 xs_html_tag("p", NULL)));
1360 1442
1361 /** user settings **/ 1443 /** user settings **/
@@ -2019,6 +2101,21 @@ xs_html *html_entry_controls(snac *user, const char *actor,
2019 xs_html_tag("p", NULL)); 2101 xs_html_tag("p", NULL));
2020 } 2102 }
2021 2103
2104 { /** emoji react **/
2105 /* the post textarea */
2106 xs *div_id = xs_fmt("%s_reply", md5);
2107 xs *form_id = xs_fmt("%s_reply_form", md5);
2108
2109 xs_html_add(controls, xs_html_tag("div",
2110 xs_html_tag("p", NULL),
2111 html_emoji(
2112 user, L("Emoji react"),
2113 div_id, form_id,
2114 ":neocat:", id,
2115 emoji_reacted(user, id))),
2116 xs_html_tag("p", NULL));
2117 }
2118
2022 { /** reply **/ 2119 { /** reply **/
2023 /* the post textarea */ 2120 /* the post textarea */
2024 xs *ct = build_mentions(user, msg); 2121 xs *ct = build_mentions(user, msg);
@@ -2345,6 +2442,168 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
2345 xs_html_add(snac_content_wrap, 2442 xs_html_add(snac_content_wrap,
2346 snac_content); 2443 snac_content);
2347 2444
2445 /* add all emoji reacts */
2446 int is_emoji = 0;
2447 {
2448 int c = 0;
2449 const xs_dict *k;
2450 xs *ls = xs_list_new();
2451 xs *sfrl = xs_dict_new();
2452 xs *rl = object_get_emoji_reacts(id);
2453
2454 xs_dict *m = NULL;
2455 while (xs_list_next(rl, &v, &c)) {
2456 if (valid_status(object_get_by_md5(v, &m))) {
2457 const char *content = xs_dict_get(m, "content");
2458 const char *actor = xs_dict_get(m, "actor");
2459 const xs_list *contentl = xs_dict_get(sfrl, content);
2460 xs *actors = xs_list_new();
2461 actors = xs_list_append(actors, actor);
2462 char me = actor && user && strcmp(actor, user->actor) == 0;
2463 int count = 1;
2464
2465 if (contentl) {
2466 count = atoi(xs_list_get(contentl, 0)) + 1;
2467 const xs_list *actorsc = xs_list_get(contentl, 1);
2468 if (strncmp(xs_list_get(contentl, 2), "1", 1) == 0)
2469 me = 1;
2470
2471 if (xs_list_in(actorsc, actor) != -1) {
2472 xs_free(actors);
2473 actors = xs_dup(actorsc);
2474 }
2475 else
2476 actors = xs_list_cat(actors, actorsc);
2477 }
2478
2479 xs *fl = xs_list_new();
2480 fl = xs_list_append(fl, xs_fmt("%d", count), actors, xs_fmt("%d", me));
2481 sfrl = xs_dict_append(sfrl, content, fl);
2482 }
2483 }
2484
2485 c = 0;
2486
2487 while (xs_list_next(rl, &k, &c)) {
2488 if (valid_status(object_get_by_md5(k, &m))) {
2489 const xs_dict *tag = xs_dict_get(m, "tag");
2490 const xs_dict *ide = xs_dict_get(m, "id");
2491
2492 const char *content = xs_dict_get(m, "content");
2493 const char *shortname;
2494 shortname = xs_dict_get(m, "content");
2495
2496 const xs_list *items = xs_dict_get(sfrl, content);
2497 const char *nb = xs_list_get(items, 0);
2498 const xs_list *actors = xs_list_get(items, 1);
2499 const char me = *xs_list_get(items, 2) == '1';
2500
2501 if (!xs_is_null(nb)) {
2502 is_emoji = 1;
2503
2504 const char *act = atoi(nb) > 1 ?
2505 xs_fmt("%d different actors \n\t%s", atoi(nb), xs_join(actors, ",\n\t")) :
2506 xs_dict_get(m, "actor");
2507
2508 xs *class = xs_list_new();
2509 class = xs_list_append(class, "snac-reaction");
2510
2511 xs_html *ret = NULL;
2512 if (tag && shortname) {
2513 xs *cl = xs_list_new();
2514 cl = xs_list_append(cl, "snac-reaction-image");
2515 xs *emoji = _replace_shortnames(xs_dup(shortname), tag, 2, proxy, cl, act);
2516
2517 if (me)
2518 class = xs_list_append(class, "snac-reacted");
2519
2520 ret = xs_html_tag("button",
2521 xs_html_attr("type", "submit"),
2522 xs_html_attr("name", "action"),
2523 xs_html_attr("value", me ? L("EmojiReact") : L("EmojiUnreact")),
2524 xs_html_raw(emoji),
2525 xs_html_tag("span",
2526 xs_html_raw(nb),
2527 xs_html_attr("style", "padding-left: 5px;")),
2528 xs_html_attr("title", act),
2529 xs_html_attr("class", xs_join(class, " ")));
2530
2531 if (!(ide && xs_startswith(ide, srv_baseurl)))
2532 xs_html_add(ret, xs_html_attr("disabled", "true"));
2533 }
2534 else if (shortname) {
2535 xs *sn = xs_dup(shortname);
2536 const char *sna = sn;
2537 unsigned int utf = xs_utf8_dec((const char **)&sna);
2538
2539 if (xs_is_emoji(utf)) {
2540 const char *style = "font-size: large;";
2541 if (me)
2542 class = xs_list_append(class, "snac-reacted");
2543 ret = xs_html_tag("button",
2544 xs_html_attr("type", "submit"),
2545 xs_html_attr("name", "action"),
2546 xs_html_attr("value", me ? L("EmojiUnreact") : L("EmojiReact")),
2547 xs_html_raw(xs_fmt("&#%d", utf)),
2548 xs_html_tag("span",
2549 xs_html_raw(nb),
2550 xs_html_attr("style", "font-size: initial; padding-left: 5px;")),
2551 xs_html_attr("title", act),
2552 xs_html_attr("class", xs_join(class, " ")),
2553 xs_html_attr("style", style));
2554 }
2555 }
2556 if (ret) {
2557 xs *s1;
2558 if (user) {
2559 xs *action = xs_fmt("%s/admin/action", user->actor);
2560 xs *form_id = xs_fmt("%s_reply_form", md5);
2561
2562 xs_html *form =
2563 xs_html_tag("form",
2564 xs_html_attr("autocomplete", "off"),
2565 xs_html_attr("method", "post"),
2566 xs_html_attr("action", action),
2567 xs_html_attr("enctype", "multipart/form-data"),
2568 xs_html_attr("style", "display: inline-flex;"
2569 "vertical-align: middle;"),
2570 xs_html_attr("id", form_id),
2571 xs_html_sctag("input",
2572 xs_html_attr("type", "hidden"),
2573 xs_html_attr("name", "id"),
2574 xs_html_attr("value", id)),
2575 xs_html_sctag("input",
2576 xs_html_attr("type", "hidden"),
2577 xs_html_attr("name", "eid"),
2578 xs_html_attr("value", shortname)),
2579 ret);
2580 s1 = xs_html_render(form);
2581 }
2582 else
2583 s1 = xs_html_render(ret);
2584
2585 ls = xs_list_append(ls, s1);
2586 sfrl = xs_dict_del(sfrl, content);
2587 }
2588 }
2589 }
2590 }
2591
2592 c = 0;
2593
2594 xs_html *emoji_div;
2595 if (xs_list_len(ls) > 0) {
2596 emoji_div = xs_html_tag("div", xs_html_text(L("Emoji reactions: ")),
2597 xs_html_attr("class", "snac-reaction-div"));
2598
2599 while (ls != NULL && xs_list_next(ls, &k, &c))
2600 xs_html_add(emoji_div, xs_html_raw(k));
2601
2602 xs_html_add(snac_content_wrap, emoji_div);
2603 }
2604
2605 }
2606
2348 { 2607 {
2349 /** build the content string **/ 2608 /** build the content string **/
2350 const char *content = xs_dict_get(msg, "content"); 2609 const char *content = xs_dict_get(msg, "content");
@@ -2371,7 +2630,8 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
2371 2630
2372 c = xs_replace_i(c, "<br><br>", "<p>"); 2631 c = xs_replace_i(c, "<br><br>", "<p>");
2373 2632
2374 c = xs_str_cat(c, "<p>"); 2633 if (is_emoji == 0)
2634 c = xs_str_cat(c, "<p>");
2375 2635
2376 /* replace the :shortnames: */ 2636 /* replace the :shortnames: */
2377 c = replace_shortnames(c, xs_dict_get(msg, "tag"), 2, proxy); 2637 c = replace_shortnames(c, xs_dict_get(msg, "tag"), 2, proxy);
@@ -3686,9 +3946,18 @@ xs_str *html_notifications(snac *user, int skip, int show)
3686 if (strcmp(type, "EmojiReact") == 0 || strcmp(type, "Like") == 0) { 3946 if (strcmp(type, "EmojiReact") == 0 || strcmp(type, "Like") == 0) {
3687 const char *content = xs_dict_get_path(noti, "msg.content"); 3947 const char *content = xs_dict_get_path(noti, "msg.content");
3688 3948
3949 xs *cd = xs_dup(content);
3950 const char *sna = cd;
3951 const xs_dict *tag = xs_dict_get_path(noti, "msg.tag");
3952 unsigned int utf = xs_utf8_dec((const char **)&sna);
3953
3954 int isEmoji = 0;
3955 if (xs_is_emoji(utf) || (tag && xs_list_len(tag) > 0))
3956 isEmoji = 1;
3957
3689 if (xs_type(content) == XSTYPE_STRING) { 3958 if (xs_type(content) == XSTYPE_STRING) {
3690 xs *emoji = replace_shortnames(xs_dup(content), xs_dict_get_path(noti, "msg.tag"), 1, proxy); 3959 xs *emoji = replace_shortnames(xs_dup(content), xs_dict_get_path(noti, "msg.tag"), 1, proxy);
3691 wrk = xs_fmt("%s (%s&#xFE0F;)", type, emoji); 3960 wrk = xs_fmt("%s (%s&#xFE0F;)", isEmoji ? "EmojiReact" : "Like", emoji);
3692 label = wrk; 3961 label = wrk;
3693 } 3962 }
3694 } 3963 }
@@ -4583,8 +4852,8 @@ int html_get_handler(const xs_dict *req, const char *q_path,
4583 xs *msg = msg_admiration(&snac, id, *action == 'L' ? "Like" : "Announce"); 4852 xs *msg = msg_admiration(&snac, id, *action == 'L' ? "Like" : "Announce");
4584 4853
4585 if (msg != NULL) { 4854 if (msg != NULL) {
4855 timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, *action == 'L' ? 1 : 0, msg);
4586 enqueue_message(&snac, msg); 4856 enqueue_message(&snac, msg);
4587 timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, *action == 'L' ? 1 : 0);
4588 4857
4589 status = HTTP_STATUS_SEE_OTHER; 4858 status = HTTP_STATUS_SEE_OTHER;
4590 } 4859 }
@@ -4892,12 +5161,36 @@ int html_post_handler(const xs_dict *req, const char *q_path,
4892 5161
4893 status = HTTP_STATUS_SEE_OTHER; 5162 status = HTTP_STATUS_SEE_OTHER;
4894 5163
5164 if (strcmp(action, L("EmojiUnreact")) == 0) { /** **/
5165 const char *eid = xs_dict_get(p_vars, "eid");
5166
5167 if (eid != NULL) {
5168 xs *n_msg = msg_emoji_unreact(&snac, id, eid);
5169
5170 if (n_msg != NULL)
5171 enqueue_message(&snac, n_msg);
5172 }
5173 }
5174 else
5175 if (strcmp(action, L("EmojiReact")) == 0) { /** **/
5176 const char *eid = xs_dict_get(p_vars, "eid");
5177
5178 eid = xs_strip_chars_i(xs_dup(eid), ":");
5179
5180 const xs_dict *ret = msg_emoji_init(&snac, id, eid);
5181 /* fails if either invalid or already reacted */
5182 if (!ret)
5183 ret = msg_emoji_unreact(&snac, id, eid);
5184 if (!ret)
5185 status = HTTP_STATUS_NOT_FOUND;
5186 }
5187 else
4895 if (strcmp(action, L("Like")) == 0) { /** **/ 5188 if (strcmp(action, L("Like")) == 0) { /** **/
4896 xs *msg = msg_admiration(&snac, id, "Like"); 5189 xs *msg = msg_admiration(&snac, id, "Like");
4897 5190
4898 if (msg != NULL) { 5191 if (msg != NULL) {
5192 timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, 1, msg);
4899 enqueue_message(&snac, msg); 5193 enqueue_message(&snac, msg);
4900 timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, 1);
4901 } 5194 }
4902 } 5195 }
4903 else 5196 else
@@ -4905,8 +5198,8 @@ int html_post_handler(const xs_dict *req, const char *q_path,
4905 xs *msg = msg_admiration(&snac, id, "Announce"); 5198 xs *msg = msg_admiration(&snac, id, "Announce");
4906 5199
4907 if (msg != NULL) { 5200 if (msg != NULL) {
5201 timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, 0, msg);
4908 enqueue_message(&snac, msg); 5202 enqueue_message(&snac, msg);
4909 timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, 0);
4910 } 5203 }
4911 } 5204 }
4912 else 5205 else
diff --git a/main.c b/main.c
index 6915bd0..4b0463e 100644
--- a/main.c
+++ b/main.c
@@ -498,7 +498,7 @@ int main(int argc, char *argv[])
498 498
499 if (msg != NULL) { 499 if (msg != NULL) {
500 enqueue_message(&snac, msg); 500 enqueue_message(&snac, msg);
501 timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, 0); 501 timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, 0, "");
502 502
503 if (dbglevel) { 503 if (dbglevel) {
504 xs_json_dump(msg, 4, stdout); 504 xs_json_dump(msg, 4, stdout);
diff --git a/mastoapi.c b/mastoapi.c
index 9503447..1d99cd5 100644
--- a/mastoapi.c
+++ b/mastoapi.c
@@ -1158,6 +1158,97 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
1158 st = xs_dict_append(st, "tags", htl); 1158 st = xs_dict_append(st, "tags", htl);
1159 st = xs_dict_append(st, "emojis", eml); 1159 st = xs_dict_append(st, "emojis", eml);
1160 } 1160 }
1161 {
1162 xs *rl = object_get_emoji_reacts(id);
1163 xs *frl = xs_list_new(); /* final */
1164 xs *sfrl = xs_dict_new(); /* seen */
1165 int c = 0;
1166 const char *v;
1167
1168 xs_dict *msg = NULL;
1169 while (xs_list_next(rl, &v, &c)) {
1170 if (valid_status(object_get_by_md5(v, &msg))) {
1171 const char *content = xs_dict_get(msg, "content");
1172 const char *actor = xs_dict_get(msg, "actor");
1173 const xs_list *contentl = xs_dict_get(sfrl, content);
1174 /* NOTE: idk when there are no actor, but i encountered that bug.
1175 * Probably because of one of my previous attempts.
1176 * Keeping this just in case, can remove later */
1177 const char *me = actor && strcmp(actor, snac->actor) == 0 ?
1178 xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE);
1179 int count = 1;
1180
1181 if (contentl) {
1182 count = atoi(xs_list_get(contentl, 0)) + 1;
1183 if (strncmp(xs_list_get(contentl, 1), xs_stock(XSTYPE_TRUE), 1) == 0)
1184 me = xs_stock(XSTYPE_TRUE);
1185 }
1186
1187 xs *fl = xs_list_new();
1188 fl = xs_list_append(fl, xs_fmt("%d", count), me);
1189 sfrl = xs_dict_append(sfrl, content, fl);
1190 }
1191 }
1192
1193 c = 0;
1194
1195 while (xs_list_next(rl, &v, &c)) {
1196 if (valid_status(object_get_by_md5(v, &msg))) {
1197 xs *d1 = xs_dict_new();
1198
1199 const xs_dict *icon = xs_dict_get(xs_list_get(xs_dict_get(msg, "tag"), 0), "icon");
1200 const char *o_url = xs_dict_get(icon, "url");
1201 const char *name = xs_dict_get(msg, "content");
1202 const char *actor = xs_dict_get(msg, "actor");
1203
1204 xs *nm = xs_dup(name);
1205 xs *url = NULL;
1206
1207 if (!xs_is_null(o_url)) {
1208 if (actor && snac && !strcmp(actor, snac->actor))
1209 url = make_url(o_url, NULL, 1);
1210 else
1211 url = xs_dup(o_url);
1212 }
1213
1214 xs *accounts = xs_list_new();
1215 if (actor) {
1216 xs *d2 = xs_dict_new();
1217 object_get(actor, &d2);
1218 xs *e_acct = mastoapi_account(snac, d2);
1219 accounts = xs_list_append(accounts, e_acct);
1220 }
1221
1222 const xs_list *item = xs_dict_get(sfrl, nm);
1223 const xs_str *nb = xs_list_get(item, 0);
1224 const xs_val *me = xs_list_get(item, 1);
1225 if (item == NULL)
1226 continue;
1227
1228 if (nm && strcmp(nm, "")) {
1229 if (url && strcmp(url, "")) {
1230 d1 = xs_dict_append(d1, "name", nm);
1231 d1 = xs_dict_append(d1, "shortcode", nm);
1232 d1 = xs_dict_append(d1, "accounts", accounts);
1233 d1 = xs_dict_append(d1, "me", me);
1234 d1 = xs_dict_append(d1, "url", url);
1235 d1 = xs_dict_append(d1, "static_url", url);
1236 d1 = xs_dict_append(d1, "visible_in_picker", xs_stock(XSTYPE_TRUE));
1237 d1 = xs_dict_append(d1, "count", nb);
1238 } else {
1239 d1 = xs_dict_append(d1, "name", nm);
1240 d1 = xs_dict_append(d1, "count", nb);
1241 d1 = xs_dict_append(d1, "me", me);
1242 d1 = xs_dict_append(d1, "visible_in_picker", xs_stock(XSTYPE_TRUE));
1243 }
1244 sfrl = xs_dict_del(sfrl, nm);
1245 frl = xs_list_append(frl, d1);
1246 }
1247 }
1248 }
1249
1250 st = xs_dict_append(st, "reactions", frl);
1251 }
1161 1252
1162 xs_free(idx); 1253 xs_free(idx);
1163 xs_free(ixc); 1254 xs_free(ixc);
@@ -2202,15 +2293,24 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2202 if (noti == NULL) 2293 if (noti == NULL)
2203 continue; 2294 continue;
2204 2295
2296 const xs_dict *tag = xs_list_get(xs_dict_get_path(noti, "msg.tag"), 0);
2297
2205 const char *type = xs_dict_get(noti, "type"); 2298 const char *type = xs_dict_get(noti, "type");
2206 const char *utype = xs_dict_get(noti, "utype"); 2299 const char *utype = xs_dict_get(noti, "utype");
2207 const char *objid = xs_dict_get(noti, "objid"); 2300 const char *objid = xs_dict_get(noti, "objid");
2208 const char *id = xs_dict_get(noti, "id"); 2301 const char *id = xs_dict_get(noti, "id");
2209 const char *actid = xs_dict_get(noti, "actor"); 2302 const char *actid = xs_dict_get(noti, "actor");
2303
2304 int isEmoji = 0;
2305
2210 xs *fid = xs_replace(id, ".", ""); 2306 xs *fid = xs_replace(id, ".", "");
2211 xs *actor = NULL; 2307 xs *actor = NULL;
2212 xs *entry = NULL; 2308 xs *entry = NULL;
2213 2309
2310 if (tag) {
2311 isEmoji = strcmp(xs_dict_get(tag, "type"), "Emoji") ? 0 : 1;
2312 }
2313
2214 if (!valid_status(actor_get(actid, &actor))) 2314 if (!valid_status(actor_get(actid, &actor)))
2215 continue; 2315 continue;
2216 2316
@@ -2234,9 +2334,12 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2234 } 2334 }
2235 2335
2236 /* convert the type */ 2336 /* convert the type */
2237 if (strcmp(type, "Like") == 0 || strcmp(type, "EmojiReact") == 0) 2337 if (strcmp(type, "Like") == 0 && !isEmoji)
2238 type = "favourite"; 2338 type = "favourite";
2239 else 2339 else
2340 if (isEmoji || strcmp(type, "EmojiReact") == 0)
2341 type = "reaction";
2342 else
2240 if (strcmp(type, "Announce") == 0) 2343 if (strcmp(type, "Announce") == 0)
2241 type = "reblog"; 2344 type = "reblog";
2242 else 2345 else
@@ -2277,8 +2380,29 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2277 if (strcmp(type, "follow") != 0 && !xs_is_null(objid)) { 2380 if (strcmp(type, "follow") != 0 && !xs_is_null(objid)) {
2278 xs *st = mastoapi_status(&snac1, entry); 2381 xs *st = mastoapi_status(&snac1, entry);
2279 2382
2280 if (st) 2383 if (st) {
2281 mn = xs_dict_append(mn, "status", st); 2384 mn = xs_dict_append(mn, "status", st);
2385
2386 if (strcmp(type, "reaction") == 0 && !xs_is_null(objid)) {
2387 const char *eid = NULL;
2388 const char *url = NULL;
2389 int utf = 0;
2390
2391 const xs_dict *tag = xs_list_get(xs_dict_get_path(noti, "msg.tag"), 0);
2392 const char *content = xs_dict_get_path(noti, "msg.content");
2393
2394 url = xs_dict_get(xs_dict_get(tag, "icon"), "url");
2395 eid = xs_dict_get(tag, "name");
2396
2397 if (eid && url) {
2398 mn = xs_dict_append(mn, "emoji", eid);
2399 mn = xs_dict_append(mn, "emoji_url", url);
2400 }
2401
2402 if (xs_is_emoji((utf = xs_utf8_dec(&content))))
2403 mn = xs_dict_append(mn, "name", xs_fmt("&#%d;", utf));
2404 }
2405 }
2282 } 2406 }
2283 2407
2284 out = xs_list_append(out, mn); 2408 out = xs_list_append(out, mn);
@@ -2594,6 +2718,11 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2594 "\"max_expiration\":2629746," 2718 "\"max_expiration\":2629746,"
2595 "\"max_options\":8,\"min_expiration\":300}"); 2719 "\"max_options\":8,\"min_expiration\":300}");
2596 cfg = xs_dict_append(cfg, "polls", d14); 2720 cfg = xs_dict_append(cfg, "polls", d14);
2721
2722
2723 xs *d15 = xs_json_loads("{\"max_reactions\":50}");
2724 cfg = xs_dict_append(cfg, "reactions", d15);
2725
2597 } 2726 }
2598 2727
2599 ins = xs_dict_append(ins, "configuration", cfg); 2728 ins = xs_dict_append(ins, "configuration", cfg);
@@ -3219,7 +3348,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
3219 3348
3220 if (n_msg != NULL) { 3349 if (n_msg != NULL) {
3221 enqueue_message(&snac, n_msg); 3350 enqueue_message(&snac, n_msg);
3222 timeline_admire(&snac, xs_dict_get(n_msg, "object"), snac.actor, 1); 3351 timeline_admire(&snac, xs_dict_get(n_msg, "object"), snac.actor, 1, msg);
3223 3352
3224 out = mastoapi_status(&snac, msg); 3353 out = mastoapi_status(&snac, msg);
3225 } 3354 }
@@ -3235,12 +3364,35 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
3235 } 3364 }
3236 } 3365 }
3237 else 3366 else
3367 if (strcmp(op, "react") == 0) { /** **/
3368 const char *eid = xs_list_get(l, 5);
3369 xs *n_msg = msg_emoji_init(&snac, id, eid);
3370 if (n_msg)
3371 out = mastoapi_status(&snac, n_msg);
3372 }
3373 else
3374 if (strcmp(op, "unreact") == 0) { /** **/
3375 const char *eid = xs_list_get(l, 5);
3376 xs *content = xs_fmt("%s", eid);
3377
3378 if (eid) {
3379 xs *n_msg = msg_emoji_unreact(&snac, id, content);
3380
3381 if (n_msg != NULL) {
3382 enqueue_message(&snac, n_msg);
3383
3384 out = mastoapi_status(&snac, msg);
3385 }
3386 }
3387 }
3388
3389 else
3238 if (strcmp(op, "reblog") == 0) { /** **/ 3390 if (strcmp(op, "reblog") == 0) { /** **/
3239 xs *n_msg = msg_admiration(&snac, id, "Announce"); 3391 xs *n_msg = msg_admiration(&snac, id, "Announce");
3240 3392
3241 if (n_msg != NULL) { 3393 if (n_msg != NULL) {
3242 enqueue_message(&snac, n_msg); 3394 enqueue_message(&snac, n_msg);
3243 timeline_admire(&snac, xs_dict_get(n_msg, "object"), snac.actor, 0); 3395 timeline_admire(&snac, xs_dict_get(n_msg, "object"), snac.actor, 0, msg);
3244 3396
3245 out = mastoapi_status(&snac, msg); 3397 out = mastoapi_status(&snac, msg);
3246 } 3398 }
diff --git a/snac.h b/snac.h
index e1d25f2..3d98aac 100644
--- a/snac.h
+++ b/snac.h
@@ -146,12 +146,15 @@ void object_touch(const char *id);
146int object_admire(const char *id, const char *actor, int like); 146int object_admire(const char *id, const char *actor, int like);
147int object_unadmire(const char *id, const char *actor, int like); 147int object_unadmire(const char *id, const char *actor, int like);
148 148
149int object_emoji_react(const char *mid, const char *eid);
150int object_rm_emoji_react(const char *mid, const char *eid);
149int object_likes_len(const char *id); 151int object_likes_len(const char *id);
150int object_announces_len(const char *id); 152int object_announces_len(const char *id);
151 153
152xs_list *object_children(const char *id); 154xs_list *object_children(const char *id);
153xs_list *object_likes(const char *id); 155xs_list *object_likes(const char *id);
154xs_list *object_announces(const char *id); 156xs_list *object_announces(const char *id);
157xs_list *object_get_emoji_reacts(const char *id);
155int object_parent(const char *md5, char parent[MD5_HEX_SIZE]); 158int object_parent(const char *md5, char parent[MD5_HEX_SIZE]);
156 159
157int object_user_cache_add(snac *snac, const char *id, const char *cachedir); 160int object_user_cache_add(snac *snac, const char *id, const char *cachedir);
@@ -180,7 +183,8 @@ xs_str *user_index_fn(snac *user, const char *idx_name);
180xs_list *timeline_simple_list(snac *user, const char *idx_name, int skip, int show, int *more); 183xs_list *timeline_simple_list(snac *user, const char *idx_name, int skip, int show, int *more);
181xs_list *timeline_list(snac *snac, const char *idx_name, int skip, int show, int *more); 184xs_list *timeline_list(snac *snac, const char *idx_name, int skip, int show, int *more);
182int timeline_add(snac *snac, const char *id, const xs_dict *o_msg); 185int timeline_add(snac *snac, const char *id, const xs_dict *o_msg);
183int timeline_admire(snac *snac, const char *id, const char *admirer, int like); 186int timeline_admire(snac *snac, const char *id, const char *admirer, int like, xs_dict *msg);
187int timeline_emoji_react(const char *atto, const char *id, xs_dict *o_msg);
184 188
185xs_list *timeline_top_level(snac *snac, const xs_list *list); 189xs_list *timeline_top_level(snac *snac, const xs_list *list);
186void timeline_add_mark(snac *user); 190void timeline_add_mark(snac *user);
@@ -201,6 +205,8 @@ void unmute(snac *snac, const char *actor);
201int is_muted(snac *snac, const char *actor); 205int is_muted(snac *snac, const char *actor);
202xs_list *muted_list(snac *user); 206xs_list *muted_list(snac *user);
203 207
208const xs_str *emoji_reacted(snac *user, const char *id);
209
204int is_bookmarked(snac *user, const char *id); 210int is_bookmarked(snac *user, const char *id);
205int bookmark(snac *user, const char *id); 211int bookmark(snac *user, const char *id);
206int unbookmark(snac *user, const char *id); 212int unbookmark(snac *user, const char *id);
@@ -358,6 +364,8 @@ xs_list *get_attachments(const xs_dict *msg);
358 364
359xs_dict *msg_admiration(snac *snac, const char *object, const char *type); 365xs_dict *msg_admiration(snac *snac, const char *object, const char *type);
360xs_dict *msg_repulsion(snac *user, const char *id, const char *type); 366xs_dict *msg_repulsion(snac *user, const char *id, const char *type);
367xs_dict *msg_emoji_init(snac *user, const char *mid, const char *eid);
368xs_dict *msg_emoji_unreact(snac *user, const char *id, const char *type);
361xs_dict *msg_create(snac *snac, const xs_dict *object); 369xs_dict *msg_create(snac *snac, const xs_dict *object);
362xs_dict *msg_follow(snac *snac, const char *actor); 370xs_dict *msg_follow(snac *snac, const char *actor);
363 371
diff --git a/xs_unicode.h b/xs_unicode.h
index 67b3827..0b4de1c 100644
--- a/xs_unicode.h
+++ b/xs_unicode.h
@@ -22,6 +22,7 @@
22 int xs_unicode_nfc(unsigned int base, unsigned int diac, unsigned int *cpoint); 22 int xs_unicode_nfc(unsigned int base, unsigned int diac, unsigned int *cpoint);
23 int xs_unicode_is_alpha(unsigned int cpoint); 23 int xs_unicode_is_alpha(unsigned int cpoint);
24 int xs_unicode_is_right_to_left(unsigned int cpoint); 24 int xs_unicode_is_right_to_left(unsigned int cpoint);
25 int xs_is_emoji(unsigned int cpoint);
25 26
26#ifdef _XS_H 27#ifdef _XS_H
27 xs_str *xs_utf8_insert(xs_str *str, unsigned int cpoint, int *offset); 28 xs_str *xs_utf8_insert(xs_str *str, unsigned int cpoint, int *offset);
@@ -134,6 +135,12 @@ static unsigned int xs_unicode_width_table[] = {
134 0x20000, 0x2fffd, 2 /* more CJK */ 135 0x20000, 0x2fffd, 2 /* more CJK */
135}; 136};
136 137
138/* magic number from https://en.wikipedia.org/wiki/Emoji#Unicode_blocks */
139int xs_is_emoji(unsigned int cpoint) {
140/* returns wether the input is an utf8 emoji */
141 return cpoint > 0x00A9 && cpoint < 0x1ffff;
142}
143
137int xs_unicode_width(unsigned int cpoint) 144int xs_unicode_width(unsigned int cpoint)
138/* returns the width in columns of a Unicode codepoint (somewhat simplified) */ 145/* returns the width in columns of a Unicode codepoint (somewhat simplified) */
139{ 146{
diff --git a/xs_url.h b/xs_url.h
index 7bdff49..222771f 100644
--- a/xs_url.h
+++ b/xs_url.h
@@ -6,6 +6,7 @@
6 6
7xs_str *xs_url_dec(const char *str); 7xs_str *xs_url_dec(const char *str);
8xs_str *xs_url_enc(const char *str); 8xs_str *xs_url_enc(const char *str);
9xs_str *xs_url_dec_emoji(const char *str);
9xs_dict *xs_url_vars(const char *str); 10xs_dict *xs_url_vars(const char *str);
10xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *header); 11xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *header);
11 12
@@ -79,6 +80,38 @@ xs_str *xs_url_dec(const char *str)
79} 80}
80 81
81 82
83xs_str *xs_url_dec_emoji(const char *str)
84/* decodes an URL, returns NULL if not every char is an encoded hex */
85{
86 xs_str *s = xs_str_new(NULL);
87
88 while (*str) {
89 if (!xs_is_string(str))
90 break;
91
92 if (*str == '%') {
93 unsigned int i;
94
95 if (sscanf(str + 1, "%02x", &i) == 1) {
96 unsigned char uc = i;
97
98 if (!xs_is_string((char *)&uc))
99 break;
100
101 s = xs_append_m(s, (char *)&uc, 1);
102 str += 2;
103 }
104 }
105 else
106 return NULL;
107
108 str++;
109 }
110
111 return s;
112}
113
114
82xs_str *xs_url_enc(const char *str) 115xs_str *xs_url_enc(const char *str)
83/* URL-encodes a string (RFC 3986) */ 116/* URL-encodes a string (RFC 3986) */
84{ 117{