summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Oliver2025-08-23 11:04:11 +0200
committerGravatar Oliver2025-08-23 11:04:11 +0200
commite50a6ba146def4accc36d9d6cc4be43e4a758711 (patch)
tree7490d0b360b2d85e514ea9816075e661e3f98b7b
parentMerge pull request 'updates holen' (#10) from grunfink/snac2:master into master (diff)
parentAdded 'collect_replies' info in the about screen. (diff)
downloadsnac2-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.md2
-rw-r--r--activitypub.c94
-rw-r--r--data.c13
-rw-r--r--doc/snac.17
-rw-r--r--main.c10
-rw-r--r--snac.h3
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
13Added nodeinfo 2.1 support. 13Added nodeinfo 2.1 support.
14 14
15Fixed boosts from the command line not showing in the public timeline (contributed by xvello).
16
15## 2.81 17## 2.81
16 18
17If 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. 19If 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
939void 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
939void notify(snac *snac, const char *type, const char *utype, const char *actor, const xs_dict *msg) 1027void 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
diff --git a/data.c b/data.c
index 70bd031..ca5e3ed 100644
--- a/data.c
+++ b/data.c
@@ -3576,6 +3576,19 @@ void enqueue_notify_webhook(snac *user, const xs_dict *noti, int retries)
3576} 3576}
3577 3577
3578 3578
3579void 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
3579int was_question_voted(snac *user, const char *id) 3592int 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{
diff --git a/doc/snac.1 b/doc/snac.1
index fd43338..134ee6e 100644
--- a/doc/snac.1
+++ b/doc/snac.1
@@ -71,6 +71,10 @@ standalone one.
71If you set this checkbox, your text will not be sent when you 71If you set this checkbox, your text will not be sent when you
72push the Post button, but stored for later modification in 72push the Post button, but stored for later modification in
73the "Drafts" section. 73the "Drafts" section.
74.It Language
75A dropdown to select which language your post will be written
76in. This control only appears after you configure the set of
77languages you expect to use in the User Settings (see below).
74.It Scheduled post... 78.It Scheduled post...
75This dropdown menu allows setting a date and time for the 79This dropdown menu allows setting a date and time for the
76post publication. 80post publication.
@@ -188,6 +192,9 @@ can be selected here.
188.It Time zone 192.It Time zone
189The time zone the user is on (default: UTC). Only 193The time zone the user is on (default: UTC). Only
190used for scheduled posts. 194used for scheduled posts.
195.It Languages you usually post in
196Fill this string with the ISO 639 Language Codes you expect to
197write your posts in, space-separated (example: en fr pt_BR).
191.It Password 198.It Password
192Write the same string in these two fields to change your 199Write the same string in these two fields to change your
193password. Don't write anything if you don't want to do this. 200password. Don't write anything if you don't want to do this.
diff --git a/main.c b/main.c
index c4c298f..e01b6a2 100644
--- a/main.c
+++ b/main.c
@@ -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;
diff --git a/snac.h b/snac.h
index c060363..363855f 100644
--- a/snac.h
+++ b/snac.h
@@ -297,6 +297,7 @@ void enqueue_verify_links(snac *user);
297void enqueue_actor_refresh(snac *user, const char *actor, int forward_secs); 297void enqueue_actor_refresh(snac *user, const char *actor, int forward_secs);
298void enqueue_webmention(const xs_dict *msg); 298void enqueue_webmention(const xs_dict *msg);
299void enqueue_notify_webhook(snac *user, const xs_dict *noti, int retries); 299void enqueue_notify_webhook(snac *user, const xs_dict *noti, int retries);
300void enqueue_collect_replies(snac *user, const char *post);
300 301
301int was_question_voted(snac *user, const char *id); 302int was_question_voted(snac *user, const char *id);
302 303
@@ -334,6 +335,8 @@ const char *default_avatar_base64(void);
334 335
335xs_str *process_tags(snac *snac, const char *content, xs_list **tag); 336xs_str *process_tags(snac *snac, const char *content, xs_list **tag);
336 337
338void collect_replies(snac *user, const char *id);
339
337const char *get_atto(const xs_dict *msg); 340const char *get_atto(const xs_dict *msg);
338const char *get_in_reply_to(const xs_dict *msg); 341const char *get_in_reply_to(const xs_dict *msg);
339xs_list *get_attachments(const xs_dict *msg); 342xs_list *get_attachments(const xs_dict *msg);