summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--activitypub.c108
-rw-r--r--data.c10
-rw-r--r--html.c144
-rw-r--r--main.c13
-rw-r--r--mastoapi.c21
-rw-r--r--snac.h8
6 files changed, 240 insertions, 64 deletions
diff --git a/activitypub.c b/activitypub.c
index b4c6eaa..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)
569 } 569 }
570 570
571 while (xs_list_iter(&l, &v)) { 571 while (xs_list_iter(&l, &v)) {
572 if (expand_public && strcmp(v, public_address) == 0) { 572 if (expand_public) {
573 /* iterate the followers and add them */ 573 if (strcmp(v, public_address) == 0 ||
574 xs *fwers = follower_list(snac); 574 /* check if it's a followers collection URL */
575 const char *actor; 575 (xs_type(v) == XSTYPE_STRING &&
576 576 strcmp(v, xs_fmt("%s/followers", snac->actor)) == 0) ||
577 char *p = fwers; 577 (xs_type(v) == XSTYPE_LIST &&
578 while (xs_list_iter(&p, &actor)) 578 xs_list_in(v, xs_fmt("%s/followers", snac->actor)) != -1)) {
579 xs_set_add(&rcpts, actor); 579 /* iterate the followers and add them */
580 xs *fwers = follower_list(snac);
581 const char *actor;
582
583 char *p = fwers;
584 while (xs_list_iter(&p, &actor))
585 xs_set_add(&rcpts, actor);
586 }
580 } 587 }
581 else 588 else
582 xs_set_add(&rcpts, v); 589 xs_set_add(&rcpts, v);
@@ -1840,8 +1847,7 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts,
1840 xs_list *p; 1847 xs_list *p;
1841 const xs_val *v; 1848 const xs_val *v;
1842 1849
1843 /* FIXME: implement scope 3 */ 1850 const int priv = (scope == SCOPE_MENTIONED || scope == SCOPE_FOLLOWERS);
1844 int priv = scope == 1;
1845 1851
1846 if (rcpts == NULL) 1852 if (rcpts == NULL)
1847 to = xs_list_new(); 1853 to = xs_list_new();
@@ -1901,9 +1907,13 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts,
1901 if ((v = xs_dict_get(p_msg, "conversation"))) 1907 if ((v = xs_dict_get(p_msg, "conversation")))
1902 msg = xs_dict_append(msg, "conversation", v); 1908 msg = xs_dict_append(msg, "conversation", v);
1903 1909
1904 /* if this message is public, ours will also be */ 1910 /* if this message is public or unlisted, ours will also be */
1905 if (!priv && is_msg_public(p_msg) && xs_list_in(to, public_address) == -1) 1911 const int orig_scope = get_msg_visibility(p_msg);
1912 if (!priv && orig_scope == SCOPE_PUBLIC && xs_list_in(to, public_address) == -1)
1906 to = xs_list_append(to, public_address); 1913 to = xs_list_append(to, public_address);
1914 else
1915 if (!priv && orig_scope == SCOPE_UNLISTED && xs_list_in(cc, public_address) == -1)
1916 cc = xs_list_append(cc, public_address);
1907 } 1917 }
1908 1918
1909 irt = xs_dup(in_reply_to); 1919 irt = xs_dup(in_reply_to);
@@ -1947,28 +1957,50 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts,
1947 if (ctxt == NULL) 1957 if (ctxt == NULL)
1948 ctxt = xs_fmt("%s#ctxt", id); 1958 ctxt = xs_fmt("%s#ctxt", id);
1949 1959
1950 /* add all mentions to the cc */ 1960 /* add all mentions to the appropriate field */
1951 p = tag; 1961 p = tag;
1952 while (xs_list_iter(&p, &v)) { 1962 while (xs_list_iter(&p, &v)) {
1953 if (xs_type(v) == XSTYPE_DICT) { 1963 if (xs_type(v) == XSTYPE_DICT) {
1954 const char *t; 1964 const char *t;
1955 1965
1956 if (!xs_is_null(t = xs_dict_get(v, "type")) && strcmp(t, "Mention") == 0) { 1966 if (!xs_is_null(t = xs_dict_get(v, "type")) && strcmp(t, "Mention") == 0) {
1957 if (!xs_is_null(t = xs_dict_get(v, "href"))) 1967 if (!xs_is_null(t = xs_dict_get(v, "href"))) {
1958 cc = xs_list_append(cc, t); 1968 if (scope == SCOPE_MENTIONED) {
1969 /* for DMs, mentions go to 'to' */
1970 to = xs_list_append(to, t);
1971 } else {
1972 /* for other visibility levels, mentions go to 'cc' */
1973 cc = xs_list_append(cc, t);
1974 }
1975 }
1959 } 1976 }
1960 } 1977 }
1961 } 1978 }
1962 1979
1963 if (scope == 2) { 1980 if (scope == SCOPE_UNLISTED) {
1964 /* Mastodon's "quiet public": add public address to cc */ 1981 /* Mastodon's "quiet public": remove from to, add public address to cc */
1982 int idx;
1983 if ((idx = xs_list_in(to, public_address)) != -1)
1984 to = xs_list_del(to, idx);
1965 if (xs_list_in(cc, public_address) == -1) 1985 if (xs_list_in(cc, public_address) == -1)
1966 cc = xs_list_append(cc, public_address); 1986 cc = xs_list_append(cc, public_address);
1967 } 1987 }
1968 else 1988 else
1969 /* no recipients? must be for everybody */ 1989 if (scope == SCOPE_FOLLOWERS) {
1970 if (!priv && xs_list_len(to) == 0) 1990 /* followers-only: add followers collection to to */
1971 to = xs_list_append(to, public_address); 1991 xs *followers_url = xs_fmt("%s/followers", snac->actor);
1992 if (xs_list_in(to, followers_url) == -1)
1993 to = xs_list_append(to, followers_url);
1994 }
1995 else
1996 if (scope == SCOPE_PUBLIC) {
1997 /* public: ensure public address is in to and not in cc */
1998 int idx;
1999 if ((idx = xs_list_in(cc, public_address)) != -1)
2000 cc = xs_list_del(cc, idx);
2001 if (xs_list_in(to, public_address) == -1)
2002 to = xs_list_append(to, public_address);
2003 }
1972 2004
1973 /* delete all cc recipients that also are in the to */ 2005 /* delete all cc recipients that also are in the to */
1974 p = to; 2006 p = to;
@@ -2113,6 +2145,42 @@ xs_dict *msg_question(snac *user, const char *content, xs_list *attach,
2113 return msg; 2145 return msg;
2114} 2146}
2115 2147
2148int get_msg_visibility(const xs_dict *msg)
2149/* determine visibility from message based on CC, TO and /followers mark */
2150{
2151 const xs_val *to = xs_dict_get(msg, "to");
2152 const xs_val *cc = xs_dict_get(msg, "cc");
2153
2154 /* check if it's unlisted (public in cc but not in to) */
2155 int pub_in_to = 0;
2156 int pub_in_cc = 0;
2157
2158 if ((xs_type(to) == XSTYPE_STRING && strcmp(to, public_address) == 0) ||
2159 (xs_type(to) == XSTYPE_LIST && xs_list_in(to, public_address) != -1))
2160 pub_in_to = 1;
2161
2162 if ((xs_type(cc) == XSTYPE_STRING && strcmp(cc, public_address) == 0) ||
2163 (xs_type(cc) == XSTYPE_LIST && xs_list_in(cc, public_address) != -1))
2164 pub_in_cc = 1;
2165
2166 if (!pub_in_to && pub_in_cc) {
2167 return SCOPE_UNLISTED;
2168 }
2169 if (pub_in_to && !pub_in_cc) {
2170 return SCOPE_PUBLIC;
2171 }
2172
2173 xs *followers_url = xs_fmt("%s/followers", xs_dict_get(msg, "attributedTo"));
2174
2175 if ((xs_type(to) == XSTYPE_STRING && strcmp(to, followers_url) == 0) ||
2176 (xs_type(to) == XSTYPE_LIST && xs_list_in(to, followers_url) != -1))
2177 return SCOPE_FOLLOWERS;
2178 else
2179 return SCOPE_MENTIONED;
2180 /* should be unreachable, someone violated the protocol. */
2181 /* better treat it as followers-only to make sure we don't leak information.*/
2182 return SCOPE_FOLLOWERS;
2183}
2116 2184
2117int update_question(snac *user, const char *id) 2185int update_question(snac *user, const char *id)
2118/* updates the poll counts */ 2186/* updates the poll counts */
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)
1456 xs *msg = NULL; 1456 xs *msg = NULL;
1457 1457
1458 if (valid_status(object_get(id, &msg))) { 1458 if (valid_status(object_get(id, &msg))) {
1459 const int scope = get_msg_visibility(msg);
1459 /* if its ours and is public, also store in public */ 1460 /* if its ours and is public, also store in public */
1460 if (is_msg_public(msg)) { 1461 if (scope == SCOPE_PUBLIC) {
1461 if (object_user_cache_add(snac, id, "public") >= 0) { 1462 if (object_user_cache_add(snac, id, "public") >= 0) {
1462 /* also add it to the instance public timeline */ 1463 /* also add it to the instance public timeline */
1463 xs *ipt = xs_fmt("%s/public.idx", srv_basedir); 1464 xs *ipt = xs_fmt("%s/public.idx", srv_basedir);
1464 index_add(ipt, id); 1465 index_add(ipt, id);
1465 } 1466 }
1466 else 1467 else
1467 srv_debug(1, xs_fmt("Not added to public instance index %s", id)); 1468 srv_debug(1, xs_fmt("Not added to public instance index %s, visibility %d", id, scope));
1468 } 1469 }
1469 else 1470 else
1470 /* also add it to public, it will be discarded later */ 1471 /* also add it to public, it will be discarded later */
@@ -2222,7 +2223,10 @@ void tag_index(const char *id, const xs_dict *obj)
2222 const xs_list *tags = xs_dict_get(obj, "tag"); 2223 const xs_list *tags = xs_dict_get(obj, "tag");
2223 xs *md5_id = xs_md5_hex(id, strlen(id)); 2224 xs *md5_id = xs_md5_hex(id, strlen(id));
2224 2225
2225 if (is_msg_public(obj) && xs_type(tags) == XSTYPE_LIST && xs_list_len(tags) > 0) { 2226 if (get_msg_visibility(obj) != SCOPE_PUBLIC)
2227 return;
2228
2229 if (xs_type(tags) == XSTYPE_LIST && xs_list_len(tags) > 0) {
2226 xs *g_tag_dir = xs_fmt("%s/tag", srv_basedir); 2230 xs *g_tag_dir = xs_fmt("%s/tag", srv_basedir);
2227 2231
2228 mkdirx(g_tag_dir); 2232 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
165 165
166 166
167xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, 167xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
168 const char *udate, const char *url, int priv, 168 const char *udate, const char *url, int scope,
169 int in_people, const char *proxy, const char *lang, 169 int in_people, const char *proxy, const char *lang,
170 const char *md5) 170 const char *md5)
171{ 171{
@@ -249,12 +249,35 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date,
249 xs_html_raw("🤝"))); 249 xs_html_raw("🤝")));
250 } 250 }
251 251
252 if (priv) { 252 if (scope != -1) {
253 xs_html_add(actor_icon, 253 if (scope == SCOPE_FOLLOWERS) {
254 xs_html_text(" "), 254 xs_html_add(actor_icon,
255 xs_html_tag("span", 255 xs_html_text(" "),
256 xs_html_attr("title", "private"), 256 xs_html_tag("span",
257 xs_html_raw("🔒"))); 257 xs_html_attr("title", "followers"),
258 xs_html_raw("🔒"))); // emoji of a lock
259 }
260 else if (scope == SCOPE_PUBLIC) {
261 xs_html_add(actor_icon,
262 xs_html_text(" "),
263 xs_html_tag("span",
264 xs_html_attr("title", "public"),
265 xs_html_raw("🌐"))); // emoji of a globe
266 }
267 else if (scope == SCOPE_UNLISTED) {
268 xs_html_add(actor_icon,
269 xs_html_text(" "),
270 xs_html_tag("span",
271 xs_html_attr("title", "unlisted"),
272 xs_html_raw("🔓"))); // emoji of an unlocked lock
273 }
274 else if (scope == SCOPE_MENTIONED) {
275 xs_html_add(actor_icon,
276 xs_html_text(" "),
277 xs_html_tag("span",
278 xs_html_attr("title", "mentioned"),
279 xs_html_raw("✉️"))); // emoji of a mail
280 }
258 } 281 }
259 282
260 if (xs_is_null(date)) { 283 if (xs_is_null(date)) {
@@ -342,18 +365,17 @@ xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg,
342 const char *date = NULL; 365 const char *date = NULL;
343 const char *udate = NULL; 366 const char *udate = NULL;
344 const char *url = NULL; 367 const char *url = NULL;
345 int priv = 0;
346 const char *type = xs_dict_get(msg, "type"); 368 const char *type = xs_dict_get(msg, "type");
369 const int scope = get_msg_visibility(msg);
347 370
348 if (xs_match(type, POSTLIKE_OBJECT_TYPE)) 371 if (xs_match(type, POSTLIKE_OBJECT_TYPE))
349 url = xs_dict_get(msg, "id"); 372 url = xs_dict_get(msg, "id");
350 373
351 priv = !is_msg_public(msg);
352 374
353 date = xs_dict_get(msg, "published"); 375 date = xs_dict_get(msg, "published");
354 udate = xs_dict_get(msg, "updated"); 376 udate = xs_dict_get(msg, "updated");
355 377
356 actor_icon = html_actor_icon(user, actor, date, udate, url, priv, 0, proxy, lang, md5); 378 actor_icon = html_actor_icon(user, actor, date, udate, url, scope, 0, proxy, lang, md5);
357 } 379 }
358 380
359 return actor_icon; 381 return actor_icon;
@@ -365,7 +387,7 @@ xs_html *html_note(snac *user, const char *summary,
365 const char *ta_plh, const char *ta_content, 387 const char *ta_plh, const char *ta_content,
366 const char *edit_id, const char *actor_id, 388 const char *edit_id, const char *actor_id,
367 const xs_val *cw_yn, const char *cw_text, 389 const xs_val *cw_yn, const char *cw_text,
368 const xs_val *mnt_only, const char *redir, 390 const int scope, const char *redir,
369 const char *in_reply_to, int poll, 391 const char *in_reply_to, int poll,
370 const xs_list *att_files, const xs_list *att_alt_texts, 392 const xs_list *att_files, const xs_list *att_alt_texts,
371 int is_draft, const char *published, 393 int is_draft, const char *published,
@@ -417,14 +439,44 @@ xs_html *html_note(snac *user, const char *summary,
417 xs_html_attr("name", "to"), 439 xs_html_attr("name", "to"),
418 xs_html_attr("value", actor_id))); 440 xs_html_attr("value", actor_id)));
419 else { 441 else {
420 /* no actor_id; ask for mentioned_only */
421 xs_html_add(form, 442 xs_html_add(form,
422 xs_html_tag("p", NULL), 443 xs_html_tag("p",
423 xs_html_text(L("Only for mentioned people: ")), 444 xs_html_text(L("Visibility: ")),
424 xs_html_sctag("input", 445 xs_html_tag("label",
425 xs_html_attr("type", "checkbox"), 446 xs_html_sctag("input",
426 xs_html_attr("name", "mentioned_only"), 447 xs_html_attr("type", "radio"),
427 xs_html_attr(xs_type(mnt_only) == XSTYPE_TRUE ? "checked" : "", NULL))); 448 xs_html_attr("name", "visibility"),
449 xs_html_attr("value", "public"),
450 xs_html_attr(scope == SCOPE_PUBLIC ? "checked" : "", NULL)),
451 xs_html_text(" "),
452 xs_html_text(L("Public"))),
453 xs_html_text(" "),
454 xs_html_tag("label",
455 xs_html_sctag("input",
456 xs_html_attr("type", "radio"),
457 xs_html_attr("name", "visibility"),
458 xs_html_attr("value", "unlisted"),
459 xs_html_attr(scope == SCOPE_UNLISTED ? "checked" : "", NULL)),
460 xs_html_text(" "),
461 xs_html_text(L("Unlisted"))),
462 xs_html_text(" "),
463 xs_html_tag("label",
464 xs_html_sctag("input",
465 xs_html_attr("type", "radio"),
466 xs_html_attr("name", "visibility"),
467 xs_html_attr("value", "followers"),
468 xs_html_attr(scope == SCOPE_FOLLOWERS ? "checked" : "", NULL)),
469 xs_html_text(" "),
470 xs_html_text(L("Followers-only"))),
471 xs_html_text(" "),
472 xs_html_tag("label",
473 xs_html_sctag("input",
474 xs_html_attr("type", "radio"),
475 xs_html_attr("name", "visibility"),
476 xs_html_attr("value", "mentioned"),
477 xs_html_attr(scope == SCOPE_MENTIONED ? "checked" : "", NULL)),
478 xs_html_text(" "),
479 xs_html_text(L("Direct Message")))));
428 } 480 }
429 481
430 if (redir) 482 if (redir)
@@ -1236,7 +1288,7 @@ xs_html *html_top_controls(snac *user)
1236 L("What's on your mind?"), "", 1288 L("What's on your mind?"), "",
1237 NULL, NULL, 1289 NULL, NULL,
1238 xs_stock(XSTYPE_FALSE), "", 1290 xs_stock(XSTYPE_FALSE), "",
1239 xs_stock(XSTYPE_FALSE), NULL, 1291 SCOPE_PUBLIC, NULL,
1240 NULL, 1, NULL, NULL, 0, NULL, NULL), 1292 NULL, 1, NULL, NULL, 0, NULL, NULL),
1241 1293
1242 /** operations **/ 1294 /** operations **/
@@ -1942,6 +1994,8 @@ xs_html *html_entry_controls(snac *user, const char *actor,
1942 xs_dict_next(cmap, &note_lang, &dummy, &c); 1994 xs_dict_next(cmap, &note_lang, &dummy, &c);
1943 } 1995 }
1944 1996
1997 const int edit_scope = get_msg_visibility(msg);
1998
1945 xs_html_add(controls, xs_html_tag("div", 1999 xs_html_add(controls, xs_html_tag("div",
1946 xs_html_tag("p", NULL), 2000 xs_html_tag("p", NULL),
1947 html_note(user, L("Edit..."), 2001 html_note(user, L("Edit..."),
@@ -1949,7 +2003,7 @@ xs_html *html_entry_controls(snac *user, const char *actor,
1949 "", prev_src, 2003 "", prev_src,
1950 id, NULL, 2004 id, NULL,
1951 xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"), 2005 xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"),
1952 xs_stock(is_msg_public(msg) ? XSTYPE_FALSE : XSTYPE_TRUE), redir, 2006 edit_scope, redir,
1953 NULL, 0, att_files, att_alt_texts, is_draft(user, id), 2007 NULL, 0, att_files, att_alt_texts, is_draft(user, id),
1954 xs_dict_get(msg, "published"), note_lang)), 2008 xs_dict_get(msg, "published"), note_lang)),
1955 xs_html_tag("p", NULL)); 2009 xs_html_tag("p", NULL));
@@ -1962,6 +2016,8 @@ xs_html *html_entry_controls(snac *user, const char *actor,
1962 xs *form_id = xs_fmt("%s_reply_form", md5); 2016 xs *form_id = xs_fmt("%s_reply_form", md5);
1963 xs *redir = xs_fmt("%s_entry", md5); 2017 xs *redir = xs_fmt("%s_entry", md5);
1964 2018
2019 const int scope = get_msg_visibility(msg);
2020
1965 xs_html_add(controls, xs_html_tag("div", 2021 xs_html_add(controls, xs_html_tag("div",
1966 xs_html_tag("p", NULL), 2022 xs_html_tag("p", NULL),
1967 html_note(user, L("Reply..."), 2023 html_note(user, L("Reply..."),
@@ -1969,7 +2025,7 @@ xs_html *html_entry_controls(snac *user, const char *actor,
1969 "", ct, 2025 "", ct,
1970 NULL, NULL, 2026 NULL, NULL,
1971 xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"), 2027 xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"),
1972 xs_stock(is_msg_public(msg) ? XSTYPE_FALSE : XSTYPE_TRUE), redir, 2028 scope, redir,
1973 id, 0, NULL, NULL, 0, NULL, NULL)), 2029 id, 0, NULL, NULL, 0, NULL, NULL)),
1974 xs_html_tag("p", NULL)); 2030 xs_html_tag("p", NULL));
1975 } 2031 }
@@ -2061,6 +2117,12 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
2061 xs_html_attr("name", s1))); 2117 xs_html_attr("name", s1)));
2062 } 2118 }
2063 2119
2120 /* don't show followers-only notes from not followed users */
2121 if (user && get_msg_visibility(msg) == SCOPE_FOLLOWERS &&
2122 strcmp(user->actor, actor) != 0 && following_check(user, actor) == 0) {
2123 return NULL;
2124 }
2125
2064 if ((user == NULL || strcmp(actor, user->actor) != 0) 2126 if ((user == NULL || strcmp(actor, user->actor) != 0)
2065 && !valid_status(actor_get(actor, NULL))) { 2127 && !valid_status(actor_get(actor, NULL))) {
2066 2128
@@ -2275,7 +2337,6 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
2275 2337
2276 has_title = 1; 2338 has_title = 1;
2277 } 2339 }
2278
2279 snac_content = xs_html_tag("div", NULL); 2340 snac_content = xs_html_tag("div", NULL);
2280 } 2341 }
2281 2342
@@ -3089,8 +3150,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
3089 xs_html_text(title))); 3150 xs_html_text(title)));
3090 } 3151 }
3091 3152
3092 xs_html_add(body, 3153 xs_html_add(body, posts);
3093 posts);
3094 3154
3095 int mark_shown = 0; 3155 int mark_shown = 0;
3096 3156
@@ -3130,8 +3190,9 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
3130 if (user == NULL && is_msg_from_private_user(msg)) 3190 if (user == NULL && is_msg_from_private_user(msg))
3131 continue; 3191 continue;
3132 3192
3133 /* is this message a non-public reply? */ 3193 const int scope = get_msg_visibility(msg);
3134 if (user != NULL && !is_msg_public(msg)) { 3194 if (user != NULL && scope != SCOPE_PUBLIC){
3195 /* is this message a non-public reply? */
3135 const char *irt = get_in_reply_to(msg); 3196 const char *irt = get_in_reply_to(msg);
3136 3197
3137 /* is it a reply to something not in the storage? */ 3198 /* 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,
3146 } 3207 }
3147 } 3208 }
3148 } 3209 }
3210 /* hide non-public posts from /instance view */
3211 if (page != NULL && strcmp(page, "/instance") == 0 && scope != SCOPE_PUBLIC){
3212 continue;
3213 }
3214 /* hide non-public posts viewed from outside */
3215 if (read_only && scope != SCOPE_PUBLIC){
3216 continue;
3217 }
3149 3218
3150 xs_html *entry = html_entry(user, msg, read_only, 0, v, (user && !hide_children) ? 0 : 1); 3219 xs_html *entry = html_entry(user, msg, read_only, 0, v, (user && !hide_children) ? 0 : 1);
3151 3220
@@ -3256,7 +3325,7 @@ xs_html *html_people_list(snac *user, xs_list *list, const char *header, const c
3256 xs_html_tag("div", 3325 xs_html_tag("div",
3257 xs_html_attr("class", "snac-post-header"), 3326 xs_html_attr("class", "snac-post-header"),
3258 html_actor_icon(user, actor, xs_dict_get(actor, "published"), 3327 html_actor_icon(user, actor, xs_dict_get(actor, "published"),
3259 NULL, NULL, 0, 1, proxy, NULL, NULL))); 3328 NULL, NULL, -1, 1, proxy, NULL, NULL)));
3260 3329
3261 /* content (user bio) */ 3330 /* content (user bio) */
3262 const char *c = xs_dict_get(actor, "summary"); 3331 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
3357 "", "", 3426 "", "",
3358 NULL, actor_id, 3427 NULL, actor_id,
3359 xs_stock(XSTYPE_FALSE), "", 3428 xs_stock(XSTYPE_FALSE), "",
3360 xs_stock(XSTYPE_FALSE), NULL, 3429 SCOPE_MENTIONED, NULL,
3361 NULL, 0, NULL, NULL, 0, NULL, NULL), 3430 NULL, 0, NULL, NULL, 0, NULL, NULL),
3362 xs_html_tag("p", NULL)); 3431 xs_html_tag("p", NULL));
3363 3432
@@ -3564,7 +3633,7 @@ xs_str *html_notifications(snac *user, int skip, int show)
3564 xs_html_add(entry, 3633 xs_html_add(entry,
3565 xs_html_tag("div", 3634 xs_html_tag("div",
3566 xs_html_attr("class", "snac-post"), 3635 xs_html_attr("class", "snac-post"),
3567 html_actor_icon(user, actor, NULL, NULL, NULL, 0, 0, proxy, NULL, NULL))); 3636 html_actor_icon(user, actor, NULL, NULL, NULL, -1, 0, proxy, NULL, NULL)));
3568 } 3637 }
3569 else 3638 else
3570 if (strcmp(type, "Move") == 0) { 3639 if (strcmp(type, "Move") == 0) {
@@ -3578,7 +3647,7 @@ xs_str *html_notifications(snac *user, int skip, int show)
3578 xs_html_add(entry, 3647 xs_html_add(entry,
3579 xs_html_tag("div", 3648 xs_html_tag("div",
3580 xs_html_attr("class", "snac-post"), 3649 xs_html_attr("class", "snac-post"),
3581 html_actor_icon(user, old_actor, NULL, NULL, NULL, 0, 0, proxy, NULL, NULL))); 3650 html_actor_icon(user, old_actor, NULL, NULL, NULL, -1, 0, proxy, NULL, NULL)));
3582 } 3651 }
3583 } 3652 }
3584 } 3653 }
@@ -4445,7 +4514,18 @@ int html_post_handler(const xs_dict *req, const char *q_path,
4445 const char *post_date = xs_dict_get_def(p_vars, "post_date", ""); 4514 const char *post_date = xs_dict_get_def(p_vars, "post_date", "");
4446 const char *post_time = xs_dict_get_def(p_vars, "post_time", ""); 4515 const char *post_time = xs_dict_get_def(p_vars, "post_time", "");
4447 const char *post_lang = xs_dict_get(p_vars, "post_lang"); 4516 const char *post_lang = xs_dict_get(p_vars, "post_lang");
4448 int priv = !xs_is_null(xs_dict_get(p_vars, "mentioned_only")); 4517 const char *visibility = xs_dict_get(p_vars, "visibility");
4518 int scope = SCOPE_PUBLIC; /* default to public */
4519 if (!xs_is_null(visibility)) {
4520 if (strcmp(visibility, "unlisted") == 0)
4521 scope = SCOPE_UNLISTED;
4522 else
4523 if (strcmp(visibility, "followers") == 0)
4524 scope = SCOPE_FOLLOWERS;
4525 else
4526 if (strcmp(visibility, "mentioned") == 0)
4527 scope = SCOPE_MENTIONED;
4528 }
4449 int store_as_draft = !xs_is_null(xs_dict_get(p_vars, "is_draft")); 4529 int store_as_draft = !xs_is_null(xs_dict_get(p_vars, "is_draft"));
4450 xs *attach_list = xs_list_new(); 4530 xs *attach_list = xs_list_new();
4451 4531
@@ -4528,7 +4608,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
4528 enqueue_close_question(&snac, xs_dict_get(msg, "id"), end_secs); 4608 enqueue_close_question(&snac, xs_dict_get(msg, "id"), end_secs);
4529 } 4609 }
4530 else 4610 else
4531 msg = msg_note(&snac, content_2, to, in_reply_to, attach_list, priv, post_lang, NULL); 4611 msg = msg_note(&snac, content_2, to, in_reply_to, attach_list, scope, post_lang, NULL);
4532 4612
4533 if (sensitive != NULL) { 4613 if (sensitive != NULL) {
4534 msg = xs_dict_set(msg, "sensitive", xs_stock(XSTYPE_TRUE)); 4614 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)
45 "note {basedir} {uid} {text} [files...] Sends a note with optional attachments\n" 45 "note {basedir} {uid} {text} [files...] Sends a note with optional attachments\n"
46 "note_unlisted {basedir} {uid} {text} [files...] Sends an unlisted note with optional attachments\n" 46 "note_unlisted {basedir} {uid} {text} [files...] Sends an unlisted note with optional attachments\n"
47 "note_mention {basedir} {uid} {text} [files...] Sends a note only to mentioned accounts\n" 47 "note_mention {basedir} {uid} {text} [files...] Sends a note only to mentioned accounts\n"
48 "note_followers {basedir} {uid} {text} [files...] Sends a note only to followers\n"
48 "boost|announce {basedir} {uid} {url} Boosts (announces) a post\n" 49 "boost|announce {basedir} {uid} {url} Boosts (announces) a post\n"
49 "unboost {basedir} {uid} {url} Unboosts a post\n" 50 "unboost {basedir} {uid} {url} Unboosts a post\n"
50 "resetpwd {basedir} {uid} Resets the password of a user\n" 51 "resetpwd {basedir} {uid} Resets the password of a user\n"
@@ -800,7 +801,8 @@ int main(int argc, char *argv[])
800 801
801 if (strcmp(cmd, "note") == 0 || /** **/ 802 if (strcmp(cmd, "note") == 0 || /** **/
802 strcmp(cmd, "note_unlisted") == 0 || /** **/ 803 strcmp(cmd, "note_unlisted") == 0 || /** **/
803 strcmp(cmd, "note_mention") == 0) { /** **/ 804 strcmp(cmd, "note_mention") == 0 || /** **/
805 strcmp(cmd, "note_followers") == 0) { /** **/
804 xs *content = NULL; 806 xs *content = NULL;
805 xs *msg = NULL; 807 xs *msg = NULL;
806 xs *c_msg = NULL; 808 xs *c_msg = NULL;
@@ -909,12 +911,15 @@ int main(int argc, char *argv[])
909 return 1; 911 return 1;
910 } 912 }
911 913
912 int scope = 0; 914 int scope = SCOPE_PUBLIC;
913 if (strcmp(cmd, "note_mention") == 0) 915 if (strcmp(cmd, "note_mention") == 0)
914 scope = 1; 916 scope = SCOPE_MENTIONED;
915 else 917 else
916 if (strcmp(cmd, "note_unlisted") == 0) 918 if (strcmp(cmd, "note_unlisted") == 0)
917 scope = 2; 919 scope = SCOPE_UNLISTED;
920 else
921 if (strcmp(cmd, "note_followers") == 0)
922 scope = SCOPE_FOLLOWERS;
918 923
919 msg = msg_note(&snac, content, NULL, in_reply_to, attl, scope, getenv("LANG"), post_date); 924 msg = msg_note(&snac, content, NULL, in_reply_to, attl, scope, getenv("LANG"), post_date);
920 925
diff --git a/mastoapi.c b/mastoapi.c
index e77d15f..ed46b89 100644
--- a/mastoapi.c
+++ b/mastoapi.c
@@ -884,8 +884,16 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
884 st = xs_dict_append(st, "content", s1); 884 st = xs_dict_append(st, "content", s1);
885 } 885 }
886 886
887 st = xs_dict_append(st, "visibility", 887 /* determine visibility: https://docs.joinmastodon.org/entities/Status/#visibility */
888 is_msg_public(msg) ? "public" : "private"); 888 const int scope = get_msg_visibility(msg);
889 if (scope == SCOPE_PUBLIC)
890 st = xs_dict_append(st, "visibility", "public");
891 else if (scope == SCOPE_FOLLOWERS)
892 st = xs_dict_append(st, "visibility", "private");
893 else if (scope == SCOPE_MENTIONED)
894 st = xs_dict_append(st, "visibility", "direct");
895 else if (scope == SCOPE_UNLISTED)
896 st = xs_dict_append(st, "visibility", "unlisted");
889 897
890 tmp = xs_dict_get(msg, "sensitive"); 898 tmp = xs_dict_get(msg, "sensitive");
891 if (xs_is_null(tmp)) 899 if (xs_is_null(tmp))
@@ -2856,12 +2864,15 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2856 } 2864 }
2857 2865
2858 /* prepare the message */ 2866 /* prepare the message */
2859 int scope = 1; 2867 int scope = SCOPE_MENTIONED;
2860 if (strcmp(visibility, "unlisted") == 0) 2868 if (strcmp(visibility, "unlisted") == 0)
2861 scope = 2; 2869 scope = SCOPE_UNLISTED;
2862 else 2870 else
2863 if (strcmp(visibility, "public") == 0) 2871 if (strcmp(visibility, "public") == 0)
2864 scope = 0; 2872 scope = SCOPE_PUBLIC;
2873 else
2874 if (strcmp(visibility, "private") == 0)
2875 scope = SCOPE_FOLLOWERS;
2865 2876
2866 xs *msg = msg_note(&snac, content, NULL, irt, attach_list, scope, language, NULL); 2877 xs *msg = msg_note(&snac, content, NULL, irt, attach_list, scope, language, NULL);
2867 2878
diff --git a/snac.h b/snac.h
index 1a58e0b..dc9d19a 100644
--- a/snac.h
+++ b/snac.h
@@ -78,6 +78,13 @@ typedef struct {
78 78
79extern srv_state *p_state; 79extern srv_state *p_state;
80 80
81enum {
82 SCOPE_PUBLIC = 0,
83 SCOPE_MENTIONED = 1,
84 SCOPE_UNLISTED = 2,
85 SCOPE_FOLLOWERS = 3,
86};
87
81void snac_log(snac *user, xs_str *str); 88void snac_log(snac *user, xs_str *str);
82#define snac_debug(user, level, str) do { if (dbglevel >= (level)) \ 89#define snac_debug(user, level, str) do { if (dbglevel >= (level)) \
83 { snac_log((user), (str)); } } while (0) 90 { snac_log((user), (str)); } } while (0)
@@ -366,6 +373,7 @@ xs_dict *msg_accept(snac *snac, const xs_val *object, const char *to);
366xs_dict *msg_question(snac *user, const char *content, xs_list *attach, 373xs_dict *msg_question(snac *user, const char *content, xs_list *attach,
367 const xs_list *opts, int multiple, int end_secs); 374 const xs_list *opts, int multiple, int end_secs);
368xs_dict *msg_replies(snac *user, const char *id, int fill); 375xs_dict *msg_replies(snac *user, const char *id, int fill);
376int get_msg_visibility(const xs_dict *msg);
369 377
370int activitypub_request(snac *snac, const char *url, xs_dict **data); 378int activitypub_request(snac *snac, const char *url, xs_dict **data);
371int actor_request(snac *user, const char *actor, xs_dict **data); 379int actor_request(snac *user, const char *actor, xs_dict **data);