From 09d268495c12d3c00a7293fbf445830bcc9d74c3 Mon Sep 17 00:00:00 2001 From: default Date: Sat, 23 Nov 2024 17:08:57 +0100 Subject: 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; + + metadata = xs_str_new(NULL); - if (*metadata) - metadata = xs_str_cat(metadata, "\n"); - metadata = xs_str_cat(metadata, kp); + 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 }; -- cgit v1.2.3 From c88d7e72f0531114e7e000f79ee6ce9a11f23952 Mon Sep 17 00:00:00 2001 From: default Date: Sat, 23 Nov 2024 17:16:31 +0100 Subject: 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) { -- cgit v1.2.3 From 36c3030231ed806c29c182e38cb0957993c835d1 Mon Sep 17 00:00:00 2001 From: default Date: Sat, 23 Nov 2024 17:33:49 +0100 Subject: 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) { -- cgit v1.2.3 From 35b35ec3afff47be872004448e6ba81a6a339131 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 08:03:51 +0100 Subject: 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; } -- cgit v1.2.3 From 7287776fd1f659619e211ee482ba0d6d64ddbf0b Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 08:17:38 +0100 Subject: New function pending_add(). --- activitypub.c | 31 ++++++++++++++++++++----------- data.c | 22 ++++++++++++++++++++++ snac.h | 2 ++ 3 files changed, 44 insertions(+), 11 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); - - 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); + 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); - timeline_add(snac, id, f_msg); + 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); + } - follower_add(snac, actor); + timeline_add(snac, id, f_msg); + + follower_add(snac, actor); + + snac_log(snac, xs_fmt("new follower %s", 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); -- cgit v1.2.3 From b91177cb464ffdb118d50569592be3f9789dbef6 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 08:31:01 +0100 Subject: 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); -- cgit v1.2.3 From 129049edf4752495f768b247253cb7ffc848b0cc Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 08:37:19 +0100 Subject: 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); -- cgit v1.2.3 From 96576d2c5df41b91e915a31d5c0c039f4852f583 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 08:46:26 +0100 Subject: 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)); } -- cgit v1.2.3 From 9fb84bcb3aa4c6a8061e4b892dc5bd1826b44715 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 08:54:43 +0100 Subject: 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"); -- cgit v1.2.3 From 0ddbe7e53b8331d2de42ad29ceca35b715f1c121 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 08:58:02 +0100 Subject: 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); -- cgit v1.2.3 From 0db1cf05d1013f9b1e972ae79822f9eda4dede59 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 09:14:59 +0100 Subject: 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); -- cgit v1.2.3 From 93f70d6759e0bc67326ca78283cd9627f8ca4580 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 09:25:59 +0100 Subject: 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); -- cgit v1.2.3 From 76654f1bfa76cef43fbb88d316b4c677fd8a89bb Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 09:26:54 +0100 Subject: 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 -- cgit v1.2.3 From 9fee9708090b752a619e5f855870d46c486886d8 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 09:30:40 +0100 Subject: 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). -- cgit v1.2.3 From a2665c3cc9bb94a7969a904bfcd210e20cf038e7 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 09:37:59 +0100 Subject: 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; -- cgit v1.2.3 From 45f29ede8779bf0e599e3e07089fc4ecef54d627 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 10:33:42 +0100 Subject: 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. -- cgit v1.2.3 From e0fb6a70648297bc02a64e6d4390874a9c6085db Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 18:36:33 +0100 Subject: 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); -- cgit v1.2.3 From a5a7ab9ba5b35659148ee6a20ddbd512c5aa53fd Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 18:49:17 +0100 Subject: 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 */ -- cgit v1.2.3 From 3f8db422ce66912a5414968eeae867f9ac12d946 Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 20:21:48 +0100 Subject: 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); -- cgit v1.2.3 From 6e32dd16d8e1801a7ba8149f5654dfcbb893c9fa Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 20:22:58 +0100 Subject: 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). -- cgit v1.2.3 From 474a750e692370a3f0adbe71c15d6c7b927b7e4a Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 20:25:17 +0100 Subject: 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"))))); } -- cgit v1.2.3 From 13647f790d967a046b9bd2f290541c37a0b7ea6f Mon Sep 17 00:00:00 2001 From: default Date: Sun, 24 Nov 2024 20:33:22 +0100 Subject: 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"), -- cgit v1.2.3 From 15f352fe8311c0b435228a99e040fdc5e1c08ed4 Mon Sep 17 00:00:00 2001 From: default Date: Mon, 25 Nov 2024 08:15:34 +0100 Subject: 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)); -- cgit v1.2.3 From f549af800520b565cd95783c40ef886f7f0d8603 Mon Sep 17 00:00:00 2001 From: default Date: Mon, 25 Nov 2024 09:09:56 +0100 Subject: 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). -- cgit v1.2.3 From 2eff3ffb664d372d68ab2d953466d7ba946c96c2 Mon Sep 17 00:00:00 2001 From: default Date: Mon, 25 Nov 2024 10:08:50 +0100 Subject: 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) { -- cgit v1.2.3 From b3be8e06734c6428376514024af7cabf13774dce Mon Sep 17 00:00:00 2001 From: default Date: Mon, 25 Nov 2024 10:24:47 +0100 Subject: 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; -- cgit v1.2.3 From 5165df0ceb6fee36acbe382c7d41b65d2295286a Mon Sep 17 00:00:00 2001 From: default Date: Mon, 25 Nov 2024 10:30:28 +0100 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 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 -- cgit v1.2.3 From a30b2b61b4be0e18439c6cde695b59f9a215ebe8 Mon Sep 17 00:00:00 2001 From: default Date: Mon, 25 Nov 2024 10:34:12 +0100 Subject: 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"); -- cgit v1.2.3 From ebb1b00457d7a362c08a40aaadd91968f837bca1 Mon Sep 17 00:00:00 2001 From: default Date: Mon, 25 Nov 2024 10:43:56 +0100 Subject: 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 -- cgit v1.2.3 From fa5672941d9416fae4920ee813001c1d48a86533 Mon Sep 17 00:00:00 2001 From: default Date: Tue, 26 Nov 2024 17:14:35 +0100 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 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). -- cgit v1.2.3 From 22d05cded1f1cb23b90ded9f9c1ad24b02718c2b Mon Sep 17 00:00:00 2001 From: default Date: Tue, 26 Nov 2024 17:15:54 +0100 Subject: 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 -- cgit v1.2.3 From 203db3b09e3ebf8677f75e83e5470b99c74de470 Mon Sep 17 00:00:00 2001 From: default Date: Wed, 4 Dec 2024 05:51:17 +0100 Subject: 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 -- cgit v1.2.3 From e0ba66a53e8cd89b900b895675588c29d5bb7a2d Mon Sep 17 00:00:00 2001 From: default Date: Wed, 4 Dec 2024 05:53:12 +0100 Subject: 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; -- cgit v1.2.3 From a6e13e40da960e0d456924c213037f0b6da6ed16 Mon Sep 17 00:00:00 2001 From: default Date: Wed, 4 Dec 2024 06:25:46 +0100 Subject: 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/")) { -- cgit v1.2.3 From 93bc87ecb6b8d1f49733be2bddf1a7301092888a Mon Sep 17 00:00:00 2001 From: default Date: Wed, 4 Dec 2024 07:06:50 +0100 Subject: 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/")) { -- cgit v1.2.3 From a45a0d68e3d8a942a2bf516d5086920dbcb3a862 Mon Sep 17 00:00:00 2001 From: default Date: Wed, 4 Dec 2024 18:05:41 +0100 Subject: 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, -- cgit v1.2.3 From 2edce5e0ec5d098a7b879df20496cd636fa6d192 Mon Sep 17 00:00:00 2001 From: default Date: Wed, 4 Dec 2024 21:45:49 +0100 Subject: 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); + } } } -- cgit v1.2.3 From e6d8cc26eaef1482b66acaf5f934536dde709109 Mon Sep 17 00:00:00 2001 From: default Date: Wed, 4 Dec 2024 21:58:19 +0100 Subject: 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; -- cgit v1.2.3 From b27031cc46b04224585c34eeb70eda396fbeaca2 Mon Sep 17 00:00:00 2001 From: default Date: Thu, 5 Dec 2024 09:13:14 +0100 Subject: 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); -- cgit v1.2.3 From 4b17a846987e6cb2c59497a19222a36239155971 Mon Sep 17 00:00:00 2001 From: default Date: Thu, 5 Dec 2024 09:32:50 +0100 Subject: 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). -- cgit v1.2.3 From 3b8972567776f5ad05b1324e59ebbd6aaabb685c Mon Sep 17 00:00:00 2001 From: default Date: Thu, 5 Dec 2024 09:37:57 +0100 Subject: 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. -- cgit v1.2.3 From 198d801b05ad2ea4ffdbda495ebb4605e0c0ae97 Mon Sep 17 00:00:00 2001 From: default Date: Thu, 5 Dec 2024 10:02:09 +0100 Subject: 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. -- cgit v1.2.3 From 221eeabf0c00f076ee8ae670811aabbc5b11a826 Mon Sep 17 00:00:00 2001 From: default Date: Thu, 5 Dec 2024 10:03:52 +0100 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 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). -- cgit v1.2.3