From 5cda2f709c03222f02f088f3b9047b5ca7aba42a Mon Sep 17 00:00:00 2001 From: grunfink Date: Sun, 31 Aug 2025 10:03:11 +0200 Subject: Some improvements to collect_replies(). --- activitypub.c | 53 +++++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/activitypub.c b/activitypub.c index 065fbcd..34137da 100644 --- a/activitypub.c +++ b/activitypub.c @@ -946,36 +946,33 @@ void collect_replies(snac *user, const char *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)); + const xs_dict *replies_first = xs_dict_get_path(obj, "replies.first"); + if (!xs_is_dict(replies_first)) { + snac_debug(user, 1, xs_fmt("collect_replies: object '%s' does not have replies.first", id)); return; } - /* pick the first level replies (may be empty) */ - const xs_list *level0_replies = xs_dict_get_path(obj, "replies.first.items"); + const xs_list *level0_replies = xs_dict_get(replies_first, "items"); + const xs_list *level1_replies = NULL; + const char *next = xs_dict_get(replies_first, "next"); 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)); + if (xs_is_string(next) && !valid_status(activitypub_request(user, next, &reply_obj))) { + snac_debug(user, 1, xs_fmt("collect_replies: error getting next replies object '%s'", next)); return; } - xs *items = NULL; + if (xs_is_dict(reply_obj)) + level1_replies = xs_dict_get(reply_obj, "items"); + + xs *items = xs_list_new(); if (xs_is_list(level0_replies)) - items = xs_list_cat(xs_dup(level0_replies), level1_replies); - else - items = xs_dup(level1_replies); + items = xs_list_cat(items, level0_replies); + + if (xs_is_list(level1_replies)) + items = xs_list_cat(items, level1_replies); const xs_val *v; @@ -2538,10 +2535,14 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) snac_log(snac, xs_fmt("malformed message: no 'id' field")); else if (object_here(id)) { - object_add_ow(id, object); - timeline_touch(snac); + if (xs_startswith(id, srv_baseurl) && !xs_startswith(id, actor)) + snac_log(snac, xs_fmt("ignored incorrect 'Update' %s %s", actor, id)); + else { + object_add_ow(id, object); + timeline_touch(snac); - snac_log(snac, xs_fmt("updated '%s' %s", utype, id)); + snac_log(snac, xs_fmt("updated '%s' %s", utype, id)); + } } else snac_log(snac, xs_fmt("dropped update for unknown '%s' %s", utype, id)); @@ -2578,8 +2579,12 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) snac_log(snac, xs_fmt("malformed message: no 'id' field")); else if (object_here(object)) { - timeline_del(snac, object); - snac_debug(snac, 1, xs_fmt("new 'Delete' %s %s", actor, object)); + if (xs_startswith(object, srv_baseurl) && !xs_startswith(object, actor)) + snac_log(snac, xs_fmt("ignored incorrect 'Delete' %s %s", actor, object)); + else { + timeline_del(snac, object); + snac_debug(snac, 1, xs_fmt("new 'Delete' %s %s", actor, object)); + } } else snac_debug(snac, 1, xs_fmt("ignored 'Delete' for unknown object %s", object)); -- cgit v1.2.3 From 074e19d9492733dba62f87d2b8f823440cb80288 Mon Sep 17 00:00:00 2001 From: grunfink Date: Sun, 31 Aug 2025 14:07:27 +0200 Subject: Added a 'replies' object to Notes. --- activitypub.c | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ snac.h | 1 + 2 files changed, 77 insertions(+) diff --git a/activitypub.c b/activitypub.c index 34137da..31a9cb3 100644 --- a/activitypub.c +++ b/activitypub.c @@ -1264,6 +1264,46 @@ xs_dict *msg_collection(snac *snac, const char *id, int items) } +xs_dict *msg_replies(snac *user, const char *id, int fill) +/* creates a CollectionPage with replies of id */ +{ + xs *r_id = xs_replace(id, "/p/", "/r/"); + xs *r_idp = xs_fmt("%s#page", r_id); + xs *r_idh = xs_fmt("%s#hdr", r_id); + + xs_dict *msg = msg_base(user, "CollectionPage", r_idp, NULL, NULL, NULL); + + msg = xs_dict_set(msg, "partOf", r_idh); + + xs *items = xs_list_new(); + if (fill) { + xs *children = object_children(id); + const char *md5; + + xs_list_foreach(children, md5) { + xs *obj = NULL; + + if (valid_status(object_get_by_md5(md5, &obj)) && is_msg_public(obj)) { + const char *c_id = xs_dict_get(obj, "id"); + + if (xs_is_string(c_id)) + items = xs_list_append(items, c_id); + } + } + } + else { + xs *r_idl = xs_fmt("%s#local", r_id); + + msg = xs_dict_del(msg, "@context"); + msg = xs_dict_set(msg, "id", r_idl); + } + + msg = xs_dict_set(msg, "items", items); + + return msg; +} + + xs_dict *msg_accept(snac *snac, const xs_val *object, const char *to) /* creates an Accept message (as a response to a Follow) */ { @@ -1868,6 +1908,22 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, } } + if (!priv) { + /* create the replies object */ + xs *replies = xs_dict_new(); + xs *r_id = xs_replace(id, "/p/", "/r/"); + xs *h_id = xs_fmt("%s#hdr", r_id); + xs *n_id = xs_fmt("%s#page", r_id); + xs *rp = msg_replies(snac, id, 0); + + replies = xs_dict_set(replies, "id", h_id); + replies = xs_dict_set(replies, "type", "Collection"); + replies = xs_dict_set(replies, "next", n_id); + replies = xs_dict_set(replies, "first", rp); + + msg = xs_dict_set(msg, "replies", replies); + } + return msg; } @@ -3395,6 +3451,26 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, if (valid_status(status) && !is_msg_public(msg)) status = HTTP_STATUS_NOT_FOUND; } + else + if (xs_startswith(p_path, "r/")) { + /* replies to a post */ + xs *s = xs_dup(p_path); + s[0] = 'p'; + + xs *id = xs_fmt("%s/%s", snac.actor, s); + + xs *obj = NULL; + status = object_get(id, &obj); + + /* don't return non-public objects */ + if (!valid_status(status)) + status = HTTP_STATUS_NOT_FOUND; + else + if (!is_msg_public(obj)) + status = HTTP_STATUS_NOT_FOUND; + else + msg = msg_replies(&snac, id, 1); + } else status = HTTP_STATUS_NOT_FOUND; diff --git a/snac.h b/snac.h index 363855f..58ccd0b 100644 --- a/snac.h +++ b/snac.h @@ -360,6 +360,7 @@ xs_dict *msg_move(snac *user, const char *new_account); xs_dict *msg_accept(snac *snac, const xs_val *object, const char *to); xs_dict *msg_question(snac *user, const char *content, xs_list *attach, const xs_list *opts, int multiple, int end_secs); +xs_dict *msg_replies(snac *user, const char *id, int fill); int activitypub_request(snac *snac, const char *url, xs_dict **data); int actor_request(snac *user, const char *actor, xs_dict **data); -- cgit v1.2.3 From 877a4d7a2230cfbd2a3d2c751762b7fc28084575 Mon Sep 17 00:00:00 2001 From: grunfink Date: Sun, 31 Aug 2025 14:14:41 +0200 Subject: More replies tweaks. --- activitypub.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/activitypub.c b/activitypub.c index 31a9cb3..09da229 100644 --- a/activitypub.c +++ b/activitypub.c @@ -1292,10 +1292,9 @@ xs_dict *msg_replies(snac *user, const char *id, int fill) } } else { - xs *r_idl = xs_fmt("%s#local", r_id); - msg = xs_dict_del(msg, "@context"); - msg = xs_dict_set(msg, "id", r_idl); + msg = xs_dict_del(msg, "id"); + msg = xs_dict_set(msg, "next", r_idp); } msg = xs_dict_set(msg, "items", items); @@ -1913,12 +1912,10 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, xs *replies = xs_dict_new(); xs *r_id = xs_replace(id, "/p/", "/r/"); xs *h_id = xs_fmt("%s#hdr", r_id); - xs *n_id = xs_fmt("%s#page", r_id); xs *rp = msg_replies(snac, id, 0); replies = xs_dict_set(replies, "id", h_id); replies = xs_dict_set(replies, "type", "Collection"); - replies = xs_dict_set(replies, "next", n_id); replies = xs_dict_set(replies, "first", rp); msg = xs_dict_set(msg, "replies", replies); -- cgit v1.2.3 From 2b2e679affd64ece96443519e6529ab40fc44437 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 2 Sep 2025 10:01:26 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 0a3d07f..8dfc18c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,6 @@ # Release Notes -## UNRELEASED +## 2.82 The language in which a post is written can now be set from the UI; you must configure the list of languages you usually post in in the User Settings. @@ -16,6 +16,10 @@ Added nodeinfo 2.1 support. Fixed boosts from the command line not showing in the public timeline (contributed by xvello). +Updated several language files (contributed by zen and daltux). + +Retrieving a post's replies is now possible via ActivityPub. + ## 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. -- cgit v1.2.3 From d23fc31ba9c58e81d872d283ccdd1228865378b2 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 2 Sep 2025 10:01:49 +0200 Subject: Version 2.82 RELEASED. --- snac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snac.h b/snac.h index 58ccd0b..403bba0 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ -#define VERSION "2.82-dev" +#define VERSION "2.82" #define USER_AGENT "snac/" VERSION -- cgit v1.2.3 From 59f3b5465ecca02cb90310fd1dbc46456d6a4908 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 3 Sep 2025 06:08:13 +0200 Subject: New function collect_outbox(). --- activitypub.c | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.c | 5 +++ snac.h | 1 + 3 files changed, 107 insertions(+) diff --git a/activitypub.c b/activitypub.c index 09da229..b9ef86b 100644 --- a/activitypub.c +++ b/activitypub.c @@ -1021,6 +1021,107 @@ void collect_replies(snac *user, const char *id) } +void collect_outbox(snac *user, const char *actor_id) +/* gets an actor's outbox and inserts a bunch of posts in a user's timeline */ +{ + int status; + xs *actor = NULL; + + if (!valid_status(status = actor_request(user, actor_id, &actor))) { + snac_debug(user, 1, xs_fmt("collect_outbox: cannot get actor object '%s' %d", actor_id, status)); + return; + } + + xs *outbox = NULL; + const char *outbox_url = xs_dict_get(actor, "outbox"); + + if (!xs_is_string(outbox_url)) + return; + + if (!valid_status(status = activitypub_request(user, outbox_url, &outbox))) { + snac_debug(user, 1, xs_fmt("collect_outbox: cannot get actor outbox '%s' %d", outbox_url, status)); + return; + } + + const xs_list *ordered_items = xs_dict_get(outbox, "orderedItems"); + + if (!xs_is_list(ordered_items)) { + /* the list is not here; does it have a 'first'? */ + const char *first = xs_dict_get(outbox, "first"); + + if (xs_is_string(first)) { + /* download this instead */ + xs *first2 = xs_dup(first); + xs_free(outbox); + + if (!valid_status(status = activitypub_request(user, first2, &outbox))) { + snac_debug(user, 1, xs_fmt("collect_outbox: cannot get first page of outbox '%s' %d", first2, status)); + return; + } + + /* last chance */ + ordered_items = xs_dict_get(outbox, "orderedItems"); + } + } + + if (!xs_is_list(ordered_items)) { + snac_debug(user, 1, xs_fmt("collect_outbox: cannot get list of posts for actor '%s' outbox", actor_id)); + return; + } + + /* well, ok, then */ + int max = 4; + const xs_val *v; + + xs_list_foreach(ordered_items, v) { + if (max == 0) + break; + + xs *post = NULL; + + if (xs_is_string(v)) { + /* it's probably the post url */ + if (!valid_status(activitypub_request(user, v, &post))) + continue; + } + else + if (xs_is_dict(v)) + post = xs_dup(v); + + if (post == NULL) + continue; + + const char *type = xs_dict_get(post, "type"); + + if (!xs_is_string(type) || strcmp(type, "Create")) { + /* not a post */ + continue; + } + + const xs_dict *object = xs_dict_get(post, "object"); + + if (!xs_is_dict(object)) + continue; + + type = xs_dict_get(object, "type"); + const char *id = xs_dict_get(object, "id"); + const char *attr_to = get_atto(object); + + if (!xs_is_string(type) || !xs_is_string(id) || !xs_is_string(attr_to)) + continue; + + if (!timeline_here(user, id)) { + timeline_add(user, id, object); + snac_log(user, xs_fmt("new '%s' (collect_outbox) %s %s", type, attr_to, id)); + } + else + snac_debug(user, 1, xs_fmt("collect_outbox: post '%s' already here", id)); + + max--; + } +} + + void notify(snac *snac, const char *type, const char *utype, const char *actor, const xs_dict *msg) /* notifies the user of relevant events */ { diff --git a/main.c b/main.c index e01b6a2..474d684 100644 --- a/main.c +++ b/main.c @@ -738,6 +738,11 @@ int main(int argc, char *argv[]) return 0; } + if (strcmp(cmd, "collect_outbox") == 0) { /** **/ + collect_outbox(&snac, url); + return 0; + } + if (strcmp(cmd, "insert") == 0) { /** **/ int status; xs *data = NULL; diff --git a/snac.h b/snac.h index 403bba0..1366d04 100644 --- a/snac.h +++ b/snac.h @@ -336,6 +336,7 @@ 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); +void collect_outbox(snac *user, const char *actor_id); const char *get_atto(const xs_dict *msg); const char *get_in_reply_to(const xs_dict *msg); -- cgit v1.2.3 From d40e47f70964ad7e41fc06a8aa281db30a24050b Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 3 Sep 2025 06:12:50 +0200 Subject: New function enqueue_collect_outbox(). --- activitypub.c | 6 ++++++ data.c | 13 +++++++++++++ main.c | 2 +- snac.h | 1 + 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/activitypub.c b/activitypub.c index b9ef86b..c186095 100644 --- a/activitypub.c +++ b/activitypub.c @@ -3079,6 +3079,12 @@ void process_user_queue_item(snac *user, xs_dict *q_item) collect_replies(user, post); } + else + if (strcmp(type, "collect_outbox") == 0) { + const char *actor_id = xs_dict_get(q_item, "message"); + + collect_outbox(user, actor_id); + } else snac_log(user, xs_fmt("unexpected user q_item type '%s'", type)); } diff --git a/data.c b/data.c index ca5e3ed..0ea74b7 100644 --- a/data.c +++ b/data.c @@ -3589,6 +3589,19 @@ void enqueue_collect_replies(snac *user, const char *post) } +void enqueue_collect_outbox(snac *user, const char *actor_id) +/* enqueues a collect outbox request */ +{ + xs *qmsg = _new_qmsg("collect_outbox", actor_id, 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_outbox %s", actor_id)); +} + + int was_question_voted(snac *user, const char *id) /* returns true if the user voted in this poll */ { diff --git a/main.c b/main.c index 474d684..6abac61 100644 --- a/main.c +++ b/main.c @@ -739,7 +739,7 @@ int main(int argc, char *argv[]) } if (strcmp(cmd, "collect_outbox") == 0) { /** **/ - collect_outbox(&snac, url); + enqueue_collect_outbox(&snac, url); return 0; } diff --git a/snac.h b/snac.h index 1366d04..5cbfcae 100644 --- a/snac.h +++ b/snac.h @@ -298,6 +298,7 @@ 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); +void enqueue_collect_outbox(snac *user, const char *actor_id); int was_question_voted(snac *user, const char *id); -- cgit v1.2.3 From d358f5bcd0dfe326a5e0361237ac238a25785292 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 3 Sep 2025 06:14:51 +0200 Subject: Call enqueue_collect_outbox() after a Follow Accept. --- activitypub.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/activitypub.c b/activitypub.c index c186095..2c0aa98 100644 --- a/activitypub.c +++ b/activitypub.c @@ -2588,6 +2588,9 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) if (following_check(snac, actor)) { following_add(snac, actor, msg); snac_log(snac, xs_fmt("confirmed follow from %s", actor)); + + /* request a bit of this fellow's outbox */ + enqueue_collect_outbox(snac, actor); } else snac_log(snac, xs_fmt("spurious follow accept from %s", actor)); -- cgit v1.2.3 From 4661d7f5bc076be896c7192bb63a7c7d726993da Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 3 Sep 2025 06:38:07 +0200 Subject: Renamed function list_content() to list_members(). --- data.c | 6 +++--- main.c | 6 +++--- mastoapi.c | 8 ++++---- snac.h | 2 +- utils.c | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/data.c b/data.c index 0ea74b7..7e90a06 100644 --- a/data.c +++ b/data.c @@ -2429,8 +2429,8 @@ xs_list *list_timeline(snac *user, const char *list, int skip, int show) } -xs_val *list_content(snac *user, const char *list, const char *actor_md5, int op) -/* list content management */ +xs_val *list_members(snac *user, const char *list, const char *actor_md5, int op) +/* list member management */ { xs_val *l = NULL; @@ -2443,7 +2443,7 @@ xs_val *list_content(snac *user, const char *list, const char *actor_md5, int op xs *fn = xs_fmt("%s/list/%s.lst", user->basedir, list); switch (op) { - case 0: /** list content **/ + case 0: /** list members **/ l = index_list(fn, XS_ALL); break; diff --git a/main.c b/main.c index 6abac61..882da33 100644 --- a/main.c +++ b/main.c @@ -354,7 +354,7 @@ int main(int argc, char *argv[]) xs *lid = list_maint(&snac, url, 4); if (lid != NULL) { - xs *lcont = list_content(&snac, lid, NULL, 0); + xs *lcont = list_members(&snac, lid, NULL, 0); const char *md5; xs_list_foreach(lcont, md5) { @@ -410,7 +410,7 @@ int main(int argc, char *argv[]) if (valid_status(webfinger_request(account, &actor, &uid))) { xs *md5 = xs_md5_hex(actor, strlen(actor)); - list_content(&snac, lid, md5, 1); + list_members(&snac, lid, md5, 1); printf("Actor %s (%s) added to list '%s' (%s)\n", actor, uid, url, lid); } else @@ -433,7 +433,7 @@ int main(int argc, char *argv[]) if (lid != NULL) { xs *md5 = xs_md5_hex(account, strlen(account)); - list_content(&snac, lid, md5, 2); + list_members(&snac, lid, md5, 2); printf("Actor %s deleted from list '%s' (%s)\n", account, url, lid); } else diff --git a/mastoapi.c b/mastoapi.c index 42f3a47..568426b 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -1560,7 +1560,7 @@ xs_list *mastoapi_account_lists(snac *user, const char *uid) const char *list_id = xs_list_get(li, 0); const char *list_title = xs_list_get(li, 1); if (uid) { - xs *users = list_content(user, list_id, NULL, 0); + xs *users = list_members(user, list_id, NULL, 0); if (xs_list_in(users, actor_md5) == -1) continue; } @@ -2087,7 +2087,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, p = xs_list_get(l, -2); if (p && xs_is_hex(p)) { - xs *actors = list_content(&snac1, p, NULL, 0); + xs *actors = list_members(&snac1, p, NULL, 0); xs *out = xs_list_new(); int c = 0; const char *v; @@ -3297,7 +3297,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, const char *v; while (xs_list_next(accts, &v, &c)) { - list_content(&snac, id, v, 1); + list_members(&snac, id, v, 1); } xs *out = xs_dict_new(); @@ -3507,7 +3507,7 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path, const char *v; while (xs_list_next(accts, &v, &c)) { - list_content(&snac, p, v, 2); + list_members(&snac, p, v, 2); } } else { diff --git a/snac.h b/snac.h index 5cbfcae..a871576 100644 --- a/snac.h +++ b/snac.h @@ -233,7 +233,7 @@ xs_list *tag_search(const char *tag, int skip, int show); xs_val *list_maint(snac *user, const char *list, int op); xs_str *list_timeline_fn(snac *user, const char *list); xs_list *list_timeline(snac *user, const char *list, int skip, int show); -xs_val *list_content(snac *user, const char *list_id, const char *actor_md5, int op); +xs_val *list_members(snac *user, const char *list_id, const char *actor_md5, int op); void list_distribute(snac *user, const char *who, const xs_dict *post); int actor_add(const char *actor, const xs_dict *msg); diff --git a/utils.c b/utils.c index 8db20bd..9a60879 100644 --- a/utils.c +++ b/utils.c @@ -681,7 +681,7 @@ void export_csv(snac *user) const char *lid = xs_list_get(li, 0); const char *ltitle = xs_list_get(li, 1); - xs *actors = list_content(user, lid, NULL, 0); + xs *actors = list_members(user, lid, NULL, 0); const char *md5; xs_list_foreach(actors, md5) { @@ -907,7 +907,7 @@ void import_list_csv(snac *user, const char *ifn) if (valid_status(webfinger_request(acct, &url, &uid))) { xs *actor_md5 = xs_md5_hex(url, strlen(url)); - list_content(user, list_id, actor_md5, 1); + list_members(user, list_id, actor_md5, 1); snac_log(user, xs_fmt("Added %s to list %s", url, lname)); if (!following_check(user, url)) { -- cgit v1.2.3 From 080a22c7942c6129256fb0a0aae2a9b478687a19 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 3 Sep 2025 13:51:33 +0200 Subject: In snac_init(), added a message mentioning .po files. --- utils.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/utils.c b/utils.c index 9a60879..bdee09d 100644 --- a/utils.c +++ b/utils.c @@ -229,6 +229,9 @@ int snac_init(const char *basedir) xs *ibdir = xs_fmt("%s/inbox", srv_basedir); mkdirx(ibdir); + xs *langdir = xs_fmt("%s/lang", srv_basedir); + mkdirx(langdir); + xs *gfn = xs_fmt("%s/greeting.html", srv_basedir); if ((f = fopen(gfn, "w")) == NULL) { printf("ERROR: cannot create '%s'\n", gfn); @@ -253,7 +256,10 @@ int snac_init(const char *basedir) xs_json_dump(srv_config, 4, f); fclose(f); - printf("Done.\n"); + printf("Done.\n\n"); + + printf("Wanted web UI language files (.po) must be copied manually to %s\n", langdir); + return 0; } -- cgit v1.2.3 From 9060ccd8e7826d384211552e4d5c5597ca97dc43 Mon Sep 17 00:00:00 2001 From: Xavier Vello Date: Wed, 3 Sep 2025 14:48:37 +0200 Subject: Document the update command --- doc/snac.1 | 2 ++ main.c | 1 + 2 files changed, 3 insertions(+) diff --git a/doc/snac.1 b/doc/snac.1 index 134ee6e..6c9b3d3 100644 --- a/doc/snac.1 +++ b/doc/snac.1 @@ -282,6 +282,8 @@ necessary information will be prompted for. Deletes a user, unfollowing all accounts first. .It Cm resetpwd Ar basedir Ar uid Resets a user's password to a new, random one. +.It Cm update Ar basedir Ar uid +Sends a user's updated profile to following instances. .It Cm queue Ar basedir Ar uid Processes the output queue of the specified user, sending all enqueued messages and re-enqueing the failing ones. This command diff --git a/main.c b/main.c index e01b6a2..b855e06 100644 --- a/main.c +++ b/main.c @@ -30,6 +30,7 @@ int usage(const char *cmd) "upgrade {basedir} Upgrade to a new version\n" "adduser {basedir} [{uid}] Adds a new user\n" "deluser {basedir} {uid} Deletes a user\n" + "update {basedir} {uid} Sends a user's updated profile\n" "httpd {basedir} Starts the HTTPD daemon\n" "purge {basedir} Purges old data\n" "state {basedir} Prints server state\n" -- cgit v1.2.3