diff options
| author | 2025-09-28 15:46:56 +0200 | |
|---|---|---|
| committer | 2025-09-28 15:46:56 +0200 | |
| commit | ab698c2bd6cbe74b2cca0a17fe210e159a43a2ce (patch) | |
| tree | 868fa6b77c7697f972cb7b9ea8d3ff49ab8652e1 | |
| parent | Temporary tweak. (diff) | |
| parent | Merge branch 'master' into feature/visibility (diff) | |
| download | penes-snac2-ab698c2bd6cbe74b2cca0a17fe210e159a43a2ce.tar.gz penes-snac2-ab698c2bd6cbe74b2cca0a17fe210e159a43a2ce.tar.xz penes-snac2-ab698c2bd6cbe74b2cca0a17fe210e159a43a2ce.zip | |
Merge pull request 'implementing scopes' (#474) from byte/snac2:feature/visibility into master
Reviewed-on: https://codeberg.org/grunfink/snac2/pulls/474
| -rw-r--r-- | activitypub.c | 108 | ||||
| -rw-r--r-- | data.c | 10 | ||||
| -rw-r--r-- | html.c | 144 | ||||
| -rw-r--r-- | main.c | 13 | ||||
| -rw-r--r-- | mastoapi.c | 21 | ||||
| -rw-r--r-- | snac.h | 8 |
6 files changed, 240 insertions, 64 deletions
diff --git a/activitypub.c b/activitypub.c index 2d3d903..4ed2805 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 | ||
| 2148 | int 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 | ||
| 2117 | int update_question(snac *user, const char *id) | 2185 | int update_question(snac *user, const char *id) |
| 2118 | /* updates the poll counts */ | 2186 | /* updates the poll counts */ |
| @@ -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); |
| @@ -165,7 +165,7 @@ xs_str *format_text_with_emoji(snac *user, const char *text, int ems, const char | |||
| 165 | 165 | ||
| 166 | 166 | ||
| 167 | xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, | 167 | xs_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, ¬e_lang, &dummy, &c); | 1994 | xs_dict_next(cmap, ¬e_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)); |
| @@ -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 | ||
| @@ -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 | ||
| @@ -78,6 +78,13 @@ typedef struct { | |||
| 78 | 78 | ||
| 79 | extern srv_state *p_state; | 79 | extern srv_state *p_state; |
| 80 | 80 | ||
| 81 | enum { | ||
| 82 | SCOPE_PUBLIC = 0, | ||
| 83 | SCOPE_MENTIONED = 1, | ||
| 84 | SCOPE_UNLISTED = 2, | ||
| 85 | SCOPE_FOLLOWERS = 3, | ||
| 86 | }; | ||
| 87 | |||
| 81 | void snac_log(snac *user, xs_str *str); | 88 | void 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) |
| @@ -367,6 +374,7 @@ xs_dict *msg_accept(snac *snac, const xs_val *object, const char *to); | |||
| 367 | xs_dict *msg_question(snac *user, const char *content, xs_list *attach, | 374 | xs_dict *msg_question(snac *user, const char *content, xs_list *attach, |
| 368 | const xs_list *opts, int multiple, int end_secs); | 375 | const xs_list *opts, int multiple, int end_secs); |
| 369 | xs_dict *msg_replies(snac *user, const char *id, int fill); | 376 | xs_dict *msg_replies(snac *user, const char *id, int fill); |
| 377 | int get_msg_visibility(const xs_dict *msg); | ||
| 370 | 378 | ||
| 371 | int activitypub_request(snac *snac, const char *url, xs_dict **data); | 379 | int activitypub_request(snac *snac, const char *url, xs_dict **data); |
| 372 | int actor_request(snac *user, const char *actor, xs_dict **data); | 380 | int actor_request(snac *user, const char *actor, xs_dict **data); |