From dcf5acaf538924aab532ea95bb311e4b80538856 Mon Sep 17 00:00:00 2001 From: byte Date: Sun, 14 Sep 2025 00:30:00 +0200 Subject: implementing visibility scopes --- activitypub.c | 120 +++++++++++++++++++++++++++++++++++++++--------- data.c | 10 ++-- html.c | 144 +++++++++++++++++++++++++++++++++++++++++++++------------- main.c | 13 ++++-- mastoapi.c | 23 +++++++--- snac.h | 8 ++++ 6 files changed, 252 insertions(+), 66 deletions(-) diff --git a/activitypub.c b/activitypub.c index 05c3fb1..1efd9ec 100644 --- a/activitypub.c +++ b/activitypub.c @@ -569,14 +569,21 @@ xs_list *recipient_list(snac *snac, const xs_dict *msg, int expand_public) } while (xs_list_iter(&l, &v)) { - if (expand_public && strcmp(v, public_address) == 0) { - /* iterate the followers and add them */ - xs *fwers = follower_list(snac); - const char *actor; - - char *p = fwers; - while (xs_list_iter(&p, &actor)) - xs_set_add(&rcpts, actor); + if (expand_public) { + if (strcmp(v, public_address) == 0 || + /* check if it's a followers collection URL */ + (xs_type(v) == XSTYPE_STRING && + strcmp(v, xs_fmt("%s/followers", snac->actor)) == 0) || + (xs_type(v) == XSTYPE_LIST && + xs_list_in(v, xs_fmt("%s/followers", snac->actor)) != -1)) { + /* iterate the followers and add them */ + xs *fwers = follower_list(snac); + const char *actor; + + char *p = fwers; + while (xs_list_iter(&p, &actor)) + xs_set_add(&rcpts, actor); + } } else xs_set_add(&rcpts, v); @@ -1840,8 +1847,7 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, xs_list *p; const xs_val *v; - /* FIXME: implement scope 3 */ - int priv = scope == 1; + const int priv = (scope == SCOPE_MENTIONED || scope == SCOPE_FOLLOWERS); if (rcpts == NULL) to = xs_list_new(); @@ -1901,9 +1907,13 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, if ((v = xs_dict_get(p_msg, "conversation"))) msg = xs_dict_append(msg, "conversation", v); - /* if this message is public, ours will also be */ - if (!priv && is_msg_public(p_msg) && xs_list_in(to, public_address) == -1) + /* if this message is public or unlisted, ours will also be */ + const int orig_scope = get_msg_visibility(p_msg); + if (!priv && orig_scope == SCOPE_PUBLIC && xs_list_in(to, public_address) == -1) to = xs_list_append(to, public_address); + else + if (!priv && orig_scope == SCOPE_UNLISTED && xs_list_in(cc, public_address) == -1) + cc = xs_list_append(cc, public_address); } irt = xs_dup(in_reply_to); @@ -1947,28 +1957,50 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, if (ctxt == NULL) ctxt = xs_fmt("%s#ctxt", id); - /* add all mentions to the cc */ + /* add all mentions to the appropriate field */ p = tag; while (xs_list_iter(&p, &v)) { if (xs_type(v) == XSTYPE_DICT) { const char *t; if (!xs_is_null(t = xs_dict_get(v, "type")) && strcmp(t, "Mention") == 0) { - if (!xs_is_null(t = xs_dict_get(v, "href"))) - cc = xs_list_append(cc, t); + if (!xs_is_null(t = xs_dict_get(v, "href"))) { + if (scope == SCOPE_MENTIONED) { + /* for DMs, mentions go to 'to' */ + to = xs_list_append(to, t); + } else { + /* for other visibility levels, mentions go to 'cc' */ + cc = xs_list_append(cc, t); + } + } } } } - if (scope == 2) { - /* Mastodon's "quiet public": add public address to cc */ + if (scope == SCOPE_UNLISTED) { + /* Mastodon's "quiet public": remove from to, add public address to cc */ + int idx; + if ((idx = xs_list_in(to, public_address)) != -1) + to = xs_list_del(to, idx); if (xs_list_in(cc, public_address) == -1) cc = xs_list_append(cc, public_address); } else - /* no recipients? must be for everybody */ - if (!priv && xs_list_len(to) == 0) - to = xs_list_append(to, public_address); + if (scope == SCOPE_FOLLOWERS) { + /* followers-only: add followers collection to to */ + xs *followers_url = xs_fmt("%s/followers", snac->actor); + if (xs_list_in(to, followers_url) == -1) + to = xs_list_append(to, followers_url); + } + else + if (scope == SCOPE_PUBLIC) { + /* public: ensure public address is in to and not in cc */ + int idx; + if ((idx = xs_list_in(cc, public_address)) != -1) + cc = xs_list_del(cc, idx); + if (xs_list_in(to, public_address) == -1) + to = xs_list_append(to, public_address); + } /* delete all cc recipients that also are in the to */ p = to; @@ -2113,6 +2145,42 @@ xs_dict *msg_question(snac *user, const char *content, xs_list *attach, return msg; } +int get_msg_visibility(const xs_dict *msg) +/* determine visibility from message based on CC, TO and /followers mark */ +{ + const xs_val *to = xs_dict_get(msg, "to"); + const xs_val *cc = xs_dict_get(msg, "cc"); + + /* check if it's unlisted (public in cc but not in to) */ + int pub_in_to = 0; + int pub_in_cc = 0; + + if ((xs_type(to) == XSTYPE_STRING && strcmp(to, public_address) == 0) || + (xs_type(to) == XSTYPE_LIST && xs_list_in(to, public_address) != -1)) + pub_in_to = 1; + + if ((xs_type(cc) == XSTYPE_STRING && strcmp(cc, public_address) == 0) || + (xs_type(cc) == XSTYPE_LIST && xs_list_in(cc, public_address) != -1)) + pub_in_cc = 1; + + if (!pub_in_to && pub_in_cc) { + return SCOPE_UNLISTED; + } + if (pub_in_to && !pub_in_cc) { + return SCOPE_PUBLIC; + } + + xs *followers_url = xs_fmt("%s/followers", xs_dict_get(msg, "attributedTo")); + + if ((xs_type(to) == XSTYPE_STRING && strcmp(to, followers_url) == 0) || + (xs_type(to) == XSTYPE_LIST && xs_list_in(to, followers_url) != -1)) + return SCOPE_FOLLOWERS; + else + return SCOPE_MENTIONED; + /* should be unreachable, someone violated the protocol. */ + /* better treat it as followers-only to make sure we don't leak information.*/ + return SCOPE_FOLLOWERS; +} int update_question(snac *user, const char *id) /* updates the poll counts */ @@ -3156,7 +3224,7 @@ void process_queue_item(xs_dict *q_item) } if (instance_failure(inbox, 0)) { - srv_debug(1, xs_fmt("too many failures for instance %s", inbox)); + srv_debug(1, xs_fmt("output message error: too many failures for instance %s", inbox)); return; } @@ -3502,6 +3570,8 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, p_path = xs_list_get(l, 2); + const xs_dict *q_vars = xs_dict_get(req, "q_vars"); + *ctype = "application/activity+json"; int show_contact_metrics = xs_is_true(xs_dict_get(snac.config, "show_contact_metrics")); @@ -3595,7 +3665,15 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, if (!is_msg_public(obj)) status = HTTP_STATUS_NOT_FOUND; else + if (xs_dict_get(q_vars, "page")) msg = msg_replies(&snac, id, 1); + else { + const xs_dict *replies = xs_dict_get(obj, "replies"); + if (xs_is_dict(replies)) { + msg = xs_dup(replies); + msg = xs_dict_set(msg, "@context", "https:/""/www.w3.org/ns/activitystreams"); + } + } } else status = HTTP_STATUS_NOT_FOUND; diff --git a/data.c b/data.c index 4a16a82..9ef0d42 100644 --- a/data.c +++ b/data.c @@ -1456,15 +1456,16 @@ void timeline_update_indexes(snac *snac, const char *id) xs *msg = NULL; if (valid_status(object_get(id, &msg))) { + const int scope = get_msg_visibility(msg); /* if its ours and is public, also store in public */ - if (is_msg_public(msg)) { + if (scope == SCOPE_PUBLIC) { if (object_user_cache_add(snac, id, "public") >= 0) { /* also add it to the instance public timeline */ xs *ipt = xs_fmt("%s/public.idx", srv_basedir); index_add(ipt, id); } else - srv_debug(1, xs_fmt("Not added to public instance index %s", id)); + srv_debug(1, xs_fmt("Not added to public instance index %s, visibility %d", id, scope)); } else /* also add it to public, it will be discarded later */ @@ -2222,7 +2223,10 @@ void tag_index(const char *id, const xs_dict *obj) const xs_list *tags = xs_dict_get(obj, "tag"); xs *md5_id = xs_md5_hex(id, strlen(id)); - if (is_msg_public(obj) && xs_type(tags) == XSTYPE_LIST && xs_list_len(tags) > 0) { + if (get_msg_visibility(obj) != SCOPE_PUBLIC) + return; + + if (xs_type(tags) == XSTYPE_LIST && xs_list_len(tags) > 0) { xs *g_tag_dir = xs_fmt("%s/tag", srv_basedir); mkdirx(g_tag_dir); diff --git a/html.c b/html.c index ed9e739..2fca837 100644 --- a/html.c +++ b/html.c @@ -165,7 +165,7 @@ xs_str *format_text_with_emoji(snac *user, const char *text, int ems, const char xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, - const char *udate, const char *url, int priv, + const char *udate, const char *url, int scope, int in_people, const char *proxy, const char *lang, const char *md5) { @@ -249,12 +249,35 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, xs_html_raw("🤝"))); } - if (priv) { - xs_html_add(actor_icon, - xs_html_text(" "), - xs_html_tag("span", - xs_html_attr("title", "private"), - xs_html_raw("🔒"))); + if (scope != -1) { + if (scope == SCOPE_FOLLOWERS) { + xs_html_add(actor_icon, + xs_html_text(" "), + xs_html_tag("span", + xs_html_attr("title", "followers"), + xs_html_raw("🔒"))); // emoji of a lock + } + else if (scope == SCOPE_PUBLIC) { + xs_html_add(actor_icon, + xs_html_text(" "), + xs_html_tag("span", + xs_html_attr("title", "public"), + xs_html_raw("🌐"))); // emoji of a globe + } + else if (scope == SCOPE_UNLISTED) { + xs_html_add(actor_icon, + xs_html_text(" "), + xs_html_tag("span", + xs_html_attr("title", "unlisted"), + xs_html_raw("🔓"))); // emoji of an unlocked lock + } + else if (scope == SCOPE_MENTIONED) { + xs_html_add(actor_icon, + xs_html_text(" "), + xs_html_tag("span", + xs_html_attr("title", "mentioned"), + xs_html_raw("✉️"))); // emoji of a mail + } } if (xs_is_null(date)) { @@ -342,18 +365,17 @@ xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg, const char *date = NULL; const char *udate = NULL; const char *url = NULL; - int priv = 0; const char *type = xs_dict_get(msg, "type"); + const int scope = get_msg_visibility(msg); if (xs_match(type, POSTLIKE_OBJECT_TYPE)) url = xs_dict_get(msg, "id"); - priv = !is_msg_public(msg); date = xs_dict_get(msg, "published"); udate = xs_dict_get(msg, "updated"); - actor_icon = html_actor_icon(user, actor, date, udate, url, priv, 0, proxy, lang, md5); + actor_icon = html_actor_icon(user, actor, date, udate, url, scope, 0, proxy, lang, md5); } return actor_icon; @@ -365,7 +387,7 @@ xs_html *html_note(snac *user, const char *summary, const char *ta_plh, const char *ta_content, const char *edit_id, const char *actor_id, const xs_val *cw_yn, const char *cw_text, - const xs_val *mnt_only, const char *redir, + const int scope, const char *redir, const char *in_reply_to, int poll, const xs_list *att_files, const xs_list *att_alt_texts, int is_draft, const char *published, @@ -417,14 +439,44 @@ xs_html *html_note(snac *user, const char *summary, xs_html_attr("name", "to"), xs_html_attr("value", actor_id))); else { - /* no actor_id; ask for mentioned_only */ xs_html_add(form, - xs_html_tag("p", NULL), - xs_html_text(L("Only for mentioned people: ")), - xs_html_sctag("input", - xs_html_attr("type", "checkbox"), - xs_html_attr("name", "mentioned_only"), - xs_html_attr(xs_type(mnt_only) == XSTYPE_TRUE ? "checked" : "", NULL))); + xs_html_tag("p", + xs_html_text(L("Visibility: ")), + xs_html_tag("label", + xs_html_sctag("input", + xs_html_attr("type", "radio"), + xs_html_attr("name", "visibility"), + xs_html_attr("value", "public"), + xs_html_attr(scope == SCOPE_PUBLIC ? "checked" : "", NULL)), + xs_html_text(" "), + xs_html_text(L("Public"))), + xs_html_text(" "), + xs_html_tag("label", + xs_html_sctag("input", + xs_html_attr("type", "radio"), + xs_html_attr("name", "visibility"), + xs_html_attr("value", "unlisted"), + xs_html_attr(scope == SCOPE_UNLISTED ? "checked" : "", NULL)), + xs_html_text(" "), + xs_html_text(L("Unlisted"))), + xs_html_text(" "), + xs_html_tag("label", + xs_html_sctag("input", + xs_html_attr("type", "radio"), + xs_html_attr("name", "visibility"), + xs_html_attr("value", "followers"), + xs_html_attr(scope == SCOPE_FOLLOWERS ? "checked" : "", NULL)), + xs_html_text(" "), + xs_html_text(L("Followers-only"))), + xs_html_text(" "), + xs_html_tag("label", + xs_html_sctag("input", + xs_html_attr("type", "radio"), + xs_html_attr("name", "visibility"), + xs_html_attr("value", "mentioned"), + xs_html_attr(scope == SCOPE_MENTIONED ? "checked" : "", NULL)), + xs_html_text(" "), + xs_html_text(L("Direct Message"))))); } if (redir) @@ -1236,7 +1288,7 @@ xs_html *html_top_controls(snac *user) L("What's on your mind?"), "", NULL, NULL, xs_stock(XSTYPE_FALSE), "", - xs_stock(XSTYPE_FALSE), NULL, + SCOPE_PUBLIC, NULL, NULL, 1, NULL, NULL, 0, NULL, NULL), /** operations **/ @@ -1942,6 +1994,8 @@ xs_html *html_entry_controls(snac *user, const char *actor, xs_dict_next(cmap, ¬e_lang, &dummy, &c); } + const int edit_scope = get_msg_visibility(msg); + xs_html_add(controls, xs_html_tag("div", xs_html_tag("p", NULL), html_note(user, L("Edit..."), @@ -1949,7 +2003,7 @@ xs_html *html_entry_controls(snac *user, const char *actor, "", prev_src, id, NULL, xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"), - xs_stock(is_msg_public(msg) ? XSTYPE_FALSE : XSTYPE_TRUE), redir, + edit_scope, redir, NULL, 0, att_files, att_alt_texts, is_draft(user, id), xs_dict_get(msg, "published"), note_lang)), xs_html_tag("p", NULL)); @@ -1962,6 +2016,8 @@ xs_html *html_entry_controls(snac *user, const char *actor, xs *form_id = xs_fmt("%s_reply_form", md5); xs *redir = xs_fmt("%s_entry", md5); + const int scope = get_msg_visibility(msg); + xs_html_add(controls, xs_html_tag("div", xs_html_tag("p", NULL), html_note(user, L("Reply..."), @@ -1969,7 +2025,7 @@ xs_html *html_entry_controls(snac *user, const char *actor, "", ct, NULL, NULL, xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"), - xs_stock(is_msg_public(msg) ? XSTYPE_FALSE : XSTYPE_TRUE), redir, + scope, redir, id, 0, NULL, NULL, 0, NULL, NULL)), xs_html_tag("p", NULL)); } @@ -2061,6 +2117,12 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, xs_html_attr("name", s1))); } + /* don't show followers-only notes from not followed users */ + if (user && get_msg_visibility(msg) == SCOPE_FOLLOWERS && + strcmp(user->actor, actor) != 0 && following_check(user, actor) == 0) { + return NULL; + } + if ((user == NULL || strcmp(actor, user->actor) != 0) && !valid_status(actor_get(actor, NULL))) { @@ -2275,7 +2337,6 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, has_title = 1; } - snac_content = xs_html_tag("div", NULL); } @@ -3089,8 +3150,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, xs_html_text(title))); } - xs_html_add(body, - posts); + xs_html_add(body, posts); int mark_shown = 0; @@ -3130,8 +3190,9 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, if (user == NULL && is_msg_from_private_user(msg)) continue; - /* is this message a non-public reply? */ - if (user != NULL && !is_msg_public(msg)) { + const int scope = get_msg_visibility(msg); + if (user != NULL && scope != SCOPE_PUBLIC){ + /* is this message a non-public reply? */ const char *irt = get_in_reply_to(msg); /* is it a reply to something not in the storage? */ @@ -3146,6 +3207,14 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, } } } + /* hide non-public posts from /instance view */ + if (page != NULL && strcmp(page, "/instance") == 0 && scope != SCOPE_PUBLIC){ + continue; + } + /* hide non-public posts viewed from outside */ + if (read_only && scope != SCOPE_PUBLIC){ + continue; + } xs_html *entry = html_entry(user, msg, read_only, 0, v, (user && !hide_children) ? 0 : 1); @@ -3256,7 +3325,7 @@ xs_html *html_people_list(snac *user, xs_list *list, const char *header, const c xs_html_tag("div", xs_html_attr("class", "snac-post-header"), html_actor_icon(user, actor, xs_dict_get(actor, "published"), - NULL, NULL, 0, 1, proxy, NULL, NULL))); + NULL, NULL, -1, 1, proxy, NULL, NULL))); /* content (user bio) */ const char *c = xs_dict_get(actor, "summary"); @@ -3357,7 +3426,7 @@ xs_html *html_people_list(snac *user, xs_list *list, const char *header, const c "", "", NULL, actor_id, xs_stock(XSTYPE_FALSE), "", - xs_stock(XSTYPE_FALSE), NULL, + SCOPE_MENTIONED, NULL, NULL, 0, NULL, NULL, 0, NULL, NULL), xs_html_tag("p", NULL)); @@ -3564,7 +3633,7 @@ xs_str *html_notifications(snac *user, int skip, int show) xs_html_add(entry, xs_html_tag("div", xs_html_attr("class", "snac-post"), - html_actor_icon(user, actor, NULL, NULL, NULL, 0, 0, proxy, NULL, NULL))); + html_actor_icon(user, actor, NULL, NULL, NULL, -1, 0, proxy, NULL, NULL))); } else if (strcmp(type, "Move") == 0) { @@ -3578,7 +3647,7 @@ xs_str *html_notifications(snac *user, int skip, int show) xs_html_add(entry, xs_html_tag("div", xs_html_attr("class", "snac-post"), - html_actor_icon(user, old_actor, NULL, NULL, NULL, 0, 0, proxy, NULL, NULL))); + html_actor_icon(user, old_actor, NULL, NULL, NULL, -1, 0, proxy, NULL, NULL))); } } } @@ -4445,7 +4514,18 @@ int html_post_handler(const xs_dict *req, const char *q_path, const char *post_date = xs_dict_get_def(p_vars, "post_date", ""); const char *post_time = xs_dict_get_def(p_vars, "post_time", ""); const char *post_lang = xs_dict_get(p_vars, "post_lang"); - int priv = !xs_is_null(xs_dict_get(p_vars, "mentioned_only")); + const char *visibility = xs_dict_get(p_vars, "visibility"); + int scope = SCOPE_PUBLIC; /* default to public */ + if (!xs_is_null(visibility)) { + if (strcmp(visibility, "unlisted") == 0) + scope = SCOPE_UNLISTED; + else + if (strcmp(visibility, "followers") == 0) + scope = SCOPE_FOLLOWERS; + else + if (strcmp(visibility, "mentioned") == 0) + scope = SCOPE_MENTIONED; + } int store_as_draft = !xs_is_null(xs_dict_get(p_vars, "is_draft")); xs *attach_list = xs_list_new(); @@ -4528,7 +4608,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, enqueue_close_question(&snac, xs_dict_get(msg, "id"), end_secs); } else - msg = msg_note(&snac, content_2, to, in_reply_to, attach_list, priv, post_lang, NULL); + msg = msg_note(&snac, content_2, to, in_reply_to, attach_list, scope, post_lang, NULL); if (sensitive != NULL) { msg = xs_dict_set(msg, "sensitive", xs_stock(XSTYPE_TRUE)); diff --git a/main.c b/main.c index f119197..46f88d2 100644 --- a/main.c +++ b/main.c @@ -45,6 +45,7 @@ int usage(const char *cmd) "note {basedir} {uid} {text} [files...] Sends a note with optional attachments\n" "note_unlisted {basedir} {uid} {text} [files...] Sends an unlisted note with optional attachments\n" "note_mention {basedir} {uid} {text} [files...] Sends a note only to mentioned accounts\n" + "note_followers {basedir} {uid} {text} [files...] Sends a note only to followers\n" "boost|announce {basedir} {uid} {url} Boosts (announces) a post\n" "unboost {basedir} {uid} {url} Unboosts a post\n" "resetpwd {basedir} {uid} Resets the password of a user\n" @@ -800,7 +801,8 @@ int main(int argc, char *argv[]) if (strcmp(cmd, "note") == 0 || /** **/ strcmp(cmd, "note_unlisted") == 0 || /** **/ - strcmp(cmd, "note_mention") == 0) { /** **/ + strcmp(cmd, "note_mention") == 0 || /** **/ + strcmp(cmd, "note_followers") == 0) { /** **/ xs *content = NULL; xs *msg = NULL; xs *c_msg = NULL; @@ -909,12 +911,15 @@ int main(int argc, char *argv[]) return 1; } - int scope = 0; + int scope = SCOPE_PUBLIC; if (strcmp(cmd, "note_mention") == 0) - scope = 1; + scope = SCOPE_MENTIONED; else if (strcmp(cmd, "note_unlisted") == 0) - scope = 2; + scope = SCOPE_UNLISTED; + else + if (strcmp(cmd, "note_followers") == 0) + scope = SCOPE_FOLLOWERS; msg = msg_note(&snac, content, NULL, in_reply_to, attl, scope, getenv("LANG"), post_date); diff --git a/mastoapi.c b/mastoapi.c index 568426b..ed46b89 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -884,8 +884,16 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) st = xs_dict_append(st, "content", s1); } - st = xs_dict_append(st, "visibility", - is_msg_public(msg) ? "public" : "private"); + /* determine visibility: https://docs.joinmastodon.org/entities/Status/#visibility */ + const int scope = get_msg_visibility(msg); + if (scope == SCOPE_PUBLIC) + st = xs_dict_append(st, "visibility", "public"); + else if (scope == SCOPE_FOLLOWERS) + st = xs_dict_append(st, "visibility", "private"); + else if (scope == SCOPE_MENTIONED) + st = xs_dict_append(st, "visibility", "direct"); + else if (scope == SCOPE_UNLISTED) + st = xs_dict_append(st, "visibility", "unlisted"); tmp = xs_dict_get(msg, "sensitive"); if (xs_is_null(tmp)) @@ -1767,7 +1775,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, if (valid_status(timeline_get_by_md5(&snac2, v, &msg))) { /* add only posts by the author */ if (strcmp(xs_dict_get(msg, "type"), "Note") == 0 && - xs_startswith(xs_dict_get(msg, "id"), snac2.actor)) { + xs_startswith(xs_dict_get(msg, "id"), snac2.actor) && is_msg_public(msg)) { xs *st = mastoapi_status(&snac2, msg); if (st) @@ -2856,12 +2864,15 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, } /* prepare the message */ - int scope = 1; + int scope = SCOPE_MENTIONED; if (strcmp(visibility, "unlisted") == 0) - scope = 2; + scope = SCOPE_UNLISTED; else if (strcmp(visibility, "public") == 0) - scope = 0; + scope = SCOPE_PUBLIC; + else + if (strcmp(visibility, "private") == 0) + scope = SCOPE_FOLLOWERS; xs *msg = msg_note(&snac, content, NULL, irt, attach_list, scope, language, NULL); diff --git a/snac.h b/snac.h index 0027b59..984300b 100644 --- a/snac.h +++ b/snac.h @@ -78,6 +78,13 @@ typedef struct { extern srv_state *p_state; +enum { + SCOPE_PUBLIC = 0, + SCOPE_MENTIONED = 1, + SCOPE_UNLISTED = 2, + SCOPE_FOLLOWERS = 3, +}; + void snac_log(snac *user, xs_str *str); #define snac_debug(user, level, str) do { if (dbglevel >= (level)) \ { snac_log((user), (str)); } } while (0) @@ -366,6 +373,7 @@ xs_dict *msg_accept(snac *snac, const xs_val *object, const char *to); xs_dict *msg_question(snac *user, const char *content, xs_list *attach, const xs_list *opts, int multiple, int end_secs); xs_dict *msg_replies(snac *user, const char *id, int fill); +int get_msg_visibility(const xs_dict *msg); int activitypub_request(snac *snac, const char *url, xs_dict **data); int actor_request(snac *user, const char *actor, xs_dict **data); -- cgit v1.2.3