diff options
Diffstat (limited to 'data.c')
| -rw-r--r-- | data.c | 162 |
1 files changed, 147 insertions, 15 deletions
| @@ -1,5 +1,5 @@ | |||
| 1 | /* snac - A simple, minimalistic ActivityPub instance */ | 1 | /* snac - A simple, minimalistic ActivityPub instance */ |
| 2 | /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ | 2 | /* copyright (c) 2022 - 2026 grunfink et al. / MIT license */ |
| 3 | 3 | ||
| 4 | #include "xs.h" | 4 | #include "xs.h" |
| 5 | #include "xs_hex.h" | 5 | #include "xs_hex.h" |
| @@ -89,8 +89,15 @@ int srv_open(const char *basedir, int auto_upgrade) | |||
| 89 | else { | 89 | else { |
| 90 | if (xs_number_get(xs_dict_get(srv_config, "layout")) < disk_layout) | 90 | if (xs_number_get(xs_dict_get(srv_config, "layout")) < disk_layout) |
| 91 | error = xs_fmt("ERROR: disk layout changed - execute 'snac upgrade' first"); | 91 | error = xs_fmt("ERROR: disk layout changed - execute 'snac upgrade' first"); |
| 92 | else | 92 | else { |
| 93 | ret = 1; | 93 | if (!check_strip_tool()) { |
| 94 | const char *mp = xs_dict_get(srv_config, "mogrify_path"); | ||
| 95 | if (mp == NULL) mp = "mogrify"; | ||
| 96 | error = xs_fmt("ERROR: strip_exif enabled but '%s' not found or not working (set 'mogrify_path' in server.json)", mp); | ||
| 97 | } | ||
| 98 | else | ||
| 99 | ret = 1; | ||
| 100 | } | ||
| 94 | } | 101 | } |
| 95 | } | 102 | } |
| 96 | 103 | ||
| @@ -1043,6 +1050,14 @@ xs_list *object_children(const char *id) | |||
| 1043 | } | 1050 | } |
| 1044 | 1051 | ||
| 1045 | 1052 | ||
| 1053 | xs_list *object_get_emoji_reacts(const char *id) | ||
| 1054 | /* returns the list of an object's emoji reactions */ | ||
| 1055 | { | ||
| 1056 | xs *fn = _object_index_fn(id, "_e.idx"); | ||
| 1057 | return index_list(fn, XS_ALL); | ||
| 1058 | } | ||
| 1059 | |||
| 1060 | |||
| 1046 | xs_list *object_likes(const char *id) | 1061 | xs_list *object_likes(const char *id) |
| 1047 | { | 1062 | { |
| 1048 | xs *fn = _object_index_fn(id, "_l.idx"); | 1063 | xs *fn = _object_index_fn(id, "_l.idx"); |
| @@ -1086,12 +1101,26 @@ int object_admire(const char *id, const char *actor, int like) | |||
| 1086 | 1101 | ||
| 1087 | 1102 | ||
| 1088 | int object_unadmire(const char *id, const char *actor, int like) | 1103 | int object_unadmire(const char *id, const char *actor, int like) |
| 1089 | /* actor no longer likes or announces this object */ | 1104 | /* actor retrives their likes, announces or emojis this object */ |
| 1090 | { | 1105 | { |
| 1106 | switch (like) { | ||
| 1107 | case 0: | ||
| 1108 | like = 'a'; | ||
| 1109 | break; | ||
| 1110 | case 1: | ||
| 1111 | like = 'l'; | ||
| 1112 | break; | ||
| 1113 | case 2: | ||
| 1114 | like = 'e'; | ||
| 1115 | break; | ||
| 1116 | } | ||
| 1091 | int status; | 1117 | int status; |
| 1092 | xs *fn = _object_fn(id); | 1118 | xs *fn = _object_fn(id); |
| 1093 | 1119 | ||
| 1094 | fn = xs_replace_i(fn, ".json", like ? "_l.idx" : "_a.idx"); | 1120 | char sfx[7] = "_x.idx"; |
| 1121 | sfx[1] = like; | ||
| 1122 | |||
| 1123 | fn = xs_replace_i(fn, ".json", sfx); | ||
| 1095 | 1124 | ||
| 1096 | status = index_del(fn, actor); | 1125 | status = index_del(fn, actor); |
| 1097 | 1126 | ||
| @@ -1099,7 +1128,46 @@ int object_unadmire(const char *id, const char *actor, int like) | |||
| 1099 | index_gc(fn); | 1128 | index_gc(fn); |
| 1100 | 1129 | ||
| 1101 | srv_debug(0, | 1130 | srv_debug(0, |
| 1102 | xs_fmt("object_unadmire (%s) %s %s %d", like ? "Like" : "Announce", actor, fn, status)); | 1131 | xs_fmt("object_unadmire (%s) %s %s %d", like >= 'e' ? |
| 1132 | (like == 'l' ? "Like" : "EmojiReact") : "Announce" , actor, fn, status)); | ||
| 1133 | |||
| 1134 | return status; | ||
| 1135 | } | ||
| 1136 | |||
| 1137 | int object_emoji_react(const char *mid, const char *eid) | ||
| 1138 | /* actor reacts w/ an emoji */ | ||
| 1139 | { | ||
| 1140 | int status = HTTP_STATUS_OK; | ||
| 1141 | xs *fn = _object_fn(mid); | ||
| 1142 | |||
| 1143 | fn = xs_replace_i(fn, ".json", "_e.idx"); | ||
| 1144 | |||
| 1145 | if (!index_in(fn, eid)) { | ||
| 1146 | status = index_add(fn, eid); | ||
| 1147 | |||
| 1148 | srv_debug(1, xs_fmt("object_emoji_react (%s) added %s to %s", "EmojiReact", eid, fn)); | ||
| 1149 | } | ||
| 1150 | |||
| 1151 | return status; | ||
| 1152 | } | ||
| 1153 | |||
| 1154 | |||
| 1155 | int object_rm_emoji_react(const char *mid, const char *eid) | ||
| 1156 | /* actor retrives their emoji reaction */ | ||
| 1157 | { | ||
| 1158 | int status; | ||
| 1159 | xs *fn = _object_fn(mid); | ||
| 1160 | |||
| 1161 | fn = xs_replace_i(fn, ".json", "_e.idx"); | ||
| 1162 | |||
| 1163 | status = index_del(fn, eid); | ||
| 1164 | object_del(eid); | ||
| 1165 | |||
| 1166 | if (valid_status(status)) | ||
| 1167 | index_gc(fn); | ||
| 1168 | |||
| 1169 | srv_debug(0, | ||
| 1170 | xs_fmt("object_unadmire (EmojiReact) %s %s %d", eid, fn, status)); | ||
| 1103 | 1171 | ||
| 1104 | return status; | 1172 | return status; |
| 1105 | } | 1173 | } |
| @@ -1358,6 +1426,20 @@ int pending_count(snac *user) | |||
| 1358 | } | 1426 | } |
| 1359 | 1427 | ||
| 1360 | 1428 | ||
| 1429 | int is_msg_mine(snac *user, const char *id) | ||
| 1430 | /* returns true if a post id is by the given user */ | ||
| 1431 | { | ||
| 1432 | int ret = 0; | ||
| 1433 | |||
| 1434 | if (xs_is_string(id)) { | ||
| 1435 | xs *s1 = xs_fmt("%s/", user->actor); | ||
| 1436 | ret = xs_startswith(id, s1); | ||
| 1437 | } | ||
| 1438 | |||
| 1439 | return ret; | ||
| 1440 | } | ||
| 1441 | |||
| 1442 | |||
| 1361 | /** timeline **/ | 1443 | /** timeline **/ |
| 1362 | 1444 | ||
| 1363 | double timeline_mtime(snac *snac) | 1445 | double timeline_mtime(snac *snac) |
| @@ -1453,7 +1535,7 @@ void timeline_update_indexes(snac *snac, const char *id) | |||
| 1453 | { | 1535 | { |
| 1454 | object_user_cache_add(snac, id, "private"); | 1536 | object_user_cache_add(snac, id, "private"); |
| 1455 | 1537 | ||
| 1456 | if (xs_startswith(id, snac->actor)) { | 1538 | if (is_msg_mine(snac, id)) { |
| 1457 | xs *msg = NULL; | 1539 | xs *msg = NULL; |
| 1458 | 1540 | ||
| 1459 | if (valid_status(object_get(id, &msg))) { | 1541 | if (valid_status(object_get(id, &msg))) { |
| @@ -1492,19 +1574,48 @@ int timeline_add(snac *snac, const char *id, const xs_dict *o_msg) | |||
| 1492 | } | 1574 | } |
| 1493 | 1575 | ||
| 1494 | 1576 | ||
| 1495 | int timeline_admire(snac *snac, const char *id, const char *admirer, int like) | 1577 | int timeline_emoji_react(const char *act, const char *id, const xs_dict *msg_o) |
| 1496 | /* updates a timeline entry with a new admiration */ | 1578 | /* adds an emoji reaction to a message */ |
| 1497 | { | 1579 | { |
| 1580 | xs *msg = xs_dup(msg_o); | ||
| 1581 | msg = xs_dict_append(msg, "attributedTo", act); | ||
| 1582 | msg = xs_dict_set(msg, "type", "EmojiReact"); | ||
| 1583 | const char *emote_id = xs_dict_get(msg, "id"); | ||
| 1584 | |||
| 1585 | int ret = object_add(emote_id, msg); | ||
| 1586 | if (ret == HTTP_STATUS_OK || ret == HTTP_STATUS_CREATED) | ||
| 1587 | ret = object_emoji_react(id, emote_id); | ||
| 1588 | |||
| 1589 | return ret; | ||
| 1590 | } | ||
| 1591 | |||
| 1592 | |||
| 1593 | int timeline_admire(snac *snac, const char *id, | ||
| 1594 | const char *admirer, int like, const xs_dict *msg) | ||
| 1595 | /* updates a timeline entry with a new admiration or emoji reaction */ | ||
| 1596 | { | ||
| 1597 | int ret; | ||
| 1598 | const char *content = xs_dict_get_path(msg, "content"); | ||
| 1599 | const char *type = xs_dict_get_path(msg, "type"); | ||
| 1600 | |||
| 1498 | /* if we are admiring this, add to both timelines */ | 1601 | /* if we are admiring this, add to both timelines */ |
| 1499 | if (!like && strcmp(admirer, snac->actor) == 0) { | 1602 | if (!like && strcmp(admirer, snac->actor) == 0) { |
| 1500 | object_user_cache_add(snac, id, "public"); | 1603 | object_user_cache_add(snac, id, "public"); |
| 1501 | object_user_cache_add(snac, id, "private"); | 1604 | object_user_cache_add(snac, id, "private"); |
| 1502 | } | 1605 | } |
| 1503 | 1606 | ||
| 1504 | int ret = object_admire(id, admirer, like); | 1607 | /* use utf <3 as a like, as it is ugly */ |
| 1608 | if (type && xs_match(type, "Like|EmojiReact|Emoji") && | ||
| 1609 | content && strcmp(content, "❤") != 0) { | ||
| 1610 | ret = timeline_emoji_react(snac->actor, id, msg); | ||
| 1611 | snac_debug(snac, 1, xs_fmt("timeline_emoji_react %s", id)); | ||
| 1612 | } | ||
| 1505 | 1613 | ||
| 1506 | snac_debug(snac, 1, xs_fmt("timeline_admire (%s) %s %s", | 1614 | else { |
| 1507 | like ? "Like" : "Announce", id, admirer)); | 1615 | ret = object_admire(id, admirer, like); |
| 1616 | snac_debug(snac, 1, xs_fmt("timeline_admire (%s) %s %s", | ||
| 1617 | like ? "Like" : "Announce", id, admirer)); | ||
| 1618 | } | ||
| 1508 | 1619 | ||
| 1509 | return ret; | 1620 | return ret; |
| 1510 | } | 1621 | } |
| @@ -1853,6 +1964,25 @@ xs_list *muted_list(snac *user) | |||
| 1853 | return l; | 1964 | return l; |
| 1854 | } | 1965 | } |
| 1855 | 1966 | ||
| 1967 | /** emojis react **/ | ||
| 1968 | |||
| 1969 | xs_str *emoji_reacted(snac *user, const char *id) | ||
| 1970 | /* returns the emoji an user reacted to a message */ | ||
| 1971 | { | ||
| 1972 | xs *emojis = object_get_emoji_reacts(id); | ||
| 1973 | int c = 0; | ||
| 1974 | const char *v; | ||
| 1975 | |||
| 1976 | while (xs_list_next(emojis, &v, &c)) { | ||
| 1977 | xs *msg = NULL; | ||
| 1978 | if (object_get_by_md5(v, &msg)) { | ||
| 1979 | const xs_val *act = xs_dict_get(msg, "actor"); | ||
| 1980 | if (act && strcmp(act, user->actor) == 0) | ||
| 1981 | return xs_dup(xs_dict_get(msg, "content")); | ||
| 1982 | } | ||
| 1983 | } | ||
| 1984 | return NULL; | ||
| 1985 | } | ||
| 1856 | 1986 | ||
| 1857 | /** bookmarking **/ | 1987 | /** bookmarking **/ |
| 1858 | 1988 | ||
| @@ -1913,7 +2043,7 @@ int pin(snac *user, const char *id) | |||
| 1913 | { | 2043 | { |
| 1914 | int ret = -2; | 2044 | int ret = -2; |
| 1915 | 2045 | ||
| 1916 | if (xs_startswith(id, user->actor)) { | 2046 | if (is_msg_mine(user, id)) { |
| 1917 | if (is_pinned(user, id)) | 2047 | if (is_pinned(user, id)) |
| 1918 | ret = -3; | 2048 | ret = -3; |
| 1919 | else | 2049 | else |
| @@ -2587,6 +2717,8 @@ void static_put(snac *snac, const char *id, const char *data, int size) | |||
| 2587 | if (fn && (f = fopen(fn, "wb")) != NULL) { | 2717 | if (fn && (f = fopen(fn, "wb")) != NULL) { |
| 2588 | fwrite(data, size, 1, f); | 2718 | fwrite(data, size, 1, f); |
| 2589 | fclose(f); | 2719 | fclose(f); |
| 2720 | |||
| 2721 | strip_media(fn); | ||
| 2590 | } | 2722 | } |
| 2591 | } | 2723 | } |
| 2592 | 2724 | ||
| @@ -3513,7 +3645,7 @@ void enqueue_output(snac *snac, const xs_dict *msg, | |||
| 3513 | const xs_str *inbox, int retries, int p_status) | 3645 | const xs_str *inbox, int retries, int p_status) |
| 3514 | /* enqueues an output message to an inbox */ | 3646 | /* enqueues an output message to an inbox */ |
| 3515 | { | 3647 | { |
| 3516 | if (xs_startswith(inbox, snac->actor)) { | 3648 | if (is_msg_mine(snac, inbox)) { |
| 3517 | snac_debug(snac, 1, xs_str_new("refusing enqueue to myself")); | 3649 | snac_debug(snac, 1, xs_str_new("refusing enqueue to myself")); |
| 3518 | return; | 3650 | return; |
| 3519 | } | 3651 | } |
| @@ -4041,7 +4173,7 @@ void delete_purged_posts(snac *user, int days) | |||
| 4041 | if (xs_is_dict(msg)) { | 4173 | if (xs_is_dict(msg)) { |
| 4042 | const char *id = xs_dict_get(msg, "id"); | 4174 | const char *id = xs_dict_get(msg, "id"); |
| 4043 | 4175 | ||
| 4044 | if (xs_is_string(id) && xs_startswith(id, user->actor)) { | 4176 | if (xs_is_string(id) && is_msg_mine(user, id)) { |
| 4045 | xs *d_msg = msg_delete(user, id); | 4177 | xs *d_msg = msg_delete(user, id); |
| 4046 | 4178 | ||
| 4047 | enqueue_message(user, d_msg); | 4179 | enqueue_message(user, d_msg); |