aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOliver <zen@noreply.codeberg.org>2025-08-23 11:04:11 +0200
committerOliver <zen@noreply.codeberg.org>2025-08-23 11:04:11 +0200
commite50a6ba146def4accc36d9d6cc4be43e4a758711 (patch)
tree7490d0b360b2d85e514ea9816075e661e3f98b7b
parenteecfeec3745333451de6a2b1754f43f171e23e5d (diff)
parent3611f42fe202c6ec198c34dbc0a5939b8287583e (diff)
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.
Added nodeinfo 2.1 support.
+Fixed boosts from the command line not showing in the public timeline (contributed by xvello).
+
## 2.81
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)
}
+void collect_replies(snac *user, const char *id)
+/* collects all replies for a post */
+{
+ xs *obj = NULL;
+
+ if (!valid_status(object_get(id, &obj))) {
+ snac_debug(user, 1, xs_fmt("collect_replies: object '%s' is not here", id));
+ return;
+ }
+
+ const char *next = xs_dict_get_path(obj, "replies.first.next");
+ if (!xs_is_string(next)) {
+ snac_debug(user, 1, xs_fmt("collect_replies: object '%s' does not have a replies.first.next URL", id));
+ return;
+ }
+
+ /* pick the first level replies (may be empty) */
+ const xs_list *level0_replies = xs_dict_get_path(obj, "replies.first.items");
+
+ xs *reply_obj = NULL;
+
+ if (!valid_status(object_get(next, &reply_obj))) {
+ if (!valid_status(activitypub_request(user, next, &reply_obj))) {
+ snac_debug(user, 1, xs_fmt("collect_replies: cannot get replies object '%s'", next));
+ return;
+ }
+ }
+
+ const xs_list *level1_replies = xs_dict_get(reply_obj, "items");
+ if (!xs_is_list(level1_replies)) {
+ snac_debug(user, 1, xs_fmt("collect_replies: cannot get reply items from object '%s'", next));
+ return;
+ }
+
+ xs *items = NULL;
+
+ if (xs_is_list(level0_replies))
+ items = xs_list_cat(xs_dup(level0_replies), level1_replies);
+ else
+ items = xs_dup(level1_replies);
+
+ const xs_val *v;
+
+ xs_list_foreach(items, v) {
+ xs *reply = NULL;
+
+ if (xs_is_string(v)) {
+ /* request the object */
+ if (!valid_status(object_get(v, &reply))) {
+ if (!valid_status(activitypub_request(user, v, &reply))) {
+ snac_debug(user, 1, xs_fmt("collect_replies: error requesting object '%s'", v));
+ continue;
+ }
+ }
+ }
+ else
+ if (xs_is_dict(v)) {
+ /* actually the post object */
+ reply = xs_dup(v);
+ }
+
+ if (!xs_is_dict(reply))
+ continue;
+
+ const char *id = xs_dict_get(reply, "id");
+ const char *type = xs_dict_get(reply, "type");
+ const char *attr_to = get_atto(reply);
+
+ if (!xs_is_string(id) || !xs_is_string(type) || !xs_is_string(attr_to))
+ continue;
+
+ if (!xs_match(type, POSTLIKE_OBJECT_TYPE))
+ continue;
+
+ if (timeline_here(user, id)) {
+ snac_debug(user, 1, xs_fmt("collect_replies: item already in timeline %s", id));
+ continue;
+ }
+
+ enqueue_actor_refresh(user, attr_to, 0);
+
+ timeline_add(user, id, reply);
+
+ snac_log(user, xs_fmt("new '%s' (collect_replies) %s %s", type, attr_to, id));
+ }
+}
+
+
void notify(snac *snac, const char *type, const char *utype, const char *actor, const xs_dict *msg)
/* notifies the user of relevant events */
{
@@ -2827,6 +2915,12 @@ void process_user_queue_item(snac *user, xs_dict *q_item)
}
}
else
+ if (strcmp(type, "collect_replies") == 0) {
+ const char *post = xs_dict_get(q_item, "message");
+
+ collect_replies(user, post);
+ }
+ else
snac_log(user, xs_fmt("unexpected user q_item type '%s'", type));
}
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)
}
+void enqueue_collect_replies(snac *user, const char *post)
+/* enqueues a collect replies request */
+{
+ xs *qmsg = _new_qmsg("collect_replies", post, 0);
+ const char *ntid = xs_dict_get(qmsg, "ntid");
+ xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid);
+
+ qmsg = _enqueue_put(fn, qmsg);
+
+ snac_debug(user, 1, xs_fmt("enqueue_collect_replies %s", post));
+}
+
+
int was_question_voted(snac *user, const char *id)
/* returns true if the user voted in this poll */
{
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.
If you set this checkbox, your text will not be sent when you
push the Post button, but stored for later modification in
the "Drafts" section.
+.It Language
+A dropdown to select which language your post will be written
+in. This control only appears after you configure the set of
+languages you expect to use in the User Settings (see below).
.It Scheduled post...
This dropdown menu allows setting a date and time for the
post publication.
@@ -188,6 +192,9 @@ can be selected here.
.It Time zone
The time zone the user is on (default: UTC). Only
used for scheduled posts.
+.It Languages you usually post in
+Fill this string with the ISO 639 Language Codes you expect to
+write your posts in, space-separated (example: en fr pt_BR).
.It Password
Write the same string in these two fields to change your
password. 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)
"unfollow {basedir} {uid} {actor} Unfollows an actor\n"
"request {basedir} {uid} {url} Requests an object\n"
"insert {basedir} {uid} {url} Requests an object and inserts it into the timeline\n"
+ "collect_replies {basedir} {uid} {url} Collects all replies from a post\n"
"actor {basedir} [{uid}] {url} Requests an actor\n"
"note {basedir} {uid} {text} [files...] Sends a note with optional attachments\n"
"note_unlisted {basedir} {uid} {text} [files...] Sends an unlisted note with optional attachments\n"
@@ -61,7 +62,7 @@ int usage(const char *cmd)
"verify_links {basedir} {uid} Verifies a user's links (in the metadata)\n"
"search {basedir} {uid} {regex} Searches posts by content\n"
"export_csv {basedir} {uid} Exports followers, lists, MUTEd and bookmarks to CSV\n"
- "export_posts {basedir} {iod} Exports all posts to outbox.json\n"
+ "export_posts {basedir} {uid} Exports all posts to outbox.json\n"
"alias {basedir} {uid} {account} Sets account (@user@host or actor url) as an alias\n"
"migrate {basedir} {uid} Migrates to the account defined as the alias\n"
"import_csv {basedir} {uid} Imports data from CSV files\n"
@@ -494,6 +495,7 @@ int main(int argc, char *argv[])
if (msg != NULL) {
enqueue_message(&snac, msg);
+ timeline_admire(&snac, xs_dict_get(msg, "object"), snac.actor, 0);
if (dbglevel) {
xs_json_dump(msg, 4, stdout);
@@ -730,6 +732,12 @@ int main(int argc, char *argv[])
return 0;
}
+ if (strcmp(cmd, "collect_replies") == 0) { /** **/
+ enqueue_collect_replies(&snac, url);
+
+ return 0;
+ }
+
if (strcmp(cmd, "insert") == 0) { /** **/
int status;
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);
void enqueue_actor_refresh(snac *user, const char *actor, int forward_secs);
void enqueue_webmention(const xs_dict *msg);
void enqueue_notify_webhook(snac *user, const xs_dict *noti, int retries);
+void enqueue_collect_replies(snac *user, const char *post);
int was_question_voted(snac *user, const char *id);
@@ -334,6 +335,8 @@ const char *default_avatar_base64(void);
xs_str *process_tags(snac *snac, const char *content, xs_list **tag);
+void collect_replies(snac *user, const char *id);
+
const char *get_atto(const xs_dict *msg);
const char *get_in_reply_to(const xs_dict *msg);
xs_list *get_attachments(const xs_dict *msg);