From 29caa37fd768df07de98c292c6db3196906cbf98 Mon Sep 17 00:00:00 2001 From: default Date: Sat, 16 Nov 2024 07:25:25 +0100 Subject: [PATCH 01/60] Allow underscores in hashtags. --- activitypub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/activitypub.c b/activitypub.c index 0b2fc6a..031b9ac 100644 --- a/activitypub.c +++ b/activitypub.c @@ -724,7 +724,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)) { From 90a959cb858711ea1dd9a802a4f86991abc1c904 Mon Sep 17 00:00:00 2001 From: default Date: Sat, 16 Nov 2024 07:26:44 +0100 Subject: [PATCH 02/60] Updated TODO. --- TODO.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/TODO.md b/TODO.md index 619364c..5054b1f 100644 --- a/TODO.md +++ b/TODO.md @@ -357,3 +357,7 @@ Fix a crash when posting from the links browser (2.63, 2024-11-08T15:57:25+0100) Fix some repeated images in Lemmy posts (2.63, 2024-11-08T15:57:25+0100). Fix a crash when posting an image from the tooot mobile app (2.63, 2024-11-11T19:42:11+0100). + +Fix some URL proxying (2.64, 2024-11-16T07:26:23+0100). + +Allow underscores in hashtags (2.64, 2024-11-16T07:26:23+0100). From 0858f57d8a17aac6c2ca7fc2fc6b7008486293fe Mon Sep 17 00:00:00 2001 From: default Date: Sun, 17 Nov 2024 10:09:41 +0100 Subject: [PATCH 03/60] Updated TODO. --- TODO.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/TODO.md b/TODO.md index 5054b1f..5e0a302 100644 --- a/TODO.md +++ b/TODO.md @@ -16,6 +16,8 @@ Important: deleting a follower should do more that just delete the object, see h ## Wishlist +Add a pidfile. + Implement Proxying for Media Links to Enhance User Privacy (see https://codeberg.org/grunfink/snac2/issues/219 for more information). Consider showing only posts by the account owner (not full trees) (see https://codeberg.org/grunfink/snac2/issues/217 for more information). From 876c892960c7e45daf62369ea58f47ad42bfb98e Mon Sep 17 00:00:00 2001 From: default Date: Sun, 17 Nov 2024 10:21:15 +0100 Subject: [PATCH 04/60] The server creates a pidfile inside the base directory. --- httpd.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/httpd.c b/httpd.c index 1613e1f..81d2f9e 100644 --- a/httpd.c +++ b/httpd.c @@ -774,6 +774,7 @@ void httpd(void) xs *sem_name = NULL; xs *shm_name = NULL; sem_t anon_job_sem; + xs *pidfile = xs_fmt("%s/server.pid", srv_basedir); address = xs_dict_get(srv_config, "address"); @@ -809,6 +810,17 @@ void httpd(void) srv_log(xs_fmt("httpd%s start %s %s", p_state->use_fcgi ? " (FastCGI)" : "", full_address, USER_AGENT)); + { + FILE *f; + + if ((f = fopen(pidfile, "w")) != NULL) { + fprintf(f, "%d\n", getpid()); + fclose(f); + } + else + srv_log(xs_fmt("Cannot create %s: %s", pidfile, strerror(errno))); + } + /* show the number of usable file descriptors */ struct rlimit r; getrlimit(RLIMIT_NOFILE, &r); @@ -894,4 +906,6 @@ void httpd(void) srv_log(xs_fmt("httpd%s stop %s (run time: %s)", p_state->use_fcgi ? " (FastCGI)" : "", full_address, uptime)); + + unlink(pidfile); } From ec5ba68d00a5db05f61f539a6cde9d9906388e83 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 17 Nov 2024 10:21:51 +0100 Subject: [PATCH 05/60] Updated TODO. --- TODO.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/TODO.md b/TODO.md index 5e0a302..0d8c64d 100644 --- a/TODO.md +++ b/TODO.md @@ -16,8 +16,6 @@ Important: deleting a follower should do more that just delete the object, see h ## Wishlist -Add a pidfile. - Implement Proxying for Media Links to Enhance User Privacy (see https://codeberg.org/grunfink/snac2/issues/219 for more information). Consider showing only posts by the account owner (not full trees) (see https://codeberg.org/grunfink/snac2/issues/217 for more information). @@ -363,3 +361,5 @@ Fix a crash when posting an image from the tooot mobile app (2.63, 2024-11-11T19 Fix some URL proxying (2.64, 2024-11-16T07:26:23+0100). Allow underscores in hashtags (2.64, 2024-11-16T07:26:23+0100). + +Add a pidfile (2.64, 2024-11-17T10:21:29+0100). From 2416945748994def6a96163a939348a22ad541a3 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 17 Nov 2024 10:23:43 +0100 Subject: [PATCH 06/60] In the insert cmdline op, don't re-add if it's already in the timeline. --- main.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main.c b/main.c index c6fff5f..c285fac 100644 --- a/main.c +++ b/main.c @@ -558,7 +558,11 @@ int main(int argc, char *argv[]) if (data != NULL) { xs_json_dump(data, 4, stdout); enqueue_actor_refresh(&snac, xs_dict_get(data, "attributedTo"), 0); - timeline_add(&snac, url, data); + + if (!timeline_here(&snac, url)) + timeline_add(&snac, url, data); + else + printf("Post %s already here\n", url); } return 0; From 442b46abc0afeedfeffba7f5317fa5b350ef9c56 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 17 Nov 2024 10:53:47 +0100 Subject: [PATCH 07/60] mastoapi: added more checks in the notifications code. --- mastoapi.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mastoapi.c b/mastoapi.c index c9d71b9..f80c730 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -1784,6 +1784,10 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, mn = xs_dict_append(mn, "created_at", xs_dict_get(noti, "date")); xs *acct = mastoapi_account(&snac1, actor); + + if (acct == NULL) + continue; + mn = xs_dict_append(mn, "account", acct); if (strcmp(type, "follow") != 0 && !xs_is_null(objid)) { From 437829a83376b9094689469d7d4c30eab48f556e Mon Sep 17 00:00:00 2001 From: default Date: Sun, 17 Nov 2024 11:08:25 +0100 Subject: [PATCH 08/60] mastoapi: more tweaks to notifications code. --- mastoapi.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/mastoapi.c b/mastoapi.c index f80c730..73b9a8b 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -1727,11 +1727,11 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, if (logged_in) { xs *l = notify_list(&snac1, 0, 64); xs *out = xs_list_new(); - xs_list *p = l; const xs_dict *v; const xs_list *excl = xs_dict_get(args, "exclude_types[]"); + const char *max_id = xs_dict_get(args, "max_id"); - while (xs_list_iter(&p, &v)) { + xs_list_foreach(l, v) { xs *noti = notify_get(&snac1, v); if (noti == NULL) @@ -1740,6 +1740,8 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, const char *type = xs_dict_get(noti, "type"); const char *utype = xs_dict_get(noti, "utype"); const char *objid = xs_dict_get(noti, "objid"); + const char *id = xs_dict_get(noti, "id"); + xs *fid = xs_replace(id, ".", ""); xs *actor = NULL; xs *entry = NULL; @@ -1752,6 +1754,13 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, if (is_hidden(&snac1, objid)) continue; + if (max_id) { + if (strcmp(fid, max_id) == 0) + max_id = NULL; + + continue; + } + /* convert the type */ if (strcmp(type, "Like") == 0 || strcmp(type, "EmojiReact") == 0) type = "favourite"; @@ -1778,8 +1787,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, mn = xs_dict_append(mn, "type", type); - xs *id = xs_replace(xs_dict_get(noti, "id"), ".", ""); - mn = xs_dict_append(mn, "id", id); + mn = xs_dict_append(mn, "id", fid); mn = xs_dict_append(mn, "created_at", xs_dict_get(noti, "date")); From a7974cbaa7037292c8883271fc2d0983698882a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Kooman?= Date: Mon, 18 Nov 2024 11:46:14 +0100 Subject: [PATCH 09/60] do not autocapitalize "Login: " masto api field on Chrome/Safari the Mastodon API OAuth login page auto capitalizes the first char of the "Login: " field. This disabled that. --- mastoapi.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mastoapi.c b/mastoapi.c index c9d71b9..076f8aa 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -171,7 +171,7 @@ const char *login_page = "" "

%s OAuth identify

\n" "
%s
\n" "
\n" -"

Login:

\n" +"

Login:

\n" "

Password:

\n" "\n" "\n" From 4daacf6b9d60b6fc9fedbbf634ed1c9490095ba1 Mon Sep 17 00:00:00 2001 From: default Date: Mon, 18 Nov 2024 20:38:30 +0100 Subject: [PATCH 10/60] Updated TODO. --- TODO.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/TODO.md b/TODO.md index 0d8c64d..0f2cbd0 100644 --- a/TODO.md +++ b/TODO.md @@ -16,10 +16,6 @@ Important: deleting a follower should do more that just delete the object, see h ## Wishlist -Implement Proxying for Media Links to Enhance User Privacy (see https://codeberg.org/grunfink/snac2/issues/219 for more information). - -Consider showing only posts by the account owner (not full trees) (see https://codeberg.org/grunfink/snac2/issues/217 for more information). - Add support for subscribing and posting to relays (see https://codeberg.org/grunfink/snac2/issues/216 for more information). The instance timeline should also show boosts from users. @@ -363,3 +359,7 @@ Fix some URL proxying (2.64, 2024-11-16T07:26:23+0100). Allow underscores in hashtags (2.64, 2024-11-16T07:26:23+0100). Add a pidfile (2.64, 2024-11-17T10:21:29+0100). + +Implement Proxying for Media Links to Enhance User Privacy (see https://codeberg.org/grunfink/snac2/issues/219 for more information) (2024-11-18T20:36:39+0100). + +Consider showing only posts by the account owner (not full trees) (see https://codeberg.org/grunfink/snac2/issues/217 for more information) (2024-11-18T20:36:39+0100). From 085caa7747a3bbebbd2ec09b3264dc6fcc5a7624 Mon Sep 17 00:00:00 2001 From: default Date: Tue, 19 Nov 2024 06:46:14 +0100 Subject: [PATCH 11/60] New function get_in_reply_to(). --- activitypub.c | 18 +++++++++++++++--- html.c | 4 ++-- mastoapi.c | 2 +- snac.h | 1 + 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/activitypub.c b/activitypub.c index 031b9ac..473675d 100644 --- a/activitypub.c +++ b/activitypub.c @@ -183,6 +183,18 @@ const char *get_atto(const xs_dict *msg) } +const char *get_in_reply_to(const xs_dict *msg) +/* gets the inReplyTo id */ +{ + const xs_val *in_reply_to = xs_dict_get(msg, "inReplyTo"); + + if (xs_type(in_reply_to) == XSTYPE_DICT) + in_reply_to = xs_dict_get(in_reply_to, "id"); + + return in_reply_to; +} + + xs_list *get_attachments(const xs_dict *msg) /* unify the garbage fire that are the attachments */ { @@ -373,7 +385,7 @@ int timeline_request(snac *snac, const char **id, xs_str **wrk, int level) } /* does it have an ancestor? */ - const char *in_reply_to = xs_dict_get(object, "inReplyTo"); + const char *in_reply_to = get_in_reply_to(object); /* store */ timeline_add(snac, nid, object); @@ -671,7 +683,7 @@ int is_msg_for_me(snac *snac, const xs_dict *c_msg) return 3; /* is this message a reply to another? */ - const char *irt = xs_dict_get(msg, "inReplyTo"); + const char *irt = get_in_reply_to(msg); if (!xs_is_null(irt)) { xs *r_msg = NULL; @@ -1957,7 +1969,7 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) if (xs_match(utype, "Note|Article")) { /** **/ const char *id = xs_dict_get(object, "id"); - const char *in_reply_to = xs_dict_get(object, "inReplyTo"); + const char *in_reply_to = get_in_reply_to(object); const char *atto = get_atto(object); xs *wrk = NULL; diff --git a/html.c b/html.c index 3a2b14f..74de47d 100644 --- a/html.c +++ b/html.c @@ -1670,7 +1670,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, if (strcmp(type, "Note") == 0) { if (level == 0) { /* is the parent not here? */ - const char *parent = xs_dict_get(msg, "inReplyTo"); + const char *parent = get_in_reply_to(msg); if (user && !xs_is_null(parent) && *parent && !timeline_here(user, parent)) { xs_html_add(post_header, @@ -2329,7 +2329,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, /* is this message a non-public reply? */ if (user != NULL && !is_msg_public(msg)) { - const char *irt = xs_dict_get(msg, "inReplyTo"); + const char *irt = get_in_reply_to(msg); /* is it a reply to something not in the storage? */ if (!xs_is_null(irt) && !object_here(irt)) { diff --git a/mastoapi.c b/mastoapi.c index 825f207..1dd91dc 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -1024,7 +1024,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) st = xs_dict_append(st, "in_reply_to_id", xs_stock(XSTYPE_NULL)); st = xs_dict_append(st, "in_reply_to_account_id", xs_stock(XSTYPE_NULL)); - tmp = xs_dict_get(msg, "inReplyTo"); + tmp = get_in_reply_to(msg); if (!xs_is_null(tmp)) { xs *irto = NULL; diff --git a/snac.h b/snac.h index ad2793e..8020978 100644 --- a/snac.h +++ b/snac.h @@ -296,6 +296,7 @@ const char *default_avatar_base64(void); xs_str *process_tags(snac *snac, const char *content, xs_list **tag); const char *get_atto(const xs_dict *msg); +const char *get_in_reply_to(const xs_dict *msg); xs_list *get_attachments(const xs_dict *msg); xs_dict *msg_admiration(snac *snac, const char *object, const char *type); From fc9ba8f02647035e2d9ebcc29b32c586f90a10fd Mon Sep 17 00:00:00 2001 From: default Date: Tue, 19 Nov 2024 06:59:53 +0100 Subject: [PATCH 12/60] More usage of get_in_reply_to(). --- data.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data.c b/data.c index 1cd69a5..f2fa521 100644 --- a/data.c +++ b/data.c @@ -799,7 +799,7 @@ int _object_add(const char *id, const xs_dict *obj, int ow) fclose(f); /* does this object has a parent? */ - const char *in_reply_to = xs_dict_get(obj, "inReplyTo"); + const char *in_reply_to = get_in_reply_to(obj); if (!xs_is_null(in_reply_to) && *in_reply_to) { /* update the children index of the parent */ From 3a9118524831d1f3a9168034c1655e515f162020 Mon Sep 17 00:00:00 2001 From: default Date: Tue, 19 Nov 2024 15:51:32 +0100 Subject: [PATCH 13/60] Updated documentation. --- doc/snac.5 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/snac.5 b/doc/snac.5 index 1b28d25..0168430 100644 --- a/doc/snac.5 +++ b/doc/snac.5 @@ -209,6 +209,8 @@ web interface. .It Pa history/ This directory contains generated HTML files. They may be snapshots of the local timeline in previous months or other cached data. +.It Pa server.pid +This file stores the server PID in a single text line. .El .Sh SEE ALSO .Xr snac 1 , From 781c0edcc67b317fbba147a22da614dc3599c0d2 Mon Sep 17 00:00:00 2001 From: default Date: Wed, 20 Nov 2024 15:59:36 +0100 Subject: [PATCH 14/60] mastoapi: fixed crash in posts without 'published' field. --- mastoapi.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/mastoapi.c b/mastoapi.c index 1dd91dc..a529990 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -827,7 +827,16 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg) st = xs_dict_append(st, "url", id); st = xs_dict_append(st, "account", acct); - xs *fd = mastoapi_date(xs_dict_get(msg, "published")); + const char *published = xs_dict_get(msg, "published"); + xs *fd = NULL; + + if (published) + fd = mastoapi_date(published); + else { + xs *p = xs_str_iso_date(0); + fd = mastoapi_date(p); + } + st = xs_dict_append(st, "created_at", fd); { From c507e30607812c4c2ab2dd27835eb5f2ea6a9742 Mon Sep 17 00:00:00 2001 From: default Date: Wed, 20 Nov 2024 18:10:02 +0100 Subject: [PATCH 15/60] New function xs_unicode_right_to_left(). --- xs_unicode.h | 24 ++++++++++++++++++++++++ xs_unicode_tbl.h | 15 +++++++++++++++ xs_version.h | 2 +- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/xs_unicode.h b/xs_unicode.h index 9663190..cfcd8ff 100644 --- a/xs_unicode.h +++ b/xs_unicode.h @@ -21,6 +21,7 @@ int xs_unicode_nfd(unsigned int cpoint, unsigned int *base, unsigned int *diac); int xs_unicode_nfc(unsigned int base, unsigned int diac, unsigned int *cpoint); int xs_unicode_is_alpha(unsigned int cpoint); + int xs_unicode_is_right_to_left(unsigned int cpoint); #ifdef _XS_H xs_str *xs_utf8_insert(xs_str *str, unsigned int cpoint, int *offset); @@ -350,6 +351,29 @@ int xs_unicode_is_alpha(unsigned int cpoint) } +int xs_unicode_is_right_to_left(unsigned int cpoint) +/* checks if a codepoint is a right-to-left letter */ +{ + int b = 0; + int t = xs_countof(xs_unicode_right_to_left_table) / 2 - 1; + + while (t >= b) { + int n = (b + t) / 2; + unsigned int *p = &xs_unicode_right_to_left_table[n * 2]; + + if (cpoint < p[0]) + t = n - 1; + else + if (cpoint > p[1]) + b = n + 1; + else + return 1; + } + + return 0; +} + + #ifdef _XS_H xs_str *xs_utf8_to_upper(const char *str) diff --git a/xs_unicode_tbl.h b/xs_unicode_tbl.h index ecd537b..ee8ce97 100644 --- a/xs_unicode_tbl.h +++ b/xs_unicode_tbl.h @@ -726,5 +726,20 @@ static unsigned int xs_unicode_alpha_table[] = { 0x1E7E0, 0x1E8C4, 0x1E900, 0x1E943, 0x1EE00, 0x1EEBB, 0x20000, 0x323AF, }; +static unsigned int xs_unicode_right_to_left_table[] = { + 0x05BE, 0x05BE, 0x05C0, 0x05C0, 0x05C3, 0x05C3, 0x05C6, 0x05C6, + 0x05D0, 0x05F4, 0x0608, 0x0608, 0x060B, 0x060B, 0x060D, 0x060D, + 0x061B, 0x064A, 0x066D, 0x066F, 0x0671, 0x06D5, 0x06E5, 0x06E6, + 0x06EE, 0x06EF, 0x06FA, 0x0710, 0x0712, 0x072F, 0x074D, 0x07A5, + 0x07B1, 0x07EA, 0x07F4, 0x07F5, 0x07FA, 0x07FA, 0x07FE, 0x0815, + 0x081A, 0x081A, 0x0824, 0x0824, 0x0828, 0x0828, 0x0830, 0x0858, + 0x085E, 0x088E, 0x08A0, 0x08C9, 0x200F, 0x200F, 0xFB1D, 0xFB1D, + 0xFB1F, 0xFB28, 0xFB2A, 0xFD3D, 0xFD50, 0xFDC7, 0xFDF0, 0xFDFC, + 0xFE70, 0xFEFC, 0x10800, 0x1091B, 0x10920, 0x10A00, 0x10A10, 0x10A35, + 0x10A40, 0x10AE4, 0x10AEB, 0x10B35, 0x10B40, 0x10D23, 0x10E80, 0x10EA9, + 0x10EAD, 0x10EB1, 0x10F00, 0x10F45, 0x10F51, 0x10F81, 0x10F86, 0x10FF6, + 0x1E800, 0x1E8CF, 0x1E900, 0x1E943, 0x1E94B, 0x1EEBB, +}; + #endif /* _XS_UNICODE_TBL_H */ diff --git a/xs_version.h b/xs_version.h index 84f7c5b..7c4246b 100644 --- a/xs_version.h +++ b/xs_version.h @@ -1 +1 @@ -/* 35997d2dbc505320a62d3130daa95f638be8bb26 2024-11-05T16:47:36+01:00 */ +/* ab0749f821f1c98d16cbec53201bdf2ba2a24a43 2024-11-20T17:02:42+01:00 */ From b0dd7f59a2228898b0f70fc931cf59c3f4991762 Mon Sep 17 00:00:00 2001 From: default Date: Thu, 21 Nov 2024 07:21:32 +0100 Subject: [PATCH 16/60] Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f46f958..6ec959c 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,17 @@ # Release Notes +## 2.64 + +Some tweaks for better integration with https://bsky.brid.gy (the BlueSky bridge by brid.gy). + +A corner case bug in the media proxying code has been fixed. + +Hashtags can now include underscores. + +The server now creates a pidfile inside the data directory. + +Mastodon API: fixed a crash in the notification code, fixed autocapitalization in the OAuth login field (contributed by fkooman). + ## 2.63 The server can now act as a proxy for all image, audio or video media coming from other account's posts (both from the Web UI and the Mastodon API). This way, other servers will see media requests coming from the server IP, not the user's, improving privacy. This is controlled by setting the `proxy_media` boolean field to `server.json` to true. From 775fb14706d92af9106b2ac0b87b4c4bd1f5b3ca Mon Sep 17 00:00:00 2001 From: default Date: Thu, 21 Nov 2024 15:12:22 +0100 Subject: [PATCH 17/60] Version 2.64 RELEASED. --- snac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snac.h b/snac.h index 8020978..0d62eb2 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2024 grunfink et al. / MIT license */ -#define VERSION "2.63" +#define VERSION "2.64" #define USER_AGENT "snac/" VERSION From 09d268495c12d3c00a7293fbf445830bcc9d74c3 Mon Sep 17 00:00:00 2001 From: default Date: Sat, 23 Nov 2024 17:08:57 +0100 Subject: [PATCH 18/60] The 'metadata' field in user.json is now a string instead of a dict. --- activitypub.c | 25 ++++++++++++++++- html.c | 74 +++++++++++++++++++++++++++++++-------------------- 2 files changed, 69 insertions(+), 30 deletions(-) diff --git a/activitypub.c b/activitypub.c index 473675d..8c0c423 100644 --- a/activitypub.c +++ b/activitypub.c @@ -1218,7 +1218,30 @@ xs_dict *msg_actor(snac *snac) } /* add the metadata as attachments of PropertyValue */ - const xs_dict *metadata = xs_dict_get(snac->config, "metadata"); + xs *metadata = NULL; + const xs_dict *md = xs_dict_get(snac->config, "metadata"); + + if (xs_type(md) == XSTYPE_DICT) + metadata = xs_dup(md); + else + if (xs_type(md) == XSTYPE_STRING) { + metadata = xs_dict_new(); + xs *l = xs_split(md, "\r\n"); + const char *ll; + + xs_list_foreach(l, ll) { + xs *kv = xs_split_n(ll, "=", 1); + const char *k = xs_list_get(kv, 0); + const char *v = xs_list_get(kv, 1); + + if (k && v) { + xs *kk = xs_strip_i(xs_dup(k)); + xs *vv = xs_strip_i(xs_dup(v)); + metadata = xs_dict_set(metadata, kk, vv); + } + } + } + if (xs_type(metadata) == XSTYPE_DICT) { xs *attach = xs_list_new(); const xs_str *k; diff --git a/html.c b/html.c index 74de47d..7f7c2c7 100644 --- a/html.c +++ b/html.c @@ -843,7 +843,31 @@ static xs_html *html_user_body(snac *user, int read_only) xs_html_add(top_user, top_user_bio); - const xs_dict *metadata = xs_dict_get(user->config, "metadata"); + xs *metadata = NULL; + const xs_dict *md = xs_dict_get(user->config, "metadata"); + + if (xs_type(md) == XSTYPE_DICT) + metadata = xs_dup(md); + else + if (xs_type(md) == XSTYPE_STRING) { + /* convert to dict for easier iteration */ + metadata = xs_dict_new(); + xs *l = xs_split(md, "\r\n"); + const char *ll; + + xs_list_foreach(l, ll) { + xs *kv = xs_split_n(ll, "=", 1); + const char *k = xs_list_get(kv, 0); + const char *v = xs_list_get(kv, 1); + + if (k && v) { + xs *kk = xs_strip_i(xs_dup(k)); + xs *vv = xs_strip_i(xs_dup(v)); + metadata = xs_dict_set(metadata, kk, vv); + } + } + } + if (xs_type(metadata) == XSTYPE_DICT) { const xs_str *k; const xs_str *v; @@ -1026,19 +1050,28 @@ xs_html *html_top_controls(snac *snac) const xs_val *auto_boost = xs_dict_get(snac->config, "auto_boost"); const xs_val *coll_thrds = xs_dict_get(snac->config, "collapse_threads"); - xs *metadata = xs_str_new(NULL); + xs *metadata = NULL; const xs_dict *md = xs_dict_get(snac->config, "metadata"); - const xs_str *k; - const xs_str *v; - int c = 0; - while (xs_dict_next(md, &k, &v, &c)) { - xs *kp = xs_fmt("%s=%s", k, v); + if (xs_type(md) == XSTYPE_DICT) { + const xs_str *k; + const xs_str *v; - if (*metadata) - metadata = xs_str_cat(metadata, "\n"); - metadata = xs_str_cat(metadata, kp); + metadata = xs_str_new(NULL); + + xs_dict_foreach(md, k, v) { + xs *kp = xs_fmt("%s=%s", k, v); + + if (*metadata) + metadata = xs_str_cat(metadata, "\n"); + metadata = xs_str_cat(metadata, kp); + } } + else + if (xs_type(md) == XSTYPE_STRING) + metadata = xs_dup(md); + else + metadata = xs_str_new(NULL); xs *user_setup_action = xs_fmt("%s/admin/user-setup", snac->actor); @@ -3706,25 +3739,8 @@ int html_post_handler(const xs_dict *req, const char *q_path, else snac.config = xs_dict_set(snac.config, "collapse_threads", xs_stock(XSTYPE_FALSE)); - if ((v = xs_dict_get(p_vars, "metadata")) != NULL) { - /* split the metadata and store it as a dict */ - xs_dict *md = xs_dict_new(); - xs *l = xs_split(v, "\n"); - xs_list *p = l; - const xs_str *kp; - - while (xs_list_iter(&p, &kp)) { - xs *kpl = xs_split_n(kp, "=", 1); - if (xs_list_len(kpl) == 2) { - xs *k2 = xs_strip_i(xs_dup(xs_list_get(kpl, 0))); - xs *v2 = xs_strip_i(xs_dup(xs_list_get(kpl, 1))); - - md = xs_dict_set(md, k2, v2); - } - } - - snac.config = xs_dict_set(snac.config, "metadata", md); - } + if ((v = xs_dict_get(p_vars, "metadata")) != NULL) + snac.config = xs_dict_set(snac.config, "metadata", v); /* uploads */ const char *uploads[] = { "avatar", "header", NULL }; From c88d7e72f0531114e7e000f79ee6ce9a11f23952 Mon Sep 17 00:00:00 2001 From: default Date: Sat, 23 Nov 2024 17:16:31 +0100 Subject: [PATCH 19/60] Minor tweak to new metadata. --- activitypub.c | 2 +- html.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/activitypub.c b/activitypub.c index 8c0c423..8bcb6b0 100644 --- a/activitypub.c +++ b/activitypub.c @@ -1226,7 +1226,7 @@ xs_dict *msg_actor(snac *snac) else if (xs_type(md) == XSTYPE_STRING) { metadata = xs_dict_new(); - xs *l = xs_split(md, "\r\n"); + xs *l = xs_split(md, "\n"); const char *ll; xs_list_foreach(l, ll) { diff --git a/html.c b/html.c index 7f7c2c7..df9e4e3 100644 --- a/html.c +++ b/html.c @@ -852,7 +852,7 @@ static xs_html *html_user_body(snac *user, int read_only) if (xs_type(md) == XSTYPE_STRING) { /* convert to dict for easier iteration */ metadata = xs_dict_new(); - xs *l = xs_split(md, "\r\n"); + xs *l = xs_split(md, "\n"); const char *ll; xs_list_foreach(l, ll) { From 36c3030231ed806c29c182e38cb0957993c835d1 Mon Sep 17 00:00:00 2001 From: default Date: Sat, 23 Nov 2024 17:33:49 +0100 Subject: [PATCH 20/60] In user_persist(), only publish the actor to the world if a relevant field has changed. This way, changing user preferences does not trigger an automatic send storm. --- data.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/data.c b/data.c index f2fa521..bbebdb8 100644 --- a/data.c +++ b/data.c @@ -336,6 +336,32 @@ int user_persist(snac *snac, int publish) xs *bfn = xs_fmt("%s.bak", fn); FILE *f; + if (publish) { + /* check if any of the relevant fields have really changed */ + if ((f = fopen(fn, "r")) != NULL) { + xs *old = xs_json_load(f); + fclose(f); + + if (old != NULL) { + int nw = 0; + const char *fields[] = { "header", "avatar", "name", "bio", "metadata", NULL }; + + for (int n = 0; fields[n]; n++) { + const char *of = xs_dict_get(old, fields[n]); + const char *nf = xs_dict_get(snac->config, fields[n]); + + if (xs_type(of) != XSTYPE_STRING || xs_type(nf) != XSTYPE_STRING || strcmp(of, nf)) { + nw = 1; + break; + } + } + + if (!nw) + publish = 0; + } + } + } + rename(fn, bfn); if ((f = fopen(fn, "w")) != NULL) { From 35b35ec3afff47be872004448e6ba81a6a339131 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 08:03:51 +0100 Subject: [PATCH 21/60] Return the 'manuallyApprovesFollowers' actor field according to user configuration. --- activitypub.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/activitypub.c b/activitypub.c index 8bcb6b0..649b1f2 100644 --- a/activitypub.c +++ b/activitypub.c @@ -1287,6 +1287,10 @@ xs_dict *msg_actor(snac *snac) msg = xs_dict_set(msg, "alsoKnownAs", loaka); } + const xs_val *manually = xs_dict_get(snac->config, "approve_followers"); + msg = xs_dict_set(msg, "manuallyApprovesFollowers", + xs_stock(xs_is_true(manually) ? XSTYPE_TRUE : XSTYPE_FALSE)); + return msg; } From 7287776fd1f659619e211ee482ba0d6d64ddbf0b Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 08:17:38 +0100 Subject: [PATCH 22/60] New function pending_add(). --- activitypub.c | 33 +++++++++++++++++++++------------ data.c | 22 ++++++++++++++++++++++ snac.h | 2 ++ 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/activitypub.c b/activitypub.c index 649b1f2..3ab093f 100644 --- a/activitypub.c +++ b/activitypub.c @@ -1927,22 +1927,31 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) object_add(actor, actor_obj); } - xs *f_msg = xs_dup(msg); - xs *reply = msg_accept(snac, f_msg, actor); + if (xs_is_true(xs_dict_get(snac->config, "approve_followers"))) { + pending_add(snac, actor, msg); - post_message(snac, actor, reply); + snac_log(snac, xs_fmt("new pending follower approval %s", actor)); + } + else { + /* automatic following */ + xs *f_msg = xs_dup(msg); + xs *reply = msg_accept(snac, f_msg, actor); - if (xs_is_null(xs_dict_get(f_msg, "published"))) { - /* add a date if it doesn't include one (Mastodon) */ - xs *date = xs_str_utctime(0, ISO_DATE_SPEC); - f_msg = xs_dict_set(f_msg, "published", date); + post_message(snac, actor, reply); + + if (xs_is_null(xs_dict_get(f_msg, "published"))) { + /* add a date if it doesn't include one (Mastodon) */ + xs *date = xs_str_utctime(0, ISO_DATE_SPEC); + f_msg = xs_dict_set(f_msg, "published", date); + } + + timeline_add(snac, id, f_msg); + + follower_add(snac, actor); + + snac_log(snac, xs_fmt("new follower %s", actor)); } - timeline_add(snac, id, f_msg); - - follower_add(snac, actor); - - snac_log(snac, xs_fmt("new follower %s", actor)); do_notify = 1; } else diff --git a/data.c b/data.c index bbebdb8..fa631e1 100644 --- a/data.c +++ b/data.c @@ -1202,6 +1202,28 @@ xs_list *follower_list(snac *snac) } +/** pending followers **/ + +int pending_add(snac *user, const char *actor, const xs_dict *msg) +/* stores the follow message for later confirmation */ +{ + xs *dir = xs_fmt("%s/pending", user->basedir); + xs *md5 = xs_md5_hex(actor, strlen(actor)); + xs *fn = xs_fmt("%s/%s.json", dir, md5); + FILE *f; + + mkdirx(dir); + + if ((f = fopen(fn, "w")) == NULL) + return -1; + + xs_json_dump(msg, 4, f); + fclose(f); + + return 0; +} + + /** timeline **/ double timeline_mtime(snac *snac) diff --git a/snac.h b/snac.h index 0d62eb2..bcddab4 100644 --- a/snac.h +++ b/snac.h @@ -141,6 +141,8 @@ int follower_del(snac *snac, const char *actor); int follower_check(snac *snac, const char *actor); xs_list *follower_list(snac *snac); +int pending_add(snac *user, const char *actor, const xs_dict *msg); + double timeline_mtime(snac *snac); int timeline_touch(snac *snac); int timeline_here(snac *snac, const char *md5); From b91177cb464ffdb118d50569592be3f9789dbef6 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 08:31:01 +0100 Subject: [PATCH 23/60] New function pending_get() and pending_list(). --- data.c | 38 ++++++++++++++++++++++++++++++++++++++ snac.h | 2 ++ 2 files changed, 40 insertions(+) diff --git a/data.c b/data.c index fa631e1..79c7001 100644 --- a/data.c +++ b/data.c @@ -1224,6 +1224,44 @@ int pending_add(snac *user, const char *actor, const xs_dict *msg) } +xs_dict *pending_get(snac *user, const char *actor) +/* returns the pending follow confirmation for the actor */ +{ + xs *md5 = xs_md5_hex(actor, strlen(actor)); + xs *fn = xs_fmt("%s/pending/%s.json", user->basedir, md5); + xs_dict *msg = NULL; + FILE *f; + + if ((f = fopen(fn, "r")) != NULL) { + msg = xs_json_load(f); + fclose(f); + } + + return msg; +} + + +xs_list *pending_list(snac *user) +/* returns a list of pending follow confirmations */ +{ + xs *spec = xs_fmt("%s/pending/""*.json", user->basedir); + xs *l = xs_glob(spec, 0, 0); + xs_list *r = xs_list_new(); + const char *v; + + xs_list_foreach(l, v) { + const char *actor = xs_dict_get(v, "actor"); + + if (xs_type(actor) == XSTYPE_STRING) { + xs *md5 = xs_md5_hex(actor, strlen(actor)); + r = xs_list_append(r, md5); + } + } + + return r; +} + + /** timeline **/ double timeline_mtime(snac *snac) diff --git a/snac.h b/snac.h index bcddab4..25f7b74 100644 --- a/snac.h +++ b/snac.h @@ -142,6 +142,8 @@ int follower_check(snac *snac, const char *actor); xs_list *follower_list(snac *snac); int pending_add(snac *user, const char *actor, const xs_dict *msg); +xs_dict *pending_get(snac *user, const char *actor); +xs_list *pending_list(snac *user); double timeline_mtime(snac *snac); int timeline_touch(snac *snac); From 129049edf4752495f768b247253cb7ffc848b0cc Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 08:37:19 +0100 Subject: [PATCH 24/60] New function pending_check() and pending_del(). --- data.c | 20 ++++++++++++++++++++ snac.h | 2 ++ 2 files changed, 22 insertions(+) diff --git a/data.c b/data.c index 79c7001..8bc35aa 100644 --- a/data.c +++ b/data.c @@ -1224,6 +1224,16 @@ int pending_add(snac *user, const char *actor, const xs_dict *msg) } +int pending_check(snac *user, const char *actor) +/* checks if there is a pending follow confirmation for the actor */ +{ + xs *md5 = xs_md5_hex(actor, strlen(actor)); + xs *fn = xs_fmt("%s/pending/%s.json", user->basedir, md5); + + return mtime(fn) != 0; +} + + xs_dict *pending_get(snac *user, const char *actor) /* returns the pending follow confirmation for the actor */ { @@ -1241,6 +1251,16 @@ xs_dict *pending_get(snac *user, const char *actor) } +void pending_del(snac *user, const char *actor) +/* deletes a pending follow confirmation for the actor */ +{ + xs *md5 = xs_md5_hex(actor, strlen(actor)); + xs *fn = xs_fmt("%s/pending/%s.json", user->basedir, md5); + + unlink(fn); +} + + xs_list *pending_list(snac *user) /* returns a list of pending follow confirmations */ { diff --git a/snac.h b/snac.h index 25f7b74..b4cb525 100644 --- a/snac.h +++ b/snac.h @@ -142,7 +142,9 @@ int follower_check(snac *snac, const char *actor); xs_list *follower_list(snac *snac); int pending_add(snac *user, const char *actor, const xs_dict *msg); +int pending_check(snac *user, const char *actor); xs_dict *pending_get(snac *user, const char *actor); +void pending_del(snac *user, const char *actor); xs_list *pending_list(snac *user); double timeline_mtime(snac *snac); From 96576d2c5df41b91e915a31d5c0c039f4852f583 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 08:46:26 +0100 Subject: [PATCH 25/60] On unfollow, also delete from the pending follow list. --- activitypub.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/activitypub.c b/activitypub.c index 3ab093f..7a7e346 100644 --- a/activitypub.c +++ b/activitypub.c @@ -1972,6 +1972,11 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req) snac_log(snac, xs_fmt("no longer following us %s", actor)); do_notify = 1; } + else + if (pending_check(snac, actor)) { + pending_del(snac, actor); + snac_log(snac, xs_fmt("cancelled pending follow from %s", actor)); + } else snac_log(snac, xs_fmt("error deleting follower %s", actor)); } From 9fb84bcb3aa4c6a8061e4b892dc5bd1826b44715 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 08:54:43 +0100 Subject: [PATCH 26/60] The people page shows 'Approve' and 'Discard' buttons for pending follows. --- data.c | 20 +++++++++++++++----- html.c | 29 +++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/data.c b/data.c index 8bc35aa..82a1776 100644 --- a/data.c +++ b/data.c @@ -1270,12 +1270,22 @@ xs_list *pending_list(snac *user) const char *v; xs_list_foreach(l, v) { - const char *actor = xs_dict_get(v, "actor"); + FILE *f; + xs *msg = NULL; - if (xs_type(actor) == XSTYPE_STRING) { - xs *md5 = xs_md5_hex(actor, strlen(actor)); - r = xs_list_append(r, md5); - } + if ((f = fopen(v, "r")) == NULL) + continue; + + msg = xs_json_load(f); + fclose(f); + + if (msg == NULL) + continue; + + const char *actor = xs_dict_get(msg, "actor"); + + if (xs_type(actor) == XSTYPE_STRING) + r = xs_list_append(r, actor); } return r; diff --git a/html.c b/html.c index df9e4e3..2d556e8 100644 --- a/html.c +++ b/html.c @@ -2470,10 +2470,9 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t, cons xs_html_tag("summary", xs_html_text("...")))); - xs_list *p = list; const char *actor_id; - while (xs_list_iter(&p, &actor_id)) { + xs_list_foreach(list, actor_id) { xs *md5 = xs_md5_hex(actor_id, strlen(actor_id)); xs *actor = NULL; @@ -2542,6 +2541,14 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t, cons html_button("limit", L("Limit"), L("Block announces (boosts) from this user"))); } + if (pending_check(snac, actor_id)) { + xs_html_add(form, + html_button("approve", L("Approve"), + L("Approve this follow request"))); + + xs_html_add(form, + html_button("discard", L("Discard"), L("Discard this follow request"))); + } else { xs_html_add(form, html_button("follow", L("Follow"), @@ -2596,13 +2603,23 @@ xs_str *html_people(snac *user) xs *wing = following_list(user); xs *wers = follower_list(user); + xs_html *lists = xs_html_tag("div", + xs_html_attr("class", "snac-posts")); + + if (xs_is_true(xs_dict_get(user->config, "approve_followers"))) { + xs *pending = pending_list(user); + xs_html_add(lists, + html_people_list(user, pending, L("Pending follow confirmations"), "p", proxy)); + } + + xs_html_add(lists, + html_people_list(user, wing, L("People you follow"), "i", proxy), + html_people_list(user, wers, L("People that follow you"), "e", proxy)); + xs_html *html = xs_html_tag("html", html_user_head(user, NULL, NULL), xs_html_add(html_user_body(user, 0), - xs_html_tag("div", - xs_html_attr("class", "snac-posts"), - html_people_list(user, wing, L("People you follow"), "i", proxy), - html_people_list(user, wers, L("People that follow you"), "e", proxy)), + lists, html_footer())); return xs_html_render_s(html, "\n"); From 0ddbe7e53b8331d2de42ad29ceca35b715f1c121 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 08:58:02 +0100 Subject: [PATCH 27/60] Pending follow notifications are shown as "Follow Request". --- html.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/html.c b/html.c index 2d556e8..990fb94 100644 --- a/html.c +++ b/html.c @@ -2711,6 +2711,9 @@ xs_str *html_notifications(snac *user, int skip, int show) label = wrk; } } + else + if (strcmp(type, "Follow") == 0 && pending_check(user, actor_id)) + label = L("Follow Request"); xs *s_date = xs_crop_i(xs_dup(date), 0, 10); From 0db1cf05d1013f9b1e972ae79822f9eda4dede59 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 09:14:59 +0100 Subject: [PATCH 28/60] New web actions "Approve" and "Discard". --- html.c | 28 ++++++++++++++++++++++++++++ snac.h | 1 + 2 files changed, 29 insertions(+) diff --git a/html.c b/html.c index 990fb94..576d94f 100644 --- a/html.c +++ b/html.c @@ -3699,6 +3699,34 @@ int html_post_handler(const xs_dict *req, const char *q_path, unbookmark(&snac, id); timeline_touch(&snac); } + else + if (strcmp(action, L("Approve")) == 0) { /** **/ + xs *fwreq = pending_get(&snac, actor); + + if (fwreq != NULL) { + xs *reply = msg_accept(&snac, fwreq, actor); + + enqueue_message(&snac, reply); + + if (xs_is_null(xs_dict_get(fwreq, "published"))) { + /* add a date if it doesn't include one (Mastodon) */ + xs *date = xs_str_utctime(0, ISO_DATE_SPEC); + fwreq = xs_dict_set(fwreq, "published", date); + } + + timeline_add(&snac, xs_dict_get(fwreq, "id"), fwreq); + + follower_add(&snac, actor); + + pending_del(&snac, actor); + + snac_log(&snac, xs_fmt("new follower %s", actor)); + } + } + else + if (strcmp(action, L("Discard")) == 0) { /** **/ + pending_del(&snac, actor); + } else status = HTTP_STATUS_NOT_FOUND; diff --git a/snac.h b/snac.h index b4cb525..fc7a657 100644 --- a/snac.h +++ b/snac.h @@ -320,6 +320,7 @@ xs_dict *msg_update(snac *snac, const xs_dict *object); xs_dict *msg_ping(snac *user, const char *rcpt); xs_dict *msg_pong(snac *user, const char *rcpt, const char *object); xs_dict *msg_move(snac *user, const char *new_account); +xs_dict *msg_accept(snac *snac, const xs_val *object, const char *to); xs_dict *msg_question(snac *user, const char *content, xs_list *attach, const xs_list *opts, int multiple, int end_secs); From 93f70d6759e0bc67326ca78283cd9627f8ca4580 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 09:25:59 +0100 Subject: [PATCH 29/60] Added web UI for the 'approve_followers' checkbox. --- html.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/html.c b/html.c index 576d94f..9c04b99 100644 --- a/html.c +++ b/html.c @@ -1049,6 +1049,7 @@ xs_html *html_top_controls(snac *snac) const xs_val *a_private = xs_dict_get(snac->config, "private"); const xs_val *auto_boost = xs_dict_get(snac->config, "auto_boost"); const xs_val *coll_thrds = xs_dict_get(snac->config, "collapse_threads"); + const xs_val *pending = xs_dict_get(snac->config, "approve_followers"); xs *metadata = NULL; const xs_dict *md = xs_dict_get(snac->config, "metadata"); @@ -1220,6 +1221,15 @@ xs_html *html_top_controls(snac *snac) xs_html_tag("label", xs_html_attr("for", "collapse_threads"), xs_html_text(L("Collapse top threads by default")))), + xs_html_tag("p", + xs_html_sctag("input", + xs_html_attr("type", "checkbox"), + xs_html_attr("name", "approve_followers"), + xs_html_attr("id", "approve_followers"), + xs_html_attr(xs_is_true(pending) ? "checked" : "", NULL)), + xs_html_tag("label", + xs_html_attr("for", "approve_followers"), + xs_html_text(L("Follow requests must be approved")))), xs_html_tag("p", xs_html_text(L("Profile metadata (key=value pairs in each line):")), xs_html_sctag("br", NULL), @@ -3786,6 +3796,10 @@ int html_post_handler(const xs_dict *req, const char *q_path, snac.config = xs_dict_set(snac.config, "collapse_threads", xs_stock(XSTYPE_TRUE)); else snac.config = xs_dict_set(snac.config, "collapse_threads", xs_stock(XSTYPE_FALSE)); + if ((v = xs_dict_get(p_vars, "approve_followers")) != NULL && strcmp(v, "on") == 0) + snac.config = xs_dict_set(snac.config, "approve_followers", xs_stock(XSTYPE_TRUE)); + else + snac.config = xs_dict_set(snac.config, "approve_followers", xs_stock(XSTYPE_FALSE)); if ((v = xs_dict_get(p_vars, "metadata")) != NULL) snac.config = xs_dict_set(snac.config, "metadata", v); From 76654f1bfa76cef43fbb88d316b4c677fd8a89bb Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 09:26:54 +0100 Subject: [PATCH 30/60] Bumped version. --- snac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snac.h b/snac.h index fc7a657..7a262a9 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2024 grunfink et al. / MIT license */ -#define VERSION "2.64" +#define VERSION "2.65-dev" #define USER_AGENT "snac/" VERSION From 9fee9708090b752a619e5f855870d46c486886d8 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 09:30:40 +0100 Subject: [PATCH 31/60] Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 6ec959c..10ba384 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,9 @@ # Release Notes +## UNRELEASED + +Added a new user option to disable automatic follow confirmations (follow requests must be manually approved from the people page). + ## 2.64 Some tweaks for better integration with https://bsky.brid.gy (the BlueSky bridge by brid.gy). From a2665c3cc9bb94a7969a904bfcd210e20cf038e7 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 09:37:59 +0100 Subject: [PATCH 32/60] Minor tweak to user_persist(). --- data.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/data.c b/data.c index 82a1776..4e5851a 100644 --- a/data.c +++ b/data.c @@ -350,6 +350,9 @@ int user_persist(snac *snac, int publish) const char *of = xs_dict_get(old, fields[n]); const char *nf = xs_dict_get(snac->config, fields[n]); + if (of == NULL && nf == NULL) + continue; + if (xs_type(of) != XSTYPE_STRING || xs_type(nf) != XSTYPE_STRING || strcmp(of, nf)) { nw = 1; break; From 45f29ede8779bf0e599e3e07089fc4ecef54d627 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 10:33:42 +0100 Subject: [PATCH 33/60] Updated documentation. --- doc/snac.1 | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/doc/snac.1 b/doc/snac.1 index 4c40ac9..6d719cb 100644 --- a/doc/snac.1 +++ b/doc/snac.1 @@ -129,6 +129,24 @@ Just what it says in the tin. This is to mitigate spammers coming from Fediverse instances with lax / open registration processes. Please take note that this also avoids possibly legitimate people trying to contact you. +.It This account is a bot +Set this checkbox if this account behaves like a bot (i.e. +posts are automatically generated). +.It Auto-boost all mentions to this account +If this toggle is set, all mentions to this account are boosted +to all followers. This can be used to create groups. +.It This account is private +If this toggle is set, posts are not published via the public +web interface, only via the ActivityPub protocol. +.It Collapse top threads by default +If this toggle is set, the private timeline will always show +conversations collapsed by default. This allows easier navigation +through long threads. +.It Follow requests must be approved +If this toggle is set, follow requests are not automatically +accepted, but notified and stored for later review. Pending +follow requests will be shown in the people page to be +approved or discarded. .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. @@ -349,4 +367,4 @@ See the LICENSE file for details. .Sh CAVEATS Use the Fediverse sparingly. Don't fear the MUTE button. .Sh BUGS -Probably plenty. Some issues may be even documented in the TODO.md file. +Probably many. Some issues may be even documented in the TODO.md file. From e0fb6a70648297bc02a64e6d4390874a9c6085db Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 18:36:33 +0100 Subject: [PATCH 34/60] Fixed bio processing in the public page. --- html.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/html.c b/html.c index 9c04b99..05fa2cf 100644 --- a/html.c +++ b/html.c @@ -829,16 +829,16 @@ static xs_html *html_user_body(snac *user, int read_only) } if (read_only) { - xs *es1 = encode_html(xs_dict_get(user->config, "bio")); xs *tags = xs_list_new(); - xs *bio1 = not_really_markdown(es1, NULL, &tags); + xs *bio1 = not_really_markdown(xs_dict_get(user->config, "bio"), NULL, &tags); xs *bio2 = process_tags(user, bio1, &tags); + xs *bio3 = sanitize(bio2); - bio2 = replace_shortnames(bio2, tags, 2, proxy); + bio3 = replace_shortnames(bio3, tags, 2, proxy); xs_html *top_user_bio = xs_html_tag("div", xs_html_attr("class", "p-note snac-top-user-bio"), - xs_html_raw(bio2)); /* already sanitized */ + xs_html_raw(bio3)); /* already sanitized */ xs_html_add(top_user, top_user_bio); From a5a7ab9ba5b35659148ee6a20ddbd512c5aa53fd Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 18:49:17 +0100 Subject: [PATCH 35/60] Backport from xs. --- xs_url.h | 8 ++++---- xs_version.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/xs_url.h b/xs_url.h index cd540fa..ac43585 100644 --- a/xs_url.h +++ b/xs_url.h @@ -106,13 +106,13 @@ xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *hea if (xs_list_len(l1) != 2) return NULL; - boundary = xs_dup(xs_list_get(l1, 1)); + xs *t_boundary = xs_dup(xs_list_get(l1, 1)); /* Tokodon sends the boundary header with double quotes surrounded */ - if (xs_between("\"", boundary, "\"") != 0) - boundary = xs_strip_chars_i(boundary, "\""); + if (xs_between("\"", t_boundary, "\"") != 0) + t_boundary = xs_strip_chars_i(t_boundary, "\""); - boundary = xs_fmt("--%s", boundary); + boundary = xs_fmt("--%s", t_boundary); } bsz = strlen(boundary); diff --git a/xs_version.h b/xs_version.h index 7c4246b..770366a 100644 --- a/xs_version.h +++ b/xs_version.h @@ -1 +1 @@ -/* ab0749f821f1c98d16cbec53201bdf2ba2a24a43 2024-11-20T17:02:42+01:00 */ +/* 297f71e198be7819213e9122e1e78c3b963111bc 2024-11-24T18:48:42+01:00 */ From 3f8db422ce66912a5414968eeae867f9ac12d946 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 20:21:48 +0100 Subject: [PATCH 36/60] The search box also searches accounts (via webfinger). --- html.c | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/html.c b/html.c index 05fa2cf..5774894 100644 --- a/html.c +++ b/html.c @@ -2972,6 +2972,48 @@ int html_get_handler(const xs_dict *req, const char *q_path, const char *q = xs_dict_get(q_vars, "q"); if (q && *q) { + if (xs_regex_match(q, "^@?[a-zA-Z0-9_]+@[a-zA-Z0-9-]+\\.")) { + /** search account **/ + xs *actor = NULL; + xs *acct = NULL; + xs *l = xs_list_new(); + xs_html *page = NULL; + + if (valid_status(webfinger_request(q, &actor, &acct))) { + xs *actor_obj = NULL; + + if (valid_status(actor_request(&snac, actor, &actor_obj))) { + actor_add(actor, actor_obj); + + /* create a people list with only one element */ + l = xs_list_append(xs_list_new(), actor); + + xs *title = xs_fmt(L("Search results for account %s"), q); + + page = html_people_list(&snac, l, title, "wf", NULL); + } + } + + if (page == NULL) { + xs *title = xs_fmt(L("Account %s not found"), q); + + page = xs_html_tag("div", + xs_html_tag("h2", + xs_html_attr("class", "snac-header"), + xs_html_text(title))); + } + + xs_html *html = xs_html_tag("html", + html_user_head(&snac, NULL, NULL), + xs_html_add(html_user_body(&snac, 0), + page, + html_footer())); + + *body = xs_html_render_s(html, "\n"); + *b_size = strlen(*body); + status = HTTP_STATUS_OK; + } + else if (*q == '#') { /** search by tag **/ xs *tl = tag_search(q, skip, show + 1); From 6e32dd16d8e1801a7ba8149f5654dfcbb893c9fa Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 20:22:58 +0100 Subject: [PATCH 37/60] Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 10ba384..122b00f 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -4,6 +4,8 @@ Added a new user option to disable automatic follow confirmations (follow requests must be manually approved from the people page). +The search box also searches for accounts (via webfinger). + ## 2.64 Some tweaks for better integration with https://bsky.brid.gy (the BlueSky bridge by brid.gy). From 474a750e692370a3f0adbe71c15d6c7b927b7e4a Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 20:25:17 +0100 Subject: [PATCH 38/60] Changed help for the search box. --- html.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/html.c b/html.c index 5774894..b65a170 100644 --- a/html.c +++ b/html.c @@ -770,7 +770,7 @@ static xs_html *html_user_body(snac *user, int read_only) xs_html_sctag("input", xs_html_attr("type", "text"), xs_html_attr("name", "q"), - xs_html_attr("title", L("Search posts by content (regular expression) or #tag")), + xs_html_attr("title", L("Search posts by content (regular expression), @user@host accounts, or #tag")), xs_html_attr("placeholder", L("Content search"))))); } From 13647f790d967a046b9bd2f290541c37a0b7ea6f Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 20:33:22 +0100 Subject: [PATCH 39/60] Fixed missing else in html people list. --- html.c | 1 + 1 file changed, 1 insertion(+) diff --git a/html.c b/html.c index b65a170..f8fb876 100644 --- a/html.c +++ b/html.c @@ -2551,6 +2551,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t, cons html_button("limit", L("Limit"), L("Block announces (boosts) from this user"))); } + else if (pending_check(snac, actor_id)) { xs_html_add(form, html_button("approve", L("Approve"), From 15f352fe8311c0b435228a99e040fdc5e1c08ed4 Mon Sep 17 00:00:00 2001 From: default Date: Mon, 25 Nov 2024 08:15:34 +0100 Subject: [PATCH 40/60] When importing CSV lists, also follow that accounts. --- utils.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/utils.c b/utils.c index 4f5ac55..42200aa 100644 --- a/utils.c +++ b/utils.c @@ -782,6 +782,21 @@ void import_csv(snac *user) list_content(user, list_id, actor_md5, 1); snac_log(user, xs_fmt("Added %s to list %s", url, lname)); + + if (!following_check(user, url)) { + xs *msg = msg_follow(user, url); + + if (msg == NULL) { + snac_log(user, xs_fmt("Cannot follow %s -- server down?", acct)); + continue; + } + + following_add(user, url, msg); + + enqueue_output_by_actor(user, msg, url, 0); + + snac_log(user, xs_fmt("Following %s", url)); + } } else snac_log(user, xs_fmt("Webfinger error while adding %s to list %s", acct, lname)); From f549af800520b565cd95783c40ef886f7f0d8603 Mon Sep 17 00:00:00 2001 From: default Date: Mon, 25 Nov 2024 09:09:56 +0100 Subject: [PATCH 41/60] Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 122b00f..b960fe2 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,6 +6,8 @@ Added a new user option to disable automatic follow confirmations (follow reques The search box also searches for accounts (via webfinger). +The list CSV import process now follows all accounts in the list (so that [Mastodon Follow Packs](https://mastodonmigration.wordpress.com/?p=995) can be directly used). + ## 2.64 Some tweaks for better integration with https://bsky.brid.gy (the BlueSky bridge by brid.gy). From 2eff3ffb664d372d68ab2d953466d7ba946c96c2 Mon Sep 17 00:00:00 2001 From: default Date: Mon, 25 Nov 2024 10:08:50 +0100 Subject: [PATCH 42/60] Most CSV import code has been moved to their own functions. --- snac.h | 4 ++++ utils.c | 36 ++++++++++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/snac.h b/snac.h index 7a262a9..9e00351 100644 --- a/snac.h +++ b/snac.h @@ -404,6 +404,10 @@ void verify_links(snac *user); void export_csv(snac *user); int migrate_account(snac *user); + +void import_blocked_accounts_csv(snac *user, const char *fn); +void import_following_accounts_csv(snac *user, const char *fn); +void import_list_csv(snac *user, const char *fn); void import_csv(snac *user); typedef enum { diff --git a/utils.c b/utils.c index 42200aa..b0d8193 100644 --- a/utils.c +++ b/utils.c @@ -670,13 +670,11 @@ void export_csv(snac *user) } -void import_csv(snac *user) -/* import CSV files from Mastodon */ +void import_blocked_accounts_csv(snac *user, const char *fn) +/* imports a Mastodon CSV file of blocked accounts */ { FILE *f; - const char *fn; - fn = "blocked_accounts.csv"; if ((f = fopen(fn, "r")) != NULL) { snac_log(user, xs_fmt("Importing from %s...", fn)); @@ -704,8 +702,14 @@ void import_csv(snac *user) } else snac_log(user, xs_fmt("Cannot open file %s", fn)); +} + + +void import_following_accounts_csv(snac *user, const char *fn) +/* imports a Mastodon CSV file of accounts to follow */ +{ + FILE *f; - fn = "following_accounts.csv"; if ((f = fopen(fn, "r")) != NULL) { snac_log(user, xs_fmt("Importing from %s...", fn)); @@ -757,8 +761,14 @@ void import_csv(snac *user) } else snac_log(user, xs_fmt("Cannot open file %s", fn)); +} + + +void import_list_csv(snac *user, const char *fn) +/* imports a Mastodon CSV file list */ +{ + FILE *f; - fn = "lists.csv"; if ((f = fopen(fn, "r")) != NULL) { snac_log(user, xs_fmt("Importing from %s...", fn)); @@ -808,6 +818,20 @@ void import_csv(snac *user) } else snac_log(user, xs_fmt("Cannot open file %s", fn)); +} + + +void import_csv(snac *user) +/* import CSV files from Mastodon */ +{ + FILE *f; + const char *fn; + + import_blocked_accounts_csv(user, "blocked_accounts.csv"); + + import_following_accounts_csv(user, "following_accounts.csv"); + + import_list_csv(user, "lists.csv"); fn = "bookmarks.csv"; if ((f = fopen(fn, "r")) != NULL) { From b3be8e06734c6428376514024af7cabf13774dce Mon Sep 17 00:00:00 2001 From: default Date: Mon, 25 Nov 2024 10:24:47 +0100 Subject: [PATCH 43/60] New cmdline ops 'import_list' and 'import_block_list'. --- main.c | 14 ++++++++++++++ utils.c | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/main.c b/main.c index c285fac..088d382 100644 --- a/main.c +++ b/main.c @@ -52,6 +52,8 @@ int usage(void) printf("alias {basedir} {uid} {account} Sets account (@user@host or actor url) as an alias\n"); printf("migrate {basedir} {uid} Migrates to the account defined as the alias\n"); printf("import_csv {basedir} {uid} Imports data from CSV files into current directory\n"); + printf("import_list {basedir} {uid} {file} Imports a Mastodon CSV list file\n"); + printf("import_block_list {basedir} {uid} {file} Imports a Mastodon CSV block list file\n"); return 1; } @@ -589,6 +591,18 @@ int main(int argc, char *argv[]) return 0; } + if (strcmp(cmd, "import_list") == 0) { /** **/ + import_list_csv(&snac, url); + + return 0; + } + + if (strcmp(cmd, "import_block_list") == 0) { /** **/ + import_blocked_accounts_csv(&snac, url); + + return 0; + } + if (strcmp(cmd, "note") == 0) { /** **/ xs *content = NULL; xs *msg = NULL; diff --git a/utils.c b/utils.c index b0d8193..df3b55d 100644 --- a/utils.c +++ b/utils.c @@ -681,7 +681,7 @@ void import_blocked_accounts_csv(snac *user, const char *fn) while (!feof(f)) { xs *l = xs_strip_i(xs_readline(f)); - if (*l) { + if (*l && strchr(l, '@') != NULL) { xs *url = NULL; xs *uid = NULL; From 5165df0ceb6fee36acbe382c7d41b65d2295286a Mon Sep 17 00:00:00 2001 From: default Date: Mon, 25 Nov 2024 10:30:28 +0100 Subject: [PATCH 44/60] 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 b960fe2..283d2c4 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,7 +6,9 @@ Added a new user option to disable automatic follow confirmations (follow reques The search box also searches for accounts (via webfinger). -The list CSV import process now follows all accounts in the list (so that [Mastodon Follow Packs](https://mastodonmigration.wordpress.com/?p=995) can be directly used). +New command-line action `import_list`, to import a Mastodon list in CSV format (so that [Mastodon Follow Packs](https://mastodonmigration.wordpress.com/?p=995) can be directly used). + +New command-line action `import_block_list`, to import a Mastodon list of accounts to be blocked in CSV format. ## 2.64 From a30b2b61b4be0e18439c6cde695b59f9a215ebe8 Mon Sep 17 00:00:00 2001 From: default Date: Mon, 25 Nov 2024 10:34:12 +0100 Subject: [PATCH 45/60] Fixed typo. --- main.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.c b/main.c index 088d382..76a7961 100644 --- a/main.c +++ b/main.c @@ -51,7 +51,7 @@ int usage(void) printf("export_csv {basedir} {uid} Exports data as CSV files into current directory\n"); printf("alias {basedir} {uid} {account} Sets account (@user@host or actor url) as an alias\n"); printf("migrate {basedir} {uid} Migrates to the account defined as the alias\n"); - printf("import_csv {basedir} {uid} Imports data from CSV files into current directory\n"); + printf("import_csv {basedir} {uid} Imports data from CSV files in the current directory\n"); printf("import_list {basedir} {uid} {file} Imports a Mastodon CSV list file\n"); printf("import_block_list {basedir} {uid} {file} Imports a Mastodon CSV block list file\n"); From ebb1b00457d7a362c08a40aaadd91968f837bca1 Mon Sep 17 00:00:00 2001 From: default Date: Mon, 25 Nov 2024 10:43:56 +0100 Subject: [PATCH 46/60] Updated documentation. --- doc/snac.1 | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/snac.1 b/doc/snac.1 index 6d719cb..c2691d0 100644 --- a/doc/snac.1 +++ b/doc/snac.1 @@ -280,6 +280,13 @@ section 'Migrating from snac to Mastodon'). Starts a migration from this account to the one set as an alias (see .Xr snac 8 , section 'Migrating from snac to Mastodon'). +.It Cm import_csv Ar basedir Ar uid +Imports CSV data files from a Mastodon export. This command expects the +following files to be in the current directory: +.Pa bookmarks.csv , +.Pa blocked_accounts.csv , +.Pa lists.csv , and +.Pa following_accounts.csv . .It Cm state Ar basedir Dumps the current state of the server and its threads. For example: .Bd -literal -offset indent @@ -302,6 +309,11 @@ in-memory job queue. The thread state can be: waiting (idle waiting for a job to be assigned), input or output (processing I/O packets) or stopped (not running, only to be seen while starting or stopping the server). +.It Cm import_list Ar basedir Ar uid Ar file +Imports a Mastodon list in CSV format. This option can be used to +import "Mastodon Follow Packs". +.It Cm import_block_list Ar basedir Ar uid Ar file +Imports a Mastodon list of accounts to be blocked in CSV format. .El .Ss Migrating an account to/from Mastodon See From fa5672941d9416fae4920ee813001c1d48a86533 Mon Sep 17 00:00:00 2001 From: default Date: Tue, 26 Nov 2024 17:14:35 +0100 Subject: [PATCH 47/60] 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 283d2c4..b19c604 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,6 +1,6 @@ # Release Notes -## UNRELEASED +## 2.65 Added a new user option to disable automatic follow confirmations (follow requests must be manually approved from the people page). From 22d05cded1f1cb23b90ded9f9c1ad24b02718c2b Mon Sep 17 00:00:00 2001 From: default Date: Tue, 26 Nov 2024 17:15:54 +0100 Subject: [PATCH 48/60] Version 2.65 RELEASED. --- snac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/snac.h b/snac.h index 9e00351..3f4fb81 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2024 grunfink et al. / MIT license */ -#define VERSION "2.65-dev" +#define VERSION "2.65" #define USER_AGENT "snac/" VERSION From 203db3b09e3ebf8677f75e83e5470b99c74de470 Mon Sep 17 00:00:00 2001 From: default Date: Wed, 4 Dec 2024 05:51:17 +0100 Subject: [PATCH 49/60] Fixed bug in blocked instance rejection. --- html.c | 3 +++ mastoapi.c | 3 +++ snac.h | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/html.c b/html.c index f8fb876..26831f5 100644 --- a/html.c +++ b/html.c @@ -1524,6 +1524,9 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, if ((read_only || !user) && !is_msg_public(msg)) return NULL; + if (is_instance_blocked(id)) + return NULL; + if (user && level == 0 && xs_is_true(xs_dict_get(user->config, "collapse_threads"))) collapse_threads = 1; diff --git a/mastoapi.c b/mastoapi.c index a529990..0332629 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -1349,6 +1349,9 @@ xs_list *mastoapi_timeline(snac *user, const xs_dict *args, const char *index_fn if (!xs_match(type, POSTLIKE_OBJECT_TYPE)) continue; + if (is_instance_blocked(id)) + continue; + const char *from = NULL; if (strcmp(type, "Page") == 0) from = xs_dict_get(msg, "audience"); diff --git a/snac.h b/snac.h index 3f4fb81..a3c055b 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2024 grunfink et al. / MIT license */ -#define VERSION "2.65" +#define VERSION "2.66-dev" #define USER_AGENT "snac/" VERSION From e0ba66a53e8cd89b900b895675588c29d5bb7a2d Mon Sep 17 00:00:00 2001 From: default Date: Wed, 4 Dec 2024 05:53:12 +0100 Subject: [PATCH 50/60] Check for existing id. --- html.c | 2 +- mastoapi.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/html.c b/html.c index 26831f5..9a01bea 100644 --- a/html.c +++ b/html.c @@ -1524,7 +1524,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, if ((read_only || !user) && !is_msg_public(msg)) return NULL; - if (is_instance_blocked(id)) + if (id && is_instance_blocked(id)) return NULL; if (user && level == 0 && xs_is_true(xs_dict_get(user->config, "collapse_threads"))) diff --git a/mastoapi.c b/mastoapi.c index 0332629..ee9ecec 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -1349,7 +1349,7 @@ xs_list *mastoapi_timeline(snac *user, const xs_dict *args, const char *index_fn if (!xs_match(type, POSTLIKE_OBJECT_TYPE)) continue; - if (is_instance_blocked(id)) + if (id && is_instance_blocked(id)) continue; const char *from = NULL; From a6e13e40da960e0d456924c213037f0b6da6ed16 Mon Sep 17 00:00:00 2001 From: default Date: Wed, 4 Dec 2024 06:25:46 +0100 Subject: [PATCH 51/60] msg_collection() now includes an argument for totalItems. --- activitypub.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/activitypub.c b/activitypub.c index 7a7e346..59a69ec 100644 --- a/activitypub.c +++ b/activitypub.c @@ -1038,15 +1038,14 @@ xs_dict *msg_base(snac *snac, const char *type, const char *id, } -xs_dict *msg_collection(snac *snac, const char *id) +xs_dict *msg_collection(snac *snac, const char *id, int items) /* creates an empty OrderedCollection message */ { xs_dict *msg = msg_base(snac, "OrderedCollection", id, NULL, NULL, NULL); - xs *ol = xs_list_new(); + xs *n = xs_number_new(items); msg = xs_dict_append(msg, "attributedTo", snac->actor); - msg = xs_dict_append(msg, "orderedItems", ol); - msg = xs_dict_append(msg, "totalItems", xs_stock(0)); + msg = xs_dict_append(msg, "totalItems", n); return msg; } @@ -2850,7 +2849,6 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, if (strcmp(p_path, "outbox") == 0 || strcmp(p_path, "featured") == 0) { xs *id = xs_fmt("%s/%s", snac.actor, p_path); xs *list = xs_list_new(); - msg = msg_collection(&snac, id); const char *v; int tc = 0; @@ -2872,14 +2870,18 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, } /* replace the 'orderedItems' with the latest posts */ - xs *items = xs_number_new(xs_list_len(list)); + msg = msg_collection(&snac, id, xs_list_len(list)); msg = xs_dict_set(msg, "orderedItems", list); - msg = xs_dict_set(msg, "totalItems", items); } else - if (strcmp(p_path, "followers") == 0 || strcmp(p_path, "following") == 0) { + if (strcmp(p_path, "followers") == 0) { xs *id = xs_fmt("%s/%s", snac.actor, p_path); - msg = msg_collection(&snac, id); + msg = msg_collection(&snac, id, 0); + } + else + if (strcmp(p_path, "following") == 0) { + xs *id = xs_fmt("%s/%s", snac.actor, p_path); + msg = msg_collection(&snac, id, 0); } else if (xs_startswith(p_path, "p/")) { From 93bc87ecb6b8d1f49733be2bddf1a7301092888a Mon Sep 17 00:00:00 2001 From: default Date: Wed, 4 Dec 2024 07:06:50 +0100 Subject: [PATCH 52/60] New user variable show_contact_metrics. --- activitypub.c | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/activitypub.c b/activitypub.c index 59a69ec..773df78 100644 --- a/activitypub.c +++ b/activitypub.c @@ -2836,6 +2836,8 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, *ctype = "application/activity+json"; + int show_contact_metrics = xs_is_true(xs_dict_get(snac.config, "show_contact_metrics")); + if (p_path == NULL) { /* if there was no component after the user, it's an actor request */ msg = msg_actor(&snac); @@ -2875,13 +2877,27 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, } else if (strcmp(p_path, "followers") == 0) { + int total = 0; + + if (show_contact_metrics) { + xs *l = follower_list(&snac); + total = xs_list_len(l); + } + xs *id = xs_fmt("%s/%s", snac.actor, p_path); - msg = msg_collection(&snac, id, 0); + msg = msg_collection(&snac, id, total); } else if (strcmp(p_path, "following") == 0) { + int total = 0; + + if (show_contact_metrics) { + xs *l = following_list(&snac); + total = xs_list_len(l); + } + xs *id = xs_fmt("%s/%s", snac.actor, p_path); - msg = msg_collection(&snac, id, 0); + msg = msg_collection(&snac, id, total); } else if (xs_startswith(p_path, "p/")) { From a45a0d68e3d8a942a2bf516d5086920dbcb3a862 Mon Sep 17 00:00:00 2001 From: default Date: Wed, 4 Dec 2024 18:05:41 +0100 Subject: [PATCH 53/60] Also show contact metrics in the public page, if the user wants. --- html.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/html.c b/html.c index 9a01bea..97405fe 100644 --- a/html.c +++ b/html.c @@ -938,6 +938,18 @@ static xs_html *html_user_body(snac *user, int read_only) xs_html_add(top_user, snac_metadata); } + + if (xs_is_true(xs_dict_get(user->config, "show_contact_metrics"))) { + xs *fwers = follower_list(user); + xs *fwing = following_list(user); + + xs *s1 = xs_fmt(L("%d following %d followers"), + xs_list_len(fwing), xs_list_len(fwers)); + + xs_html_add(top_user, + xs_html_tag("p", + xs_html_text(s1))); + } } xs_html_add(body, From 2edce5e0ec5d098a7b879df20496cd636fa6d192 Mon Sep 17 00:00:00 2001 From: default Date: Wed, 4 Dec 2024 21:45:49 +0100 Subject: [PATCH 54/60] mastoapi: show contact metrics if the user want. --- mastoapi.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mastoapi.c b/mastoapi.c index ee9ecec..4efe182 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -663,6 +663,17 @@ xs_dict *mastoapi_account(snac *logged, const xs_dict *actor) if (user_open(&user, prefu)) { val_links = user.links; metadata = xs_dict_get_def(user.config, "metadata", xs_stock(XSTYPE_DICT)); + + /* does this user want to publish their contact metrics? */ + if (xs_is_true(xs_dict_get(user.config, "show_contact_metrics"))) { + xs *fwing = following_list(&user); + xs *fwers = follower_list(&user); + xs *ni = xs_number_new(xs_list_len(fwing)); + xs *ne = xs_number_new(xs_list_len(fwers)); + + acct = xs_dict_append(acct, "followers_count", ne); + acct = xs_dict_append(acct, "following_count", ni); + } } } From e6d8cc26eaef1482b66acaf5f934536dde709109 Mon Sep 17 00:00:00 2001 From: default Date: Wed, 4 Dec 2024 21:58:19 +0100 Subject: [PATCH 55/60] mastoapi: also fill metrics from credentials_get(). --- mastoapi.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mastoapi.c b/mastoapi.c index 4efe182..990898b 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -1286,6 +1286,17 @@ void credentials_get(char **body, char **ctype, int *status, snac snac) acct = xs_dict_append(acct, "following_count", xs_stock(0)); acct = xs_dict_append(acct, "statuses_count", xs_stock(0)); + /* does this user want to publish their contact metrics? */ + if (xs_is_true(xs_dict_get(snac.config, "show_contact_metrics"))) { + xs *fwing = following_list(&snac); + xs *fwers = follower_list(&snac); + xs *ni = xs_number_new(xs_list_len(fwing)); + xs *ne = xs_number_new(xs_list_len(fwers)); + + acct = xs_dict_append(acct, "followers_count", ne); + acct = xs_dict_append(acct, "following_count", ni); + } + *body = xs_json_dumps(acct, 4); *ctype = "application/json"; *status = HTTP_STATUS_OK; From b27031cc46b04224585c34eeb70eda396fbeaca2 Mon Sep 17 00:00:00 2001 From: default Date: Thu, 5 Dec 2024 09:13:14 +0100 Subject: [PATCH 56/60] Added web UI for the 'show_contact_metrics' toggle. --- html.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/html.c b/html.c index 97405fe..edb7e1e 100644 --- a/html.c +++ b/html.c @@ -1062,6 +1062,7 @@ xs_html *html_top_controls(snac *snac) const xs_val *auto_boost = xs_dict_get(snac->config, "auto_boost"); const xs_val *coll_thrds = xs_dict_get(snac->config, "collapse_threads"); const xs_val *pending = xs_dict_get(snac->config, "approve_followers"); + const xs_val *show_foll = xs_dict_get(snac->config, "show_contact_metrics"); xs *metadata = NULL; const xs_dict *md = xs_dict_get(snac->config, "metadata"); @@ -1242,6 +1243,15 @@ xs_html *html_top_controls(snac *snac) xs_html_tag("label", xs_html_attr("for", "approve_followers"), xs_html_text(L("Follow requests must be approved")))), + xs_html_tag("p", + xs_html_sctag("input", + xs_html_attr("type", "checkbox"), + xs_html_attr("name", "show_contact_metrics"), + xs_html_attr("id", "show_contact_metrics"), + xs_html_attr(xs_is_true(show_foll) ? "checked" : "", NULL)), + xs_html_tag("label", + xs_html_attr("for", "show_contact_metrics"), + xs_html_text(L("Publish follower and following metrics")))), xs_html_tag("p", xs_html_text(L("Profile metadata (key=value pairs in each line):")), xs_html_sctag("br", NULL), @@ -3858,6 +3868,10 @@ int html_post_handler(const xs_dict *req, const char *q_path, snac.config = xs_dict_set(snac.config, "approve_followers", xs_stock(XSTYPE_TRUE)); else snac.config = xs_dict_set(snac.config, "approve_followers", xs_stock(XSTYPE_FALSE)); + if ((v = xs_dict_get(p_vars, "show_contact_metrics")) != NULL && strcmp(v, "on") == 0) + snac.config = xs_dict_set(snac.config, "show_contact_metrics", xs_stock(XSTYPE_TRUE)); + else + snac.config = xs_dict_set(snac.config, "show_contact_metrics", xs_stock(XSTYPE_FALSE)); if ((v = xs_dict_get(p_vars, "metadata")) != NULL) snac.config = xs_dict_set(snac.config, "metadata", v); From 4b17a846987e6cb2c59497a19222a36239155971 Mon Sep 17 00:00:00 2001 From: default Date: Thu, 5 Dec 2024 09:32:50 +0100 Subject: [PATCH 57/60] Updated RELEASE_NOTES. --- RELEASE_NOTES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b19c604..7e76677 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,11 @@ # Release Notes +## UNRELEASED + +As many users have asked for it, there is now an option to make the number of followed and following accounts public (still disabled by default). + +Some fixes to blocked instances code (posts from them were sometimes shown). + ## 2.65 Added a new user option to disable automatic follow confirmations (follow requests must be manually approved from the people page). From 3b8972567776f5ad05b1324e59ebbd6aaabb685c Mon Sep 17 00:00:00 2001 From: default Date: Thu, 5 Dec 2024 09:37:57 +0100 Subject: [PATCH 58/60] Updated documentation. --- doc/snac.1 | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/snac.1 b/doc/snac.1 index c2691d0..dd84de6 100644 --- a/doc/snac.1 +++ b/doc/snac.1 @@ -147,6 +147,9 @@ If this toggle is set, follow requests are not automatically accepted, but notified and stored for later review. Pending follow requests will be shown in the people page to be approved or discarded. +.It Publish follower and following metrics +If this toggle is set, the number of followers and following +accounts are made public. .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. From 198d801b05ad2ea4ffdbda495ebb4605e0c0ae97 Mon Sep 17 00:00:00 2001 From: default Date: Thu, 5 Dec 2024 10:02:09 +0100 Subject: [PATCH 59/60] Updated documentation. --- doc/snac.1 | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/snac.1 b/doc/snac.1 index dd84de6..efba67e 100644 --- a/doc/snac.1 +++ b/doc/snac.1 @@ -149,7 +149,8 @@ follow requests will be shown in the people page to be approved or discarded. .It Publish follower and following metrics If this toggle is set, the number of followers and following -accounts are made public. +accounts are made public (this is only the number; the specific +lists of accounts are never published). .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. From 221eeabf0c00f076ee8ae670811aabbc5b11a826 Mon Sep 17 00:00:00 2001 From: default Date: Thu, 5 Dec 2024 10:03:52 +0100 Subject: [PATCH 60/60] 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 7e76677..2218c20 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,7 +2,7 @@ ## UNRELEASED -As many users have asked for it, there is now an option to make the number of followed and following accounts public (still disabled by default). +As many users have asked for it, there is now an option to make the number of followed and following accounts public (still disabled by default). These are only the numbers; the lists themselves are never published. Some fixes to blocked instances code (posts from them were sometimes shown).