diff options
Diffstat (limited to 'mastoapi.c')
| -rw-r--r-- | mastoapi.c | 342 |
1 files changed, 270 insertions, 72 deletions
@@ -1,5 +1,5 @@ /* snac - A simple, minimalistic ActivityPub instance */ -/* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ +/* copyright (c) 2022 - 2026 grunfink et al. / MIT license */ #ifndef NO_MASTODON_API @@ -1158,6 +1158,103 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) st = xs_dict_append(st, "tags", htl); st = xs_dict_append(st, "emojis", eml); } + { + xs *rl = object_get_emoji_reacts(id); + xs *frl = xs_list_new(); /* final */ + xs *sfrl = xs_dict_new(); /* seen */ + int c = 0; + const char *v; + + while (xs_list_next(rl, &v, &c)) { + xs *msg = NULL; + if (valid_status(object_get_by_md5(v, &msg))) { + const char *content = xs_dict_get(msg, "content"); + const char *actor = xs_dict_get(msg, "actor"); + const xs_list *contentl = xs_dict_get(sfrl, content); + + if ((snac && is_muted(snac, actor)) || is_instance_blocked(actor)) + continue; + + /* NOTE: idk when there are no actor, but i encountered that bug. + * Probably because of one of my previous attempts. + * Keeping this just in case, can remove later */ + const char *me = actor && snac && strcmp(actor, snac->actor) == 0 ? + xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE); + int count = 1; + + if (contentl) { + count = atoi(xs_list_get(contentl, 0)) + 1; + if (strncmp(xs_list_get(contentl, 1), xs_stock(XSTYPE_TRUE), 1) == 0) + me = xs_stock(XSTYPE_TRUE); + } + + xs *fl = xs_list_new(); + xs *c1 = xs_fmt("%d", count); + fl = xs_list_append(fl, c1, me); + sfrl = xs_dict_append(sfrl, content, fl); + } + } + + c = 0; + + while (xs_list_next(rl, &v, &c)) { + xs *msg = NULL; + if (valid_status(object_get_by_md5(v, &msg))) { + xs *d1 = xs_dict_new(); + + const xs_dict *icon = xs_dict_get(xs_list_get(xs_dict_get(msg, "tag"), 0), "icon"); + const char *o_url = xs_dict_get(icon, "url"); + const char *name = xs_dict_get(msg, "content"); + const char *actor = xs_dict_get(msg, "actor"); + + xs *nm = xs_dup(name); + xs *url = NULL; + + if (!xs_is_null(o_url)) { + if (actor && snac && !strcmp(actor, snac->actor)) + url = make_url(o_url, NULL, 1); + else + url = xs_dup(o_url); + } + + xs *accounts = xs_list_new(); + if (actor) { + xs *d2 = xs_dict_new(); + object_get(actor, &d2); + xs *e_acct = mastoapi_account(snac, d2); + accounts = xs_list_append(accounts, e_acct); + } + + const xs_list *item = xs_dict_get(sfrl, nm); + const xs_str *nb = xs_list_get(item, 0); + const xs_val *me = xs_list_get(item, 1); + if (item == NULL) + continue; + + if (nm && strcmp(nm, "")) { + if (url && strcmp(url, "")) { + d1 = xs_dict_append(d1, "name", nm); + d1 = xs_dict_append(d1, "shortcode", nm); + d1 = xs_dict_append(d1, "accounts", accounts); + d1 = xs_dict_append(d1, "me", me); + d1 = xs_dict_append(d1, "url", url); + d1 = xs_dict_append(d1, "static_url", url); + d1 = xs_dict_append(d1, "visible_in_picker", xs_stock(XSTYPE_TRUE)); + d1 = xs_dict_append(d1, "count", nb); + } else { + d1 = xs_dict_append(d1, "name", nm); + d1 = xs_dict_append(d1, "count", nb); + d1 = xs_dict_append(d1, "me", me); + d1 = xs_dict_append(d1, "visible_in_picker", xs_stock(XSTYPE_TRUE)); + } + sfrl = xs_dict_del(sfrl, nm); + frl = xs_list_append(frl, d1); + } + } + } + + st = xs_dict_append(st, "reactions", frl); + } xs_free(idx); xs_free(ixc); @@ -1712,6 +1809,37 @@ xs_list *mastoapi_account_lists(snac *user, const char *uid) } +xs_list *build_childrens(const xs_dict *msg, snac *snac1) { + xs_list *ret = xs_list_new(); + xs *children = object_children(xs_dict_get(msg, "id")); + char *p = children; + const xs_str *v; + + while (xs_list_iter(&p, &v)) { + xs *m2 = NULL; + + if (valid_status(timeline_get_by_md5(snac1, v, &m2))) { + if (xs_is_null(xs_dict_get(m2, "name"))) { + xs *st = mastoapi_status(snac1, m2); + + if (st) { + /* childrens children */ + xs *childs = build_childrens(m2, snac1); + ret = xs_list_append(ret, st); + if (xs_list_len(childs)) { + char *p2 = childs; + while (xs_list_iter(&p2, &v)) + ret = xs_list_append(ret, v); + + } + } + } + } + } + return ret; +} + + int mastoapi_get_handler(const xs_dict *req, const char *q_path, char **body, int *b_size, char **ctype, xs_str **link) { @@ -1892,60 +2020,64 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, } else if (strcmp(opt, "statuses") == 0) { /** **/ - /* the public list of posts of a user */ - const char *limit_s = xs_dict_get(args, "limit"); - const char *o_max_id = xs_dict_get(args, "max_id"); - int limit = limit_s ? atoi(limit_s) : 20; - xs *max_id = o_max_id ? xs_tolower_i(xs_dup(o_max_id)) : NULL; + if (logged_in || xs_type(xs_dict_get(snac2.config, "private")) == XSTYPE_FALSE) { + /* the public list of posts of a user */ + const char *limit_s = xs_dict_get(args, "limit"); + const char *o_max_id = xs_dict_get(args, "max_id"); + int limit = limit_s ? atoi(limit_s) : 20; + xs *max_id = o_max_id ? xs_tolower_i(xs_dup(o_max_id)) : NULL; - srv_debug(1, xs_fmt("account statuses: max_id=%s limit=%d", max_id ? max_id : "(null)", limit)); + srv_debug(1, xs_fmt("account statuses: max_id=%s limit=%d", max_id ? max_id : "(null)", limit)); - xs *timeline = timeline_simple_list(&snac2, "public", 0, 256, NULL); - xs_list *p = timeline; - const xs_str *v; - xs_set seen; - int cnt = 0; - int skip_until_max = max_id != NULL; + xs *timeline = timeline_simple_list(&snac2, "public", 0, 256, NULL); + xs_list *p = timeline; + const xs_str *v; + xs_set seen; + int cnt = 0; + int skip_until_max = max_id != NULL; - out = xs_list_new(); - xs_set_init(&seen); + out = xs_list_new(); + xs_set_init(&seen); - while (xs_list_iter(&p, &v) && cnt < limit) { - xs *msg = NULL; + while (xs_list_iter(&p, &v) && cnt < limit) { + xs *msg = NULL; - if (valid_status(timeline_get_by_md5(&snac2, v, &msg))) { - const char *msg_id = xs_dict_get(msg, "id"); + if (valid_status(timeline_get_by_md5(&snac2, v, &msg))) { + const char *msg_id = xs_dict_get(msg, "id"); - /* add only posts by the author */ - if (!xs_is_null(msg_id) && - strcmp(xs_dict_get(msg, "type"), "Note") == 0 && - xs_startswith(xs_dict_get(msg, "id"), snac2.actor) && is_msg_public(msg)) { + /* add only posts by the author */ + if (!xs_is_null(msg_id) && + strcmp(xs_dict_get(msg, "type"), "Note") == 0 && + is_msg_mine(&snac2, xs_dict_get(msg, "id")) && is_msg_public(msg)) { - /* if max_id is set, skip entries until we find it */ - if (skip_until_max) { - xs *mid = mastoapi_id(msg); - if (strcmp(mid, max_id) == 0) { - skip_until_max = 0; - srv_debug(2, xs_fmt("account statuses: found max_id, starting from next post")); + /* if max_id is set, skip entries until we find it */ + if (skip_until_max) { + xs *mid = mastoapi_id(msg); + if (strcmp(mid, max_id) == 0) { + skip_until_max = 0; + srv_debug(2, xs_fmt("account statuses: found max_id, starting from next post")); + } + continue; } - continue; - } - /* deduplicate by message id */ - if (xs_set_add(&seen, msg_id) == 1) { - xs *st = mastoapi_status(&snac2, msg); + /* deduplicate by message id */ + if (xs_set_add(&seen, msg_id) == 1) { + xs *st = mastoapi_status(&snac2, msg); - if (st) { - out = xs_list_append(out, st); - cnt++; + if (st) { + out = xs_list_append(out, st); + cnt++; + } } } } } - } - srv_debug(1, xs_fmt("account statuses: returning %d posts (requested %d)", cnt, limit)); - xs_set_free(&seen); + srv_debug(1, xs_fmt("account statuses: returning %d posts (requested %d)", cnt, limit)); + xs_set_free(&seen); + } + else + status = HTTP_STATUS_UNAUTHORIZED; } else if (strcmp(opt, "featured_tags") == 0) { @@ -2202,15 +2334,24 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, if (noti == NULL) continue; + const xs_dict *tag = xs_list_get(xs_dict_get_path(noti, "msg.tag"), 0); + const char *type = xs_dict_get(noti, "type"); const char *utype = xs_dict_get(noti, "utype"); const char *objid = xs_dict_get(noti, "objid"); const char *id = xs_dict_get(noti, "id"); const char *actid = xs_dict_get(noti, "actor"); + + int isEmoji = 0; + xs *fid = xs_replace(id, ".", ""); xs *actor = NULL; xs *entry = NULL; + if (tag) { + isEmoji = strcmp(xs_dict_get(tag, "type"), "Emoji") ? 0 : 1; + } + if (!valid_status(actor_get(actid, &actor))) continue; @@ -2234,9 +2375,12 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, } /* convert the type */ - if (strcmp(type, "Like") == 0 || strcmp(type, "EmojiReact") == 0) + if (strcmp(type, "Like") == 0 && !isEmoji) type = "favourite"; else + if (isEmoji || strcmp(type, "EmojiReact") == 0) + type = "reaction"; + else if (strcmp(type, "Announce") == 0) type = "reblog"; else @@ -2277,8 +2421,31 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, if (strcmp(type, "follow") != 0 && !xs_is_null(objid)) { xs *st = mastoapi_status(&snac1, entry); - if (st) + if (st) { mn = xs_dict_append(mn, "status", st); + + if (strcmp(type, "reaction") == 0 && !xs_is_null(objid)) { + const char *eid = NULL; + const char *url = NULL; + int utf = 0; + + const xs_dict *tag = xs_list_get(xs_dict_get_path(noti, "msg.tag"), 0); + const char *content = xs_dict_get_path(noti, "msg.content"); + + url = xs_dict_get(xs_dict_get(tag, "icon"), "url"); + eid = xs_dict_get(tag, "name"); + + if (eid && url) { + mn = xs_dict_append(mn, "emoji", eid); + mn = xs_dict_append(mn, "emoji_url", url); + } + + if (xs_is_emoji((utf = xs_utf8_dec(&content)))) { + xs *s1 = xs_fmt("&#%d;", utf); + mn = xs_dict_append(mn, "name", s1); + } + } + } } out = xs_list_append(out, mn); @@ -2480,19 +2647,33 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, if (strcmp(cmd, "/v1/custom_emojis") == 0) { /** **/ xs *emo = emojis(); xs *list = xs_list_new(); - int c = 0; const xs_str *k; const xs_val *v; - while(xs_dict_next(emo, &k, &v, &c)) { + xs_dict_foreach(emo, k, v) { xs *current = xs_dict_new(); - if (xs_startswith(v, "https://") && xs_startswith((xs_mime_by_ext(v)), "image/")) { + if ((xs_startswith(v, "https://") && xs_startswith((xs_mime_by_ext(v)), "image/")) || xs_type(v) == XSTYPE_DICT) { /* remove first and last colon */ - xs *shortcode = xs_replace(k, ":", ""); - current = xs_dict_append(current, "shortcode", shortcode); - current = xs_dict_append(current, "url", v); - current = xs_dict_append(current, "static_url", v); - current = xs_dict_append(current, "visible_in_picker", xs_stock(XSTYPE_TRUE)); - list = xs_list_append(list, current); + if (xs_type(v) == XSTYPE_DICT) { + const char *v2; + const char *cat = k; + xs_dict_foreach(v, k, v2) { + xs *shortcode = xs_replace(k, ":", ""); + current = xs_dict_append(current, "shortcode", shortcode); + current = xs_dict_append(current, "url", v2); + current = xs_dict_append(current, "static_url", v2); + current = xs_dict_append(current, "visible_in_picker", xs_stock(XSTYPE_TRUE)); + current = xs_dict_append(current, "category", cat); + list = xs_list_append(list, current); + } + } + else { + xs *shortcode = xs_replace(k, ":", ""); + current = xs_dict_append(current, "shortcode", shortcode); + current = xs_dict_append(current, "url", v); + current = xs_dict_append(current, "static_url", v); + current = xs_dict_append(current, "visible_in_picker", xs_stock(XSTYPE_TRUE)); + list = xs_list_append(list, current); + } } } *body = xs_json_dumps(list, 0); @@ -2594,6 +2775,11 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, "\"max_expiration\":2629746," "\"max_options\":8,\"min_expiration\":300}"); cfg = xs_dict_append(cfg, "polls", d14); + + + xs *d15 = xs_json_loads("{\"max_reactions\":50}"); + cfg = xs_dict_append(cfg, "reactions", d15); + } ins = xs_dict_append(ins, "configuration", cfg); @@ -2680,8 +2866,6 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, /* return ancestors and children */ xs *anc = xs_list_new(); xs *des = xs_list_new(); - xs_list *p; - const xs_str *v; char pid[MD5_HEX_SIZE]; /* build the [grand]parent list, moving up */ @@ -2701,21 +2885,9 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, } /* build the children list */ - xs *children = object_children(xs_dict_get(msg, "id")); - p = children; - - while (xs_list_iter(&p, &v)) { - xs *m2 = NULL; - - if (valid_status(timeline_get_by_md5(&snac1, v, &m2))) { - if (xs_is_null(xs_dict_get(m2, "name"))) { - xs *st = mastoapi_status(&snac1, m2); - - if (st) - des = xs_list_append(des, st); - } - } - } + xs *childs = build_childrens(msg, &snac1); + if (xs_list_len(childs) > 0) + des = xs_list_cat(des, childs); out = xs_dict_new(); out = xs_dict_append(out, "ancestors", anc); @@ -3044,7 +3216,10 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, xs *app = xs_dict_new(); xs *id = xs_replace_i(tid(0), ".", ""); xs *csec = random_str(); - xs *vkey = random_str(); + xs *vkey = xs_dup(xs_dict_get(srv_config, "vkey")); + if (vkey == NULL) + vkey = random_str(); + xs *cid = NULL; /* pick a non-existent random cid */ @@ -3216,7 +3391,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, if (n_msg != NULL) { enqueue_message(&snac, n_msg); - timeline_admire(&snac, xs_dict_get(n_msg, "object"), snac.actor, 1); + timeline_admire(&snac, xs_dict_get(n_msg, "object"), snac.actor, 1, msg); out = mastoapi_status(&snac, msg); } @@ -3232,12 +3407,35 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, } } else + if (strcmp(op, "react") == 0) { /** **/ + const char *eid = xs_list_get(l, 5); + xs *n_msg = msg_emoji_init(&snac, id, eid); + if (n_msg) + out = mastoapi_status(&snac, n_msg); + } + else + if (strcmp(op, "unreact") == 0) { /** **/ + const char *eid = xs_list_get(l, 5); + xs *content = xs_fmt("%s", eid); + + if (eid) { + xs *n_msg = msg_emoji_unreact(&snac, id, content); + + if (n_msg != NULL) { + enqueue_message(&snac, n_msg); + + out = mastoapi_status(&snac, msg); + } + } + } + + else if (strcmp(op, "reblog") == 0) { /** **/ xs *n_msg = msg_admiration(&snac, id, "Announce"); if (n_msg != NULL) { enqueue_message(&snac, n_msg); - timeline_admire(&snac, xs_dict_get(n_msg, "object"), snac.actor, 0); + timeline_admire(&snac, xs_dict_get(n_msg, "object"), snac.actor, 0, msg); out = mastoapi_status(&snac, msg); } @@ -3824,7 +4022,7 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path, if (valid_status(object_get_by_md5(p, &obj))) { const char *id = xs_dict_get(obj, "id"); - if (xs_is_string(id) && xs_startswith(id, snac.actor)) { + if (xs_is_string(id) && is_msg_mine(&snac, id)) { xs *out = mastoapi_status(&snac, obj); xs *msg = msg_delete(&snac, id); |