From 29caa37fd768df07de98c292c6db3196906cbf98 Mon Sep 17 00:00:00 2001 From: default Date: Sat, 16 Nov 2024 07:25:25 +0100 Subject: Allow underscores in hashtags. --- activitypub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'activitypub.c') 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)) { -- cgit From 085caa7747a3bbebbd2ec09b3264dc6fcc5a7624 Mon Sep 17 00:00:00 2001 From: default Date: Tue, 19 Nov 2024 06:46:14 +0100 Subject: 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(-) (limited to 'activitypub.c') 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); -- cgit 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(-) (limited to 'activitypub.c') 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 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(-) (limited to 'activitypub.c') 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 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(+) (limited to 'activitypub.c') 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 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(-) (limited to 'activitypub.c') 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 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(+) (limited to 'activitypub.c') 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 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(-) (limited to 'activitypub.c') 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 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(-) (limited to 'activitypub.c') 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