commit ab698c2
Merge pull request 'implementing scopes' (#474) from byte/snac2:feature/visibility into master Reviewed-on: https://codeberg.org/grunfink/snac2/pulls/474
6 files changed,
+240,
-64
+88,
-20
1@@ -569,14 +569,21 @@ xs_list *recipient_list(snac *snac, const xs_dict *msg, int expand_public)
2 }
3
4 while (xs_list_iter(&l, &v)) {
5- if (expand_public && strcmp(v, public_address) == 0) {
6- /* iterate the followers and add them */
7- xs *fwers = follower_list(snac);
8- const char *actor;
9-
10- char *p = fwers;
11- while (xs_list_iter(&p, &actor))
12- xs_set_add(&rcpts, actor);
13+ if (expand_public) {
14+ if (strcmp(v, public_address) == 0 ||
15+ /* check if it's a followers collection URL */
16+ (xs_type(v) == XSTYPE_STRING &&
17+ strcmp(v, xs_fmt("%s/followers", snac->actor)) == 0) ||
18+ (xs_type(v) == XSTYPE_LIST &&
19+ xs_list_in(v, xs_fmt("%s/followers", snac->actor)) != -1)) {
20+ /* iterate the followers and add them */
21+ xs *fwers = follower_list(snac);
22+ const char *actor;
23+
24+ char *p = fwers;
25+ while (xs_list_iter(&p, &actor))
26+ xs_set_add(&rcpts, actor);
27+ }
28 }
29 else
30 xs_set_add(&rcpts, v);
31@@ -1840,8 +1847,7 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts,
32 xs_list *p;
33 const xs_val *v;
34
35- /* FIXME: implement scope 3 */
36- int priv = scope == 1;
37+ const int priv = (scope == SCOPE_MENTIONED || scope == SCOPE_FOLLOWERS);
38
39 if (rcpts == NULL)
40 to = xs_list_new();
41@@ -1901,9 +1907,13 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts,
42 if ((v = xs_dict_get(p_msg, "conversation")))
43 msg = xs_dict_append(msg, "conversation", v);
44
45- /* if this message is public, ours will also be */
46- if (!priv && is_msg_public(p_msg) && xs_list_in(to, public_address) == -1)
47+ /* if this message is public or unlisted, ours will also be */
48+ const int orig_scope = get_msg_visibility(p_msg);
49+ if (!priv && orig_scope == SCOPE_PUBLIC && xs_list_in(to, public_address) == -1)
50 to = xs_list_append(to, public_address);
51+ else
52+ if (!priv && orig_scope == SCOPE_UNLISTED && xs_list_in(cc, public_address) == -1)
53+ cc = xs_list_append(cc, public_address);
54 }
55
56 irt = xs_dup(in_reply_to);
57@@ -1947,28 +1957,50 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts,
58 if (ctxt == NULL)
59 ctxt = xs_fmt("%s#ctxt", id);
60
61- /* add all mentions to the cc */
62+ /* add all mentions to the appropriate field */
63 p = tag;
64 while (xs_list_iter(&p, &v)) {
65 if (xs_type(v) == XSTYPE_DICT) {
66 const char *t;
67
68 if (!xs_is_null(t = xs_dict_get(v, "type")) && strcmp(t, "Mention") == 0) {
69- if (!xs_is_null(t = xs_dict_get(v, "href")))
70- cc = xs_list_append(cc, t);
71+ if (!xs_is_null(t = xs_dict_get(v, "href"))) {
72+ if (scope == SCOPE_MENTIONED) {
73+ /* for DMs, mentions go to 'to' */
74+ to = xs_list_append(to, t);
75+ } else {
76+ /* for other visibility levels, mentions go to 'cc' */
77+ cc = xs_list_append(cc, t);
78+ }
79+ }
80 }
81 }
82 }
83
84- if (scope == 2) {
85- /* Mastodon's "quiet public": add public address to cc */
86+ if (scope == SCOPE_UNLISTED) {
87+ /* Mastodon's "quiet public": remove from to, add public address to cc */
88+ int idx;
89+ if ((idx = xs_list_in(to, public_address)) != -1)
90+ to = xs_list_del(to, idx);
91 if (xs_list_in(cc, public_address) == -1)
92 cc = xs_list_append(cc, public_address);
93 }
94 else
95- /* no recipients? must be for everybody */
96- if (!priv && xs_list_len(to) == 0)
97- to = xs_list_append(to, public_address);
98+ if (scope == SCOPE_FOLLOWERS) {
99+ /* followers-only: add followers collection to to */
100+ xs *followers_url = xs_fmt("%s/followers", snac->actor);
101+ if (xs_list_in(to, followers_url) == -1)
102+ to = xs_list_append(to, followers_url);
103+ }
104+ else
105+ if (scope == SCOPE_PUBLIC) {
106+ /* public: ensure public address is in to and not in cc */
107+ int idx;
108+ if ((idx = xs_list_in(cc, public_address)) != -1)
109+ cc = xs_list_del(cc, idx);
110+ if (xs_list_in(to, public_address) == -1)
111+ to = xs_list_append(to, public_address);
112+ }
113
114 /* delete all cc recipients that also are in the to */
115 p = to;
116@@ -2113,6 +2145,42 @@ xs_dict *msg_question(snac *user, const char *content, xs_list *attach,
117 return msg;
118 }
119
120+int get_msg_visibility(const xs_dict *msg)
121+/* determine visibility from message based on CC, TO and /followers mark */
122+{
123+ const xs_val *to = xs_dict_get(msg, "to");
124+ const xs_val *cc = xs_dict_get(msg, "cc");
125+
126+ /* check if it's unlisted (public in cc but not in to) */
127+ int pub_in_to = 0;
128+ int pub_in_cc = 0;
129+
130+ if ((xs_type(to) == XSTYPE_STRING && strcmp(to, public_address) == 0) ||
131+ (xs_type(to) == XSTYPE_LIST && xs_list_in(to, public_address) != -1))
132+ pub_in_to = 1;
133+
134+ if ((xs_type(cc) == XSTYPE_STRING && strcmp(cc, public_address) == 0) ||
135+ (xs_type(cc) == XSTYPE_LIST && xs_list_in(cc, public_address) != -1))
136+ pub_in_cc = 1;
137+
138+ if (!pub_in_to && pub_in_cc) {
139+ return SCOPE_UNLISTED;
140+ }
141+ if (pub_in_to && !pub_in_cc) {
142+ return SCOPE_PUBLIC;
143+ }
144+
145+ xs *followers_url = xs_fmt("%s/followers", xs_dict_get(msg, "attributedTo"));
146+
147+ if ((xs_type(to) == XSTYPE_STRING && strcmp(to, followers_url) == 0) ||
148+ (xs_type(to) == XSTYPE_LIST && xs_list_in(to, followers_url) != -1))
149+ return SCOPE_FOLLOWERS;
150+ else
151+ return SCOPE_MENTIONED;
152+ /* should be unreachable, someone violated the protocol. */
153+ /* better treat it as followers-only to make sure we don't leak information.*/
154+ return SCOPE_FOLLOWERS;
155+}
156
157 int update_question(snac *user, const char *id)
158 /* updates the poll counts */
M
data.c
+7,
-3
1@@ -1456,15 +1456,16 @@ void timeline_update_indexes(snac *snac, const char *id)
2 xs *msg = NULL;
3
4 if (valid_status(object_get(id, &msg))) {
5+ const int scope = get_msg_visibility(msg);
6 /* if its ours and is public, also store in public */
7- if (is_msg_public(msg)) {
8+ if (scope == SCOPE_PUBLIC) {
9 if (object_user_cache_add(snac, id, "public") >= 0) {
10 /* also add it to the instance public timeline */
11 xs *ipt = xs_fmt("%s/public.idx", srv_basedir);
12 index_add(ipt, id);
13 }
14 else
15- srv_debug(1, xs_fmt("Not added to public instance index %s", id));
16+ srv_debug(1, xs_fmt("Not added to public instance index %s, visibility %d", id, scope));
17 }
18 else
19 /* also add it to public, it will be discarded later */
20@@ -2222,7 +2223,10 @@ void tag_index(const char *id, const xs_dict *obj)
21 const xs_list *tags = xs_dict_get(obj, "tag");
22 xs *md5_id = xs_md5_hex(id, strlen(id));
23
24- if (is_msg_public(obj) && xs_type(tags) == XSTYPE_LIST && xs_list_len(tags) > 0) {
25+ if (get_msg_visibility(obj) != SCOPE_PUBLIC)
26+ return;
27+
28+ if (xs_type(tags) == XSTYPE_LIST && xs_list_len(tags) > 0) {
29 xs *g_tag_dir = xs_fmt("%s/tag", srv_basedir);
30
31 mkdirx(g_tag_dir);
M
html.c
+112,
-32
1@@ -165,7 +165,7 @@ xs_str *format_text_with_emoji(snac *user, const char *text, int ems, const char
2
3
4 xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
5- const char *udate, const char *url, int priv,
6+ const char *udate, const char *url, int scope,
7 int in_people, const char *proxy, const char *lang,
8 const char *md5)
9 {
10@@ -249,12 +249,35 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
11 xs_html_raw("🤝")));
12 }
13
14- if (priv) {
15- xs_html_add(actor_icon,
16- xs_html_text(" "),
17- xs_html_tag("span",
18- xs_html_attr("title", "private"),
19- xs_html_raw("🔒")));
20+ if (scope != -1) {
21+ if (scope == SCOPE_FOLLOWERS) {
22+ xs_html_add(actor_icon,
23+ xs_html_text(" "),
24+ xs_html_tag("span",
25+ xs_html_attr("title", "followers"),
26+ xs_html_raw("🔒"))); // emoji of a lock
27+ }
28+ else if (scope == SCOPE_PUBLIC) {
29+ xs_html_add(actor_icon,
30+ xs_html_text(" "),
31+ xs_html_tag("span",
32+ xs_html_attr("title", "public"),
33+ xs_html_raw("🌐"))); // emoji of a globe
34+ }
35+ else if (scope == SCOPE_UNLISTED) {
36+ xs_html_add(actor_icon,
37+ xs_html_text(" "),
38+ xs_html_tag("span",
39+ xs_html_attr("title", "unlisted"),
40+ xs_html_raw("🔓"))); // emoji of an unlocked lock
41+ }
42+ else if (scope == SCOPE_MENTIONED) {
43+ xs_html_add(actor_icon,
44+ xs_html_text(" "),
45+ xs_html_tag("span",
46+ xs_html_attr("title", "mentioned"),
47+ xs_html_raw("✉️"))); // emoji of a mail
48+ }
49 }
50
51 if (xs_is_null(date)) {
52@@ -342,18 +365,17 @@ xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg,
53 const char *date = NULL;
54 const char *udate = NULL;
55 const char *url = NULL;
56- int priv = 0;
57 const char *type = xs_dict_get(msg, "type");
58+ const int scope = get_msg_visibility(msg);
59
60 if (xs_match(type, POSTLIKE_OBJECT_TYPE))
61 url = xs_dict_get(msg, "id");
62
63- priv = !is_msg_public(msg);
64
65 date = xs_dict_get(msg, "published");
66 udate = xs_dict_get(msg, "updated");
67
68- actor_icon = html_actor_icon(user, actor, date, udate, url, priv, 0, proxy, lang, md5);
69+ actor_icon = html_actor_icon(user, actor, date, udate, url, scope, 0, proxy, lang, md5);
70 }
71
72 return actor_icon;
73@@ -365,7 +387,7 @@ xs_html *html_note(snac *user, const char *summary,
74 const char *ta_plh, const char *ta_content,
75 const char *edit_id, const char *actor_id,
76 const xs_val *cw_yn, const char *cw_text,
77- const xs_val *mnt_only, const char *redir,
78+ const int scope, const char *redir,
79 const char *in_reply_to, int poll,
80 const xs_list *att_files, const xs_list *att_alt_texts,
81 int is_draft, const char *published,
82@@ -417,14 +439,44 @@ xs_html *html_note(snac *user, const char *summary,
83 xs_html_attr("name", "to"),
84 xs_html_attr("value", actor_id)));
85 else {
86- /* no actor_id; ask for mentioned_only */
87 xs_html_add(form,
88- xs_html_tag("p", NULL),
89- xs_html_text(L("Only for mentioned people: ")),
90- xs_html_sctag("input",
91- xs_html_attr("type", "checkbox"),
92- xs_html_attr("name", "mentioned_only"),
93- xs_html_attr(xs_type(mnt_only) == XSTYPE_TRUE ? "checked" : "", NULL)));
94+ xs_html_tag("p",
95+ xs_html_text(L("Visibility: ")),
96+ xs_html_tag("label",
97+ xs_html_sctag("input",
98+ xs_html_attr("type", "radio"),
99+ xs_html_attr("name", "visibility"),
100+ xs_html_attr("value", "public"),
101+ xs_html_attr(scope == SCOPE_PUBLIC ? "checked" : "", NULL)),
102+ xs_html_text(" "),
103+ xs_html_text(L("Public"))),
104+ xs_html_text(" "),
105+ xs_html_tag("label",
106+ xs_html_sctag("input",
107+ xs_html_attr("type", "radio"),
108+ xs_html_attr("name", "visibility"),
109+ xs_html_attr("value", "unlisted"),
110+ xs_html_attr(scope == SCOPE_UNLISTED ? "checked" : "", NULL)),
111+ xs_html_text(" "),
112+ xs_html_text(L("Unlisted"))),
113+ xs_html_text(" "),
114+ xs_html_tag("label",
115+ xs_html_sctag("input",
116+ xs_html_attr("type", "radio"),
117+ xs_html_attr("name", "visibility"),
118+ xs_html_attr("value", "followers"),
119+ xs_html_attr(scope == SCOPE_FOLLOWERS ? "checked" : "", NULL)),
120+ xs_html_text(" "),
121+ xs_html_text(L("Followers-only"))),
122+ xs_html_text(" "),
123+ xs_html_tag("label",
124+ xs_html_sctag("input",
125+ xs_html_attr("type", "radio"),
126+ xs_html_attr("name", "visibility"),
127+ xs_html_attr("value", "mentioned"),
128+ xs_html_attr(scope == SCOPE_MENTIONED ? "checked" : "", NULL)),
129+ xs_html_text(" "),
130+ xs_html_text(L("Direct Message")))));
131 }
132
133 if (redir)
134@@ -1236,7 +1288,7 @@ xs_html *html_top_controls(snac *user)
135 L("What's on your mind?"), "",
136 NULL, NULL,
137 xs_stock(XSTYPE_FALSE), "",
138- xs_stock(XSTYPE_FALSE), NULL,
139+ SCOPE_PUBLIC, NULL,
140 NULL, 1, NULL, NULL, 0, NULL, NULL),
141
142 /** operations **/
143@@ -1942,6 +1994,8 @@ xs_html *html_entry_controls(snac *user, const char *actor,
144 xs_dict_next(cmap, ¬e_lang, &dummy, &c);
145 }
146
147+ const int edit_scope = get_msg_visibility(msg);
148+
149 xs_html_add(controls, xs_html_tag("div",
150 xs_html_tag("p", NULL),
151 html_note(user, L("Edit..."),
152@@ -1949,7 +2003,7 @@ xs_html *html_entry_controls(snac *user, const char *actor,
153 "", prev_src,
154 id, NULL,
155 xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"),
156- xs_stock(is_msg_public(msg) ? XSTYPE_FALSE : XSTYPE_TRUE), redir,
157+ edit_scope, redir,
158 NULL, 0, att_files, att_alt_texts, is_draft(user, id),
159 xs_dict_get(msg, "published"), note_lang)),
160 xs_html_tag("p", NULL));
161@@ -1962,6 +2016,8 @@ xs_html *html_entry_controls(snac *user, const char *actor,
162 xs *form_id = xs_fmt("%s_reply_form", md5);
163 xs *redir = xs_fmt("%s_entry", md5);
164
165+ const int scope = get_msg_visibility(msg);
166+
167 xs_html_add(controls, xs_html_tag("div",
168 xs_html_tag("p", NULL),
169 html_note(user, L("Reply..."),
170@@ -1969,7 +2025,7 @@ xs_html *html_entry_controls(snac *user, const char *actor,
171 "", ct,
172 NULL, NULL,
173 xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"),
174- xs_stock(is_msg_public(msg) ? XSTYPE_FALSE : XSTYPE_TRUE), redir,
175+ scope, redir,
176 id, 0, NULL, NULL, 0, NULL, NULL)),
177 xs_html_tag("p", NULL));
178 }
179@@ -2061,6 +2117,12 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
180 xs_html_attr("name", s1)));
181 }
182
183+ /* don't show followers-only notes from not followed users */
184+ if (user && get_msg_visibility(msg) == SCOPE_FOLLOWERS &&
185+ strcmp(user->actor, actor) != 0 && following_check(user, actor) == 0) {
186+ return NULL;
187+ }
188+
189 if ((user == NULL || strcmp(actor, user->actor) != 0)
190 && !valid_status(actor_get(actor, NULL))) {
191
192@@ -2275,7 +2337,6 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
193
194 has_title = 1;
195 }
196-
197 snac_content = xs_html_tag("div", NULL);
198 }
199
200@@ -3089,8 +3150,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
201 xs_html_text(title)));
202 }
203
204- xs_html_add(body,
205- posts);
206+ xs_html_add(body, posts);
207
208 int mark_shown = 0;
209
210@@ -3130,8 +3190,9 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
211 if (user == NULL && is_msg_from_private_user(msg))
212 continue;
213
214- /* is this message a non-public reply? */
215- if (user != NULL && !is_msg_public(msg)) {
216+ const int scope = get_msg_visibility(msg);
217+ if (user != NULL && scope != SCOPE_PUBLIC){
218+ /* is this message a non-public reply? */
219 const char *irt = get_in_reply_to(msg);
220
221 /* is it a reply to something not in the storage? */
222@@ -3146,6 +3207,14 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
223 }
224 }
225 }
226+ /* hide non-public posts from /instance view */
227+ if (page != NULL && strcmp(page, "/instance") == 0 && scope != SCOPE_PUBLIC){
228+ continue;
229+ }
230+ /* hide non-public posts viewed from outside */
231+ if (read_only && scope != SCOPE_PUBLIC){
232+ continue;
233+ }
234
235 xs_html *entry = html_entry(user, msg, read_only, 0, v, (user && !hide_children) ? 0 : 1);
236
237@@ -3256,7 +3325,7 @@ xs_html *html_people_list(snac *user, xs_list *list, const char *header, const c
238 xs_html_tag("div",
239 xs_html_attr("class", "snac-post-header"),
240 html_actor_icon(user, actor, xs_dict_get(actor, "published"),
241- NULL, NULL, 0, 1, proxy, NULL, NULL)));
242+ NULL, NULL, -1, 1, proxy, NULL, NULL)));
243
244 /* content (user bio) */
245 const char *c = xs_dict_get(actor, "summary");
246@@ -3357,7 +3426,7 @@ xs_html *html_people_list(snac *user, xs_list *list, const char *header, const c
247 "", "",
248 NULL, actor_id,
249 xs_stock(XSTYPE_FALSE), "",
250- xs_stock(XSTYPE_FALSE), NULL,
251+ SCOPE_MENTIONED, NULL,
252 NULL, 0, NULL, NULL, 0, NULL, NULL),
253 xs_html_tag("p", NULL));
254
255@@ -3564,7 +3633,7 @@ xs_str *html_notifications(snac *user, int skip, int show)
256 xs_html_add(entry,
257 xs_html_tag("div",
258 xs_html_attr("class", "snac-post"),
259- html_actor_icon(user, actor, NULL, NULL, NULL, 0, 0, proxy, NULL, NULL)));
260+ html_actor_icon(user, actor, NULL, NULL, NULL, -1, 0, proxy, NULL, NULL)));
261 }
262 else
263 if (strcmp(type, "Move") == 0) {
264@@ -3578,7 +3647,7 @@ xs_str *html_notifications(snac *user, int skip, int show)
265 xs_html_add(entry,
266 xs_html_tag("div",
267 xs_html_attr("class", "snac-post"),
268- html_actor_icon(user, old_actor, NULL, NULL, NULL, 0, 0, proxy, NULL, NULL)));
269+ html_actor_icon(user, old_actor, NULL, NULL, NULL, -1, 0, proxy, NULL, NULL)));
270 }
271 }
272 }
273@@ -4445,7 +4514,18 @@ int html_post_handler(const xs_dict *req, const char *q_path,
274 const char *post_date = xs_dict_get_def(p_vars, "post_date", "");
275 const char *post_time = xs_dict_get_def(p_vars, "post_time", "");
276 const char *post_lang = xs_dict_get(p_vars, "post_lang");
277- int priv = !xs_is_null(xs_dict_get(p_vars, "mentioned_only"));
278+ const char *visibility = xs_dict_get(p_vars, "visibility");
279+ int scope = SCOPE_PUBLIC; /* default to public */
280+ if (!xs_is_null(visibility)) {
281+ if (strcmp(visibility, "unlisted") == 0)
282+ scope = SCOPE_UNLISTED;
283+ else
284+ if (strcmp(visibility, "followers") == 0)
285+ scope = SCOPE_FOLLOWERS;
286+ else
287+ if (strcmp(visibility, "mentioned") == 0)
288+ scope = SCOPE_MENTIONED;
289+ }
290 int store_as_draft = !xs_is_null(xs_dict_get(p_vars, "is_draft"));
291 xs *attach_list = xs_list_new();
292
293@@ -4528,7 +4608,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
294 enqueue_close_question(&snac, xs_dict_get(msg, "id"), end_secs);
295 }
296 else
297- msg = msg_note(&snac, content_2, to, in_reply_to, attach_list, priv, post_lang, NULL);
298+ msg = msg_note(&snac, content_2, to, in_reply_to, attach_list, scope, post_lang, NULL);
299
300 if (sensitive != NULL) {
301 msg = xs_dict_set(msg, "sensitive", xs_stock(XSTYPE_TRUE));
M
main.c
+9,
-4
1@@ -45,6 +45,7 @@ int usage(const char *cmd)
2 "note {basedir} {uid} {text} [files...] Sends a note with optional attachments\n"
3 "note_unlisted {basedir} {uid} {text} [files...] Sends an unlisted note with optional attachments\n"
4 "note_mention {basedir} {uid} {text} [files...] Sends a note only to mentioned accounts\n"
5+ "note_followers {basedir} {uid} {text} [files...] Sends a note only to followers\n"
6 "boost|announce {basedir} {uid} {url} Boosts (announces) a post\n"
7 "unboost {basedir} {uid} {url} Unboosts a post\n"
8 "resetpwd {basedir} {uid} Resets the password of a user\n"
9@@ -800,7 +801,8 @@ int main(int argc, char *argv[])
10
11 if (strcmp(cmd, "note") == 0 || /** **/
12 strcmp(cmd, "note_unlisted") == 0 || /** **/
13- strcmp(cmd, "note_mention") == 0) { /** **/
14+ strcmp(cmd, "note_mention") == 0 || /** **/
15+ strcmp(cmd, "note_followers") == 0) { /** **/
16 xs *content = NULL;
17 xs *msg = NULL;
18 xs *c_msg = NULL;
19@@ -909,12 +911,15 @@ int main(int argc, char *argv[])
20 return 1;
21 }
22
23- int scope = 0;
24+ int scope = SCOPE_PUBLIC;
25 if (strcmp(cmd, "note_mention") == 0)
26- scope = 1;
27+ scope = SCOPE_MENTIONED;
28 else
29 if (strcmp(cmd, "note_unlisted") == 0)
30- scope = 2;
31+ scope = SCOPE_UNLISTED;
32+ else
33+ if (strcmp(cmd, "note_followers") == 0)
34+ scope = SCOPE_FOLLOWERS;
35
36 msg = msg_note(&snac, content, NULL, in_reply_to, attl, scope, getenv("LANG"), post_date);
37
+16,
-5
1@@ -884,8 +884,16 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
2 st = xs_dict_append(st, "content", s1);
3 }
4
5- st = xs_dict_append(st, "visibility",
6- is_msg_public(msg) ? "public" : "private");
7+ /* determine visibility: https://docs.joinmastodon.org/entities/Status/#visibility */
8+ const int scope = get_msg_visibility(msg);
9+ if (scope == SCOPE_PUBLIC)
10+ st = xs_dict_append(st, "visibility", "public");
11+ else if (scope == SCOPE_FOLLOWERS)
12+ st = xs_dict_append(st, "visibility", "private");
13+ else if (scope == SCOPE_MENTIONED)
14+ st = xs_dict_append(st, "visibility", "direct");
15+ else if (scope == SCOPE_UNLISTED)
16+ st = xs_dict_append(st, "visibility", "unlisted");
17
18 tmp = xs_dict_get(msg, "sensitive");
19 if (xs_is_null(tmp))
20@@ -2856,12 +2864,15 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
21 }
22
23 /* prepare the message */
24- int scope = 1;
25+ int scope = SCOPE_MENTIONED;
26 if (strcmp(visibility, "unlisted") == 0)
27- scope = 2;
28+ scope = SCOPE_UNLISTED;
29 else
30 if (strcmp(visibility, "public") == 0)
31- scope = 0;
32+ scope = SCOPE_PUBLIC;
33+ else
34+ if (strcmp(visibility, "private") == 0)
35+ scope = SCOPE_FOLLOWERS;
36
37 xs *msg = msg_note(&snac, content, NULL, irt, attach_list, scope, language, NULL);
38
M
snac.h
+8,
-0
1@@ -78,6 +78,13 @@ typedef struct {
2
3 extern srv_state *p_state;
4
5+enum {
6+ SCOPE_PUBLIC = 0,
7+ SCOPE_MENTIONED = 1,
8+ SCOPE_UNLISTED = 2,
9+ SCOPE_FOLLOWERS = 3,
10+};
11+
12 void snac_log(snac *user, xs_str *str);
13 #define snac_debug(user, level, str) do { if (dbglevel >= (level)) \
14 { snac_log((user), (str)); } } while (0)
15@@ -367,6 +374,7 @@ xs_dict *msg_accept(snac *snac, const xs_val *object, const char *to);
16 xs_dict *msg_question(snac *user, const char *content, xs_list *attach,
17 const xs_list *opts, int multiple, int end_secs);
18 xs_dict *msg_replies(snac *user, const char *id, int fill);
19+int get_msg_visibility(const xs_dict *msg);
20
21 int activitypub_request(snac *snac, const char *url, xs_dict **data);
22 int actor_request(snac *user, const char *actor, xs_dict **data);