From 9c033f452e9a07eb2bdedc51648269e5d285491b Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 29 Apr 2026 18:36:43 +0200 Subject: In adduser(), delete the (possible) grave. --- data.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'data.c') diff --git a/data.c b/data.c index 992a42f..7fb261c 100644 --- a/data.c +++ b/data.c @@ -3303,6 +3303,10 @@ int grave(const char *objid, int op) } break; + + case 2: /** del **/ + unlink(fn); + break; } return ret; -- cgit From d3309f8b5e43c8116f99e548bb9f874b5cdaf70e Mon Sep 17 00:00:00 2001 From: violette Date: Thu, 21 May 2026 19:40:58 +0200 Subject: Added a new 'admiration' timeline. --- data.c | 5 ++++- html.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) (limited to 'data.c') diff --git a/data.c b/data.c index 7fb261c..ad71b85 100644 --- a/data.c +++ b/data.c @@ -1606,12 +1606,15 @@ int timeline_admire(snac *snac, const char *id, const char *content = xs_dict_get_path(msg, "content"); const char *type = xs_dict_get_path(msg, "type"); - /* if we are admiring this, add to both timelines */ + /* if we are admiring this, add to both timelines, and store for later */ if (!like && strcmp(admirer, snac->actor) == 0) { object_user_cache_add(snac, id, "public"); object_user_cache_add(snac, id, "private"); } + if (strcmp(admirer, snac->actor) == 0) + object_user_cache_add(snac, id, "admire"); + /* use utf <3 as a like, as it is ugly */ if (type && xs_match(type, "Like|EmojiReact|Emoji") && content && strcmp(content, "❤") != 0) { diff --git a/html.c b/html.c index cd1746a..29dd8e8 100644 --- a/html.c +++ b/html.c @@ -3520,6 +3520,18 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, xs_html_text(L("pinned"))))); } + { + /* show the list of bookmarked posts */ + xs *url = xs_fmt("%s/admirations", user->actor); + xs_html_add(lol, + xs_html_tag("li", + xs_html_tag("a", + xs_html_attr("href", url), + xs_html_attr("class", "snac-list-link"), + xs_html_attr("title", L("Liked, boosted or reacted posts")), + xs_html_text(L("admirations"))))); + } + { /* show the list of bookmarked posts */ xs *url = xs_fmt("%s/bookmarks", user->actor); @@ -5042,6 +5054,22 @@ int html_get_handler(const xs_dict *req, const char *q_path, } } else + if (strcmp(p_path, "admirations") == 0) { /** list of admired posts **/ + if (!login(&snac, req)) { + *body = xs_dup(uid); + status = HTTP_STATUS_UNAUTHORIZED; + } + else { + int more = 0; + xs *list = timeline_list(&snac, "admire", skip, show, &more); + + *body = html_timeline(&snac, list, 0, skip, show, + more, L("Liked, boosted or reacted posts"), "/admirations", 0, error, terse); + *b_size = strlen(*body); + status = HTTP_STATUS_OK; + } + } + else if (strcmp(p_path, "bookmarks") == 0) { /** list of bookmarked posts **/ if (!login(&snac, req)) { *body = xs_dup(uid); -- cgit From 5c5736001d6fca95271801008aad6eedeb7deda5 Mon Sep 17 00:00:00 2001 From: grunfink Date: Thu, 21 May 2026 19:52:50 +0200 Subject: Also purge the new admire/ subdirectory. --- data.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'data.c') diff --git a/data.c b/data.c index ad71b85..9f94320 100644 --- a/data.c +++ b/data.c @@ -4418,9 +4418,10 @@ void purge_user(snac *snac) _purge_user_subdir(snac, "pending", priv_days); _purge_user_subdir(snac, "public", pub_days); + _purge_user_subdir(snac, "admire", pub_days); - const char *idxs[] = { "followers.idx", "private.idx", "public.idx", - "pinned.idx", "bookmark.idx", "draft.idx", "sched.idx", NULL }; + const char *idxs[] = { "followers.idx", "private.idx", "public.idx", "pinned.idx", + "bookmark.idx", "draft.idx", "sched.idx", "admire.idx", NULL }; for (n = 0; idxs[n]; n++) { xs *idx = xs_fmt("%s/%s", snac->basedir, idxs[n]); -- cgit From 9dabd90f16fec972d31b0b758bdb87d07c4654e3 Mon Sep 17 00:00:00 2001 From: Santtu Lakkala Date: Fri, 22 May 2026 09:56:47 +0300 Subject: Fix memory leak --- data.c | 1 + 1 file changed, 1 insertion(+) (limited to 'data.c') diff --git a/data.c b/data.c index 9f94320..37c9325 100644 --- a/data.c +++ b/data.c @@ -217,6 +217,7 @@ void user_free(snac *snac) xs_free(snac->key); xs_free(snac->links); xs_free(snac->actor); + xs_free(snac->actor_alt); xs_free(snac->md5); } -- cgit From 5182a3384e80462e08713903c14b9cdecf56cdfb Mon Sep 17 00:00:00 2001 From: hanchan Date: Wed, 27 May 2026 17:26:33 +0800 Subject: add keep_replied_posts and keep_replied_me feathers, and auto-mention post author when reply to a post --- data.c | 31 +++++++++++++++++++++++++++++++ doc/snac.8 | 5 +++++ html.c | 21 +++++++++++++++++++++ utils.c | 4 +++- 4 files changed, 60 insertions(+), 1 deletion(-) (limited to 'data.c') diff --git a/data.c b/data.c index 37c9325..3f7ba29 100644 --- a/data.c +++ b/data.c @@ -1562,6 +1562,37 @@ void timeline_update_indexes(snac *snac, const char *id) else /* also add it to public, it will be discarded later */ object_user_cache_add(snac, id, "public"); + + /* if `keep_replied_posts` is `true` and a local post is a reply to a remote post, add the parent remote post to the public */ + if (xs_is_true(xs_dict_get(srv_config, "keep_replied_posts"))) { + const char *in_reply_to = get_in_reply_to(msg); + + if (xs_is_string(in_reply_to) && *in_reply_to && + !is_msg_mine(snac, in_reply_to)) { + if (object_user_cache_add(snac, in_reply_to, "public") >= 0) { + snac_debug(snac, 1, + xs_fmt("keep_replied_posts: added parent %s to public", in_reply_to)); + } + } + } + } + } + else { + /* if `keep_replied_me` is `true` and a remote post is a reply to one of local posts, add it to public */ + if (xs_is_true(xs_dict_get(srv_config, "keep_replied_me"))) { + xs *msg = NULL; + + if (valid_status(object_get(id, &msg))) { + const char *in_reply_to = get_in_reply_to(msg); + + if (xs_is_string(in_reply_to) && *in_reply_to && + is_msg_mine(snac, in_reply_to)) { + if (object_user_cache_add(snac, id, "public") >= 0) { + snac_debug(snac, 1, + xs_fmt("keep_replied_me: added reply %s to public", id)); + } + } + } } } } diff --git a/doc/snac.8 b/doc/snac.8 index ef93d31..5698ce1 100644 --- a/doc/snac.8 +++ b/doc/snac.8 @@ -327,6 +327,11 @@ Overrides the default "mogrify" command name or path. Use this if the tool is no Overrides the default "ffmpeg" command name or path. Use this if the tool is not in the system PATH or has a different name. .It Ic purge_static If set to true, user static directories are purged; any file stored in these subdirectories (usually images or other media) that is not attached to any post, not used as avatar, header or favicon, and not served as an emoji, is deleted from the filesystem. Don't enable this option if any of your users are serving other files (like, e.g., CSS assets) this way. +.It Ic keep_replied_posts +If set to true, when you reply to a remote post, the parent remote post will be added to local public timeline. +.It Ic keep_replied_me +If set to true, when a remote user replies to one of local posts, the +remote reply will be added to local public timeline. .El .Pp You must restart the server to make effective these changes. diff --git a/html.c b/html.c index ecd7fd7..a553d8c 100644 --- a/html.c +++ b/html.c @@ -2035,6 +2035,27 @@ xs_str *build_mentions(snac *user, const xs_dict *msg) } } + /* also add the author of this post as a mention */ + const char *atto = get_atto(msg); + + if (xs_is_string(atto) && *atto && strcmp(atto, user->actor) != 0) { + /* check if this author is already in the mentions string */ + xs *l = xs_split(atto, "/"); + + if (xs_list_len(l) > 3) { + xs *actor_o = NULL; + if (valid_status(object_get(atto, &actor_o))) { + const char *uname = xs_dict_get(actor_o, "preferredUsername"); + if (!xs_is_null(uname) && *uname) { + xs *handle = xs_fmt("@%s@%s ", uname, xs_list_get(l, 2)); + if (xs_str_in(s, handle) == -1) { + s = xs_str_cat(s, handle); + } + } + } + } + } + if (*s) { xs *s1 = s; s = xs_fmt("\n\n\nCC: %s", s1); diff --git a/utils.c b/utils.c index c36587a..9475bd7 100644 --- a/utils.c +++ b/utils.c @@ -44,7 +44,9 @@ static const char * const default_srv_config = "{" "\"protocol\": \"https\"," "\"fastcgi\": false," "\"disable_inbox_collection\": true," - "\"disable_history\": true" + "\"disable_history\": true," + "\"keep_replied_posts\": false," + "\"keep_replied_me\": false" "}"; static const char * const default_css = -- cgit From ef7b66697f5b3261a451917d2996563a141df68a Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 24 Jun 2026 14:54:45 +0200 Subject: Implemented semi-random forward_secs in enqueue_object_request() or enqueue_actor_refresh(). If forward_secs is a negative value in these functions, it's used as the modulo of a hash of the user id. This way, a refresh storm of the same object by all users is somewhat distributed in time. --- activitypub.c | 4 ++-- data.c | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'data.c') diff --git a/activitypub.c b/activitypub.c index e9be2de..514e764 100644 --- a/activitypub.c +++ b/activitypub.c @@ -3077,7 +3077,7 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) else { /* actor / key mismatch: don't accept blindly, but request an actor update from the original source, as the Update may come from a relay and be legit */ - enqueue_actor_refresh(snac, actor, 0); + enqueue_actor_refresh(snac, actor, -10); snac_log(snac, xs_fmt("Update: mismatched actor '%s' and key '%s'", actor, key_id)); } @@ -3097,7 +3097,7 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) else if (strcmp(atto, key_id) != 0) { /* actor / key mismatch: request the object from the original source */ - enqueue_object_request(snac, id, 0); + enqueue_object_request(snac, id, -10); snac_log(snac, xs_fmt("Update: mismatched attributedTo '%s' and key '%s'", atto, key_id)); } diff --git a/data.c b/data.c index 3f7ba29..9500474 100644 --- a/data.c +++ b/data.c @@ -3858,6 +3858,11 @@ void enqueue_close_question(snac *user, const char *id, int end_secs) void enqueue_object_request(snac *user, const char *id, int forward_secs) /* enqueues the request of an object in the future */ { + if (forward_secs < 0) { + /* set it to a somewhat random delay */ + forward_secs = xs_hash_func(user->uid, strlen(user->uid)) % -forward_secs; + } + xs *qmsg = _new_qmsg("object_request", id, 0); xs *ntid = tid(forward_secs); xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); @@ -3886,6 +3891,11 @@ void enqueue_verify_links(snac *user) void enqueue_actor_refresh(snac *user, const char *actor, int forward_secs) /* enqueues an actor refresh */ { + if (forward_secs < 0) { + /* set it to a somewhat random delay */ + forward_secs = xs_hash_func(user->uid, strlen(user->uid)) % -forward_secs; + } + xs *qmsg = _new_qmsg("actor_refresh", "", 0); xs *ntid = tid(forward_secs); xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); -- cgit From 04daf694a17f0c3b9462b0b4483674f62ef3819f Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 24 Jun 2026 14:58:54 +0200 Subject: Minor logging tweak. --- data.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'data.c') diff --git a/data.c b/data.c index 9500474..52e4271 100644 --- a/data.c +++ b/data.c @@ -3905,7 +3905,7 @@ void enqueue_actor_refresh(snac *user, const char *actor, int forward_secs) qmsg = _enqueue_put(fn, qmsg); - snac_debug(user, 1, xs_fmt("enqueue_actor_refresh %s", actor)); + snac_debug(user, 1, xs_fmt("enqueue_actor_refresh %s %d", actor, forward_secs)); } -- cgit