diff options
| -rw-r--r-- | RELEASE_NOTES.md | 12 | ||||
| -rw-r--r-- | activitypub.c | 31 | ||||
| -rw-r--r-- | data.c | 22 | ||||
| -rw-r--r-- | doc/snac.5 | 4 | ||||
| -rw-r--r-- | html.c | 153 | ||||
| -rw-r--r-- | mastoapi.c | 4 | ||||
| -rw-r--r-- | po/cs.po | 32 | ||||
| -rw-r--r-- | rss.c | 2 | ||||
| -rw-r--r-- | snac.h | 3 | ||||
| -rw-r--r-- | upgrade.c | 2 | ||||
| -rw-r--r-- | webfinger.c | 4 |
11 files changed, 224 insertions, 45 deletions
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7dfb332..505d080 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md | |||
| @@ -1,5 +1,17 @@ | |||
| 1 | # Release Notes | 1 | # Release Notes |
| 2 | 2 | ||
| 3 | ## UNRELEASED | ||
| 4 | |||
| 5 | Quoted posts are now shown. | ||
| 6 | |||
| 7 | Added metadata to remote users in the people page (contributed by dandelions). | ||
| 8 | |||
| 9 | Fixed memory leak (contributed by dandelions). | ||
| 10 | |||
| 11 | Fixed user matching (contributed by rakoo). | ||
| 12 | |||
| 13 | Fixed typo in man page (contributed by spky). | ||
| 14 | |||
| 3 | ## 2.84 | 15 | ## 2.84 |
| 4 | 16 | ||
| 5 | Implemented more scopes to match other ActivityPub implementations (public, unlisted, followers-only and direct message) (contributed by byte). | 17 | Implemented more scopes to match other ActivityPub implementations (public, unlisted, followers-only and direct message) (contributed by byte). |
diff --git a/activitypub.c b/activitypub.c index 0368ac8..90230d8 100644 --- a/activitypub.c +++ b/activitypub.c | |||
| @@ -779,7 +779,7 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg) | |||
| 779 | object_get(object, &obj); | 779 | object_get(object, &obj); |
| 780 | 780 | ||
| 781 | /* if it's about one of our posts, accept it */ | 781 | /* if it's about one of our posts, accept it */ |
| 782 | if (xs_startswith(object, snac->actor)) | 782 | if (is_msg_mine(snac, object)) |
| 783 | return 2; | 783 | return 2; |
| 784 | 784 | ||
| 785 | /* blocked by hashtag? */ | 785 | /* blocked by hashtag? */ |
| @@ -1242,7 +1242,7 @@ void notify(snac *snac, const char *type, const char *utype, const char *actor, | |||
| 1242 | 1242 | ||
| 1243 | if (xs_match(type, "Like|Announce|EmojiReact")) { | 1243 | if (xs_match(type, "Like|Announce|EmojiReact")) { |
| 1244 | /* if it's not an admiration about something by us, done */ | 1244 | /* if it's not an admiration about something by us, done */ |
| 1245 | if (xs_is_null(objid) || !xs_startswith(objid, snac->actor)) | 1245 | if (xs_is_null(objid) || !is_msg_mine(snac, objid)) |
| 1246 | return; | 1246 | return; |
| 1247 | 1247 | ||
| 1248 | /* if it's an announce by our own relay, done */ | 1248 | /* if it's an announce by our own relay, done */ |
| @@ -1267,7 +1267,7 @@ void notify(snac *snac, const char *type, const char *utype, const char *actor, | |||
| 1267 | return; | 1267 | return; |
| 1268 | 1268 | ||
| 1269 | /* if it's not ours and we didn't vote, discard */ | 1269 | /* if it's not ours and we didn't vote, discard */ |
| 1270 | if (!xs_startswith(poll_id, snac->actor) && !was_question_voted(snac, poll_id)) | 1270 | if (!is_msg_mine(snac, poll_id) && !was_question_voted(snac, poll_id)) |
| 1271 | return; | 1271 | return; |
| 1272 | } | 1272 | } |
| 1273 | 1273 | ||
| @@ -2686,6 +2686,20 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) | |||
| 2686 | 2686 | ||
| 2687 | timeline_request(snac, &in_reply_to, &wrk, 0); | 2687 | timeline_request(snac, &in_reply_to, &wrk, 0); |
| 2688 | 2688 | ||
| 2689 | const char *quoted_id = xs_or(xs_dict_get(object, "quoteUri"), xs_dict_get(object, "quoteUrl")); | ||
| 2690 | |||
| 2691 | if (xs_is_string(quoted_id) && xs_match(quoted_id, "https://*|http://*")) { /** **/ | ||
| 2692 | xs *quoted_post = NULL; | ||
| 2693 | int status; | ||
| 2694 | |||
| 2695 | if (valid_status(status = activitypub_request(snac, quoted_id, "ed_post))) { | ||
| 2696 | /* got quoted post */ | ||
| 2697 | object_add(quoted_id, quoted_post); | ||
| 2698 | } | ||
| 2699 | |||
| 2700 | snac_debug(snac, 1, xs_fmt("retrieving quoted post %s %d", quoted_id, status)); | ||
| 2701 | } | ||
| 2702 | |||
| 2689 | if (timeline_add(snac, id, object)) { | 2703 | if (timeline_add(snac, id, object)) { |
| 2690 | snac_log(snac, xs_fmt("new '%s' %s %s", utype, actor, id)); | 2704 | snac_log(snac, xs_fmt("new '%s' %s %s", utype, actor, id)); |
| 2691 | do_notify = 1; | 2705 | do_notify = 1; |
| @@ -2778,10 +2792,10 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) | |||
| 2778 | if (xs_is_null(object)) | 2792 | if (xs_is_null(object)) |
| 2779 | snac_log(snac, xs_fmt("malformed message: no 'id' field")); | 2793 | snac_log(snac, xs_fmt("malformed message: no 'id' field")); |
| 2780 | else | 2794 | else |
| 2781 | if (is_muted(snac, actor) && !xs_startswith(object, snac->actor)) | 2795 | if (is_muted(snac, actor) && !is_msg_mine(snac, object)) |
| 2782 | snac_log(snac, xs_fmt("dropped 'Announce' from muted actor %s", actor)); | 2796 | snac_log(snac, xs_fmt("dropped 'Announce' from muted actor %s", actor)); |
| 2783 | else | 2797 | else |
| 2784 | if (is_limited(snac, actor) && !xs_startswith(object, snac->actor)) | 2798 | if (is_limited(snac, actor) && !is_msg_mine(snac, object)) |
| 2785 | snac_log(snac, xs_fmt("dropped 'Announce' from limited actor %s", actor)); | 2799 | snac_log(snac, xs_fmt("dropped 'Announce' from limited actor %s", actor)); |
| 2786 | else { | 2800 | else { |
| 2787 | xs *a_msg = NULL; | 2801 | xs *a_msg = NULL; |
| @@ -2792,6 +2806,9 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) | |||
| 2792 | if (valid_status(object_get(object, &a_msg))) { | 2806 | if (valid_status(object_get(object, &a_msg))) { |
| 2793 | const char *who = get_atto(a_msg); | 2807 | const char *who = get_atto(a_msg); |
| 2794 | 2808 | ||
| 2809 | /* got the admired object: instance is [back] online */ | ||
| 2810 | instance_failure(object, 2); | ||
| 2811 | |||
| 2795 | if (who && !is_muted(snac, who)) { | 2812 | if (who && !is_muted(snac, who)) { |
| 2796 | /* bring the actor */ | 2813 | /* bring the actor */ |
| 2797 | xs *who_o = NULL; | 2814 | xs *who_o = NULL; |
| @@ -2886,7 +2903,7 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) | |||
| 2886 | snac_log(snac, xs_fmt("malformed message: no 'id' field")); | 2903 | snac_log(snac, xs_fmt("malformed message: no 'id' field")); |
| 2887 | else | 2904 | else |
| 2888 | if (object_here(object)) { | 2905 | if (object_here(object)) { |
| 2889 | if (xs_startswith(object, srv_baseurl) && !xs_startswith(object, actor)) | 2906 | if (xs_startswith(object, srv_baseurl) && !is_msg_mine(snac, object)) |
| 2890 | snac_log(snac, xs_fmt("ignored incorrect 'Delete' %s %s", actor, object)); | 2907 | snac_log(snac, xs_fmt("ignored incorrect 'Delete' %s %s", actor, object)); |
| 2891 | else { | 2908 | else { |
| 2892 | timeline_del(snac, object); | 2909 | timeline_del(snac, object); |
| @@ -3699,7 +3716,7 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, | |||
| 3699 | const char *type = xs_dict_get(i, "type"); | 3716 | const char *type = xs_dict_get(i, "type"); |
| 3700 | const char *id = xs_dict_get(i, "id"); | 3717 | const char *id = xs_dict_get(i, "id"); |
| 3701 | 3718 | ||
| 3702 | if (type && id && strcmp(type, "Note") == 0 && xs_startswith(id, snac.actor)) { | 3719 | if (type && id && strcmp(type, "Note") == 0 && is_msg_mine(&snac, id)) { |
| 3703 | if (is_msg_public(i)) { | 3720 | if (is_msg_public(i)) { |
| 3704 | xs *c_msg = msg_create(&snac, i); | 3721 | xs *c_msg = msg_create(&snac, i); |
| 3705 | list = xs_list_append(list, c_msg); | 3722 | list = xs_list_append(list, c_msg); |
| @@ -1358,6 +1358,20 @@ int pending_count(snac *user) | |||
| 1358 | } | 1358 | } |
| 1359 | 1359 | ||
| 1360 | 1360 | ||
| 1361 | int is_msg_mine(snac *user, const char *id) | ||
| 1362 | /* returns true if a post id is by the given user */ | ||
| 1363 | { | ||
| 1364 | int ret = 0; | ||
| 1365 | |||
| 1366 | if (xs_is_string(id)) { | ||
| 1367 | xs *s1 = xs_fmt("%s/", user->actor); | ||
| 1368 | ret = xs_startswith(id, s1); | ||
| 1369 | } | ||
| 1370 | |||
| 1371 | return ret; | ||
| 1372 | } | ||
| 1373 | |||
| 1374 | |||
| 1361 | /** timeline **/ | 1375 | /** timeline **/ |
| 1362 | 1376 | ||
| 1363 | double timeline_mtime(snac *snac) | 1377 | double timeline_mtime(snac *snac) |
| @@ -1453,7 +1467,7 @@ void timeline_update_indexes(snac *snac, const char *id) | |||
| 1453 | { | 1467 | { |
| 1454 | object_user_cache_add(snac, id, "private"); | 1468 | object_user_cache_add(snac, id, "private"); |
| 1455 | 1469 | ||
| 1456 | if (xs_startswith(id, snac->actor)) { | 1470 | if (is_msg_mine(snac, id)) { |
| 1457 | xs *msg = NULL; | 1471 | xs *msg = NULL; |
| 1458 | 1472 | ||
| 1459 | if (valid_status(object_get(id, &msg))) { | 1473 | if (valid_status(object_get(id, &msg))) { |
| @@ -1913,7 +1927,7 @@ int pin(snac *user, const char *id) | |||
| 1913 | { | 1927 | { |
| 1914 | int ret = -2; | 1928 | int ret = -2; |
| 1915 | 1929 | ||
| 1916 | if (xs_startswith(id, user->actor)) { | 1930 | if (is_msg_mine(user, id)) { |
| 1917 | if (is_pinned(user, id)) | 1931 | if (is_pinned(user, id)) |
| 1918 | ret = -3; | 1932 | ret = -3; |
| 1919 | else | 1933 | else |
| @@ -3513,7 +3527,7 @@ void enqueue_output(snac *snac, const xs_dict *msg, | |||
| 3513 | const xs_str *inbox, int retries, int p_status) | 3527 | const xs_str *inbox, int retries, int p_status) |
| 3514 | /* enqueues an output message to an inbox */ | 3528 | /* enqueues an output message to an inbox */ |
| 3515 | { | 3529 | { |
| 3516 | if (xs_startswith(inbox, snac->actor)) { | 3530 | if (is_msg_mine(snac, inbox)) { |
| 3517 | snac_debug(snac, 1, xs_str_new("refusing enqueue to myself")); | 3531 | snac_debug(snac, 1, xs_str_new("refusing enqueue to myself")); |
| 3518 | return; | 3532 | return; |
| 3519 | } | 3533 | } |
| @@ -4041,7 +4055,7 @@ void delete_purged_posts(snac *user, int days) | |||
| 4041 | if (xs_is_dict(msg)) { | 4055 | if (xs_is_dict(msg)) { |
| 4042 | const char *id = xs_dict_get(msg, "id"); | 4056 | const char *id = xs_dict_get(msg, "id"); |
| 4043 | 4057 | ||
| 4044 | if (xs_is_string(id) && xs_startswith(id, user->actor)) { | 4058 | if (xs_is_string(id) && is_msg_mine(user, id)) { |
| 4045 | xs *d_msg = msg_delete(user, id); | 4059 | xs *d_msg = msg_delete(user, id); |
| 4046 | 4060 | ||
| 4047 | enqueue_message(user, d_msg); | 4061 | enqueue_message(user, d_msg); |
| @@ -143,6 +143,8 @@ in the "last_announcement" field of the | |||
| 143 | .Pa user.json | 143 | .Pa user.json |
| 144 | file. When the file is modified, the announcement will then reappear. It can | 144 | file. When the file is modified, the announcement will then reappear. It can |
| 145 | contain only text and will be ignored if it has more than 2048 bytes. | 145 | contain only text and will be ignored if it has more than 2048 bytes. |
| 146 | .It Pa server.pid | ||
| 147 | This file stores the server PID in a single text line. | ||
| 146 | .El | 148 | .El |
| 147 | .Pp | 149 | .Pp |
| 148 | Each user directory is a subdirectory of | 150 | Each user directory is a subdirectory of |
| @@ -222,8 +224,6 @@ after executing the 'export_csv' command-line operation. | |||
| 222 | .It Pa import/ | 224 | .It Pa import/ |
| 223 | Mastodon-compatible CSV files must be copied into this directory to use | 225 | Mastodon-compatible CSV files must be copied into this directory to use |
| 224 | any of the importing functions. | 226 | any of the importing functions. |
| 225 | .It Pa server.pid | ||
| 226 | This file stores the server PID in a single text line. | ||
| 227 | .El | 227 | .El |
| 228 | .Sh SEE ALSO | 228 | .Sh SEE ALSO |
| 229 | .Xr snac 1 , | 229 | .Xr snac 1 , |
| @@ -1688,7 +1688,7 @@ xs_html *html_top_controls(snac *user) | |||
| 1688 | xs_html_text(L("Languages you usually post in:")), | 1688 | xs_html_text(L("Languages you usually post in:")), |
| 1689 | xs_html_sctag("br", NULL), | 1689 | xs_html_sctag("br", NULL), |
| 1690 | xs_html_sctag("input", | 1690 | xs_html_sctag("input", |
| 1691 | xs_html_attr("type", "next"), | 1691 | xs_html_attr("type", "text"), |
| 1692 | xs_html_attr("name", "post_langs"), | 1692 | xs_html_attr("name", "post_langs"), |
| 1693 | xs_html_attr("value", post_langs), | 1693 | xs_html_attr("value", post_langs), |
| 1694 | xs_html_attr("placeholder", L("en fr es de_AT")))), | 1694 | xs_html_attr("placeholder", L("en fr es de_AT")))), |
| @@ -1891,7 +1891,7 @@ xs_html *html_entry_controls(snac *user, const char *actor, | |||
| 1891 | xs_html_attr("name", "redir"), | 1891 | xs_html_attr("name", "redir"), |
| 1892 | xs_html_attr("value", redir)))); | 1892 | xs_html_attr("value", redir)))); |
| 1893 | 1893 | ||
| 1894 | if (!xs_startswith(id, user->actor)) { | 1894 | if (!is_msg_mine(user, id)) { |
| 1895 | if (xs_list_in(likes, user->md5) == -1) { | 1895 | if (xs_list_in(likes, user->md5) == -1) { |
| 1896 | /* not already liked; add button */ | 1896 | /* not already liked; add button */ |
| 1897 | xs_html_add(form, | 1897 | xs_html_add(form, |
| @@ -2387,6 +2387,23 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 2387 | /* c contains sanitized HTML */ | 2387 | /* c contains sanitized HTML */ |
| 2388 | xs_html_add(snac_content, | 2388 | xs_html_add(snac_content, |
| 2389 | xs_html_raw(c)); | 2389 | xs_html_raw(c)); |
| 2390 | |||
| 2391 | /* quoted post */ | ||
| 2392 | const char *quoted_id = xs_or(xs_dict_get(msg, "quoteUri"), xs_dict_get(msg, "quoteUrl")); | ||
| 2393 | |||
| 2394 | if (xs_is_string(quoted_id) && xs_match(quoted_id, "https://*|http://*")) { /** **/ | ||
| 2395 | xs *quoted_post = NULL; | ||
| 2396 | |||
| 2397 | if (valid_status(object_get(quoted_id, "ed_post))) { | ||
| 2398 | xs_html_add(snac_content, | ||
| 2399 | xs_html_tag("blockquote", | ||
| 2400 | xs_html_attr("class", "snac-quoted-post"), | ||
| 2401 | html_entry(user, quoted_post, 1, 1, NULL, 1))); | ||
| 2402 | } | ||
| 2403 | else | ||
| 2404 | if (user) | ||
| 2405 | enqueue_object_request(user, quoted_id, 0); | ||
| 2406 | } | ||
| 2390 | } | 2407 | } |
| 2391 | 2408 | ||
| 2392 | if (strcmp(type, "Question") == 0) { /** question content **/ | 2409 | if (strcmp(type, "Question") == 0) { /** question content **/ |
| @@ -2402,7 +2419,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 2402 | if (read_only) | 2419 | if (read_only) |
| 2403 | closed = 1; /* non-identified page; show as closed */ | 2420 | closed = 1; /* non-identified page; show as closed */ |
| 2404 | else | 2421 | else |
| 2405 | if (user && xs_startswith(id, user->actor)) | 2422 | if (user && is_msg_mine(user, id)) |
| 2406 | closed = 1; /* we questioned; closed for us */ | 2423 | closed = 1; /* we questioned; closed for us */ |
| 2407 | else | 2424 | else |
| 2408 | if (user && was_question_voted(user, id)) | 2425 | if (user && was_question_voted(user, id)) |
| @@ -2610,7 +2627,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 2610 | xs_html_attr("title", name)))); | 2627 | xs_html_attr("title", name)))); |
| 2611 | } | 2628 | } |
| 2612 | else | 2629 | else |
| 2613 | if (xs_startswith(type, "video/")) { | 2630 | if (xs_startswith(type, "video/") || strcmp(type, "Video") == 0) { |
| 2614 | xs_html_add(content_attachments, | 2631 | xs_html_add(content_attachments, |
| 2615 | xs_html_tag("video", | 2632 | xs_html_tag("video", |
| 2616 | xs_html_attr("preload", "none"), | 2633 | xs_html_attr("preload", "none"), |
| @@ -3214,7 +3231,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, | |||
| 3214 | continue; | 3231 | continue; |
| 3215 | } | 3232 | } |
| 3216 | /* hide non-public posts viewed from outside */ | 3233 | /* hide non-public posts viewed from outside */ |
| 3217 | if (read_only && scope != SCOPE_PUBLIC){ | 3234 | if (read_only && (scope != SCOPE_PUBLIC && scope != SCOPE_UNLISTED)) { |
| 3218 | continue; | 3235 | continue; |
| 3219 | } | 3236 | } |
| 3220 | 3237 | ||
| @@ -3331,10 +3348,11 @@ xs_html *html_people_list(snac *user, xs_list *list, const char *header, const c | |||
| 3331 | 3348 | ||
| 3332 | /* content (user bio) */ | 3349 | /* content (user bio) */ |
| 3333 | const char *c = xs_dict_get(actor, "summary"); | 3350 | const char *c = xs_dict_get(actor, "summary"); |
| 3351 | const xs_val *tag = xs_dict_get(actor, "tag"); | ||
| 3334 | 3352 | ||
| 3335 | if (!xs_is_null(c)) { | 3353 | if (!xs_is_null(c)) { |
| 3336 | xs *sc = sanitize(c); | 3354 | xs *sc = sanitize(c); |
| 3337 | sc = replace_shortnames(sc, xs_dict_get(actor, "tag"), 2, proxy); | 3355 | sc = replace_shortnames(sc, tag, 2, proxy); |
| 3338 | 3356 | ||
| 3339 | xs_html *snac_content = xs_html_tag("div", | 3357 | xs_html *snac_content = xs_html_tag("div", |
| 3340 | xs_html_attr("class", "snac-content")); | 3358 | xs_html_attr("class", "snac-content")); |
| @@ -3350,6 +3368,93 @@ xs_html *html_people_list(snac *user, xs_list *list, const char *header, const c | |||
| 3350 | xs_html_add(snac_post, snac_content); | 3368 | xs_html_add(snac_post, snac_content); |
| 3351 | } | 3369 | } |
| 3352 | 3370 | ||
| 3371 | /* add user metadata */ | ||
| 3372 | xs_html *snac_metadata = xs_html_tag("div", | ||
| 3373 | xs_html_attr("class", "snac-metadata")); | ||
| 3374 | |||
| 3375 | int count = 0; | ||
| 3376 | const xs_val *address = xs_dict_get(actor, "vcard:Address"); | ||
| 3377 | if (xs_is_string(address)) { | ||
| 3378 | xs_html_add(snac_metadata, | ||
| 3379 | xs_html_tag("span", | ||
| 3380 | xs_html_attr("class", "snac-property-name"), | ||
| 3381 | xs_html_raw("📍 Location")), | ||
| 3382 | xs_html_text(":"), | ||
| 3383 | xs_html_raw(" "), | ||
| 3384 | xs_html_tag("span", | ||
| 3385 | xs_html_attr("class", "snac-property-value p-adr"), | ||
| 3386 | xs_html_text(address)), | ||
| 3387 | xs_html_sctag("br", NULL)); | ||
| 3388 | |||
| 3389 | count++; | ||
| 3390 | } | ||
| 3391 | |||
| 3392 | const xs_val *birthday = xs_dict_get(actor, "vcard:bday"); | ||
| 3393 | if (xs_is_string(birthday)) { | ||
| 3394 | xs_html_add(snac_metadata, | ||
| 3395 | xs_html_tag("span", | ||
| 3396 | xs_html_attr("class", "snac-property-name"), | ||
| 3397 | xs_html_raw("🎂 Birthday")), | ||
| 3398 | xs_html_text(":"), | ||
| 3399 | xs_html_raw(" "), | ||
| 3400 | xs_html_tag("time", | ||
| 3401 | xs_html_attr("class", "snac-property-value dt-bday"), | ||
| 3402 | xs_html_text(birthday)), | ||
| 3403 | xs_html_sctag("br", NULL)); | ||
| 3404 | |||
| 3405 | count++; | ||
| 3406 | } | ||
| 3407 | |||
| 3408 | const xs_list *attachment = xs_dict_get(actor, "attachment"); | ||
| 3409 | if (count > 0 && xs_list_len(attachment) > 0) { | ||
| 3410 | xs_html_add(snac_metadata, | ||
| 3411 | xs_html_sctag("hr", | ||
| 3412 | xs_html_attr("class", "snac-property-divider"))); | ||
| 3413 | } | ||
| 3414 | |||
| 3415 | const xs_val *v; | ||
| 3416 | xs_list_foreach(attachment, v) { | ||
| 3417 | const char *type = xs_dict_get(v, "type"); | ||
| 3418 | const char *name = xs_dict_get(v, "name"); | ||
| 3419 | const char *value = xs_dict_get(v, "value"); | ||
| 3420 | |||
| 3421 | if (!xs_is_null(type) && !xs_is_null(name) && | ||
| 3422 | !xs_is_null(value) && strcmp(type, "PropertyValue") == 0) { | ||
| 3423 | /* both the name and the value can contain emoji */ | ||
| 3424 | xs *nam = sanitize(name); | ||
| 3425 | nam = replace_shortnames(nam, tag, 1, proxy); | ||
| 3426 | |||
| 3427 | /* todo: sometimes the value is transmitted as markdown and not html ._. */ | ||
| 3428 | xs *val = sanitize(value); | ||
| 3429 | val = replace_shortnames(val, tag, 1, proxy); | ||
| 3430 | |||
| 3431 | /* delete <p> tags, because some software sends them */ | ||
| 3432 | val = xs_replace_i(val, "<p>", ""); | ||
| 3433 | val = xs_replace_i(val, "</p>", ""); | ||
| 3434 | |||
| 3435 | xs_html_add(snac_metadata, | ||
| 3436 | xs_html_tag("span", | ||
| 3437 | xs_html_attr("class", "snac-property-name"), | ||
| 3438 | xs_html_raw(nam)), | ||
| 3439 | xs_html_text(":"), | ||
| 3440 | xs_html_raw(" "), | ||
| 3441 | xs_html_tag("span", | ||
| 3442 | xs_html_attr("class", "snac-property-value"), | ||
| 3443 | xs_html_raw(val)), | ||
| 3444 | xs_html_sctag("br", NULL)); | ||
| 3445 | |||
| 3446 | count++; | ||
| 3447 | } | ||
| 3448 | } | ||
| 3449 | |||
| 3450 | if (count > 0) { | ||
| 3451 | xs_html_add(snac_post, snac_metadata); | ||
| 3452 | } | ||
| 3453 | else { | ||
| 3454 | /* free the html, by rendering it... */ | ||
| 3455 | xs_free(xs_html_render(snac_metadata)); | ||
| 3456 | } | ||
| 3457 | |||
| 3353 | /* buttons */ | 3458 | /* buttons */ |
| 3354 | xs *btn_form_action = xs_fmt("%s/admin/action", user->actor); | 3459 | xs *btn_form_action = xs_fmt("%s/admin/action", user->actor); |
| 3355 | 3460 | ||
| @@ -3555,9 +3660,13 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 3555 | 3660 | ||
| 3556 | if (valid_status(actor_get(actor_id, &actor))) | 3661 | if (valid_status(actor_get(actor_id, &actor))) |
| 3557 | a_name = actor_name(actor, proxy); | 3662 | a_name = actor_name(actor, proxy); |
| 3558 | else | 3663 | else { |
| 3559 | a_name = xs_dup(actor_id); | 3664 | a_name = xs_dup(actor_id); |
| 3560 | 3665 | ||
| 3666 | /* actor not here: request it */ | ||
| 3667 | enqueue_actor_refresh(user, actor_id, 0); | ||
| 3668 | } | ||
| 3669 | |||
| 3561 | xs *label_sanitized = sanitize(type); | 3670 | xs *label_sanitized = sanitize(type); |
| 3562 | const char *label = label_sanitized; | 3671 | const char *label = label_sanitized; |
| 3563 | 3672 | ||
| @@ -3630,7 +3739,7 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 3630 | xs_html_attr("class", "snac-post-with-desc"), | 3739 | xs_html_attr("class", "snac-post-with-desc"), |
| 3631 | html_label); | 3740 | html_label); |
| 3632 | 3741 | ||
| 3633 | if (strcmp(type, "Follow") == 0 || strcmp(utype, "Follow") == 0 || strcmp(type, "Block") == 0) { | 3742 | if (strcmp(type, "Block") == 0) { |
| 3634 | if (actor) | 3743 | if (actor) |
| 3635 | xs_html_add(entry, | 3744 | xs_html_add(entry, |
| 3636 | xs_html_tag("div", | 3745 | xs_html_tag("div", |
| @@ -3638,6 +3747,32 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 3638 | html_actor_icon(user, actor, NULL, NULL, NULL, -1, 0, proxy, NULL, NULL))); | 3747 | html_actor_icon(user, actor, NULL, NULL, NULL, -1, 0, proxy, NULL, NULL))); |
| 3639 | } | 3748 | } |
| 3640 | else | 3749 | else |
| 3750 | if (strcmp(type, "Follow") == 0 || strcmp(utype, "Follow") == 0) { | ||
| 3751 | if (actor) { | ||
| 3752 | xs *action = xs_fmt("%s/admin/action", user->actor); | ||
| 3753 | xs_html *button = NULL; | ||
| 3754 | |||
| 3755 | if (following_check(user, actor_id)) | ||
| 3756 | button = html_button("unfollow", L("Unfollow"), L("Stop following this user's activity")); | ||
| 3757 | else | ||
| 3758 | button = html_button("follow", L("Follow"), L("Start following this user's activity")); | ||
| 3759 | |||
| 3760 | xs_html_add(entry, | ||
| 3761 | xs_html_tag("div", | ||
| 3762 | xs_html_attr("class", "snac-post"), | ||
| 3763 | html_actor_icon(user, actor, NULL, NULL, NULL, -1, 0, proxy, NULL, NULL), | ||
| 3764 | xs_html_tag("form", | ||
| 3765 | xs_html_attr("method", "post"), | ||
| 3766 | xs_html_attr("action", action), | ||
| 3767 | xs_html_sctag("input", | ||
| 3768 | xs_html_attr("type", "hidden"), | ||
| 3769 | xs_html_attr("name", "actor"), | ||
| 3770 | xs_html_attr("value", actor_id)), | ||
| 3771 | button, | ||
| 3772 | xs_html_sctag("br", NULL)))); | ||
| 3773 | } | ||
| 3774 | } | ||
| 3775 | else | ||
| 3641 | if (strcmp(type, "Move") == 0) { | 3776 | if (strcmp(type, "Move") == 0) { |
| 3642 | const xs_dict *o_msg = xs_dict_get(noti, "msg"); | 3777 | const xs_dict *o_msg = xs_dict_get(noti, "msg"); |
| 3643 | const char *target; | 3778 | const char *target; |
| @@ -4880,7 +5015,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, | |||
| 4880 | } | 5015 | } |
| 4881 | else { | 5016 | else { |
| 4882 | /* delete an entry */ | 5017 | /* delete an entry */ |
| 4883 | if (xs_startswith(id, snac.actor) && !is_draft(&snac, id)) { | 5018 | if (is_msg_mine(&snac, id) && !is_draft(&snac, id)) { |
| 4884 | /* it's a post by us: generate a delete */ | 5019 | /* it's a post by us: generate a delete */ |
| 4885 | xs *msg = msg_delete(&snac, id); | 5020 | xs *msg = msg_delete(&snac, id); |
| 4886 | 5021 | ||
| @@ -1919,7 +1919,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, | |||
| 1919 | /* add only posts by the author */ | 1919 | /* add only posts by the author */ |
| 1920 | if (!xs_is_null(msg_id) && | 1920 | if (!xs_is_null(msg_id) && |
| 1921 | strcmp(xs_dict_get(msg, "type"), "Note") == 0 && | 1921 | strcmp(xs_dict_get(msg, "type"), "Note") == 0 && |
| 1922 | xs_startswith(xs_dict_get(msg, "id"), snac2.actor) && is_msg_public(msg)) { | 1922 | is_msg_mine(&snac2, xs_dict_get(msg, "id")) && is_msg_public(msg)) { |
| 1923 | 1923 | ||
| 1924 | /* if max_id is set, skip entries until we find it */ | 1924 | /* if max_id is set, skip entries until we find it */ |
| 1925 | if (skip_until_max) { | 1925 | if (skip_until_max) { |
| @@ -3824,7 +3824,7 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path, | |||
| 3824 | if (valid_status(object_get_by_md5(p, &obj))) { | 3824 | if (valid_status(object_get_by_md5(p, &obj))) { |
| 3825 | const char *id = xs_dict_get(obj, "id"); | 3825 | const char *id = xs_dict_get(obj, "id"); |
| 3826 | 3826 | ||
| 3827 | if (xs_is_string(id) && xs_startswith(id, snac.actor)) { | 3827 | if (xs_is_string(id) && is_msg_mine(&snac, id)) { |
| 3828 | xs *out = mastoapi_status(&snac, obj); | 3828 | xs *out = mastoapi_status(&snac, obj); |
| 3829 | 3829 | ||
| 3830 | xs *msg = msg_delete(&snac, id); | 3830 | xs *msg = msg_delete(&snac, id); |
| @@ -14,7 +14,7 @@ msgstr "Citlivý obsah: " | |||
| 14 | 14 | ||
| 15 | #: html.c:448 | 15 | #: html.c:448 |
| 16 | msgid "Sensitive content description" | 16 | msgid "Sensitive content description" |
| 17 | msgstr "Varování k citlivému obsahu" | 17 | msgstr "Varování o citlivém obsahu" |
| 18 | 18 | ||
| 19 | msgid "Only for mentioned people: " | 19 | msgid "Only for mentioned people: " |
| 20 | msgstr "Pouze pro zmíněné osoby:" | 20 | msgstr "Pouze pro zmíněné osoby:" |
| @@ -185,11 +185,11 @@ msgstr "Vaše jméno" | |||
| 185 | 185 | ||
| 186 | #: html.c:1507 | 186 | #: html.c:1507 |
| 187 | msgid "Avatar: " | 187 | msgid "Avatar: " |
| 188 | msgstr "Avatar: " | 188 | msgstr "Profilový obrázek: " |
| 189 | 189 | ||
| 190 | #: html.c:1515 | 190 | #: html.c:1515 |
| 191 | msgid "Delete current avatar" | 191 | msgid "Delete current avatar" |
| 192 | msgstr "Smazat současný avatar" | 192 | msgstr "Smazat profilový obrázek" |
| 193 | 193 | ||
| 194 | #: html.c:1517 | 194 | #: html.c:1517 |
| 195 | msgid "Header image (banner): " | 195 | msgid "Header image (banner): " |
| @@ -197,7 +197,7 @@ msgstr "Obrázek v záhlaví profilu: " | |||
| 197 | 197 | ||
| 198 | #: html.c:1525 | 198 | #: html.c:1525 |
| 199 | msgid "Delete current header image" | 199 | msgid "Delete current header image" |
| 200 | msgstr "Smazat současný obrázek v záhlaví" | 200 | msgstr "Smazat obrázek v záhlaví" |
| 201 | 201 | ||
| 202 | #: html.c:1527 | 202 | #: html.c:1527 |
| 203 | msgid "Bio:" | 203 | msgid "Bio:" |
| @@ -205,11 +205,11 @@ msgstr "Bio:" | |||
| 205 | 205 | ||
| 206 | #: html.c:1533 | 206 | #: html.c:1533 |
| 207 | msgid "Write about yourself here..." | 207 | msgid "Write about yourself here..." |
| 208 | msgstr "Napište sem něco o sobě..." | 208 | msgstr "Napište něco o sobě..." |
| 209 | 209 | ||
| 210 | #: html.c:1542 | 210 | #: html.c:1542 |
| 211 | msgid "Always show sensitive content" | 211 | msgid "Always show sensitive content" |
| 212 | msgstr "Vždy zobrazit příspěvky s varováním o citlivém obsahu" | 212 | msgstr "Rozbalit citlivé příspěvky" |
| 213 | 213 | ||
| 214 | #: html.c:1544 | 214 | #: html.c:1544 |
| 215 | msgid "Email address for notifications:" | 215 | msgid "Email address for notifications:" |
| @@ -233,11 +233,11 @@ msgstr "Zahodit soukromé zprávy od lidí, které nesledujete" | |||
| 233 | 233 | ||
| 234 | #: html.c:1611 | 234 | #: html.c:1611 |
| 235 | msgid "This account is a bot" | 235 | msgid "This account is a bot" |
| 236 | msgstr "Tenhle účet je robot" | 236 | msgstr "Tento účet je robotem" |
| 237 | 237 | ||
| 238 | #: html.c:1620 | 238 | #: html.c:1620 |
| 239 | msgid "Auto-boost all mentions to this account" | 239 | msgid "Auto-boost all mentions to this account" |
| 240 | msgstr "Automaticky boostovat všechny zmíňky o tomto účtu" | 240 | msgstr "Automaticky boostit všechna zmínění tohoto účtu" |
| 241 | 241 | ||
| 242 | #: html.c:1629 | 242 | #: html.c:1629 |
| 243 | msgid "This account is private (posts are not shown through the web)" | 243 | msgid "This account is private (posts are not shown through the web)" |
| @@ -246,11 +246,11 @@ msgstr "" | |||
| 246 | 246 | ||
| 247 | #: html.c:1639 | 247 | #: html.c:1639 |
| 248 | msgid "Collapse top threads by default" | 248 | msgid "Collapse top threads by default" |
| 249 | msgstr "Zobrazovat vlákna složená" | 249 | msgstr "Složit vlákna" |
| 250 | 250 | ||
| 251 | #: html.c:1648 | 251 | #: html.c:1648 |
| 252 | msgid "Follow requests must be approved" | 252 | msgid "Follow requests must be approved" |
| 253 | msgstr "Žádosti o sledování je nutno manuálně potvrdit" | 253 | msgstr "Žádosti o sledování je nutné manuálně potvrdit" |
| 254 | 254 | ||
| 255 | #: html.c:1657 | 255 | #: html.c:1657 |
| 256 | msgid "Publish follower and following metrics" | 256 | msgid "Publish follower and following metrics" |
| @@ -274,7 +274,7 @@ msgstr "Nové heslo:" | |||
| 274 | 274 | ||
| 275 | #: html.c:1710 | 275 | #: html.c:1710 |
| 276 | msgid "Repeat new password:" | 276 | msgid "Repeat new password:" |
| 277 | msgstr "Zopakujte nové heslo:" | 277 | msgstr "Nové heslo znovu:" |
| 278 | 278 | ||
| 279 | #: html.c:1720 | 279 | #: html.c:1720 |
| 280 | msgid "Update user info" | 280 | msgid "Update user info" |
| @@ -330,7 +330,7 @@ msgstr "Odboostit" | |||
| 330 | 330 | ||
| 331 | #: html.c:1930 | 331 | #: html.c:1930 |
| 332 | msgid "I regret I boosted this" | 332 | msgid "I regret I boosted this" |
| 333 | msgstr "Boostit to byl blbej nápad" | 333 | msgstr "Boostit to byl špatný nápad" |
| 334 | 334 | ||
| 335 | #: html.c:1936 html.c:4922 | 335 | #: html.c:1936 html.c:4922 |
| 336 | msgid "Unbookmark" | 336 | msgid "Unbookmark" |
| @@ -438,7 +438,7 @@ msgstr "Událost" | |||
| 438 | 438 | ||
| 439 | #: html.c:2246 html.c:2275 | 439 | #: html.c:2246 html.c:2275 |
| 440 | msgid "boosted" | 440 | msgid "boosted" |
| 441 | msgstr "boostuje" | 441 | msgstr "boostí" |
| 442 | 442 | ||
| 443 | #: html.c:2289 | 443 | #: html.c:2289 |
| 444 | msgid "in reply to" | 444 | msgid "in reply to" |
| @@ -478,7 +478,7 @@ msgstr "Popisek..." | |||
| 478 | 478 | ||
| 479 | #: html.c:2697 | 479 | #: html.c:2697 |
| 480 | msgid "Source channel or community" | 480 | msgid "Source channel or community" |
| 481 | msgstr "" | 481 | msgstr "Původní kanál nebo komunita" |
| 482 | 482 | ||
| 483 | #: html.c:2791 | 483 | #: html.c:2791 |
| 484 | msgid "Time: " | 484 | msgid "Time: " |
| @@ -519,7 +519,7 @@ msgstr "Rozepsané příspěvky" | |||
| 519 | 519 | ||
| 520 | #: html.c:3177 | 520 | #: html.c:3177 |
| 521 | msgid "No more unseen posts" | 521 | msgid "No more unseen posts" |
| 522 | msgstr "Nic víc nového" | 522 | msgstr "Nic nového" |
| 523 | 523 | ||
| 524 | #: html.c:3181 html.c:3290 | 524 | #: html.c:3181 html.c:3290 |
| 525 | msgid "Back to top" | 525 | msgid "Back to top" |
| @@ -623,7 +623,7 @@ msgstr "Nové" | |||
| 623 | 623 | ||
| 624 | #: html.c:3707 | 624 | #: html.c:3707 |
| 625 | msgid "Already seen" | 625 | msgid "Already seen" |
| 626 | msgstr "Zobrazeno díve" | 626 | msgstr "Ji viděno" |
| 627 | 627 | ||
| 628 | #: html.c:3722 | 628 | #: html.c:3722 |
| 629 | msgid "None" | 629 | msgid "None" |
| @@ -59,7 +59,7 @@ xs_str *rss_from_timeline(snac *user, const xs_list *timeline, | |||
| 59 | const char *content = xs_dict_get(msg, "content"); | 59 | const char *content = xs_dict_get(msg, "content"); |
| 60 | const char *published = xs_dict_get(msg, "published"); | 60 | const char *published = xs_dict_get(msg, "published"); |
| 61 | 61 | ||
| 62 | if (user && !xs_startswith(id, user->actor)) | 62 | if (user && !is_msg_mine(user, id)) |
| 63 | continue; | 63 | continue; |
| 64 | 64 | ||
| 65 | if (!id || !content || !published) | 65 | if (!id || !content || !published) |
| @@ -1,7 +1,7 @@ | |||
| 1 | /* snac - A simple, minimalistic ActivityPub instance */ | 1 | /* snac - A simple, minimalistic ActivityPub instance */ |
| 2 | /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ | 2 | /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ |
| 3 | 3 | ||
| 4 | #define VERSION "2.84" | 4 | #define VERSION "2.85-dev" |
| 5 | 5 | ||
| 6 | #define USER_AGENT "snac/" VERSION | 6 | #define USER_AGENT "snac/" VERSION |
| 7 | 7 | ||
| @@ -388,6 +388,7 @@ int send_to_inbox(snac *snac, const xs_str *inbox, const xs_dict *msg, | |||
| 388 | xs_str *get_actor_inbox(const char *actor, int shared); | 388 | xs_str *get_actor_inbox(const char *actor, int shared); |
| 389 | int send_to_actor(snac *snac, const char *actor, const xs_dict *msg, | 389 | int send_to_actor(snac *snac, const char *actor, const xs_dict *msg, |
| 390 | xs_val **payload, int *p_size, int timeout); | 390 | xs_val **payload, int *p_size, int timeout); |
| 391 | int is_msg_mine(snac *user, const char *id); | ||
| 391 | int is_msg_public(const xs_dict *msg); | 392 | int is_msg_public(const xs_dict *msg); |
| 392 | int is_msg_from_private_user(const xs_dict *msg); | 393 | int is_msg_from_private_user(const xs_dict *msg); |
| 393 | int is_msg_for_me(snac *snac, const xs_dict *msg); | 394 | int is_msg_for_me(snac *snac, const xs_dict *msg); |
| @@ -213,7 +213,7 @@ int snac_upgrade(xs_str **error) | |||
| 213 | object_add_ow(id, o); | 213 | object_add_ow(id, o); |
| 214 | 214 | ||
| 215 | /* if it's from us, add to public */ | 215 | /* if it's from us, add to public */ |
| 216 | if (xs_startswith(id, snac.actor)) { | 216 | if (is_msg_mine(&snac, id)) { |
| 217 | const xs_list *p; | 217 | const xs_list *p; |
| 218 | const char *v; | 218 | const char *v; |
| 219 | int c; | 219 | int c; |
diff --git a/webfinger.c b/webfinger.c index 12ec42c..1ce5e76 100644 --- a/webfinger.c +++ b/webfinger.c | |||
| @@ -76,9 +76,9 @@ int webfinger_request_signed(snac *snac, const char *qs, xs_str **actor, xs_str | |||
| 76 | xs *url = xs_fmt("%s:/" "/%s/.well-known/webfinger?resource=%s", proto, host, resource); | 76 | xs *url = xs_fmt("%s:/" "/%s/.well-known/webfinger?resource=%s", proto, host, resource); |
| 77 | 77 | ||
| 78 | if (snac == NULL) | 78 | if (snac == NULL) |
| 79 | xs_http_request("GET", url, headers, NULL, 0, &status, &payload, &p_size, 0); | 79 | xs_free(xs_http_request("GET", url, headers, NULL, 0, &status, &payload, &p_size, 0)); |
| 80 | else | 80 | else |
| 81 | http_signed_request(snac, "GET", url, headers, NULL, 0, &status, &payload, &p_size, 0); | 81 | xs_free(http_signed_request(snac, "GET", url, headers, NULL, 0, &status, &payload, &p_size, 0)); |
| 82 | } | 82 | } |
| 83 | 83 | ||
| 84 | if (obj == NULL && valid_status(status) && payload) { | 84 | if (obj == NULL && valid_status(status) && payload) { |