diff options
| author | 2025-08-23 11:04:11 +0200 | |
|---|---|---|
| committer | 2025-08-23 11:04:11 +0200 | |
| commit | e50a6ba146def4accc36d9d6cc4be43e4a758711 (patch) | |
| tree | 7490d0b360b2d85e514ea9816075e661e3f98b7b | |
| parent | Merge pull request 'updates holen' (#10) from grunfink/snac2:master into master (diff) | |
| parent | Added 'collect_replies' info in the about screen. (diff) | |
| download | snac2-e50a6ba146def4accc36d9d6cc4be43e4a758711.tar.gz snac2-e50a6ba146def4accc36d9d6cc4be43e4a758711.tar.xz snac2-e50a6ba146def4accc36d9d6cc4be43e4a758711.zip | |
Merge pull request 'master' (#11) from grunfink/snac2:master into master
Reviewed-on: https://codeberg.org/zen/snac2/pulls/11
| -rw-r--r-- | RELEASE_NOTES.md | 2 | ||||
| -rw-r--r-- | activitypub.c | 94 | ||||
| -rw-r--r-- | data.c | 13 | ||||
| -rw-r--r-- | doc/snac.1 | 7 | ||||
| -rw-r--r-- | main.c | 10 | ||||
| -rw-r--r-- | snac.h | 3 |
6 files changed, 128 insertions, 1 deletions
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f0089fa..13a8cec 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md | |||
| @@ -12,6 +12,8 @@ Mastodon API: Fixed repeated entries in timelines. | |||
| 12 | 12 | ||
| 13 | Added nodeinfo 2.1 support. | 13 | Added nodeinfo 2.1 support. |
| 14 | 14 | ||
| 15 | Fixed boosts from the command line not showing in the public timeline (contributed by xvello). | ||
| 16 | |||
| 15 | ## 2.81 | 17 | ## 2.81 |
| 16 | 18 | ||
| 17 | If the `propagate_local_purge` configuration variable is set to `true` in `server.json`, purged local posts generate a `Delete` activity that is sent everywhere, instead of only deleted from the filesystem. | 19 | If the `propagate_local_purge` configuration variable is set to `true` in `server.json`, purged local posts generate a `Delete` activity that is sent everywhere, instead of only deleted from the filesystem. |
diff --git a/activitypub.c b/activitypub.c index 634e243..065fbcd 100644 --- a/activitypub.c +++ b/activitypub.c | |||
| @@ -936,6 +936,94 @@ xs_str *process_tags(snac *snac, const char *content, xs_list **tag) | |||
| 936 | } | 936 | } |
| 937 | 937 | ||
| 938 | 938 | ||
| 939 | void collect_replies(snac *user, const char *id) | ||
| 940 | /* collects all replies for a post */ | ||
| 941 | { | ||
| 942 | xs *obj = NULL; | ||
| 943 | |||
| 944 | if (!valid_status(object_get(id, &obj))) { | ||
| 945 | snac_debug(user, 1, xs_fmt("collect_replies: object '%s' is not here", id)); | ||
| 946 | return; | ||
| 947 | } | ||
| 948 | |||
| 949 | const char *next = xs_dict_get_path(obj, "replies.first.next"); | ||
| 950 | if (!xs_is_string(next)) { | ||
| 951 | snac_debug(user, 1, xs_fmt("collect_replies: object '%s' does not have a replies.first.next URL", id)); | ||
| 952 | return; | ||
| 953 | } | ||
| 954 | |||
| 955 | /* pick the first level replies (may be empty) */ | ||
| 956 | const xs_list *level0_replies = xs_dict_get_path(obj, "replies.first.items"); | ||
| 957 | |||
| 958 | xs *reply_obj = NULL; | ||
| 959 | |||
| 960 | if (!valid_status(object_get(next, &reply_obj))) { | ||
| 961 | if (!valid_status(activitypub_request(user, next, &reply_obj))) { | ||
| 962 | snac_debug(user, 1, xs_fmt("collect_replies: cannot get replies object '%s'", next)); | ||
| 963 | return; | ||
| 964 | } | ||
| 965 | } | ||
| 966 | |||
| 967 | const xs_list *level1_replies = xs_dict_get(reply_obj, "items"); | ||
| 968 | if (!xs_is_list(level1_replies)) { | ||
| 969 | snac_debug(user, 1, xs_fmt("collect_replies: cannot get reply items from object '%s'", next)); | ||
| 970 | return; | ||
| 971 | } | ||
| 972 | |||
| 973 | xs *items = NULL; | ||
| 974 | |||
| 975 | if (xs_is_list(level0_replies)) | ||
| 976 | items = xs_list_cat(xs_dup(level0_replies), level1_replies); | ||
| 977 | else | ||
| 978 | items = xs_dup(level1_replies); | ||
| 979 | |||
| 980 | const xs_val *v; | ||
| 981 | |||
| 982 | xs_list_foreach(items, v) { | ||
| 983 | xs *reply = NULL; | ||
| 984 | |||
| 985 | if (xs_is_string(v)) { | ||
| 986 | /* request the object */ | ||
| 987 | if (!valid_status(object_get(v, &reply))) { | ||
| 988 | if (!valid_status(activitypub_request(user, v, &reply))) { | ||
| 989 | snac_debug(user, 1, xs_fmt("collect_replies: error requesting object '%s'", v)); | ||
| 990 | continue; | ||
| 991 | } | ||
| 992 | } | ||
| 993 | } | ||
| 994 | else | ||
| 995 | if (xs_is_dict(v)) { | ||
| 996 | /* actually the post object */ | ||
| 997 | reply = xs_dup(v); | ||
| 998 | } | ||
| 999 | |||
| 1000 | if (!xs_is_dict(reply)) | ||
| 1001 | continue; | ||
| 1002 | |||
| 1003 | const char *id = xs_dict_get(reply, "id"); | ||
| 1004 | const char *type = xs_dict_get(reply, "type"); | ||
| 1005 | const char *attr_to = get_atto(reply); | ||
| 1006 | |||
| 1007 | if (!xs_is_string(id) || !xs_is_string(type) || !xs_is_string(attr_to)) | ||
| 1008 | continue; | ||
| 1009 | |||
| 1010 | if (!xs_match(type, POSTLIKE_OBJECT_TYPE)) | ||
| 1011 | continue; | ||
| 1012 | |||
| 1013 | if (timeline_here(user, id)) { | ||
| 1014 | snac_debug(user, 1, xs_fmt("collect_replies: item already in timeline %s", id)); | ||
| 1015 | continue; | ||
| 1016 | } | ||
| 1017 | |||
| 1018 | enqueue_actor_refresh(user, attr_to, 0); | ||
| 1019 | |||
| 1020 | timeline_add(user, id, reply); | ||
| 1021 | |||
| 1022 | snac_log(user, xs_fmt("new '%s' (collect_replies) %s %s", type, attr_to, id)); | ||
| 1023 | } | ||
| 1024 | } | ||
| 1025 | |||
| 1026 | |||
| 939 | void notify(snac *snac, const char *type, const char *utype, const char *actor, const xs_dict *msg) | 1027 | void notify(snac *snac, const char *type, const char *utype, const char *actor, const xs_dict *msg) |
| 940 | /* notifies the user of relevant events */ | 1028 | /* notifies the user of relevant events */ |
| 941 | { | 1029 | { |
| @@ -2827,6 +2915,12 @@ void process_user_queue_item(snac *user, xs_dict *q_item) | |||
| 2827 | } | 2915 | } |
| 2828 | } | 2916 | } |
| 2829 | else | 2917 | else |
| 2918 | if (strcmp(type, "collect_replies") == 0) { | ||
| 2919 | const char *post = xs_dict_get(q_item, "message"); | ||
| 2920 | |||
| 2921 | collect_replies(user, post); | ||
| 2922 | } | ||
| 2923 | else | ||
| 2830 | snac_log(user, xs_fmt("unexpected user q_item type '%s'", type)); | 2924 | snac_log(user, xs_fmt("unexpected user q_item type '%s'", type)); |
| 2831 | } | 2925 | } |
| 2832 | 2926 | ||
| @@ -3576,6 +3576,19 @@ void enqueue_notify_webhook(snac *user, const xs_dict *noti, int retries) | |||
| 3576 | } | 3576 | } |
| 3577 | 3577 | ||
| 3578 | 3578 | ||
| 3579 | void enqueue_collect_replies(snac *user, const char *post) | ||
| 3580 | /* enqueues a collect replies request */ | ||
| 3581 | { | ||
| 3582 | xs *qmsg = _new_qmsg("collect_replies", post, 0); | ||
| 3583 | const char *ntid = xs_dict_get(qmsg, "ntid"); | ||
| 3584 | xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); | ||
| 3585 | |||
| 3586 | qmsg = _enqueue_put(fn, qmsg); | ||
| 3587 | |||
| 3588 | snac_debug(user, 1, xs_fmt("enqueue_collect_replies %s", post)); | ||
| 3589 | } | ||
| 3590 | |||
| 3591 | |||
| 3579 | int was_question_voted(snac *user, const char *id) | 3592 | int was_question_voted(snac *user, const char *id) |
| 3580 | /* returns true if the user voted in this poll */ | 3593 | /* returns true if the user voted in this poll */ |
| 3581 | { | 3594 | { |
| @@ -71,6 +71,10 @@ standalone one. | |||
| 71 | If you set this checkbox, your text will not be sent when you | 71 | If you set this checkbox, your text will not be sent when you |
| 72 | push the Post button, but stored for later modification in | 72 | push the Post button, but stored for later modification in |
| 73 | the "Drafts" section. | 73 | the "Drafts" section. |
| 74 | .It Language | ||
| 75 | A dropdown to select which language your post will be written | ||
| 76 | in. This control only appears after you configure the set of | ||
| 77 | languages you expect to use in the User Settings (see below). | ||
| 74 | .It Scheduled post... | 78 | .It Scheduled post... |
| 75 | This dropdown menu allows setting a date and time for the | 79 | This dropdown menu allows setting a date and time for the |
| 76 | post publication. | 80 | post publication. |
| @@ -188,6 +192,9 @@ can be selected here. | |||
| 188 | .It Time zone | 192 | .It Time zone |
| 189 | The time zone the user is on (default: UTC). Only | 193 | The time zone the user is on (default: UTC). Only |
| 190 | used for scheduled posts. | 194 | used for scheduled posts. |
| 195 | .It Languages you usually post in | ||
| 196 | Fill this string with the ISO 639 Language Codes you expect to | ||
| 197 | write your posts in, space-separated (example: en fr pt_BR). | ||
| 191 | .It Password | 198 | .It Password |
| 192 | Write the same string in these two fields to change your | 199 | Write the same string in these two fields to change your |
| 193 | password. Don't write anything if you don't want to do this. | 200 | password. Don't write anything if you don't want to do this. |
| @@ -39,6 +39,7 @@ int usage(const char *cmd) | |||
| 39 | "unfollow {basedir} {uid} {actor} Unfollows an actor\n" | 39 | "unfollow {basedir} {uid} {actor} Unfollows an actor\n" |
| 40 | "request {basedir} {uid} {url} Requests an object\n" | 40 | "request {basedir} {uid} {url} Requests an object\n" |
| 41 | "insert {basedir} {uid} {url} Requests an object and inserts it into the timeline\n" | 41 | "insert {basedir} {uid} {url} Requests an object and inserts it into the timeline\n" |
| 42 | "collect_replies {basedir} {uid} {url} Collects all replies from a post\n" | ||
| 42 | "actor {basedir} [{uid}] {url} Requests an actor\n" | 43 | "actor {basedir} [{uid}] {url} Requests an actor\n" |
| 43 | "note {basedir} {uid} {text} [files...] Sends a note with optional attachments\n" | 44 | "note {basedir} {uid} {text} [files...] Sends a note with optional attachments\n" |
| 44 | "note_unlisted {basedir} {uid} {text} [files...] Sends an unlisted note with optional attachments\n" | 45 | "note_unlisted {basedir} {uid} {text} [files...] Sends an unlisted note with optional attachments\n" |
| @@ -61,7 +62,7 @@ int usage(const char *cmd) | |||
| 61 | "verify_links {basedir} {uid} Verifies a user's links (in the metadata)\n" | 62 | "verify_links {basedir} {uid} Verifies a user's links (in the metadata)\n" |
| 62 | "search {basedir} {uid} {regex} Searches posts by content\n" | 63 | "search {basedir} {uid} {regex} Searches posts by content\n" |
| 63 | "export_csv {basedir} {uid} Exports followers, lists, MUTEd and bookmarks to CSV\n" | 64 | "export_csv {basedir} {uid} Exports followers, lists, MUTEd and bookmarks to CSV\n" |
| 64 | "export_posts {basedir} {iod} Exports all posts to outbox.json\n" | 65 | "export_posts {basedir} {uid} Exports all posts to outbox.json\n" |
| 65 | "alias {basedir} {uid} {account} Sets account (@user@host or actor url) as an alias\n" | 66 | "alias {basedir} {uid} {account} Sets account (@user@host or actor url) as an alias\n" |
| 66 | "migrate {basedir} {uid} Migrates to the account defined as the alias\n" | 67 | "migrate {basedir} {uid} Migrates to the account defined as the alias\n" |
| 67 | "import_csv {basedir} {uid} Imports data from CSV files\n" | 68 | "import_csv {basedir} {uid} Imports data from CSV files\n" |
| @@ -494,6 +495,7 @@ int main(int argc, char *argv[]) | |||
| 494 | 495 | ||
| 495 | if (msg != NULL) { | 496 | if (msg != NULL) { |
| 496 | enqueue_message(&snac, msg); | 497 | enqueue_message(&snac, msg); |
| 498 | timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, 0); | ||
| 497 | 499 | ||
| 498 | if (dbglevel) { | 500 | if (dbglevel) { |
| 499 | xs_json_dump(msg, 4, stdout); | 501 | xs_json_dump(msg, 4, stdout); |
| @@ -730,6 +732,12 @@ int main(int argc, char *argv[]) | |||
| 730 | return 0; | 732 | return 0; |
| 731 | } | 733 | } |
| 732 | 734 | ||
| 735 | if (strcmp(cmd, "collect_replies") == 0) { /** **/ | ||
| 736 | enqueue_collect_replies(&snac, url); | ||
| 737 | |||
| 738 | return 0; | ||
| 739 | } | ||
| 740 | |||
| 733 | if (strcmp(cmd, "insert") == 0) { /** **/ | 741 | if (strcmp(cmd, "insert") == 0) { /** **/ |
| 734 | int status; | 742 | int status; |
| 735 | xs *data = NULL; | 743 | xs *data = NULL; |
| @@ -297,6 +297,7 @@ void enqueue_verify_links(snac *user); | |||
| 297 | void enqueue_actor_refresh(snac *user, const char *actor, int forward_secs); | 297 | void enqueue_actor_refresh(snac *user, const char *actor, int forward_secs); |
| 298 | void enqueue_webmention(const xs_dict *msg); | 298 | void enqueue_webmention(const xs_dict *msg); |
| 299 | void enqueue_notify_webhook(snac *user, const xs_dict *noti, int retries); | 299 | void enqueue_notify_webhook(snac *user, const xs_dict *noti, int retries); |
| 300 | void enqueue_collect_replies(snac *user, const char *post); | ||
| 300 | 301 | ||
| 301 | int was_question_voted(snac *user, const char *id); | 302 | int was_question_voted(snac *user, const char *id); |
| 302 | 303 | ||
| @@ -334,6 +335,8 @@ const char *default_avatar_base64(void); | |||
| 334 | 335 | ||
| 335 | xs_str *process_tags(snac *snac, const char *content, xs_list **tag); | 336 | xs_str *process_tags(snac *snac, const char *content, xs_list **tag); |
| 336 | 337 | ||
| 338 | void collect_replies(snac *user, const char *id); | ||
| 339 | |||
| 337 | const char *get_atto(const xs_dict *msg); | 340 | const char *get_atto(const xs_dict *msg); |
| 338 | const char *get_in_reply_to(const xs_dict *msg); | 341 | const char *get_in_reply_to(const xs_dict *msg); |
| 339 | xs_list *get_attachments(const xs_dict *msg); | 342 | xs_list *get_attachments(const xs_dict *msg); |