From d9b0c8e94fa8fa88ddea6c5a8ddc76dbbc458fd3 Mon Sep 17 00:00:00 2001 From: pmjv Date: Fri, 10 Jul 2026 11:16:03 +0200 Subject: Update po/cs.po --- po/cs.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/po/cs.po b/po/cs.po index 0361367..70ecb7f 100644 --- a/po/cs.po +++ b/po/cs.po @@ -941,12 +941,12 @@ msgstr "Zobrazit neveřejné příspěvky na profilu a v RSS" #: html.c:1855 msgid "Don't show posts written in these languages:" -msgstr "" +msgstr "Nezobrazovat příspěvky v těchto jazycích:" #: html.c:3572 html.c:5108 msgid "Liked, boosted or reacted posts" -msgstr "" +msgstr "Oblíbené a boostované příspěvky" #: html.c:3573 msgid "admirations" -msgstr "" +msgstr "interakce" -- cgit From 4425d8e985e35cee72acf7b22f50422c8e1ac2a3 Mon Sep 17 00:00:00 2001 From: grunfink Date: Sun, 12 Jul 2026 20:12:03 +0200 Subject: Added count of EmojiReacts to top_ten. --- data.c | 8 ++++++++ main.c | 5 +++-- snac.h | 1 + utils.c | 7 ++++--- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/data.c b/data.c index 52e4271..806938c 100644 --- a/data.c +++ b/data.c @@ -1051,6 +1051,14 @@ int object_announces_len(const char *id) } +int object_emojireacts_len(const char *id) +/* returns the number of emojireacts (without reading the index) */ +{ + xs *fn = _object_index_fn(id, "_e.idx"); + return index_len(fn); +} + + xs_list *object_children(const char *id) /* returns the list of an object's children */ { diff --git a/main.c b/main.c index e7d5b0d..9170761 100644 --- a/main.c +++ b/main.c @@ -362,9 +362,10 @@ int main(int argc, char *argv[]) const xs_list *i; xs_list_foreach(l, i) { - printf("%s %ld★ %ld↺\n", xs_list_get(i, 0), + printf("%s %ld★ %ld↺ %ld☺\n", xs_list_get(i, 0), xs_number_get_l(xs_list_get(i, 1)), - xs_number_get_l(xs_list_get(i, 2))); + xs_number_get_l(xs_list_get(i, 2)), + xs_number_get_l(xs_list_get(i, 3))); } return 0; diff --git a/snac.h b/snac.h index 69180ae..cb4ae56 100644 --- a/snac.h +++ b/snac.h @@ -157,6 +157,7 @@ int object_emoji_react(const char *mid, const char *eid); int object_rm_emoji_react(const char *mid, const char *eid); int object_likes_len(const char *id); int object_announces_len(const char *id); +int object_emojireacts_len(const char *id); xs_list *object_children(const char *id); xs_list *object_likes(const char *id); diff --git a/utils.c b/utils.c index 9475bd7..6941081 100644 --- a/utils.c +++ b/utils.c @@ -1024,8 +1024,8 @@ static int top_ten_sort(const void *v1, const void *v2) const xs_list *l1 = *(const xs_list **)v1; const xs_list *l2 = *(const xs_list **)v2; - const char *c1 = xs_list_get(l1, 3); - const char *c2 = xs_list_get(l2, 3); + const char *c1 = xs_list_get(l1, 4); + const char *c2 = xs_list_get(l2, 4); return xs_cmp(c2, c1); } @@ -1060,9 +1060,10 @@ xs_list *user_top_ten(snac *user, int count) /* get metrics */ int ls = object_likes_len(id); int as = object_announces_len(id); + int es = object_emojireacts_len(id); /* build the entry and convert to list */ - xs *s = xs_fmt("%s\t%d\t%d\t%010d", id, ls, as, ls + as); + xs *s = xs_fmt("%s\t%d\t%d\t%d\t%010d", id, ls, as, es, ls + as + es); xs *l = xs_split(s, "\t"); u_list = xs_list_append(u_list, l); -- cgit From a9668e4f55e9a3c201343b14574e05aba86d75de Mon Sep 17 00:00:00 2001 From: Filippo Dibenedetto Date: Wed, 15 Jul 2026 22:55:02 +0200 Subject: Updated Italian translation --- po/it.po | 110 +++++++++++++++++++++++++++++++-------------------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/po/it.po b/po/it.po index 9548796..69e71ec 100644 --- a/po/it.po +++ b/po/it.po @@ -162,7 +162,7 @@ msgstr "(per URL o user@host)" #: html.c:1520 html.c:2139 html.c:5737 msgid "Boost" -msgstr "Annuncia" +msgstr "Rilancia" #: html.c:1522 html.c:1539 html.c:1561 msgid "(by URL)" @@ -238,7 +238,7 @@ msgstr "Questo account è un bot" #: html.c:1797 msgid "Auto-boost all mentions to this account" -msgstr "Annuncio automatico delle citazioni a quest'account" +msgstr "Rilancio automatico delle citazioni a quest'account" #: html.c:1799 msgid "This account is private (posts are not shown through the web)" @@ -326,7 +326,7 @@ msgstr "Annuncia questo post ai tuoi seguenti" #: html.c:2144 html.c:5755 msgid "Unboost" -msgstr "Rimuovi annuncio" +msgstr "Ritira il boost" #: html.c:2144 msgid "I regret I boosted this" @@ -370,7 +370,7 @@ msgstr "Smetti di seguire il gruppo o canale" #: html.c:2174 html.c:5820 msgid "Follow Group" -msgstr "Segui grupp" +msgstr "Segui il gruppo" #: html.c:2175 msgid "Start following this group or channel" @@ -438,7 +438,7 @@ msgstr "Evento" #: html.c:2510 html.c:2539 msgid "boosted" -msgstr "Annunciato" +msgstr "rilanciato" #: html.c:2553 msgid "in reply to" @@ -539,7 +539,7 @@ msgstr "Senza limite" #: html.c:4033 msgid "Allow announces (boosts) from this user" -msgstr "Permetti annunci dall'utente" +msgstr "Permetti annunci (boost) dall'utente" #: html.c:4036 html.c:5782 msgid "Limit" @@ -547,7 +547,7 @@ msgstr "Limite" #: html.c:4037 msgid "Block announces (boosts) from this user" -msgstr "Blocca annunci dall'utente" +msgstr "Blocca annunci (boost) dall'utente" #: html.c:4046 msgid "Delete this user" @@ -737,23 +737,23 @@ msgstr "bozze" #: html.c:733 msgid "Scheduled post..." -msgstr "" +msgstr "Post programmati..." msgid "Post date and time:" -msgstr "" +msgstr "Data e ora del post:" #: html.c:3608 html.c:5153 msgid "Scheduled posts" -msgstr "" +msgstr "Post programmati" #: html.c:3609 msgid "scheduled posts" -msgstr "" +msgstr "post programmati" #: html.c:727 #, c-format msgid "Post date and time (timezone: %s):" -msgstr "" +msgstr "Data e ora del post (timezone: %s):" #: html.c:1841 msgid "Time zone:" @@ -761,7 +761,7 @@ msgstr "" #: html.c:708 msgid "Language:" -msgstr "" +msgstr "Lingua:" #: html.c:1779 msgid "Notify webhook:" @@ -773,7 +773,7 @@ msgstr "" #: html.c:1846 msgid "Languages you usually post in:" -msgstr "" +msgstr "Lingua nella quale posti di solito" #: html.c:1852 html.c:1861 msgid "en fr es de_AT" @@ -781,19 +781,19 @@ msgstr "" #: html.c:521 msgid "Visibility: " -msgstr "" +msgstr "Visibilità: " msgid "Public" -msgstr "" +msgstr "Pubblico" msgid "Unlisted" -msgstr "" +msgstr "Non elencato" msgid "Followers-only" -msgstr "" +msgstr "Solo follower" msgid "Direct Message" -msgstr "" +msgstr "Messaggio diretto" #: html.c:581 html.c:2728 html.c:2753 html.c:5701 msgid "EmojiUnreact" @@ -809,144 +809,144 @@ msgstr "" #: html.c:2803 msgid "Emoji reactions: " -msgstr "" +msgstr "Reazioni Emoji: " #: html.c:1957 msgid "Muted words..." -msgstr "" +msgstr "Parole silenziate..." #: html.c:1959 msgid "One word per line, partial matches count" -msgstr "" +msgstr "Una parola per linea, vale la corrispondenza parziale" #: html.c:1980 msgid "Update muted words" -msgstr "" +msgstr "Aggiorna le parole silenziate" #: html.c:2628 msgid "Muted: " -msgstr "" +msgstr "Silenziati: " #: html.c:4146 msgid "Contact's posts" -msgstr "" +msgstr "Post dei contatti" #: html.c:4215 msgid "More (x 10)..." -msgstr "" +msgstr "Ancora (x 10)..." #: html.c:873 msgid "End in 3 days" -msgstr "" +msgstr "Termina in 3 giorni" #: html.c:876 msgid "End in 1 year" -msgstr "" +msgstr "Termina in 1 anno" #: html.c:4303 msgid "Likes" -msgstr "" +msgstr "Like" #: html.c:4304 msgid "Emoji reacts" -msgstr "" +msgstr "Reazioni Emoji" #: html.c:4305 msgid "Mentions" -msgstr "" +msgstr "Citazioni" #: html.c:4306 msgid "Announces" -msgstr "" +msgstr "Boost" #: html.c:4307 msgid "Follows" -msgstr "" +msgstr "Ti segue" #: html.c:4308 msgid "Unfollows" -msgstr "" +msgstr "Ha smesso di seguirti" #: html.c:4309 msgid "Follow requests" -msgstr "" +msgstr "Ha chiesto di seguirti" #: html.c:4310 msgid "Blocks" -msgstr "" +msgstr "Ti ha bloccato" #: html.c:4311 msgid "Polls" -msgstr "" +msgstr "Sondaggi" #: html.c:4315 msgid "Save" -msgstr "" +msgstr "Salva" #: html.c:4323 msgid "Notifications filter..." -msgstr "" +msgstr "Filtro delle notifiche..." #: data.c:45 msgid "Jan" -msgstr "" +msgstr "Gen" #: data.c:46 msgid "Feb" -msgstr "" +msgstr "Feb" #: data.c:47 msgid "Mar" -msgstr "" +msgstr "Mar" #: data.c:48 msgid "Apr" -msgstr "" +msgstr "Apr" #: data.c:49 msgid "May" -msgstr "" +msgstr "Mag" #: data.c:50 msgid "Jun" -msgstr "" +msgstr "Giu" #: data.c:51 msgid "Jul" -msgstr "" +msgstr "Lug" #: data.c:52 msgid "Aug" -msgstr "" +msgstr "Ago" #: data.c:53 msgid "Sep" -msgstr "" +msgstr "Set" #: data.c:54 msgid "Oct" -msgstr "" +msgstr "Ott" #: data.c:55 msgid "Nov" -msgstr "" +msgstr "Mov" #: data.c:56 msgid "Dec" -msgstr "" +msgstr "Dic" #: html.c:1808 msgid "Show unlisted posts in the public page and RSS feed" -msgstr "" +msgstr "Mostra i post non elencati nella pagina pubblica e nei feed RSS" #: html.c:1855 msgid "Don't show posts written in these languages:" -msgstr "" +msgstr "Non mostrare i post scritti in queste lingue:" #: html.c:3572 html.c:5108 msgid "Liked, boosted or reacted posts" -msgstr "" +msgstr "Post piaciuti, rilanciati o con reazioni" #: html.c:3573 msgid "admirations" -msgstr "" +msgstr "apprezzati" -- cgit From e21c6ce7f7083eac7d49050e18787669c835cd08 Mon Sep 17 00:00:00 2001 From: grunfink Date: Mon, 20 Jul 2026 16:44:36 +0200 Subject: In build_mentions(), don't add the original poster as a CC. --- html.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/html.c b/html.c index 5ba00ba..4810601 100644 --- a/html.c +++ b/html.c @@ -2045,6 +2045,9 @@ xs_str *build_mentions(snac *user, const xs_dict *msg) } } +#if 0 + /* disabled by now */ + /* also add the author of this post as a mention */ const char *atto = get_atto(msg); @@ -2065,6 +2068,7 @@ xs_str *build_mentions(snac *user, const xs_dict *msg) } } } +#endif if (*s) { xs *s1 = s; -- cgit From 1a34a0d23221af98e04d6aab4f91f6acb6748794 Mon Sep 17 00:00:00 2001 From: grunfink Date: Mon, 20 Jul 2026 16:45:18 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e62be07..76feac6 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,9 @@ # Release Notes +## UNRELEASED + +The author of a replied post is no longer added to the CC: line, as it does not have much sense. + ## 2.93 Added a new *admirations* entry point to the web UI, containing a timeline of liked, boosted and reacted posts. Please take note that, after installing the new version, it will start empty, and be filled with subsequent reactions (contributed by violette). -- cgit From f77be8cb43351d50159c839b62226d5f44aed6b0 Mon Sep 17 00:00:00 2001 From: Ellie Date: Tue, 21 Jul 2026 18:40:44 +0300 Subject: add avatar and header deletion mastodon api endpoints --- mastoapi.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/mastoapi.c b/mastoapi.c index cf2f53e..4139af1 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -4104,6 +4104,24 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path, else status = HTTP_STATUS_UNAUTHORIZED; } + else + if (xs_startswith(cmd, "/v1/profile/")) { + if (logged_in) { + xs *l = xs_split(cmd, "/"); + const char *p = xs_list_get(l, -1); + + if (strcmp(p, "avatar") == 0 || strcmp(p, "header") == 0) { + snac.config = xs_dict_set(snac.config, p, ""); + + if (user_persist(&snac, 1) == 0) + credentials_get(body, ctype, &status, snac); + else + status = HTTP_STATUS_INTERNAL_SERVER_ERROR; + } + } + else + status = HTTP_STATUS_UNAUTHORIZED; + } /* user cleanup */ if (logged_in) -- cgit From 727ea03c62b10ac84fcc4c91bdebd5eaaa50e94d Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 22 Jul 2026 11:04:38 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 76feac6..2ff2a23 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,6 +4,10 @@ The author of a replied post is no longer added to the CC: line, as it does not have much sense. +Mastodon API: avatars and headers can be deleted (contributed by bm90). + +Updated Czech and Italian translations (contributed by pmjv and fidiben). + ## 2.93 Added a new *admirations* entry point to the web UI, containing a timeline of liked, boosted and reacted posts. Please take note that, after installing the new version, it will start empty, and be filled with subsequent reactions (contributed by violette). -- cgit From 4d120bccd8a031b4092db82ffbc0843fb15f9f10 Mon Sep 17 00:00:00 2001 From: voidq Date: Thu, 23 Jul 2026 19:47:05 +0800 Subject: renamed snac-btn-like to snac-btn-clear-all by mistake clear_all btn was given class snac-btn-like --- html.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html.c b/html.c index 4810601..bc4dd63 100644 --- a/html.c +++ b/html.c @@ -4337,7 +4337,7 @@ xs_str *html_notifications(snac *user, int skip, int show) xs_html_attr("id", "clear"), xs_html_sctag("input", xs_html_attr("type", "submit"), - xs_html_attr("class", "snac-btn-like"), + xs_html_attr("class", "snac-btn-clear-all"), xs_html_attr("value", L("Clear all"))))); xs_html *noti_new = NULL; -- cgit From 86852a0c2d95bbee5105294dff1f1998f93dbfed Mon Sep 17 00:00:00 2001 From: grunfink Date: Thu, 30 Jul 2026 10:21:29 +0200 Subject: Don't skip unlisted replies to unknown posts. --- html.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html.c b/html.c index bc4dd63..48aecde 100644 --- a/html.c +++ b/html.c @@ -3700,7 +3700,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, continue; const int scope = get_msg_visibility(msg); - if (user != NULL && scope != SCOPE_PUBLIC && !is_msg_mine(user, xs_dict_get(msg, "id"))) { + if (user != NULL && scope != SCOPE_PUBLIC && scope != SCOPE_UNLISTED && !is_msg_mine(user, xs_dict_get(msg, "id"))) { /* is this message a non-public reply? */ const char *irt = get_in_reply_to(msg); -- cgit From cce687bd601680741e93ab75da3d02a99c676387 Mon Sep 17 00:00:00 2001 From: grunfink Date: Fri, 31 Jul 2026 08:26:22 +0200 Subject: Mark actors from boosted posts as non-broken. --- activitypub.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/activitypub.c b/activitypub.c index 514e764..39029b6 100644 --- a/activitypub.c +++ b/activitypub.c @@ -3053,6 +3053,9 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) /* distribute the post to users following these hashtags */ followed_hashtag_distribute(a_msg); + /* actor is [back] alive */ + actor_failure(who, 2); + do_notify = 1; } else -- cgit From de81a9c59ab513ce473cdca1091fdd81de9fe45e Mon Sep 17 00:00:00 2001 From: grunfink Date: Mon, 3 Aug 2026 04:54:03 +0200 Subject: When mentioning, allow users with - in their names. --- activitypub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitypub.c b/activitypub.c index 39029b6..92eb7f5 100644 --- a/activitypub.c +++ b/activitypub.c @@ -940,7 +940,7 @@ xs_str *process_tags(snac *snac, const char *content, xs_list **tag) /* use this same server */ def_srv = xs_dup(xs_dict_get(srv_config, "host")); - split = xs_regex_split(content, "(@[A-Za-z0-9_]+(@[A-Za-z0-9\\.-]+)?|&#[0-9]+;|#(_|[^[:punct:][:space:]])+)"); + split = xs_regex_split(content, "(@[A-Za-z0-9_-]+(@[A-Za-z0-9\\.-]+)?|&#[0-9]+;|#(_|[^[:punct:][:space:]])+)"); p = split; while (xs_list_iter(&p, &v)) { -- cgit From f59e1698b453021e4c2d49b106643a9f91b5d6f6 Mon Sep 17 00:00:00 2001 From: grunfink Date: Mon, 3 Aug 2026 04:54:50 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 2ff2a23..6c84379 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,6 +4,8 @@ The author of a replied post is no longer added to the CC: line, as it does not have much sense. +Fixed mentions for users with - in their identifiers. + Mastodon API: avatars and headers can be deleted (contributed by bm90). Updated Czech and Italian translations (contributed by pmjv and fidiben). -- cgit From ce79a905201b2a0f2a2989d52be594228eb1996d Mon Sep 17 00:00:00 2001 From: grunfink Date: Mon, 3 Aug 2026 04:55:02 +0200 Subject: Bumped version. --- snac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snac.h b/snac.h index cb4ae56..8a8e7a4 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2026 grunfink et al. / MIT license */ -#define VERSION "2.93" +#define VERSION "2.94-dev" #define USER_AGENT "snac/" VERSION -- cgit From 460b6cc5891d54281bba6bcedd542d38c0bc4814 Mon Sep 17 00:00:00 2001 From: grunfink Date: Mon, 3 Aug 2026 06:26:24 +0200 Subject: changed order of nodeinfo links. --- httpd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httpd.c b/httpd.c index acbe895..216c29c 100644 --- a/httpd.c +++ b/httpd.c @@ -285,8 +285,8 @@ int server_get_handler(xs_dict *req, const char *q_path, status = HTTP_STATUS_OK; *ctype = "application/json; charset=utf-8"; *body = xs_fmt("{\"links\":[" - "{\"rel\":\"http:/" "/nodeinfo.diaspora.software/ns/schema/2.0\",\"href\":\"%s/nodeinfo_2_0\"}," - "{\"rel\":\"http:/" "/nodeinfo.diaspora.software/ns/schema/2.1\",\"href\":\"%s/nodeinfo_2_1\"}" + "{\"rel\":\"http:/" "/nodeinfo.diaspora.software/ns/schema/2.1\",\"href\":\"%s/nodeinfo_2_1\"}," + "{\"rel\":\"http:/" "/nodeinfo.diaspora.software/ns/schema/2.0\",\"href\":\"%s/nodeinfo_2_0\"}" "]}", srv_baseurl, srv_baseurl); } -- cgit From ac711dd42297c24ee2342d316d4b78e3e88912af Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 5 Aug 2026 18:06:14 +0200 Subject: mastoapi: fixed bug while changing the sensitive content string. --- mastoapi.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/mastoapi.c b/mastoapi.c index 4139af1..d29ff01 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -4251,6 +4251,16 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path, msg = xs_dict_set(msg, "tag", tag); + if (xs_is_true(xs_dict_get(args, "sensitive"))) { + const char *spoiler_text = xs_dict_get(args, "spoiler_text"); + msg = xs_dict_set(msg, "summary", spoiler_text); + msg = xs_dict_set(msg, "sensitive", xs_stock(XSTYPE_TRUE)); + } + else { + msg = xs_dict_set(msg, "summary", ""); + msg = xs_dict_set(msg, "sensitive", xs_stock(XSTYPE_FALSE)); + } + /* overwrite object, not updating the indexes */ const char *id = xs_dict_get(msg, "id"); -- cgit From 4b8d4908d5b5228015bd2c84857b5409d22e110d Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 5 Aug 2026 18:08:10 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6c84379..ac6cd46 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,7 +6,7 @@ The author of a replied post is no longer added to the CC: line, as it does not Fixed mentions for users with - in their identifiers. -Mastodon API: avatars and headers can be deleted (contributed by bm90). +Mastodon API: fixed a bug that made changing the sensitive content string impossible, avatars and headers can be deleted (contributed by bm90). Updated Czech and Italian translations (contributed by pmjv and fidiben). -- cgit From 94b8199e273e60f233bef26fb6e8b854231792f0 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 5 Aug 2026 18:18:22 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index ac6cd46..2b31c1d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,6 +6,8 @@ The author of a replied post is no longer added to the CC: line, as it does not Fixed mentions for users with - in their identifiers. +If an actor marked as broken gets a post boosted (proving that is [back] functioning), it's unmarked as such. + Mastodon API: fixed a bug that made changing the sensitive content string impossible, avatars and headers can be deleted (contributed by bm90). Updated Czech and Italian translations (contributed by pmjv and fidiben). -- cgit From 7a9bb36d137a24920455a345a134d44adbe2fe66 Mon Sep 17 00:00:00 2001 From: grunfink Date: Thu, 6 Aug 2026 06:50:49 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 2b31c1d..9d2422e 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,7 +4,9 @@ The author of a replied post is no longer added to the CC: line, as it does not have much sense. -Fixed mentions for users with - in their identifiers. +Fixed mentions for users with hyphens in their identifiers. + +Unlisted replies to unknown posts were being skipped from the timeline, so don't do that. If an actor marked as broken gets a post boosted (proving that is [back] functioning), it's unmarked as such. -- cgit From 3391e38d9482d8a6a26cb61707adc70af986db9d Mon Sep 17 00:00:00 2001 From: grunfink Date: Thu, 6 Aug 2026 07:07:48 +0200 Subject: Don't try (and fail) to parse non-UTC dates in posts. I'll provide a real fix eventually. --- html.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html.c b/html.c index 48aecde..3ea9513 100644 --- a/html.c +++ b/html.c @@ -216,7 +216,7 @@ xs_str *html_date_label(snac *user, const char *date) /* check if a user has actually set a timezone */ if (user != NULL && xs_dict_get(user->config, "tz") != NULL && - (t = xs_parse_iso_date(date, 0)) != 0) { + xs_endswith(date, "Z") && (t = xs_parse_iso_date(date, 0)) != 0) { t += xs_tz_offset(user->tz); time_t today = time(NULL); -- cgit From ca60d65cb618da37e9c842706480d8b863c77055 Mon Sep 17 00:00:00 2001 From: grunfink Date: Thu, 6 Aug 2026 07:12:40 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 9d2422e..c2ad259 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -10,6 +10,8 @@ Unlisted replies to unknown posts were being skipped from the timeline, so don't If an actor marked as broken gets a post boosted (proving that is [back] functioning), it's unmarked as such. +Don't try (and fail) to parse non-UTC dates in posts (I'll provide a real fix eventually). + Mastodon API: fixed a bug that made changing the sensitive content string impossible, avatars and headers can be deleted (contributed by bm90). Updated Czech and Italian translations (contributed by pmjv and fidiben). -- cgit From b3af60308378aaf0e5f1cf38948fc560a4298f6b Mon Sep 17 00:00:00 2001 From: grunfink Date: Thu, 6 Aug 2026 19:00:20 +0200 Subject: Fixed redirection loop in the /users special case. --- httpd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/httpd.c b/httpd.c index 216c29c..5b503b9 100644 --- a/httpd.c +++ b/httpd.c @@ -504,9 +504,9 @@ void httpd_connection(FILE *f) q_path = xs_crop_i(q_path, strlen(p), 0); /* add users endpoint redirection mimic Mastodon behaviour */ - const char *users_endpoint = "/users"; + const char *users_endpoint = "/users/"; if (xs_startswith(q_path, users_endpoint)) { - q_path = xs_crop_i(q_path, strlen(users_endpoint), 0); + q_path = xs_crop_i(q_path, strlen(users_endpoint) - 1, 0); status = HTTP_STATUS_FOUND; } -- cgit From a88c90892a0e2a2ae30c24f922fd03d8e2576697 Mon Sep 17 00:00:00 2001 From: grunfink Date: Thu, 6 Aug 2026 19:31:11 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index c2ad259..e761b88 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -12,6 +12,8 @@ If an actor marked as broken gets a post boosted (proving that is [back] functio Don't try (and fail) to parse non-UTC dates in posts (I'll provide a real fix eventually). +Fixed a redirection loop in the `/users` alias. + Mastodon API: fixed a bug that made changing the sensitive content string impossible, avatars and headers can be deleted (contributed by bm90). Updated Czech and Italian translations (contributed by pmjv and fidiben). -- cgit From 8a9f61922dd48123bbabfc879172fa4d184dc9fc Mon Sep 17 00:00:00 2001 From: grunfink Date: Fri, 7 Aug 2026 09:14:08 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index e761b88..62a074d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,6 @@ # Release Notes -## UNRELEASED +## 2.94 The author of a replied post is no longer added to the CC: line, as it does not have much sense. -- cgit From 9666850e22392b249ed1e075f28ab30b6921587f Mon Sep 17 00:00:00 2001 From: grunfink Date: Fri, 7 Aug 2026 09:14:38 +0200 Subject: Version 2.94 RELEASED. --- snac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snac.h b/snac.h index 8a8e7a4..1644315 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2026 grunfink et al. / MIT license */ -#define VERSION "2.94-dev" +#define VERSION "2.94" #define USER_AGENT "snac/" VERSION -- cgit From ffb0b87a8246adb46831787928db9ac093e1df79 Mon Sep 17 00:00:00 2001 From: Wincent Balin Date: Mon, 17 Aug 2026 05:08:08 +0200 Subject: Updated Ukrainian translation --- po/uk.po | 98 ++++++++++++++++++++++++++++++++-------------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/po/uk.po b/po/uk.po index 622c4ba..7386fe7 100644 --- a/po/uk.po +++ b/po/uk.po @@ -768,192 +768,192 @@ msgstr "Часовий пояс:" #: html.c:708 msgid "Language:" -msgstr "" +msgstr "Мова:" #: html.c:1779 msgid "Notify webhook:" -msgstr "" +msgstr "Вебхук для сповіщення:" #: html.c:1785 msgid "http://example.com/webhook" -msgstr "" +msgstr "http://example.com/webhook" #: html.c:1846 msgid "Languages you usually post in:" -msgstr "" +msgstr "Типові мови для дописів:" #: html.c:1852 html.c:1861 msgid "en fr es de_AT" -msgstr "" +msgstr "en fr es de_AT" #: html.c:521 msgid "Visibility: " -msgstr "" +msgstr "Видимість: " msgid "Public" -msgstr "" +msgstr "Публічний допис" msgid "Unlisted" -msgstr "" +msgstr "Прихований допис" msgid "Followers-only" -msgstr "" +msgstr "Тільки для підписників" msgid "Direct Message" -msgstr "" +msgstr "Приватний допис" #: html.c:581 html.c:2728 html.c:2753 html.c:5701 msgid "EmojiUnreact" -msgstr "" +msgstr "Прибрати реакцію" #: html.c:581 html.c:1559 html.c:2728 html.c:2753 html.c:5713 msgid "EmojiReact" -msgstr "" +msgstr "Відреагувати" #: html.c:2251 msgid "Emoji react..." -msgstr "" +msgstr "Реакції..." #: html.c:2803 msgid "Emoji reactions: " -msgstr "" +msgstr "Реакції: " #: html.c:1957 msgid "Muted words..." -msgstr "" +msgstr "Приглушені слова..." #: html.c:1959 msgid "One word per line, partial matches count" -msgstr "" +msgstr "Одне слово в кожному рядку, враховуються часткові збіги" #: html.c:1980 msgid "Update muted words" -msgstr "" +msgstr "Оновити приглушені слова" #: html.c:2628 msgid "Muted: " -msgstr "" +msgstr "Приглушено: " #: html.c:4146 msgid "Contact's posts" -msgstr "" +msgstr "Дописи контакту" #: html.c:4215 msgid "More (x 10)..." -msgstr "" +msgstr "Більше (x 10)..." #: html.c:873 msgid "End in 3 days" -msgstr "" +msgstr "Закінчується через 3 дні" #: html.c:876 msgid "End in 1 year" -msgstr "" +msgstr "Закінчується через 1 рік" #: html.c:4303 msgid "Likes" -msgstr "" +msgstr "Вподобайки" #: html.c:4304 msgid "Emoji reacts" -msgstr "" +msgstr "Реакції" #: html.c:4305 msgid "Mentions" -msgstr "" +msgstr "Згадки" #: html.c:4306 msgid "Announces" -msgstr "" +msgstr "Оголошення" #: html.c:4307 msgid "Follows" -msgstr "" +msgstr "Підписки" #: html.c:4308 msgid "Unfollows" -msgstr "" +msgstr "Відписки" #: html.c:4309 msgid "Follow requests" -msgstr "" +msgstr "Запити на підписку" #: html.c:4310 msgid "Blocks" -msgstr "" +msgstr "Блокування" #: html.c:4311 msgid "Polls" -msgstr "" +msgstr "Опитування" #: html.c:4315 msgid "Save" -msgstr "" +msgstr "Зберегти" #: html.c:4323 msgid "Notifications filter..." -msgstr "" +msgstr "Фільтр сповіщень..." #: data.c:45 msgid "Jan" -msgstr "" +msgstr "січ" #: data.c:46 msgid "Feb" -msgstr "" +msgstr "лют" #: data.c:47 msgid "Mar" -msgstr "" +msgstr "бер" #: data.c:48 msgid "Apr" -msgstr "" +msgstr "кві" #: data.c:49 msgid "May" -msgstr "" +msgstr "тра" #: data.c:50 msgid "Jun" -msgstr "" +msgstr "чер" #: data.c:51 msgid "Jul" -msgstr "" +msgstr "лип" #: data.c:52 msgid "Aug" -msgstr "" +msgstr "сер" #: data.c:53 msgid "Sep" -msgstr "" +msgstr "вер" #: data.c:54 msgid "Oct" -msgstr "" +msgstr "жов" #: data.c:55 msgid "Nov" -msgstr "" +msgstr "лис" #: data.c:56 msgid "Dec" -msgstr "" +msgstr "гру" #: html.c:1808 msgid "Show unlisted posts in the public page and RSS feed" -msgstr "" +msgstr "Показати приховані дописи на публічній сторінці та в RSS-каналі" #: html.c:1855 msgid "Don't show posts written in these languages:" -msgstr "" +msgstr "Не показувати дописи, написані цими мовами:" #: html.c:3572 html.c:5108 msgid "Liked, boosted or reacted posts" -msgstr "" +msgstr "Дописи з вподобайками, поширеннями та реакціями" #: html.c:3573 msgid "admirations" -msgstr "" +msgstr "захоплення" -- cgit From 5cb0853c89645e5709061f30e69da73ee9ed50f3 Mon Sep 17 00:00:00 2001 From: koru Date: Mon, 17 Aug 2026 20:21:26 +0000 Subject: Update Russian translation Signed-off-by: koru --- po/ru.po | 100 +++++++++++++++++++++++++++++++-------------------------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/po/ru.po b/po/ru.po index 0c6445e..05f3ff6 100644 --- a/po/ru.po +++ b/po/ru.po @@ -3,7 +3,7 @@ msgid "" msgstr "" "Project-Id-Version: snac\n" -"Last-Translator: grunfink\n" +"Last-Translator: koru\n" "Language: ru\n" "Content-Type: text/plain; charset=UTF-8\n" "Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && " @@ -768,192 +768,192 @@ msgstr "Часовой пояс:" #: html.c:708 msgid "Language:" -msgstr "" +msgstr "Язык:" #: html.c:1779 msgid "Notify webhook:" -msgstr "" +msgstr "Вебхук для уведомлений:" #: html.c:1785 msgid "http://example.com/webhook" -msgstr "" +msgstr "http://example.com/webhook" #: html.c:1846 msgid "Languages you usually post in:" -msgstr "" +msgstr "Языки, на которых вы обычно пишете:" #: html.c:1852 html.c:1861 msgid "en fr es de_AT" -msgstr "" +msgstr "en fr es de_AT" #: html.c:521 msgid "Visibility: " -msgstr "" +msgstr "Видимость: " msgid "Public" -msgstr "" +msgstr "Публичное" msgid "Unlisted" -msgstr "" +msgstr "Скрытое" msgid "Followers-only" -msgstr "" +msgstr "Только для подписчиков" msgid "Direct Message" -msgstr "" +msgstr "Личное сообщение" #: html.c:581 html.c:2728 html.c:2753 html.c:5701 msgid "EmojiUnreact" -msgstr "" +msgstr "Снять эмодзи-реакцию" #: html.c:581 html.c:1559 html.c:2728 html.c:2753 html.c:5713 msgid "EmojiReact" -msgstr "" +msgstr "Эмодзи-реакция" #: html.c:2251 msgid "Emoji react..." -msgstr "" +msgstr "Эмодзи-реакция..." #: html.c:2803 msgid "Emoji reactions: " -msgstr "" +msgstr "Эмодзи-реакции: " #: html.c:1957 msgid "Muted words..." -msgstr "" +msgstr "Скрытые слова..." #: html.c:1959 msgid "One word per line, partial matches count" -msgstr "" +msgstr "По одному слову на строку, учитываются частичные совпадения" #: html.c:1980 msgid "Update muted words" -msgstr "" +msgstr "Обновить скрытые слова" #: html.c:2628 msgid "Muted: " -msgstr "" +msgstr "Скрыто: " #: html.c:4146 msgid "Contact's posts" -msgstr "" +msgstr "Сообщения контакта" #: html.c:4215 msgid "More (x 10)..." -msgstr "" +msgstr "Ещё (x 10)..." #: html.c:873 msgid "End in 3 days" -msgstr "" +msgstr "Заканчивается через 3 дня" #: html.c:876 msgid "End in 1 year" -msgstr "" +msgstr "Заканчивается через 1 год" #: html.c:4303 msgid "Likes" -msgstr "" +msgstr "Лайки" #: html.c:4304 msgid "Emoji reacts" -msgstr "" +msgstr "Эмодзи-реакции" #: html.c:4305 msgid "Mentions" -msgstr "" +msgstr "Упоминания" #: html.c:4306 msgid "Announces" -msgstr "" +msgstr "Продвижения" #: html.c:4307 msgid "Follows" -msgstr "" +msgstr "Подписки" #: html.c:4308 msgid "Unfollows" -msgstr "" +msgstr "Отписки" #: html.c:4309 msgid "Follow requests" -msgstr "" +msgstr "Запросы на подписку" #: html.c:4310 msgid "Blocks" -msgstr "" +msgstr "Блокировки" #: html.c:4311 msgid "Polls" -msgstr "" +msgstr "Опросы" #: html.c:4315 msgid "Save" -msgstr "" +msgstr "Сохранить" #: html.c:4323 msgid "Notifications filter..." -msgstr "" +msgstr "Фильтр уведомлений..." #: data.c:45 msgid "Jan" -msgstr "" +msgstr "Янв" #: data.c:46 msgid "Feb" -msgstr "" +msgstr "Фев" #: data.c:47 msgid "Mar" -msgstr "" +msgstr "Мар" #: data.c:48 msgid "Apr" -msgstr "" +msgstr "Апр" #: data.c:49 msgid "May" -msgstr "" +msgstr "Май" #: data.c:50 msgid "Jun" -msgstr "" +msgstr "Июн" #: data.c:51 msgid "Jul" -msgstr "" +msgstr "Июл" #: data.c:52 msgid "Aug" -msgstr "" +msgstr "Авг" #: data.c:53 msgid "Sep" -msgstr "" +msgstr "Сен" #: data.c:54 msgid "Oct" -msgstr "" +msgstr "Окт" #: data.c:55 msgid "Nov" -msgstr "" +msgstr "Ноя" #: data.c:56 msgid "Dec" -msgstr "" +msgstr "Дек" #: html.c:1808 msgid "Show unlisted posts in the public page and RSS feed" -msgstr "" +msgstr "Показывать скрытые сообщения на публичной странице и в RSS-ленте" #: html.c:1855 msgid "Don't show posts written in these languages:" -msgstr "" +msgstr "Не показывать сообщения на этих языках:" #: html.c:3572 html.c:5108 msgid "Liked, boosted or reacted posts" -msgstr "" +msgstr "Понравившиеся, продвинутые сообщения или сообщения с реакцией" #: html.c:3573 msgid "admirations" -msgstr "" +msgstr "симпатии" -- cgit From 50e2fc7b8ad76033bbe7b0b2da0dbe4a8744d633 Mon Sep 17 00:00:00 2001 From: nicole mikołajczyk Date: Tue, 18 Aug 2026 14:41:35 +0200 Subject: don't return reactions count as string MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: nicole mikołajczyk --- mastoapi.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mastoapi.c b/mastoapi.c index d29ff01..027ec42 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -1186,13 +1186,13 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) int count = 1; if (contentl) { - count = atoi(xs_list_get(contentl, 0)) + 1; + count = xs_number_get(xs_list_get(contentl, 0)) + 1; if (strncmp(xs_list_get(contentl, 1), xs_stock(XSTYPE_TRUE), 1) == 0) me = xs_stock(XSTYPE_TRUE); } xs *fl = xs_list_new(); - xs *c1 = xs_fmt("%d", count); + xs *c1 = xs_number_new(count); fl = xs_list_append(fl, c1, me); sfrl = xs_dict_append(sfrl, content, fl); } -- cgit From 52adad8882d1504e8a8422ddb128b4976b853113 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 18 Aug 2026 17:56:13 +0200 Subject: Don't propagate 4xx error statuses from proxied media. --- html.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/html.c b/html.c index 3ea9513..ee68327 100644 --- a/html.c +++ b/html.c @@ -5328,6 +5328,9 @@ int html_get_handler(const xs_dict *req, const char *q_path, } } } + else + if (status >= 400 && status <= 499) + status = HTTP_STATUS_NOT_FOUND; snac_debug(&snac, 1, xs_fmt("Proxy for %s %d", url, status)); } -- cgit From ed9571af3b970a624f22e5158616d1dde545beb4 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 18 Aug 2026 18:44:29 +0200 Subject: Moved proxy status change after the log message. --- html.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/html.c b/html.c index ee68327..a93435e 100644 --- a/html.c +++ b/html.c @@ -5328,11 +5328,11 @@ int html_get_handler(const xs_dict *req, const char *q_path, } } } - else - if (status >= 400 && status <= 499) - status = HTTP_STATUS_NOT_FOUND; snac_debug(&snac, 1, xs_fmt("Proxy for %s %d", url, status)); + + if (status >= 400 && status <= 499) + status = HTTP_STATUS_NOT_FOUND; } } else -- cgit From 6fed0f6823bd6815d01698a1c682c2759160262b Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 18 Aug 2026 19:23:56 +0200 Subject: More proxy test fixes. --- html.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/html.c b/html.c index a93435e..8cb2370 100644 --- a/html.c +++ b/html.c @@ -5293,6 +5293,10 @@ int html_get_handler(const xs_dict *req, const char *q_path, /* pick the raw path (including optional ? arguments) */ const char *raw_path = xs_dict_get(req, "raw_path"); + /* skip the prefix and user part */ + xs *s = xs_fmt("%s/%s/", xs_dict_get(srv_config, "prefix"), snac.uid); + raw_path += strlen(s); + /* skip to where the proxy/ string starts */ raw_path += xs_str_in(raw_path, proxy_prefix); -- cgit From 84aad32421f82fa7993f0a02cb0bd48495b9ff19 Mon Sep 17 00:00:00 2001 From: grunfink Date: Thu, 20 Aug 2026 16:36:52 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 62a074d..77e5709 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,11 @@ # Release Notes +## UNRELEASED + +Added some fixes to media proxy code. + +Updated Ukrainian and Russian translations (contributed by wincentbalin and koru). + ## 2.94 The author of a replied post is no longer added to the CC: line, as it does not have much sense. -- cgit From 4f27bb07085ab90787870a4422176f29823a798d Mon Sep 17 00:00:00 2001 From: grunfink Date: Thu, 20 Aug 2026 17:39:46 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 77e5709..f9c433d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,6 +4,8 @@ Added some fixes to media proxy code. +Mastodon API: Don't return reactions count as string (contributed by mkljczk). + Updated Ukrainian and Russian translations (contributed by wincentbalin and koru). ## 2.94 -- cgit From 99544bf77226f2889bb1969eea440b2c8716b814 Mon Sep 17 00:00:00 2001 From: grunfink Date: Fri, 21 Aug 2026 17:14:02 +0200 Subject: New function xs_html_xtag(). --- xs.h | 9 ++++++--- xs_html.h | 17 +++++++++++++++++ xs_url.h | 2 +- xs_version.h | 2 +- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/xs.h b/xs.h index 8d8dd9c..0727dd2 100644 --- a/xs.h +++ b/xs.h @@ -135,6 +135,7 @@ xs_dict *xs_dict_set_path_sep(xs_dict *dict, const char *path, const xs_val *val #define xs_dict_set_path(dict, path, value) xs_dict_set_path_sep(dict, path, value, ".") xs_val *xs_val_new(xstype t); +xs_number *_xs_number_new(const char *str); xs_number *xs_number_new(double f); xs_number *xs_number_new_l(long l); double xs_number_get(const xs_number *v); @@ -164,6 +165,7 @@ uint64_t xs_hash64_func(const char *data, int size); #define xs_is_true(v) (xs_type((v)) == XSTYPE_TRUE) #define xs_is_false(v) (xs_type((v)) == XSTYPE_FALSE) +#define xs_bool(v) xs_stock((v) ? XSTYPE_TRUE : XSTYPE_FALSE) #define xs_not(v) xs_stock(xs_is_true((v)) ? XSTYPE_FALSE : XSTYPE_TRUE) #define xs_is_string(v) (xs_type((v)) == XSTYPE_STRING) #define xs_is_list(v) (xs_type((v)) == XSTYPE_LIST) @@ -706,7 +708,7 @@ xs_str *xs_tolower_i(xs_str *str) int n; for (n = 0; str[n]; n++) - str[n] = tolower(str[n]); + str[n] = tolower((int)str[n]); return str; } @@ -720,7 +722,7 @@ xs_str *xs_toupper_i(xs_str *str) int n; for (n = 0; str[n]; n++) - str[n] = toupper(str[n]); + str[n] = toupper((int)str[n]); return str; } @@ -1559,7 +1561,8 @@ xs_data *xs_data_new(const void *data, int size) _xs_put_size(v, total_size); - memcpy(&v[1 + _XS_TYPE_SIZE], data, size); + if (data) + memcpy(&v[1 + _XS_TYPE_SIZE], data, size); return v; } diff --git a/xs_html.h b/xs_html.h index ab77499..b3c7224 100644 --- a/xs_html.h +++ b/xs_html.h @@ -24,6 +24,9 @@ xs_html *_xs_html_sctag(const char *tag, xs_html *var[]); xs_html *_xs_html_container(xs_html *var[]); #define xs_html_container(...) _xs_html_container((xs_html *[]) { __VA_ARGS__, NULL }) +xs_html *_xs_html_xtag(const char *tag, xs_html *var[]); +#define xs_html_xtag(tag, ...) _xs_html_xtag(tag, (xs_html *[]) { __VA_ARGS__, NULL }) + void xs_html_render_f(xs_html *h, FILE *f); xs_str *xs_html_render_s(xs_html *tag, const char *prefix); #define xs_html_render(tag) xs_html_render_s(tag, NULL) @@ -187,6 +190,20 @@ xs_html *_xs_html_container(xs_html *var[]) } +xs_html *_xs_html_xtag(const char *tag, xs_html *var[]) +{ + if (xs_is_null(tag)) + return _xs_html_container(var); + else + if (xs_endswith(tag, "/")) { + xs *tag2 = xs_crop_i(xs_dup(tag), 0, -1); + return _xs_html_sctag(tag2, var); + } + + return _xs_html_tag(tag, var); +} + + void xs_html_render_f(xs_html *h, FILE *f) /* renders the tag and its subtags into a file */ { diff --git a/xs_url.h b/xs_url.h index e25c73b..4986181 100644 --- a/xs_url.h +++ b/xs_url.h @@ -118,7 +118,7 @@ xs_str *xs_url_enc(const char *str) xs_str *s = xs_str_new(NULL); while (*str) { - if (isalnum(*str) || strchr("-._~", *str)) { + if (isalnum((int)*str) || strchr("-._~", *str)) { s = xs_append_m(s, str, 1); } else { diff --git a/xs_version.h b/xs_version.h index 500b156..afc995f 100644 --- a/xs_version.h +++ b/xs_version.h @@ -1 +1 @@ -/* 4110cac45d3d8a6c86b6552c59ec9c73d7a3a4c0 2026-06-17T11:46:41+02:00 */ +/* 243a85157ca228412b681c8c4a57aab164d50b93 2026-08-08T07:08:26+02:00 */ -- cgit From 6ae12b03d5566b9119c78d79a96df7ac12b2c636 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 26 Aug 2026 07:24:25 +0200 Subject: In the people page, strip absurdly long location coordinates due to floating point inaccuracies. --- html.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/html.c b/html.c index 8cb2370..3f35040 100644 --- a/html.c +++ b/html.c @@ -3899,7 +3899,10 @@ xs_html *html_people_list(snac *user, xs_list *list, const char *header, const c const char *lo = xs_is_string(longitude) ? longitude : xs_number_str(longitude); if (xs_is_string(la) && xs_is_string(lo)) { - xs *label = xs_fmt("%s,%s", la, lo); + xs *sla = xs_utf8_crop_i(xs_dup(la), 0, 7); + xs *slo = xs_utf8_crop_i(xs_dup(lo), 0, 7); + + xs *label = xs_fmt("%s,%s", sla, slo); xs *url = xs_fmt("https://openstreetmap.org/search?query=%s,%s", la, lo); xs_html_add(snac_post, -- cgit From 71a83e6578f910c3baa251e4128208b201ce9e45 Mon Sep 17 00:00:00 2001 From: default Date: Wed, 26 Aug 2026 07:59:23 +0200 Subject: Use snac_op in actor_failure(). --- activitypub.c | 10 +++++----- data.c | 11 +++++++---- html.c | 2 +- snac.h | 10 +++++++++- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/activitypub.c b/activitypub.c index 92eb7f5..4de0be7 100644 --- a/activitypub.c +++ b/activitypub.c @@ -3054,7 +3054,7 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) followed_hashtag_distribute(a_msg); /* actor is [back] alive */ - actor_failure(who, 2); + actor_failure(who, OP_DEL); do_notify = 1; } @@ -3461,7 +3461,7 @@ void process_user_queue_item(snac *user, xs_dict *q_item) const char *actor = xs_dict_get(q_item, "actor"); double mtime = object_mtime(actor); - if (actor_failure(actor, 0) == -1) { + if (actor_failure(actor, OP_CHECK) == -1) { /* actor is broken beyond repair */ snac_debug(user, 1, xs_fmt("actor_refresh skipped broken actor %s", actor)); } @@ -3478,16 +3478,16 @@ void process_user_queue_item(snac *user, xs_dict *q_item) actor_add(actor, actor_o); /* mark actor and instance as working */ - actor_failure(actor, 2); + actor_failure(actor, OP_DEL); instance_failure(actor, 2); } else { if (status == HTTP_STATUS_GONE || status == HTTP_STATUS_NOT_FOUND) { - actor_failure(actor, 1); + actor_failure(actor, OP_ADD); snac_log(user, xs_fmt("actor_refresh marking actor %s as broken %d", actor, status)); } else { - actor_failure(actor, 2); + actor_failure(actor, OP_DEL); object_touch(actor); } } diff --git a/data.c b/data.c index 806938c..7a8f884 100644 --- a/data.c +++ b/data.c @@ -3233,7 +3233,7 @@ xs_list *content_search(snac *user, const char *regex, } -int actor_failure(const char *actor, int op) +int actor_failure(const char *actor, snac_op op) /* actor failure maintenance */ { int ret = 0; @@ -3242,13 +3242,13 @@ int actor_failure(const char *actor, int op) xs *fn = xs_fmt("%s/failure/%s", srv_basedir, md5); switch (op) { - case 0: /** check **/ + case OP_CHECK: /** check **/ if (mtime(fn)) ret = -1; break; - case 1: /** register a failure **/ + case OP_ADD: /** register a failure **/ if (mtime(fn) == 0.0) { FILE *f; @@ -3261,11 +3261,14 @@ int actor_failure(const char *actor, int op) break; - case 2: /** clear a failure **/ + case OP_DEL: /** clear a failure **/ /* called whenever a message comes from this instance */ unlink(fn); break; + + default: + break; } return ret; diff --git a/html.c b/html.c index 3f35040..f18e233 100644 --- a/html.c +++ b/html.c @@ -362,7 +362,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, xs_html_raw("💔"))); } - if (actor_failure(actor_id, 0) == -1) { + if (actor_failure(actor_id, OP_CHECK) == -1) { xs_html_add(actor_icon, xs_html_text(" "), xs_html_tag("span", diff --git a/snac.h b/snac.h index 1644315..602fa0c 100644 --- a/snac.h +++ b/snac.h @@ -79,6 +79,14 @@ typedef struct { enum { THST_STOP, THST_WAIT, THST_IN, THST_QUEUE } th_state[MAX_THREADS]; } srv_state; +typedef enum { + OP_CHECK = 0, + OP_ADD = 1, + OP_DEL = 2, + OP_LIST = 3, + OP_FIND = 4 +} snac_op; + extern srv_state *p_state; enum { @@ -300,7 +308,7 @@ int content_match(const char *file, const xs_dict *msg); xs_list *content_search(snac *user, const char *regex, int priv, int skip, int show, int max_secs, int *timeout); -int actor_failure(const char *actor, int op); +int actor_failure(const char *actor, snac_op op); int instance_failure(const char *url, int op); int grave(const char *objid, int op); -- cgit From 48aba53ddbca75bfdbda7d224c6d2c315fcd282a Mon Sep 17 00:00:00 2001 From: default Date: Wed, 26 Aug 2026 08:02:41 +0200 Subject: Use snac_op in instance_failure(). --- activitypub.c | 10 +++++----- data.c | 11 +++++++---- rss.c | 2 +- snac.h | 2 +- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/activitypub.c b/activitypub.c index 4de0be7..8155fe4 100644 --- a/activitypub.c +++ b/activitypub.c @@ -2594,7 +2594,7 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) } /* this instance is alive */ - instance_failure(actor, 2); + instance_failure(actor, OP_DEL); /* question votes may not have a type */ if (xs_is_null(type)) @@ -3029,7 +3029,7 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) const char *who = get_atto(a_msg); /* got the admired object: instance is [back] online */ - instance_failure(object, 2); + instance_failure(object, OP_DEL); if (who && !is_muted(snac, who)) { /* bring the actor */ @@ -3479,7 +3479,7 @@ void process_user_queue_item(snac *user, xs_dict *q_item) /* mark actor and instance as working */ actor_failure(actor, OP_DEL); - instance_failure(actor, 2); + instance_failure(actor, OP_DEL); } else { if (status == HTTP_STATUS_GONE || status == HTTP_STATUS_NOT_FOUND) { @@ -3604,7 +3604,7 @@ void process_queue_item(xs_dict *q_item) return; } - if (instance_failure(inbox, 0)) { + if (instance_failure(inbox, OP_CHECK)) { srv_debug(1, xs_fmt("output message error: too many failures for instance %s", inbox)); return; } @@ -3621,7 +3621,7 @@ void process_queue_item(xs_dict *q_item) status = send_to_inbox_raw(keyid, seckey, inbox, msg, &payload, &p_size, timeout); /* register or clear a value for this instance */ - instance_failure(inbox, valid_status(status) ? 2 : 1); + instance_failure(inbox, valid_status(status) ? OP_DEL : OP_ADD); if (payload) { if (p_size > 1024) { diff --git a/data.c b/data.c index 7a8f884..3736bc9 100644 --- a/data.c +++ b/data.c @@ -3275,7 +3275,7 @@ int actor_failure(const char *actor, snac_op op) } -int instance_failure(const char *url, int op) +int instance_failure(const char *url, snac_op op) /* do some checks and accounting on instance failures */ { int ret = 0; @@ -3290,7 +3290,7 @@ int instance_failure(const char *url, int op) xs *fn = xs_fmt("%s/failure/%s", srv_basedir, md5); switch (op) { - case 0: /** check **/ + case OP_CHECK: /** check **/ if ((mt = mtime(fn)) != 0.0) { /* grace time */ double seconds_failing = xs_number_get(xs_dict_get_def(srv_config, "max_failing_days", "15")) @@ -3302,7 +3302,7 @@ int instance_failure(const char *url, int op) break; - case 1: /** register a failure **/ + case OP_ADD: /** register a failure **/ if (mtime(fn) == 0.0) { FILE *f; @@ -3315,11 +3315,14 @@ int instance_failure(const char *url, int op) break; - case 2: /** clear a failure **/ + case OP_DEL: /** clear a failure **/ /* called whenever a message comes from this instance */ unlink(fn); break; + + default: + break; } return ret; diff --git a/rss.c b/rss.c index 9b4ae65..5ec85cb 100644 --- a/rss.c +++ b/rss.c @@ -282,7 +282,7 @@ void rss_to_timeline(snac *user, const char *url) continue; /* the instance is [back] online */ - instance_failure(id, 2); + instance_failure(id, OP_DEL); if (timeline_here(user, id)) { snac_debug(user, 1, xs_fmt("RSS entry already in timeline (id) %s", id)); diff --git a/snac.h b/snac.h index 602fa0c..d21c7ad 100644 --- a/snac.h +++ b/snac.h @@ -309,7 +309,7 @@ xs_list *content_search(snac *user, const char *regex, int priv, int skip, int show, int max_secs, int *timeout); int actor_failure(const char *actor, snac_op op); -int instance_failure(const char *url, int op); +int instance_failure(const char *url, snac_op op); int grave(const char *objid, int op); -- cgit From d7fc3d950d4654a3291066fed667f1b7d48e70bb Mon Sep 17 00:00:00 2001 From: default Date: Wed, 26 Aug 2026 08:05:16 +0200 Subject: Use snac_op in grave(). --- activitypub.c | 4 ++-- data.c | 11 +++++++---- html.c | 2 +- snac.h | 2 +- utils.c | 4 ++-- 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/activitypub.c b/activitypub.c index 8155fe4..df319f0 100644 --- a/activitypub.c +++ b/activitypub.c @@ -3951,7 +3951,7 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, uid = xs_list_get(l, 1); if (!user_open(&snac, uid)) { /* invalid user */ - status = grave(uid, 0) ? HTTP_STATUS_GONE : HTTP_STATUS_NOT_FOUND; + status = grave(uid, OP_CHECK) ? HTTP_STATUS_GONE : HTTP_STATUS_NOT_FOUND; srv_debug(1, xs_fmt("activitypub_get_handler bad user %s %d", uid, status)); return status; } @@ -4151,7 +4151,7 @@ int activitypub_post_handler(const xs_dict *req, const char *q_path, const char *uid = xs_list_get(l, 1); if (!user_open(&snac, uid)) { /* invalid user */ - status = grave(uid, 0) ? HTTP_STATUS_GONE : HTTP_STATUS_NOT_FOUND; + status = grave(uid, OP_CHECK) ? HTTP_STATUS_GONE : HTTP_STATUS_NOT_FOUND; srv_debug(1, xs_fmt("activitypub_post_handler bad user %s %d", uid, status)); return status; } diff --git a/data.c b/data.c index 3736bc9..333ed31 100644 --- a/data.c +++ b/data.c @@ -3329,7 +3329,7 @@ int instance_failure(const char *url, snac_op op) } -int grave(const char *objid, int op) +int grave(const char *objid, snac_op op) /* the graveyeard of deleted objects */ { int ret = 0; @@ -3339,11 +3339,11 @@ int grave(const char *objid, int op) FILE *f; switch (op) { - case 0: /** check **/ + case OP_CHECK: /** check **/ ret = mtime(fn) > 0.0 ? 1 : 0; break; - case 1: /** add **/ + case OP_ADD: /** add **/ mkdirx(dir); if ((f = fopen(fn, "w")) != NULL) { @@ -3353,9 +3353,12 @@ int grave(const char *objid, int op) break; - case 2: /** del **/ + case OP_DEL: /** del **/ unlink(fn); break; + + default: + break; } return ret; diff --git a/html.c b/html.c index f18e233..8f177cc 100644 --- a/html.c +++ b/html.c @@ -4717,7 +4717,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, if (!uid || !user_open(&snac, uid)) { /* invalid user */ - status = grave(uid, 0) ? HTTP_STATUS_GONE : HTTP_STATUS_NOT_FOUND; + status = grave(uid, OP_CHECK) ? HTTP_STATUS_GONE : HTTP_STATUS_NOT_FOUND; srv_debug(2, xs_fmt("html_get_handler bad user '%s' %d", xs_or(uid, "(null)"), status)); return status; } diff --git a/snac.h b/snac.h index d21c7ad..84223e6 100644 --- a/snac.h +++ b/snac.h @@ -311,7 +311,7 @@ xs_list *content_search(snac *user, const char *regex, int actor_failure(const char *actor, snac_op op); int instance_failure(const char *url, snac_op op); -int grave(const char *objid, int op); +int grave(const char *objid, snac_op op); void enqueue_input(snac *snac, const xs_dict *msg, const xs_dict *req, int retries); void enqueue_shared_input(const xs_dict *msg, const xs_dict *req, int retries); diff --git a/utils.c b/utils.c index 6941081..c1855b2 100644 --- a/utils.c +++ b/utils.c @@ -372,7 +372,7 @@ int adduser(const char *uid) } /* remove the grave */ - grave(uid, 2); + grave(uid, OP_DEL); printf("\nUser password is %s\n", pwd); @@ -467,7 +467,7 @@ int deluser(snac *user) } } - grave(user->uid, 1); + grave(user->uid, OP_ADD); rm_rf(user->basedir); -- cgit From 120308074813de49fea6728259b5d78a907ea9b2 Mon Sep 17 00:00:00 2001 From: default Date: Wed, 26 Aug 2026 08:09:07 +0200 Subject: Use snac_op in list_members(). --- data.c | 8 ++++---- main.c | 6 +++--- mastoapi.c | 8 ++++---- snac.h | 2 +- utils.c | 4 ++-- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/data.c b/data.c index 333ed31..8be649d 100644 --- a/data.c +++ b/data.c @@ -2621,7 +2621,7 @@ xs_list *list_timeline(snac *user, const char *list, int skip, int show) } -xs_val *list_members(snac *user, const char *list, const char *actor_md5, int op) +xs_val *list_members(snac *user, const char *list, const char *actor_md5, snac_op op) /* list member management */ { xs_val *l = NULL; @@ -2635,12 +2635,12 @@ xs_val *list_members(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 members **/ + case OP_LIST: /** list members **/ l = index_list(fn, XS_ALL); break; - case 1: /** append actor to list **/ + case OP_ADD: /** append actor to list **/ if (xs_is_string(actor_md5) && xs_is_hex(actor_md5)) { if (!index_in_md5(fn, actor_md5)) index_add_md5(fn, actor_md5); @@ -2648,7 +2648,7 @@ xs_val *list_members(snac *user, const char *list, const char *actor_md5, int op break; - case 2: /** delete actor from list **/ + case OP_DEL: /** delete actor from list **/ if (xs_is_string(actor_md5) && xs_is_hex(actor_md5)) index_del_md5(fn, actor_md5); diff --git a/main.c b/main.c index 9170761..dd224de 100644 --- a/main.c +++ b/main.c @@ -392,7 +392,7 @@ int main(int argc, char *argv[]) xs *lid = list_maint(&snac, url, 4); if (lid != NULL) { - xs *lcont = list_members(&snac, lid, NULL, 0); + xs *lcont = list_members(&snac, lid, NULL, OP_LIST); const char *md5; xs_list_foreach(lcont, md5) { @@ -448,7 +448,7 @@ int main(int argc, char *argv[]) if (valid_status(webfinger_request(account, &actor, &uid))) { xs *md5 = xs_md5_hex(actor, strlen(actor)); - list_members(&snac, lid, md5, 1); + list_members(&snac, lid, md5, OP_ADD); printf("Actor %s (%s) added to list '%s' (%s)\n", actor, uid, url, lid); } else @@ -471,7 +471,7 @@ int main(int argc, char *argv[]) if (lid != NULL) { xs *md5 = xs_md5_hex(account, strlen(account)); - list_members(&snac, lid, md5, 2); + list_members(&snac, lid, md5, OP_DEL); printf("Actor %s deleted from list '%s' (%s)\n", account, url, lid); } else diff --git a/mastoapi.c b/mastoapi.c index 027ec42..571d7cb 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -1795,7 +1795,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_members(user, list_id, NULL, 0); + xs *users = list_members(user, list_id, NULL, OP_LIST); if (xs_list_in(users, actor_md5) == -1) continue; } @@ -2527,7 +2527,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_members(&snac1, p, NULL, 0); + xs *actors = list_members(&snac1, p, NULL, OP_LIST); xs *out = xs_list_new(); int c = 0; const char *v; @@ -3843,7 +3843,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, const char *v; while (xs_list_next(accts, &v, &c)) { - list_members(&snac, id, v, 1); + list_members(&snac, id, v, OP_ADD); } xs *out = xs_dict_new(); @@ -4052,7 +4052,7 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path, const char *v; while (xs_list_next(accts, &v, &c)) { - list_members(&snac, p, v, 2); + list_members(&snac, p, v, OP_DEL); } } else { diff --git a/snac.h b/snac.h index 84223e6..0406a2e 100644 --- a/snac.h +++ b/snac.h @@ -262,7 +262,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_members(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, snac_op 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 c1855b2..6d202d8 100644 --- a/utils.c +++ b/utils.c @@ -705,7 +705,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_members(user, lid, NULL, 0); + xs *actors = list_members(user, lid, NULL, OP_LIST); const char *md5; xs_list_foreach(actors, md5) { @@ -935,7 +935,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_members(user, list_id, actor_md5, 1); + list_members(user, list_id, actor_md5, OP_ADD); snac_log(user, xs_fmt("Added %s to list %s", url, lname)); if (!following_check(user, url)) { -- cgit From 319d5cae0e632e1891f442194afe5e36aa48e6c4 Mon Sep 17 00:00:00 2001 From: default Date: Wed, 26 Aug 2026 08:17:03 +0200 Subject: Use snac_op in list_maint(). --- data.c | 19 +++++++++++-------- html.c | 4 ++-- main.c | 16 ++++++++-------- mastoapi.c | 8 ++++---- snac.h | 5 +++-- utils.c | 4 ++-- 6 files changed, 30 insertions(+), 26 deletions(-) diff --git a/data.c b/data.c index 8be649d..6273800 100644 --- a/data.c +++ b/data.c @@ -2474,13 +2474,13 @@ xs_list *tag_search(const char *tag, int skip, int show) /** lists **/ -xs_val *list_maint(snac *user, const char *list, int op) +xs_val *list_maint(snac *user, const char *list, snac_op op) /* list maintenance */ { xs_val *l = NULL; switch (op) { - case 0: /** list of lists **/ + case OP_LIST: /** list of lists **/ { FILE *f; xs *spec = xs_fmt("%s/list/" "*.id", user->basedir); @@ -2509,9 +2509,9 @@ xs_val *list_maint(snac *user, const char *list, int op) break; - case 1: /** create new list (list is the name) **/ + case OP_ADD: /** create new list (list is the name) **/ { - xs *lol = list_maint(user, NULL, 0); + xs *lol = list_maint(user, NULL, OP_LIST); int c = 0; const xs_list *v; int add = 1; @@ -2546,7 +2546,7 @@ xs_val *list_maint(snac *user, const char *list, int op) break; - case 2: /** delete list (list is the id) **/ + case OP_DEL: /** delete list (list is the id) **/ { if (xs_is_hex(list)) { xs *fn = xs_fmt("%s/list/%s.id", user->basedir, list); @@ -2565,7 +2565,7 @@ xs_val *list_maint(snac *user, const char *list, int op) break; - case 3: /** get list name **/ + case OP_ID: /** get list name **/ if (xs_is_hex(list)) { FILE *f; xs *fn = xs_fmt("%s/list/%s.id", user->basedir, list); @@ -2578,9 +2578,9 @@ xs_val *list_maint(snac *user, const char *list, int op) break; - case 4: /** find list id by name **/ + case OP_FIND: /** find list id by name **/ if (xs_is_string(list)) { - xs *lol = list_maint(user, NULL, 0); + xs *lol = list_maint(user, NULL, OP_LIST); const xs_list *li; xs_list_foreach(lol, li) { @@ -2590,6 +2590,9 @@ xs_val *list_maint(snac *user, const char *list, int op) } } } + + default: + break; } return l; diff --git a/html.c b/html.c index 8f177cc..503539c 100644 --- a/html.c +++ b/html.c @@ -3534,7 +3534,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, xs_html_attr("class", "snac-list-of-lists")); xs_html_add(body, lol); - xs *lists = list_maint(user, NULL, 0); /* get list of lists */ + xs *lists = list_maint(user, NULL, OP_LIST); /* get list of lists */ int ct = 0; const char *v; @@ -5179,7 +5179,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs *ttl = timeline_top_level(&snac, list); xs *base = xs_fmt("/list/%s", lid); - xs *name = list_maint(&snac, lid, 3); + xs *name = list_maint(&snac, lid, OP_ID); xs *title = xs_fmt(L("Showing timeline for list '%s'"), name); *body = html_timeline(&snac, ttl, 0, skip, show, diff --git a/main.c b/main.c index dd224de..433bfc2 100644 --- a/main.c +++ b/main.c @@ -332,7 +332,7 @@ int main(int argc, char *argv[]) } if (strcmp(cmd, "lists") == 0) { /** **/ - xs *lol = list_maint(&snac, NULL, 0); + xs *lol = list_maint(&snac, NULL, OP_LIST); const xs_list *l; xs_list_foreach(lol, l) { @@ -389,7 +389,7 @@ int main(int argc, char *argv[]) return usage(cmd); if (strcmp(cmd, "list_members") == 0) { /** **/ - xs *lid = list_maint(&snac, url, 4); + xs *lid = list_maint(&snac, url, OP_FIND); if (lid != NULL) { xs *lcont = list_members(&snac, lid, NULL, OP_LIST); @@ -410,10 +410,10 @@ int main(int argc, char *argv[]) } if (strcmp(cmd, "list_create") == 0) { /** **/ - xs *lid = list_maint(&snac, url, 4); + xs *lid = list_maint(&snac, url, OP_FIND); if (lid == NULL) { - xs *n_lid = list_maint(&snac, url, 1); + xs *n_lid = list_maint(&snac, url, OP_ADD); printf("New list named '%s' created (%s)\n", url, n_lid); } else @@ -423,10 +423,10 @@ int main(int argc, char *argv[]) } if (strcmp(cmd, "list_remove") == 0) { /** **/ - xs *lid = list_maint(&snac, url, 4); + xs *lid = list_maint(&snac, url, OP_FIND); if (lid != NULL) { - list_maint(&snac, lid, 2); + list_maint(&snac, lid, OP_DEL); printf("List '%s' (%s) deleted\n", url, lid); } else @@ -439,7 +439,7 @@ int main(int argc, char *argv[]) const char *account = GET_ARGV(); if (account != NULL) { - xs *lid = list_maint(&snac, url, 4); + xs *lid = list_maint(&snac, url, OP_FIND); if (lid != NULL) { xs *actor = NULL; @@ -466,7 +466,7 @@ int main(int argc, char *argv[]) const char *account = GET_ARGV(); if (account != NULL) { - xs *lid = list_maint(&snac, url, 4); + xs *lid = list_maint(&snac, url, OP_FIND); if (lid != NULL) { xs *md5 = xs_md5_hex(account, strlen(account)); diff --git a/mastoapi.c b/mastoapi.c index 571d7cb..81c9bf0 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -1781,7 +1781,7 @@ xs_list *mastoapi_account_lists(snac *user, const char *uid) { xs_list *out = xs_list_new(); xs *actor_md5 = NULL; - xs *lol = list_maint(user, NULL, 0); + xs *lol = list_maint(user, NULL, OP_LIST); if (uid) { if (!xs_is_hex(uid)) @@ -2549,7 +2549,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, else if (xs_is_hex(p)) { xs *out = xs_list_new(); - xs *lol = list_maint(&snac1, NULL, 0); + xs *lol = list_maint(&snac1, NULL, OP_LIST); int c = 0; const xs_list *v; @@ -3803,7 +3803,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path, if (xs_type(title) == XSTYPE_STRING) { /* add the list */ xs *out = xs_dict_new(); - xs *lid = list_maint(&snac, title, 1); + xs *lid = list_maint(&snac, title, OP_ADD); if (!xs_is_null(lid)) { out = xs_dict_append(out, "id", lid); @@ -4058,7 +4058,7 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path, else { /* delete list */ if (xs_is_hex(p)) { - list_maint(&snac, p, 2); + list_maint(&snac, p, OP_DEL); } } } diff --git a/snac.h b/snac.h index 0406a2e..a8dad16 100644 --- a/snac.h +++ b/snac.h @@ -84,7 +84,8 @@ typedef enum { OP_ADD = 1, OP_DEL = 2, OP_LIST = 3, - OP_FIND = 4 + OP_FIND = 4, + OP_ID = 5 } snac_op; extern srv_state *p_state; @@ -259,7 +260,7 @@ void tag_index(const char *id, const xs_dict *obj); xs_str *tag_fn(const char *tag); xs_list *tag_search(const char *tag, int skip, int show); -xs_val *list_maint(snac *user, const char *list, int op); +xs_val *list_maint(snac *user, const char *list, snac_op 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_members(snac *user, const char *list_id, const char *actor_md5, snac_op op); diff --git a/utils.c b/utils.c index 6d202d8..8780270 100644 --- a/utils.c +++ b/utils.c @@ -698,7 +698,7 @@ void export_csv(snac *user) if ((f = fopen(fn, "w")) != NULL) { snac_log(user, xs_fmt("Creating %s...", fn)); - xs *lol = list_maint(user, NULL, 0); + xs *lol = list_maint(user, NULL, OP_LIST); const xs_list *li; xs_list_foreach(lol, li) { @@ -927,7 +927,7 @@ void import_list_csv(snac *user, const char *ifn) if (lname && acct) { /* create the list */ - xs *list_id = list_maint(user, lname, 1); + xs *list_id = list_maint(user, lname, OP_ADD); xs *url = NULL; xs *uid = NULL; -- cgit From 03e9f05fa35329a81e9755bb247e246fd416859d Mon Sep 17 00:00:00 2001 From: default Date: Wed, 26 Aug 2026 09:02:30 +0200 Subject: Use snac_op in limited(). --- data.c | 13 ++++++++----- snac.h | 8 ++++---- utils.c | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/data.c b/data.c index 6273800..04b9e6e 100644 --- a/data.c +++ b/data.c @@ -2355,7 +2355,7 @@ int actor_get_refresh(snac *user, const char *actor, xs_dict **data) /** user limiting (announce blocks) **/ -int limited(snac *user, const char *id, int cmd) +int limited(snac *user, const char *id, snac_op op) /* announce messages from a followed (0: check, 1: limit; 2: unlimit) */ { int ret = 0; @@ -2363,12 +2363,12 @@ int limited(snac *user, const char *id, int cmd) xs *md5 = xs_md5_hex(id, strlen(id)); xs *fn = xs_fmt("%s/%s", dir, md5); - switch (cmd) { - case 0: /** check **/ + switch (op) { + case OP_CHECK: /** check **/ ret = !!(mtime(fn) > 0.0); break; - case 1: /** limit **/ + case OP_ADD: /** limit **/ mkdirx(dir); if (mtime(fn) > 0.0) @@ -2385,12 +2385,15 @@ int limited(snac *user, const char *id, int cmd) } break; - case 2: /** unlimit **/ + case OP_DEL: /** unlimit **/ if (mtime(fn) > 0.0) ret = unlink(fn); else ret = -1; break; + + default: + break; } return ret; diff --git a/snac.h b/snac.h index a8dad16..318dbd3 100644 --- a/snac.h +++ b/snac.h @@ -247,10 +247,10 @@ void schedule_add(snac *user, const char *id, const xs_dict *msg); xs_list *scheduled_list(snac *user); void scheduled_process(snac *user); -int limited(snac *user, const char *id, int cmd); -#define is_limited(user, id) limited((user), (id), 0) -#define limit(user, id) limited((user), (id), 1) -#define unlimit(user, id) limited((user), (id), 2) +int limited(snac *user, const char *id, snac_op); +#define is_limited(user, id) limited((user), (id), OP_CHECK) +#define limit(user, id) limited((user), (id), OP_ADD) +#define unlimit(user, id) limited((user), (id), OP_DEL) void hide(snac *snac, const char *id); int is_hidden(snac *snac, const char *id); diff --git a/utils.c b/utils.c index 8780270..8fab2d2 100644 --- a/utils.c +++ b/utils.c @@ -744,7 +744,7 @@ void export_csv(snac *user) webfinger_request_fake(actor, NULL, &uid); if (xs_is_string(uid)) - fprintf(f, "%s,%s,false,\n", uid, limited(user, actor, 0) ? "false" : "true"); + fprintf(f, "%s,%s,false,\n", uid, limited(user, actor, OP_CHECK) ? "false" : "true"); } fclose(f); -- cgit From 5107778280bfe66c51ab0654babaf622580beb9e Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 26 Aug 2026 10:21:23 +0200 Subject: Fixed ugly link label in 'Link' attachments when proxy media is on. --- html.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html.c b/html.c index 503539c..e5c84fb 100644 --- a/html.c +++ b/html.c @@ -3135,7 +3135,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, xs_html_tag("p", xs_html_tag("a", xs_html_attr("href", o_href), - xs_html_text(href)))); + xs_html_text(o_href)))); /* do not generate an Alt... */ name = NULL; -- cgit From 5344aca992a3f48993f1c06df77e0cdd15aae884 Mon Sep 17 00:00:00 2001 From: grunfink Date: Fri, 28 Aug 2026 12:02:52 +0200 Subject: mastoapi: Return unauthorized in several unimplemented entrypoints. --- mastoapi.c | 82 ++++++++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/mastoapi.c b/mastoapi.c index 81c9bf0..c1e345b 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -2307,10 +2307,13 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, } else if (strcmp(cmd, "/v1/conversations") == 0) { /** **/ - /* TBD */ - *body = xs_dup("[]"); - *ctype = "application/json"; - status = HTTP_STATUS_OK; + if (logged_in) { + *body = xs_dup("[]"); + *ctype = "application/json"; + status = HTTP_STATUS_OK; + } + else + status = HTTP_STATUS_UNAUTHORIZED; } else if (strcmp(cmd, "/v1/notifications") == 0) { /** **/ @@ -2471,25 +2474,37 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, else if (strcmp(cmd, "/v1/filters") == 0) { /** **/ /* snac will never have filters */ - *body = xs_dup("[]"); - *ctype = "application/json"; - status = HTTP_STATUS_OK; + if (logged_in) { + *body = xs_dup("[]"); + *ctype = "application/json"; + status = HTTP_STATUS_OK; + } + else + status = HTTP_STATUS_UNAUTHORIZED; } else if (strcmp(cmd, "/v2/filters") == 0) { /** **/ /* snac will never have filters * but still, without a v2 endpoint a short delay is introduced * in some apps */ - *body = xs_dup("[]"); - *ctype = "application/json"; - status = HTTP_STATUS_OK; + if (logged_in) { + *body = xs_dup("[]"); + *ctype = "application/json"; + status = HTTP_STATUS_OK; + } + else + status = HTTP_STATUS_UNAUTHORIZED; } else if (strcmp(cmd, "/v1/favourites") == 0) { /** **/ /* snac will never support a list of favourites */ - *body = xs_dup("[]"); - *ctype = "application/json"; - status = HTTP_STATUS_OK; + if (logged_in) { + *body = xs_dup("[]"); + *ctype = "application/json"; + status = HTTP_STATUS_OK; + } + else + status = HTTP_STATUS_UNAUTHORIZED; } else if (strcmp(cmd, "/v1/bookmarks") == 0) { /** **/ @@ -2580,10 +2595,13 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, } else if (strcmp(cmd, "/v1/scheduled_statuses") == 0) { /** **/ - /* TBD */ - *body = xs_dup("[]"); - *ctype = "application/json"; - status = HTTP_STATUS_OK; + if (logged_in) { + *body = xs_dup("[]"); + *ctype = "application/json"; + status = HTTP_STATUS_OK; + } + else + status = HTTP_STATUS_UNAUTHORIZED; } else if (strcmp(cmd, "/v1/follow_requests") == 0) { /** **/ @@ -2951,9 +2969,13 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, else if (strcmp(cmd, "/v1/preferences") == 0) { /** **/ /* TBD */ - *body = xs_dup("{}"); - *ctype = "application/json"; - status = HTTP_STATUS_OK; + if (logged_in) { + *body = xs_dup("{}"); + *ctype = "application/json"; + status = HTTP_STATUS_OK; + } + else + status = HTTP_STATUS_UNAUTHORIZED; } else if (strcmp(cmd, "/v1/markers") == 0) { /** **/ @@ -3004,15 +3026,23 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, } else if (strcmp(cmd, "/v1/blocks") == 0) { /** **/ - *body = xs_dup("[]"); - *ctype = "application/json"; - status = HTTP_STATUS_OK; + if (logged_in) { + *body = xs_dup("[]"); + *ctype = "application/json"; + status = HTTP_STATUS_OK; + } + else + status = HTTP_STATUS_UNAUTHORIZED; } else if (strcmp(cmd, "/v1/mutes") == 0) { /** **/ - *body = xs_dup("[]"); - *ctype = "application/json"; - status = HTTP_STATUS_OK; + if (logged_in) { + *body = xs_dup("[]"); + *ctype = "application/json"; + status = HTTP_STATUS_OK; + } + else + status = HTTP_STATUS_UNAUTHORIZED; } else if (strcmp(cmd, "/v1/trends/tags") == 0) { /** **/ -- cgit From d3294771d60035773ede90e6a270110dba6369c4 Mon Sep 17 00:00:00 2001 From: Claire Date: Fri, 28 Aug 2026 12:26:48 +0200 Subject: mastoapi: implemented /v1/media. --- mastoapi.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/mastoapi.c b/mastoapi.c index c1e345b..f3cc838 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -3170,6 +3170,43 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, else status = HTTP_STATUS_UNAUTHORIZED; } + else + if (xs_startswith(cmd, "/v1/media/") || xs_startswith(cmd, "/v2/media/")) { /** **/ + if (logged_in) { + xs *p = xs_split(cmd, "/"); + const char *id = xs_list_get(p, -1); + + if (xs_is_string(id)) { + xs *filename = xs_fmt("%s/static/%s", snac1.basedir, id); + + if (mtime(filename)) { + /* file exists */ + xs *alt = static_get_meta(&snac1, id); + xs *url = xs_fmt("%s/s/%s", snac1.actor, id); + xs *mime = xs_split(xs_mime_by_ext(filename), "/"); + const char *type = xs_list_get(mime, 0); + + if (!xs_match(type, "image|video|audio")) + type = "unknown"; + + xs *d = xs_dict_new(); + + d = xs_dict_set(d, "id", id); + d = xs_dict_set(d, "url", url); + d = xs_dict_set(d, "preview_url", url); + d = xs_dict_set(d, "remote_url", url); + d = xs_dict_set(d, "type", type); + d = xs_dict_set(d, "description", xs_or(alt, "")); + + *body = xs_json_dumps(d, 4); + *ctype = "application/json"; + status = HTTP_STATUS_OK; + } + } + } + else + status = HTTP_STATUS_UNAUTHORIZED; + } /* user cleanup */ if (logged_in) -- cgit From 296357c6849b69480e824608bcd1bb07f2d1e8dd Mon Sep 17 00:00:00 2001 From: grunfink Date: Fri, 28 Aug 2026 12:29:27 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f9c433d..334e4fb 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,7 +4,7 @@ Added some fixes to media proxy code. -Mastodon API: Don't return reactions count as string (contributed by mkljczk). +Mastodon API: Don't return reactions count as string (contributed by mkljczk), implemented GET /v1/media (contributed by clairemont). Updated Ukrainian and Russian translations (contributed by wincentbalin and koru). -- cgit From eb25a46304e59b16e291fb8df71d6e671a9db4fd Mon Sep 17 00:00:00 2001 From: Jeroen Domburg Date: Sun, 30 Aug 2026 11:13:36 +0800 Subject: Fix example Apache2 config to point to correct urls --- doc/snac.8 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/snac.8 b/doc/snac.8 index 5698ce1..96bb678 100644 --- a/doc/snac.8 +++ b/doc/snac.8 @@ -869,7 +869,7 @@ ProxyPreserveHost On # Main web access point - ProxyPass http://127.0.0.1:8001/social + ProxyPass http://127.0.0.1:8001/fedi # WebFinger @@ -908,7 +908,7 @@ ProxyPreserveHost On # optional (Mastodon-like "authorize interaction" entrypoint) - ProxyPass http://127.0.0.1:8001/share + ProxyPass http://127.0.0.1:8001/authorize_interaction .Ed .Pp -- cgit From f6f18197c72a38bf2e5de092873d46af7c8e04b7 Mon Sep 17 00:00:00 2001 From: grunfink Date: Sun, 30 Aug 2026 07:23:40 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 334e4fb..cab5eb5 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,6 +6,8 @@ Added some fixes to media proxy code. Mastodon API: Don't return reactions count as string (contributed by mkljczk), implemented GET /v1/media (contributed by clairemont). +Fixed bug in documentation examples (contributed by Sprite_tm). + Updated Ukrainian and Russian translations (contributed by wincentbalin and koru). ## 2.94 -- cgit From 748b578c96bb5d6c3b372d533bc374e2fb918348 Mon Sep 17 00:00:00 2001 From: grunfink Date: Sun, 30 Aug 2026 07:43:18 +0200 Subject: Fixed EmojiReact code to allow any emoticon in emojis.json, not only those with colon-wrapped ids. --- activitypub.c | 6 +++--- html.c | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/activitypub.c b/activitypub.c index df319f0..bb6de13 100644 --- a/activitypub.c +++ b/activitypub.c @@ -1607,7 +1607,7 @@ xs_dict *msg_emoji_init(snac *snac, const char *mid, const char *eid_o) { xs_dict *n_msg = msg_admiration(snac, mid, "EmojiReact"); - xs *eid = xs_strip_chars_i(xs_dup(eid_o), ":"); + xs *eid = xs_dup(eid_o); xs *content = NULL; xs *tag = xs_list_new(); xs *dict = xs_dict_new(); @@ -1623,14 +1623,14 @@ xs_dict *msg_emoji_init(snac *snac, const char *mid, const char *eid_o) content = xs_dup(eid); else if (*eid == '%') { - content = xs_url_dec_emoji(xs_dup(eid)); + content = xs_url_dec_emoji(eid); if (content == NULL) { xs_free(n_msg); return NULL; } } else { - content = xs_fmt(":%s:", eid); + content = xs_dup(eid); const char *emo = xs_dict_get(emjs, content); if (emo == NULL) { diff --git a/html.c b/html.c index e5c84fb..134cc87 100644 --- a/html.c +++ b/html.c @@ -5727,8 +5727,6 @@ int html_post_handler(const xs_dict *req, const char *q_path, if (strcmp(action, L("EmojiReact")) == 0) { /** **/ xs *eid = xs_dup(xs_dict_get(p_vars, "eid")); - eid = xs_strip_chars_i(eid, ":"); - xs *ret = msg_emoji_init(&snac, id, eid); /* fails if either invalid or already reacted */ if (!ret) { -- cgit From 2008eca193f841080abdb382f1e372f212410145 Mon Sep 17 00:00:00 2001 From: grunfink Date: Sun, 30 Aug 2026 07:43:50 +0200 Subject: Bumped version. --- snac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snac.h b/snac.h index 318dbd3..2acb1ec 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2026 grunfink et al. / MIT license */ -#define VERSION "2.94" +#define VERSION "2.95-dev" #define USER_AGENT "snac/" VERSION -- cgit From 669b86ce65115b1ded72dbc9674b5842196d1475 Mon Sep 17 00:00:00 2001 From: grunfink Date: Sun, 30 Aug 2026 07:44:58 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index cab5eb5..80a426e 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,6 +4,8 @@ Added some fixes to media proxy code. +Fixed EmojiReact code to allow any emoticon defined in `emojis.json`, not only those with colon-wrapped identifiers. + Mastodon API: Don't return reactions count as string (contributed by mkljczk), implemented GET /v1/media (contributed by clairemont). Fixed bug in documentation examples (contributed by Sprite_tm). -- cgit From eea4e1fbce6915c598a0a2211a88f7389c47c0a2 Mon Sep 17 00:00:00 2001 From: grunfink Date: Sun, 30 Aug 2026 08:03:22 +0200 Subject: Fixed a bug in Undo + EmojiReact. --- activitypub.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/activitypub.c b/activitypub.c index bb6de13..4d1a449 100644 --- a/activitypub.c +++ b/activitypub.c @@ -2836,10 +2836,9 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) } /* *key emojis are like w/ Emoji tag */ else - if ((isEmoji || strcmp(utype, "EmojiReact") == 0) && - (content && strcmp(content, "♥") != 0)) { + if (isEmoji || strcmp(utype, "EmojiReact") == 0) { const xs_val *mid = xs_dict_get(object, "id"); - int status = object_rm_emoji_react((char *)id, mid); + int status = object_rm_emoji_react(id, mid); /* ensure *key notifications type */ utype = "EmojiReact"; -- cgit From 62166d33affa48b625867b246ae1c282fc97035b Mon Sep 17 00:00:00 2001 From: grunfink Date: Sun, 30 Aug 2026 08:16:00 +0200 Subject: Added a key check to Undo. --- activitypub.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/activitypub.c b/activitypub.c index 4d1a449..5c93bec 100644 --- a/activitypub.c +++ b/activitypub.c @@ -2813,6 +2813,10 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) utype = "Follow"; } + if (strcmp(actor, key_id)) { + snac_log(snac, xs_fmt("Undo: mismatched actor '%s' and key '%s'", actor, key_id)); + } + else if (strcmp(utype, "Follow") == 0) { /** **/ if (!id) { snac_log(snac, xs_fmt("no id (msg.object.object) when " -- cgit From 61ef7a86183d7d8f5f6a5fcd6d945b91dcb68526 Mon Sep 17 00:00:00 2001 From: grunfink Date: Sun, 30 Aug 2026 16:38:58 +0200 Subject: Fixed error in notification filtering. --- data.c | 14 ++++++++++++-- html.c | 7 ++++--- snac.h | 2 +- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/data.c b/data.c index 04b9e6e..fd6c91f 100644 --- a/data.c +++ b/data.c @@ -3576,7 +3576,7 @@ xs_list *notify_list(snac *snac, int skip, int show) } -xs_list *notify_filter_list(snac *snac, xs_list *notifs) +xs_list *notify_filter_list(snac *snac, xs_list *notifs, int skip, int show) /* apply user-defined notification filter to IDs */ { const xs_dict *n_filter = xs_dict_get(snac->config, "notify_filter"); @@ -3633,7 +3633,17 @@ xs_list *notify_filter_list(snac *snac, xs_list *notifs) continue; if (strcmp(type, "Announce") == 0 && !n_ann_on) continue; + + if (skip) { + skip--; + continue; + } + flt = xs_list_append(flt, v); + show--; + + if (show == 0) + break; } return flt; } @@ -3644,7 +3654,7 @@ int notify_new_num(snac *snac) { xs *t = notify_check_time(snac, 0); xs *lst_unfilt = notify_list(snac, 0, XS_ALL); - xs *lst = notify_filter_list(snac, lst_unfilt); + xs *lst = notify_filter_list(snac, lst_unfilt, 0, XS_ALL); int cnt = 0; xs_list *p = lst; diff --git a/html.c b/html.c index 134cc87..b26c252 100644 --- a/html.c +++ b/html.c @@ -4267,7 +4267,7 @@ xs_str *html_notifications(snac *user, int skip, int show) if (xs_is_true(xs_dict_get(srv_config, "proxy_media"))) proxy = user->actor; - xs *n_list_unfilt = notify_list(user, skip, show); + xs *n_list_unfilt = notify_list(user, 0, XS_ALL); xs *n_time = notify_check_time(user, 0); xs_html *body = html_user_body(user, 0); @@ -4278,7 +4278,7 @@ xs_str *html_notifications(snac *user, int skip, int show) user->tz = xs_dict_get_def(user->config, "tz", "UTC"); // previous line invalidates user->tz n_filter = xs_dict_get(user->config, "notify_filter"); } - xs *n_list = notify_filter_list(user, n_list_unfilt); + xs *n_list = notify_filter_list(user, n_list_unfilt, skip, show); /* all filters are true by default */ const xs_val *n_def = xs_stock( XSTYPE_TRUE ); int n_likes_on = xs_is_true(xs_dict_get_def(n_filter, "likes", n_def)); @@ -4607,7 +4607,8 @@ xs_str *html_notifications(snac *user, int skip, int show) xs_html_text(L("None")))); /* add the navigation footer */ - xs *next_p = notify_list(user, skip + show, 1); + xs *next_p = notify_filter_list(user, n_list_unfilt, skip + show, 1); + if (xs_list_len(next_p)) { xs *url = xs_fmt("%s/notifications?skip=%d&show=%d", user->actor, skip + show, show); diff --git a/snac.h b/snac.h index 2acb1ec..80d2429 100644 --- a/snac.h +++ b/snac.h @@ -291,7 +291,7 @@ void notify_add(snac *snac, const char *type, const char *utype, xs_dict *notify_get(snac *snac, const char *id); int notify_new_num(snac *snac); xs_list *notify_list(snac *snac, int skip, int show); -xs_list *notify_filter_list(snac *snac, xs_list *ids); +xs_list *notify_filter_list(snac *snac, xs_list *ids, int skip, int show); void notify_clear(snac *snac); xs_dict *markers_get(snac *snac, const xs_list *markers); -- cgit From 8e521cce1f980edb6ce0bfd490e93e8ab5baa93a Mon Sep 17 00:00:00 2001 From: grunfink Date: Sun, 30 Aug 2026 16:46:38 +0200 Subject: Added notification filter for Webmentions. --- data.c | 3 +++ html.c | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/data.c b/data.c index fd6c91f..c38a0b3 100644 --- a/data.c +++ b/data.c @@ -3593,6 +3593,7 @@ xs_list *notify_filter_list(snac *snac, xs_list *notifs, int skip, int show) int n_folreq_on = xs_is_true(xs_dict_get_def(n_filter, "folreqs", n_def)); int n_blocks_on = xs_is_true(xs_dict_get_def(n_filter, "blocks", n_def)); int n_polls_on = xs_is_true(xs_dict_get_def(n_filter, "polls", n_def)); + int n_webmen_on = xs_is_true(xs_dict_get_def(n_filter, "webmentions", n_def)); const xs_str *v; xs_list *flt = xs_list_new(); @@ -3633,6 +3634,8 @@ xs_list *notify_filter_list(snac *snac, xs_list *notifs, int skip, int show) continue; if (strcmp(type, "Announce") == 0 && !n_ann_on) continue; + if (strcmp(type, "Webmention") == 0 && !n_webmen_on) + continue; if (skip) { skip--; diff --git a/html.c b/html.c index b26c252..3ba48d7 100644 --- a/html.c +++ b/html.c @@ -4246,6 +4246,7 @@ void notify_filter(snac *user, const xs_dict *p_vars) int folreq_on = (v = xs_dict_get(p_vars, "folreqs_on")) ? strcmp(v, "on") == 0 : 0; int blocks_on = (v = xs_dict_get(p_vars, "blocks_on")) ? strcmp(v, "on") == 0 : 0; int polls_on = (v = xs_dict_get(p_vars, "polls_on")) ? strcmp(v, "on") == 0 : 0; + int webmen_on = (v = xs_dict_get(p_vars, "webmentions_on")) ? strcmp(v, "on") == 0 : 0; xs *filter = xs_dict_new(); filter = xs_dict_set(filter, "likes", xs_stock(likes_on ? XSTYPE_TRUE : XSTYPE_FALSE)); filter = xs_dict_set(filter, "reacts", xs_stock(reacts_on ? XSTYPE_TRUE : XSTYPE_FALSE)); @@ -4256,6 +4257,7 @@ void notify_filter(snac *user, const xs_dict *p_vars) filter = xs_dict_set(filter, "folreqs", xs_stock(folreq_on ? XSTYPE_TRUE : XSTYPE_FALSE)); filter = xs_dict_set(filter, "blocks", xs_stock(blocks_on ? XSTYPE_TRUE : XSTYPE_FALSE)); filter = xs_dict_set(filter, "polls", xs_stock(polls_on ? XSTYPE_TRUE : XSTYPE_FALSE)); + filter = xs_dict_set(filter, "webmentions", xs_stock(webmen_on ? XSTYPE_TRUE : XSTYPE_FALSE)); user->config = xs_dict_set(user->config, "notify_filter", filter); user->tz = xs_dict_get_def(user->config, "tz", "UTC"); // previous line invalidates user->tz } @@ -4290,6 +4292,7 @@ xs_str *html_notifications(snac *user, int skip, int show) int n_folreq_on = xs_is_true(xs_dict_get_def(n_filter, "folreqs", n_def)); int n_blocks_on = xs_is_true(xs_dict_get_def(n_filter, "blocks", n_def)); int n_polls_on = xs_is_true(xs_dict_get_def(n_filter, "polls", n_def)); + int n_webmen_on = xs_is_true(xs_dict_get_def(n_filter, "webmentions", n_def)); xs_html *html = xs_html_tag("html", html_user_head(user, NULL, NULL), @@ -4316,6 +4319,7 @@ xs_str *html_notifications(snac *user, int skip, int show) html_checkbox("folreqs_on", L("Follow requests"), n_folreq_on), html_checkbox("blocks_on", L("Blocks"), n_blocks_on), html_checkbox("polls_on", L("Polls"), n_polls_on), + html_checkbox("webmentions_on", L("Webmentions"), n_webmen_on), xs_html_sctag("input", xs_html_attr("type", "submit"), xs_html_attr("class", "button"), -- cgit From 61d207d2c6cd6a1bff4a4987b2d02dc27b5a3f16 Mon Sep 17 00:00:00 2001 From: grunfink Date: Sun, 30 Aug 2026 16:51:01 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 80a426e..467bb63 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,6 +6,10 @@ Added some fixes to media proxy code. Fixed EmojiReact code to allow any emoticon defined in `emojis.json`, not only those with colon-wrapped identifiers. +Fixed a bug in notification filtering (paging was sometimes incorrect). + +Added a notification filter for Webmentions. + Mastodon API: Don't return reactions count as string (contributed by mkljczk), implemented GET /v1/media (contributed by clairemont). Fixed bug in documentation examples (contributed by Sprite_tm). -- cgit From f13cc6e95485ddda0fd22f3428a87e04b3ab369e Mon Sep 17 00:00:00 2001 From: default Date: Sun, 30 Aug 2026 17:06:09 +0200 Subject: Fixed warning in NetBSD evbarm. --- snac.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snac.c b/snac.c index f461280..8afe1f8 100644 --- a/snac.c +++ b/snac.c @@ -93,7 +93,7 @@ int validate_uid(const char *uid) return 0; while (*uid) { - if (!(isalnum(*uid) || *uid == '_')) + if (!(isalnum((int)*uid) || *uid == '_')) return 0; uid++; -- cgit From 73015e2a6f46739f1d75ae32c915552aa58cbb19 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 1 Sep 2026 08:39:27 +0200 Subject: Minor HTML tweak. --- html.c | 1 + 1 file changed, 1 insertion(+) diff --git a/html.c b/html.c index 3ba48d7..bcdeb62 100644 --- a/html.c +++ b/html.c @@ -1537,6 +1537,7 @@ xs_html *html_top_controls(snac *user) xs_html_attr("value", L("Like"))), xs_html_text(" "), xs_html_text(L("(by URL)"))), + xs_html_tag("p", NULL), xs_html_tag("form", xs_html_attr("autocomplete", "off"), xs_html_attr("method", "post"), -- cgit From 5e74f4578816812f4f6ad47390e96dcfafee37d2 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 1 Sep 2026 08:46:42 +0200 Subject: Increased debug level for _load_raw_file(). --- data.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data.c b/data.c index c38a0b3..279f7af 100644 --- a/data.c +++ b/data.c @@ -2740,7 +2740,7 @@ static int _load_raw_file(const char *fn, xs_val **data, int *size, if (etag != NULL) *etag = xs_dup(e); - srv_debug(1, xs_fmt("_load_raw_file(): %s %d", fn, status)); + srv_debug(2, xs_fmt("_load_raw_file(): %s %d", fn, status)); } } -- cgit From c38ad28d5daa44b592a04192c7eeebed644593d4 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 1 Sep 2026 14:25:12 +0200 Subject: New function text_browser_friendly() (still unused). --- html.c | 37 +++++++++++++++++++++++++++++++++++++ snac.c | 2 +- snac.h | 2 +- 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/html.c b/html.c index bcdeb62..89034ec 100644 --- a/html.c +++ b/html.c @@ -4651,6 +4651,40 @@ void set_user_lang(snac *user) } +int text_browser_friendly(const snac *user, const char *user_agent) +/* returns true if the web UI must be adapted to text browsers */ +/* basically, this means to avoid details/summary as much as possible */ +{ + if (user != NULL && xs_is_string(user_agent)) { + snac_debug(user, 1, xs_fmt("user-agent: %s", user_agent)); + + const char *text_browser_uas = xs_dict_get(user->config, "text_browser_uas"); + + if (xs_is_string(text_browser_uas)) { + xs *l = xs_split(text_browser_uas, "\n"); + const char *v; + + xs_list_foreach(l, v) { + if (*v == '\0') + continue; + + xs *v2 = xs_strip_i(xs_dup(v)); + + /* a * means "all browsers", then yes */ + if (strcmp(v2, "*") == 0) + return 1; + + /* if this string is inside user_agent, then yes */ + if (xs_str_in(user_agent, v2) != -1) + return 1; + } + } + } + + return 0; +} + + int html_get_handler(const xs_dict *req, const char *q_path, char **body, int *b_size, char **ctype, xs_str **etag, xs_str **last_modified) @@ -4665,6 +4699,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, int save = 1; int proxy = 0; int terse = 0; + int tb_friendly = 0; const char *v; const xs_dict *q_vars = xs_dict_get(req, "q_vars"); @@ -4734,6 +4769,8 @@ int html_get_handler(const xs_dict *req, const char *q_path, if (xs_is_true(xs_dict_get(srv_config, "proxy_media"))) proxy = 1; + tb_friendly = text_browser_friendly(user, xs_dict_get(req, "user-agent")); + /* return the RSS if requested by Accept header */ if (accept != NULL) { if (xs_str_in(accept, "text/xml") != -1 || diff --git a/snac.c b/snac.c index 8afe1f8..d2dbc7d 100644 --- a/snac.c +++ b/snac.c @@ -127,7 +127,7 @@ void srv_log(xs_str *str) } -void snac_log(snac *snac, xs_str *str) +void snac_log(const snac *snac, xs_str *str) /* prints a user debugging information */ { xs *o_str = str; diff --git a/snac.h b/snac.h index 80d2429..1c505ec 100644 --- a/snac.h +++ b/snac.h @@ -97,7 +97,7 @@ enum { SCOPE_FOLLOWERS = 3, }; -void snac_log(snac *user, xs_str *str); +void snac_log(const snac *user, xs_str *str); #define snac_debug(user, level, str) do { if (dbglevel >= (level)) \ { snac_log((user), (str)); } } while (0) -- cgit From 2e4a9a94208d6ecdd4b70aff7d7c7d6dddefdfaa Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 1 Sep 2026 15:01:08 +0200 Subject: More text browser friendly stuff. --- html.c | 71 +++++++++++++++++++++++++++++++++++++++++------------------------ httpd.c | 4 ++-- snac.h | 3 ++- 3 files changed, 49 insertions(+), 29 deletions(-) diff --git a/html.c b/html.c index 89034ec..5cbc096 100644 --- a/html.c +++ b/html.c @@ -2081,7 +2081,7 @@ xs_str *build_mentions(snac *user, const xs_dict *msg) xs_html *html_entry_controls(snac *user, const char *actor, - const xs_dict *msg, const char *md5) + const xs_dict *msg, const char *md5, int tb_friendly) { const char *id = xs_dict_get(msg, "id"); const char *group = xs_dict_get(msg, "audience"); @@ -2193,6 +2193,21 @@ xs_html *html_entry_controls(snac *user, const char *actor, xs_html_add(form, html_button("hide", L("Hide"), L("Hide this post and its children"))); + if (tb_friendly) { + /* generate a link a More... text pointing to the one-post-only timeline */ + xs *url = xs_fmt("%s/admin/p/%s#%s_entry", user->actor, md5, md5); + + xs_html_add(controls, + xs_html_tag("p", NULL), + xs_html_tag("a", + xs_html_attr("href", url), + xs_html_text(L("More...")))); + + /* and DONE */ + + return controls; + } + const char *prev_src = xs_dict_get(msg, "sourceContent"); if (!xs_is_null(prev_src) && strcmp(actor, user->actor) == 0) { /** edit **/ @@ -2309,7 +2324,7 @@ static const xs_str* words_in_content(const xs_list *words, const xs_val *conten xs_html *html_entry(snac *user, xs_dict *msg, int read_only, - int level, const char *md5, int hide_children) + int level, const char *md5, int hide_children, int tb_friendly) { const char *id = xs_dict_get(msg, "id"); const char *type = xs_dict_get(msg, "type"); @@ -2872,7 +2887,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, xs_html_add(snac_content, xs_html_tag("blockquote", xs_html_attr("class", "snac-quoted-post"), - html_entry(user, quoted_post, 1, level + 1, md5, 1))); + html_entry(user, quoted_post, 1, level + 1, md5, 1, tb_friendly))); } else if (user) @@ -3320,7 +3335,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, if (!read_only && user) { xs_html_add(entry, - html_entry_controls(user, actor, msg, md5)); + html_entry_controls(user, actor, msg, md5, tb_friendly)); } /** children **/ @@ -3382,7 +3397,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, so that it appears unindented just before the parent like a fucking Twitter-like thread */ xs_html_add(fch_container, - html_entry(user, f_chd, read_only, level + 1, cmd5, hide_children)); + html_entry(user, f_chd, read_only, level + 1, cmd5, hide_children, tb_friendly)); cnt++; f_cnt++; @@ -3403,7 +3418,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, if (chd != NULL) { if (xs_is_null(xs_dict_get(chd, "name"))) { xs_html *che = html_entry(user, chd, read_only, - level + 1, cmd5, hide_children); + level + 1, cmd5, hide_children, tb_friendly); if (che != NULL) { if (left > 3) { @@ -3470,7 +3485,7 @@ xs_html *html_footer(const snac *user) xs_str *html_timeline(snac *user, const xs_list *list, int read_only, int skip, int show, int show_more, const char *title, const char *page, - int utl, const char *error, int terse) + int utl, const char *error, int terse, int tb_friendly) /* returns the HTML for the timeline */ { const char *v; @@ -3537,10 +3552,9 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, xs *lists = list_maint(user, NULL, OP_LIST); /* get list of lists */ - int ct = 0; const char *v; - while (xs_list_next(lists, &v, &ct)) { + xs_list_foreach(lists, v) { const char *lname = xs_list_get(v, 1); xs *url = xs_fmt("%s/list/%s", user->actor, xs_list_get(v, 0)); xs *ttl = xs_fmt(L("Timeline for list '%s'"), lname); @@ -3664,6 +3678,11 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, int show_unlisted = user ? xs_is_true(xs_dict_get(user->config, "show_unlisted")) : 0; + /* disable text-browser friendliness if it's just one post, + to force the details/summary controls to appear */ + if (xs_list_len(list) == 1) + tb_friendly = 0; + xs_list_foreach(list, v) { xs *msg = NULL; int status; @@ -3730,7 +3749,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, continue; } - xs_html *entry = html_entry(user, msg, read_only, 0, v, (user && !hide_children) ? 0 : 1); + xs_html *entry = html_entry(user, msg, read_only, 0, v, (user && !hide_children) ? 0 : 1, tb_friendly); if (entry != NULL) xs_html_add(posts, @@ -4182,7 +4201,7 @@ xs_str *html_people_one(snac *user, const char *actor, const xs_list *list, xs_list_in((reacts = object_get_emoji_reacts(id)), actor_md5) == -1))) continue; - xs_html *entry = html_entry(user, msg, 0, 0, v, 1); + xs_html *entry = html_entry(user, msg, 0, 0, v, 1, 0); if (entry != NULL) xs_html_add(lists, @@ -4554,7 +4573,7 @@ xs_str *html_notifications(snac *user, int skip, int show) xs *md5 = xs_md5_hex(id, strlen(id)); xs *ctxt = xs_fmt("%s/admin/p/%s#%s_entry", user->actor, md5, md5); - xs_html *h = html_entry(user, obj, 0, 0, md5, 1); + xs_html *h = html_entry(user, obj, 0, 0, md5, 1, 0); if (h != NULL) { xs_html_add(entry, @@ -4822,7 +4841,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE) { /** empty public timeline for private users **/ - *body = html_timeline(&snac, NULL, 1, 0, 0, 0, NULL, "", 1, error, terse); + *body = html_timeline(&snac, NULL, 1, 0, 0, 0, NULL, "", 1, error, terse, tb_friendly); *b_size = strlen(*body); status = HTTP_STATUS_OK; } @@ -4845,7 +4864,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs *pins = pinned_list(&snac); pins = xs_list_cat(pins, list); - *body = html_timeline(&snac, pins, 1, skip, show, more, NULL, "", 1, error, terse); + *body = html_timeline(&snac, pins, 1, skip, show, more, NULL, "", 1, error, terse, tb_friendly); *b_size = strlen(*body); status = HTTP_STATUS_OK; @@ -4973,7 +4992,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs *title = xs_fmt(xs_list_len(tl) ? L("Search results for tag %s") : L("Nothing found for tag %s"), q); - *body = html_timeline(&snac, tl, 0, skip, show, more, title, page, 0, error, terse); + *body = html_timeline(&snac, tl, 0, skip, show, more, title, page, 0, error, terse, tb_friendly); *b_size = strlen(*body); status = HTTP_STATUS_OK; } @@ -4998,7 +5017,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, title = xs_fmt(L("Nothing found for '%s'"), q); *body = html_timeline(&snac, tl, 0, skip, tl_len, to || tl_len == show, - title, page, 0, error, terse); + title, page, 0, error, terse, tb_friendly); *b_size = strlen(*body); status = HTTP_STATUS_OK; } @@ -5025,7 +5044,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs *list = timeline_list(&snac, "private", skip, show, &more); *body = html_timeline(&snac, list, 0, skip, show, - more, NULL, "/admin", 1, error, terse); + more, NULL, "/admin", 1, error, terse, tb_friendly); *b_size = strlen(*body); status = HTTP_STATUS_OK; @@ -5052,7 +5071,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs *list0 = xs_list_append(xs_list_new(), md5); xs *list = timeline_top_level(&snac, list0); - *body = html_timeline(&snac, list, 0, 0, 0, 0, NULL, "/admin", 1, error, terse); + *body = html_timeline(&snac, list, 0, 0, 0, 0, NULL, "/admin", 1, error, terse, tb_friendly); *b_size = strlen(*body); status = HTTP_STATUS_OK; } @@ -5124,7 +5143,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs *next = timeline_instance_list(skip + show, 1); *body = html_timeline(&snac, list, 0, skip, show, - xs_list_len(next), L("Showing instance timeline"), "/instance", 0, error, terse); + xs_list_len(next), L("Showing instance timeline"), "/instance", 0, error, terse, tb_friendly); *b_size = strlen(*body); status = HTTP_STATUS_OK; } @@ -5139,7 +5158,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs *list = pinned_list(&snac); *body = html_timeline(&snac, list, 0, skip, show, - 0, L("Pinned posts"), "", 0, error, terse); + 0, L("Pinned posts"), "", 0, error, terse, tb_friendly); *b_size = strlen(*body); status = HTTP_STATUS_OK; } @@ -5155,7 +5174,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, 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); + more, L("Liked, boosted or reacted posts"), "/admirations", 0, error, terse, tb_friendly); *b_size = strlen(*body); status = HTTP_STATUS_OK; } @@ -5170,7 +5189,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs *list = bookmark_list(&snac); *body = html_timeline(&snac, list, 0, skip, show, - 0, L("Bookmarked posts"), "", 0, error, terse); + 0, L("Bookmarked posts"), "", 0, error, terse, tb_friendly); *b_size = strlen(*body); status = HTTP_STATUS_OK; } @@ -5185,7 +5204,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs *list = draft_list(&snac); *body = html_timeline(&snac, list, 0, skip, show, - 0, L("Post drafts"), "", 0, error, terse); + 0, L("Post drafts"), "", 0, error, terse, tb_friendly); *b_size = strlen(*body); status = HTTP_STATUS_OK; } @@ -5200,7 +5219,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs *list = scheduled_list(&snac); *body = html_timeline(&snac, list, 0, skip, show, - 0, L("Scheduled posts"), "", 0, error, terse); + 0, L("Scheduled posts"), "", 0, error, terse, tb_friendly); *b_size = strlen(*body); status = HTTP_STATUS_OK; } @@ -5226,7 +5245,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs *title = xs_fmt(L("Showing timeline for list '%s'"), name); *body = html_timeline(&snac, ttl, 0, skip, show, - xs_list_len(next), title, base, 1, error, terse); + xs_list_len(next), title, base, 1, error, terse, tb_friendly); *b_size = strlen(*body); status = HTTP_STATUS_OK; } @@ -5246,7 +5265,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, list = xs_list_append(list, md5); - *body = html_timeline(&snac, list, 1, 0, 0, 0, NULL, "", 1, error, terse); + *body = html_timeline(&snac, list, 1, 0, 0, 0, NULL, "", 1, error, terse, tb_friendly); *b_size = strlen(*body); status = HTTP_STATUS_OK; } diff --git a/httpd.c b/httpd.c index 5b503b9..3504f3e 100644 --- a/httpd.c +++ b/httpd.c @@ -258,7 +258,7 @@ int server_get_handler(xs_dict *req, const char *q_path, else { xs *page = xs_fmt("?t=%s", t); xs *title = xs_fmt(L("Search results for tag #%s"), t); - *body = html_timeline(NULL, tl, 0, skip, show, more, title, page, 0, NULL, 0); + *body = html_timeline(NULL, tl, 0, skip, show, more, title, page, 0, NULL, 0, 0); } } else @@ -266,7 +266,7 @@ int server_get_handler(xs_dict *req, const char *q_path, /** instance timeline **/ xs *tl = timeline_instance_list(0, 30); *body = html_timeline(NULL, tl, 0, 0, 0, 0, - L("Recent posts by users in this instance"), NULL, 0, NULL, 0); + L("Recent posts by users in this instance"), NULL, 0, NULL, 0, 0); } else *body = greeting_html(); diff --git a/snac.h b/snac.h index 1c505ec..2d3c7d0 100644 --- a/snac.h +++ b/snac.h @@ -438,7 +438,8 @@ xs_str *encode_html(const char *str); xs_str *html_timeline(snac *user, const xs_list *list, int read_only, int skip, int show, int show_more, - const char *title, const char *page, int utl, const char *error, int terse); + const char *title, const char *page, + int utl, const char *error, int terse, int tb_friendly); int html_get_handler(const xs_dict *req, const char *q_path, char **body, int *b_size, char **ctype, -- cgit From 3b19a508d292e52cca82b2c1d157f1018674af1b Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 1 Sep 2026 15:18:06 +0200 Subject: New user page /operations. --- html.c | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/html.c b/html.c index 5cbc096..1181644 100644 --- a/html.c +++ b/html.c @@ -4874,6 +4874,28 @@ int html_get_handler(const xs_dict *req, const char *q_path, } } else + if (strcmp(p_path, "operations") == 0) { /** operations and user settings **/ + if (!login(&snac, req)) { + *body = xs_dup(uid); + status = HTTP_STATUS_UNAUTHORIZED; + } + else { + xs_html *h_body = html_user_body(&snac, 0); + + xs_html *html = xs_html_tag("html", + html_user_head(&snac, NULL, NULL), + h_body); + + xs_html_add(h_body, + html_top_controls(&snac), + html_footer(&snac)); + + *body = xs_html_render_s(html, "\n"); + *b_size = strlen(*body); + status = HTTP_STATUS_OK; + } + } + else if (strcmp(p_path, "admin") == 0) { /** private timeline **/ if (!login(&snac, req)) { *body = xs_dup(uid); -- cgit From 32953e4d2d23e0ded2a8cbd96d1b73f6d91b8953 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 1 Sep 2026 15:26:02 +0200 Subject: More text browser friendly things. --- html.c | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/html.c b/html.c index 1181644..2b7698f 100644 --- a/html.c +++ b/html.c @@ -1465,9 +1465,18 @@ xs_html *html_checkbox(const char *form_name, const char *label, int flag) } -xs_html *html_top_controls(snac *user) +xs_html *html_top_controls(snac *user, int tb_friendly) /* generates the top controls */ { + if (tb_friendly) { + /* generate only a link to the /operations entrypoint */ + xs *url = xs_fmt("%s/operations", user->actor); + + return xs_html_tag("a", + xs_html_attr("href", url), + xs_html_text(L("Operations..."))); + } + xs *ops_action = xs_fmt("%s/admin/action", user->actor); xs_html *top_controls = xs_html_tag("div", @@ -3529,7 +3538,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, if (user && !read_only) xs_html_add(body, - html_top_controls(user)); + html_top_controls(user, tb_friendly)); if (error != NULL) { xs_html_add(body, @@ -4887,7 +4896,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, h_body); xs_html_add(h_body, - html_top_controls(&snac), + html_top_controls(&snac, 0), html_footer(&snac)); *body = xs_html_render_s(html, "\n"); -- cgit From 7a62d550de0fe0f76fe5c2c789fe331ea4fe5046 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 1 Sep 2026 16:19:37 +0200 Subject: Added web UI for setting the 'text_browser_uas' user config field. --- html.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/html.c b/html.c index 2b7698f..a639269 100644 --- a/html.c +++ b/html.c @@ -1624,6 +1624,7 @@ xs_html *html_top_controls(snac *user, int tb_friendly) const char *webhook = xs_dict_get_def(user->config, "notify_webhook", ""); const char *post_langs = xs_dict_get_def(user->config, "post_langs", ""); const char *excluded_langs = xs_dict_get_def(user->config, "excluded_langs", ""); + const char *text_browser_uas = xs_dict_get_def(user->config, "text_browser_uas", ""); xs *metadata = NULL; const xs_dict *md = xs_dict_get(user->config, "metadata"); @@ -1870,6 +1871,16 @@ xs_html *html_top_controls(snac *user, int tb_friendly) xs_html_attr("value", excluded_langs), xs_html_attr("placeholder", L("en fr es de_AT")))), + xs_html_tag("p", + xs_html_text(L("Show a simpler web UI for browsers with these user agents (one per line):")), + xs_html_sctag("br", NULL), + xs_html_tag("textarea", + xs_html_attr("name", "text_browser_uas"), + xs_html_attr("cols", "40"), + xs_html_attr("rows", "4"), + xs_html_attr("placeholder", "Links\nLynx\nNetSurf"), + xs_html_text(text_browser_uas))), + xs_html_tag("p", xs_html_text(L("New password:")), xs_html_sctag("br", NULL), @@ -4698,7 +4709,7 @@ int text_browser_friendly(const snac *user, const char *user_agent) xs *v2 = xs_strip_i(xs_dup(v)); - /* a * means "all browsers", then yes */ + /* an asterisk alone means "all browsers", so yes */ if (strcmp(v2, "*") == 0) return 1; @@ -6119,6 +6130,8 @@ int html_post_handler(const xs_dict *req, const char *q_path, if ((v = xs_dict_get(p_vars, "metadata")) != NULL) snac.config = xs_dict_set(snac.config, "metadata", v); + snac.config = xs_dict_set(snac.config, "text_browser_uas", xs_dict_get_def(p_vars, "text_browser_uas", "")); + /* uploads */ const char *uploads[] = { "avatar", "header", NULL }; int n; -- cgit From 41fe777cd0330934d83d271cbd8ea7ccabd3658d Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 1 Sep 2026 16:32:27 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 467bb63..ab1acf1 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,8 @@ ## UNRELEASED +Improved support for text-only web browsers: it's now possible to configure a set of web browser user-agent strings that will receive simpler HTML in the private timeline web UI. Basically, it consists in avoiding `details` / `summary` HTML tags as much as possible. + Added some fixes to media proxy code. Fixed EmojiReact code to allow any emoticon defined in `emojis.json`, not only those with colon-wrapped identifiers. -- cgit From d227702b9c184cf32c1c96593f4c682bf11bc774 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 1 Sep 2026 16:49:19 +0200 Subject: Updated documentation. --- doc/snac.1 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/snac.1 b/doc/snac.1 index eddab8d..0b79657 100644 --- a/doc/snac.1 +++ b/doc/snac.1 @@ -207,6 +207,13 @@ write your posts in, space-separated (example: en fr pt_BR). .It Don't show posts written in these languages A space-separated string of ISO 639 Language Codes (example: en fr de). +.It Show a simpler web UI for browsers with these user agents +A list of partial strings that will be matched against the current +web browser user agent string. If it contains any of these strings, +the private timeline is shown using simpler HTML (basically, avoiding +the details / summary tags as much as possible, which are usually not +rendered correctly if at all in basic or text-based browsers). If any +of these lines is an asterisk, the simpler web UL is always used. .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. -- cgit From dbff0032ecf575b9bfec9a0a3a9800dcdf4bd33d Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 1 Sep 2026 16:52:21 +0200 Subject: Fixed stupid typo. --- doc/snac.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/snac.1 b/doc/snac.1 index 0b79657..5d63c3c 100644 --- a/doc/snac.1 +++ b/doc/snac.1 @@ -213,7 +213,7 @@ web browser user agent string. If it contains any of these strings, the private timeline is shown using simpler HTML (basically, avoiding the details / summary tags as much as possible, which are usually not rendered correctly if at all in basic or text-based browsers). If any -of these lines is an asterisk, the simpler web UL is always used. +of these lines is an asterisk, the simpler web UI is always used. .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. -- cgit From 2085a9e91187e89a1eed83b1243521cd5038ca19 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 2 Sep 2026 09:49:02 +0200 Subject: Skip notification files that are too big. --- data.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/data.c b/data.c index 279f7af..2529713 100644 --- a/data.c +++ b/data.c @@ -3529,12 +3529,18 @@ xs_dict *notify_get(snac *snac, const char *id) fn = xs_strip_i(fn); fn = xs_str_cat(fn, ".json"); - FILE *f; xs_dict *out = NULL; - if ((f = fopen(fn, "r")) != NULL) { - out = xs_json_load(f); - fclose(f); + struct stat st; + + if (stat(fn, &st) != -1) { + if (st.st_size < 100000) { + FILE *f; + if ((f = fopen(fn, "r")) != NULL) { + out = xs_json_load(f); + fclose(f); + } + } } return out; -- cgit From dde11b15de1db1b7695794abef4d190e4de60da1 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 2 Sep 2026 09:56:42 +0200 Subject: In activitypub_post_handler(), drop input messages that are too big. --- activitypub.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/activitypub.c b/activitypub.c index 5c93bec..c148812 100644 --- a/activitypub.c +++ b/activitypub.c @@ -4110,6 +4110,12 @@ int activitypub_post_handler(const xs_dict *req, const char *q_path, xs_str_in(i_ctype, "application/ld+json") == -1) return 0; + if (p_size >= 100000) { + *body = xs_str_new("too big"); + *ctype = "text/plain"; + return HTTP_STATUS_BAD_REQUEST; + } + /* decode the message */ xs *msg = xs_json_loads(payload); const char *id = xs_dict_get(msg, "id"); -- cgit From 14db95ed9900f238ec9036d2fea9c0ad1be3ad41 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 2 Sep 2026 10:08:27 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index ab1acf1..b98d27c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,6 +2,8 @@ ## UNRELEASED +Fixed a bug in the notification page that made snac hang forever while trying to read abnormally big files. + Improved support for text-only web browsers: it's now possible to configure a set of web browser user-agent strings that will receive simpler HTML in the private timeline web UI. Basically, it consists in avoiding `details` / `summary` HTML tags as much as possible. Added some fixes to media proxy code. -- cgit From e7db1c7175631ca6bf77b63cb117da51a046db69 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 2 Sep 2026 10:23:24 +0200 Subject: Also drop too big messages in activitypub_request(). --- activitypub.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/activitypub.c b/activitypub.c index c148812..7687013 100644 --- a/activitypub.c +++ b/activitypub.c @@ -82,6 +82,9 @@ int activitypub_request(snac *user, const char *url, xs_dict **data) NULL, NULL, 0, &status, &payload, &p_size, 0); } + if (p_size >= 100000) + return HTTP_STATUS_BAD_REQUEST; + if (status == 0 || (status >= 500 && status <= 599)) { /* I found an instance running Misskey that returned 500 on signed messages but returned the object -- cgit From 7bf914ba1cb9b3bf4b24e142aa329e0bd71f2d80 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 2 Sep 2026 10:25:43 +0200 Subject: Fixed previous commit. --- activitypub.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/activitypub.c b/activitypub.c index 7687013..af009ae 100644 --- a/activitypub.c +++ b/activitypub.c @@ -82,9 +82,6 @@ int activitypub_request(snac *user, const char *url, xs_dict **data) NULL, NULL, 0, &status, &payload, &p_size, 0); } - if (p_size >= 100000) - return HTTP_STATUS_BAD_REQUEST; - if (status == 0 || (status >= 500 && status <= 599)) { /* I found an instance running Misskey that returned 500 on signed messages but returned the object @@ -99,6 +96,9 @@ int activitypub_request(snac *user, const char *url, xs_dict **data) NULL, 0, &status, &payload, &p_size, 0); } + if (p_size >= 100000) + return HTTP_STATUS_BAD_REQUEST; + if (valid_status(status)) { /* ensure it's ActivityPub data */ ctype = xs_dict_get(response, "content-type"); -- cgit From 3f5f9d0d0d5191a09ed04c8db970a6e5e077dcc1 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 2 Sep 2026 10:30:17 +0200 Subject: Increased user-agent debug level. --- html.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html.c b/html.c index a639269..1ec4296 100644 --- a/html.c +++ b/html.c @@ -4695,7 +4695,7 @@ int text_browser_friendly(const snac *user, const char *user_agent) /* basically, this means to avoid details/summary as much as possible */ { if (user != NULL && xs_is_string(user_agent)) { - snac_debug(user, 1, xs_fmt("user-agent: %s", user_agent)); + snac_debug(user, 2, xs_fmt("user-agent: %s", user_agent)); const char *text_browser_uas = xs_dict_get(user->config, "text_browser_uas"); -- cgit From f3ef715e8fc662fd981c5f67b25b2acbd54f194e Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 2 Sep 2026 12:55:54 +0200 Subject: Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b98d27c..4b5b52c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,6 @@ # Release Notes -## UNRELEASED +## 2.95 Fixed a bug in the notification page that made snac hang forever while trying to read abnormally big files. -- cgit From 32ce0553e373cb03c690a854e1b629aa10f3ade9 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 2 Sep 2026 12:56:09 +0200 Subject: Version 2.95 RELEASED. --- snac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snac.h b/snac.h index 2d3c7d0..7f92bb4 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2026 grunfink et al. / MIT license */ -#define VERSION "2.95-dev" +#define VERSION "2.95" #define USER_AGENT "snac/" VERSION -- cgit