From 859de583d9b575a830bab92a6379f77c70910a93 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 28 May 2025 07:44:34 +0200 Subject: Renamed timeline_to_rss() to rss_from_timeline(). --- httpd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'httpd.c') diff --git a/httpd.c b/httpd.c index 6f7d69b..41b2515 100644 --- a/httpd.c +++ b/httpd.c @@ -244,7 +244,7 @@ int server_get_handler(xs_dict *req, const char *q_path, if (!xs_is_null(accept) && strcmp(accept, "application/rss+xml") == 0) { xs *link = xs_fmt("%s/?t=%s", srv_baseurl, t); - *body = timeline_to_rss(NULL, tl, link, link, link); + *body = rss_from_timeline(NULL, tl, link, link, link); *ctype = "application/rss+xml; charset=utf-8"; } else { -- cgit From a1369b39c1bd3d2036af12368997648454ca5564 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 28 May 2025 09:07:19 +0200 Subject: Activated hashtag RSS polling. --- activitypub.c | 4 ++++ html.c | 3 ++- httpd.c | 42 +++++++++++++++++++++++++++++++----------- rss.c | 9 +++++---- snac.h | 2 +- 5 files changed, 43 insertions(+), 17 deletions(-) (limited to 'httpd.c') diff --git a/activitypub.c b/activitypub.c index 0cc7bcb..2cffeac 100644 --- a/activitypub.c +++ b/activitypub.c @@ -3049,6 +3049,10 @@ void process_queue_item(xs_dict *q_item) } } } + else + if (strcmp(type, "rss_poll") == 0) { + rss_poll_hashtags(); + } else srv_log(xs_fmt("unexpected q_item type '%s'", type)); } diff --git a/html.c b/html.c index 7fcc266..3d213a2 100644 --- a/html.c +++ b/html.c @@ -1601,7 +1601,8 @@ xs_html *html_top_controls(snac *user) xs_html_attr("name", "followed_hashtags"), xs_html_attr("cols", "40"), xs_html_attr("rows", "4"), - xs_html_attr("placeholder", "#cats\n#windowfriday\n#classicalmusic"), + xs_html_attr("placeholder", "#cats\n#windowfriday\n#classicalmusic\nhttps:/" + "/mastodon.social/tags/dogs"), xs_html_text(followed_hashtags)), xs_html_tag("br", NULL), diff --git a/httpd.c b/httpd.c index 41b2515..c94a542 100644 --- a/httpd.c +++ b/httpd.c @@ -705,34 +705,36 @@ static pthread_cond_t sleep_cond; static void *background_thread(void *arg) /* background thread (queue management and other things) */ { - time_t purge_time; + time_t t, purge_time, rss_time; (void)arg; + t = time(NULL); + /* first purge time */ - purge_time = time(NULL) + 10 * 60; + purge_time = t + 10 * 60; + + /* first RSS polling time */ + rss_time = t + 15 * 60; srv_log(xs_fmt("background thread started")); while (p_state->srv_running) { - time_t t; int cnt = 0; p_state->th_state[0] = THST_QUEUE; { xs *list = user_list(); - char *p; const char *uid; /* process queues for all users */ - p = list; - while (xs_list_iter(&p, &uid)) { - snac snac; + xs_list_foreach(list, uid) { + snac user; - if (user_open(&snac, uid)) { - cnt += process_user_queue(&snac); - user_free(&snac); + if (user_open(&user, uid)) { + cnt += process_user_queue(&user); + user_free(&user); } } } @@ -740,8 +742,10 @@ static void *background_thread(void *arg) /* global queue */ cnt += process_queue(); + t = time(NULL); + /* time to purge? */ - if ((t = time(NULL)) > purge_time) { + if (t > purge_time) { /* next purge time is tomorrow */ purge_time = t + 24 * 60 * 60; @@ -750,6 +754,22 @@ static void *background_thread(void *arg) job_post(q_item, 0); } + /* time to poll the RSS? */ + if (t > rss_time) { + /* next RSS poll time */ + int hours = xs_number_get(xs_dict_get_def(srv_config, "rss_poll_hours", "4")); + + /* don't hammer servers too much */ + if (hours < 1) + hours = 1; + + rss_time = t + 60 * 60 * hours; + + xs *q_item = xs_dict_new(); + q_item = xs_dict_append(q_item, "type", "rss_poll"); + job_post(q_item, 0); + } + if (cnt == 0) { /* sleep 3 seconds */ diff --git a/rss.c b/rss.c index b31ffd3..ce3e184 100644 --- a/rss.c +++ b/rss.c @@ -110,6 +110,9 @@ xs_str *rss_from_timeline(snac *user, const xs_list *timeline, void rss_to_timeline(snac *user, const char *url) /* reads an RSS and inserts all ActivityPub posts into the user's timeline */ { + if (!xs_startswith(url, "https:/") && !xs_startswith(url, "http:/")) + return; + xs *hdrs = xs_dict_new(); hdrs = xs_dict_set(hdrs, "accept", "application/rss+xml"); hdrs = xs_dict_set(hdrs, "user-agent", USER_AGENT); @@ -204,10 +207,9 @@ void rss_to_timeline(snac *user, const char *url) } -void rss_process(void) +void rss_poll_hashtags(void) /* parses all RSS from all users */ { -#if 0 xs *list = user_list(); const char *uid; @@ -215,7 +217,7 @@ void rss_process(void) snac user; if (user_open(&user, uid)) { - const xs_list *rss = xs_dict_get(user.config, "rss"); + const xs_list *rss = xs_dict_get(user.config, "followed_hashtags"); if (xs_is_list(rss)) { const char *url; @@ -227,5 +229,4 @@ void rss_process(void) user_free(&user); } } -#endif } diff --git a/snac.h b/snac.h index db51a1c..06a36f1 100644 --- a/snac.h +++ b/snac.h @@ -464,4 +464,4 @@ const char *lang_str(const char *str, const snac *user); xs_str *rss_from_timeline(snac *user, const xs_list *timeline, const char *title, const char *link, const char *desc); void rss_to_timeline(snac *user, const char *url); -void rss_process(void); +void rss_poll_hashtags(void); -- cgit From 0594197af7a4a11795150a85fafa7fa5bc6fc4f5 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 28 May 2025 09:16:23 +0200 Subject: Renamed server knob to 'rss_hashtag_poll_hours'. --- activitypub.c | 2 +- httpd.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'httpd.c') diff --git a/activitypub.c b/activitypub.c index 2cffeac..8877a27 100644 --- a/activitypub.c +++ b/activitypub.c @@ -3050,7 +3050,7 @@ void process_queue_item(xs_dict *q_item) } } else - if (strcmp(type, "rss_poll") == 0) { + if (strcmp(type, "rss_hashtag_poll") == 0) { rss_poll_hashtags(); } else diff --git a/httpd.c b/httpd.c index c94a542..15634d1 100644 --- a/httpd.c +++ b/httpd.c @@ -757,7 +757,7 @@ static void *background_thread(void *arg) /* time to poll the RSS? */ if (t > rss_time) { /* next RSS poll time */ - int hours = xs_number_get(xs_dict_get_def(srv_config, "rss_poll_hours", "4")); + int hours = xs_number_get(xs_dict_get_def(srv_config, "rss_hashtag_poll_hours", "4")); /* don't hammer servers too much */ if (hours < 1) @@ -766,7 +766,7 @@ static void *background_thread(void *arg) rss_time = t + 60 * 60 * hours; xs *q_item = xs_dict_new(); - q_item = xs_dict_append(q_item, "type", "rss_poll"); + q_item = xs_dict_append(q_item, "type", "rss_hashtag_poll"); job_post(q_item, 0); } -- cgit From f88bdd796045dd206b38e07adb13b5af4489b4c5 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 24 Jun 2025 14:06:41 +0200 Subject: Added a webmention hook. --- html.c | 20 ++++++++++++++----- httpd.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ xs_webmention.h | 42 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 5 deletions(-) (limited to 'httpd.c') diff --git a/html.c b/html.c index c472e3b..672ea44 100644 --- a/html.c +++ b/html.c @@ -868,6 +868,14 @@ xs_html *html_user_head(snac *user, const char *desc, const char *url) xs_html_attr("type", "application/activity+json"), xs_html_attr("href", url ? url : user->actor))); + /* webmention hook */ + xs *wbh = xs_fmt("%s/webmention-hook", srv_baseurl); + + xs_html_add(head, + xs_html_sctag("link", + xs_html_attr("rel", "webmention"), + xs_html_attr("href", wbh))); + return head; } @@ -3407,13 +3415,15 @@ xs_str *html_notifications(snac *user, int skip, int show) const char *actor_id = xs_dict_get(noti, "actor"); xs *actor = NULL; + xs *a_name = NULL; - if (!valid_status(actor_get(actor_id, &actor))) - continue; + if (valid_status(actor_get(actor_id, &actor))) + a_name = actor_name(actor, proxy); + else + a_name = xs_dup(actor_id); - xs *a_name = actor_name(actor, proxy); - xs *label_sanatized = sanitize(type); - const char *label = label_sanatized; + xs *label_sanitized = sanitize(type); + const char *label = label_sanitized; if (strcmp(type, "Create") == 0) label = L("Mention"); diff --git a/httpd.c b/httpd.c index 15634d1..af78e09 100644 --- a/httpd.c +++ b/httpd.c @@ -12,6 +12,7 @@ #include "xs_openssl.h" #include "xs_fcgi.h" #include "xs_html.h" +#include "xs_webmention.h" #include "snac.h" @@ -373,6 +374,63 @@ int server_get_handler(xs_dict *req, const char *q_path, } +int server_post_handler(const xs_dict *req, const char *q_path, + char *payload, int p_size, + char **body, int *b_size, char **ctype) +{ + int status = 0; + + if (strcmp(q_path, "/webmention-hook") == 0) { + status = HTTP_STATUS_BAD_REQUEST; + + const xs_dict *p_vars = xs_dict_get(req, "p_vars"); + + if (!xs_is_dict(p_vars)) + return status; + + const char *source = xs_dict_get(p_vars, "source"); + const char *target = xs_dict_get(p_vars, "target"); + + if (!xs_is_string(source) || !xs_is_string(target)) { + srv_debug(1, xs_fmt("webmention-hook bad source or target")); + return status; + } + + if (!xs_startswith(target, srv_baseurl)) { + srv_debug(1, xs_fmt("webmention-hook unknown target %s", target)); + return status; + } + + if (!object_here(target)) { + srv_debug(0, xs_fmt("webmention-hook target %s not / no longer here", target)); + return status; + } + + /* get the user */ + xs *s1 = xs_replace(target, srv_baseurl, ""); + + xs *l1 = xs_split(s1, "/"); + const char *uid = xs_list_get(l1, 1); + snac user; + + if (!xs_is_string(uid) || !user_open(&user, uid)) + return status; + + int r = xs_webmention_hook(source, target, USER_AGENT); + + if (r > 0) + notify_add(&user, "Webmention", NULL, source, target, xs_stock(XSTYPE_DICT)); + + srv_log(xs_fmt("webmention-hook source=%s target=%s %d", source, target, r)); + + user_free(&user); + status = HTTP_STATUS_OK; + } + + return status; +} + + void httpd_connection(FILE *f) /* the connection processor */ { @@ -444,6 +502,10 @@ void httpd_connection(FILE *f) else if (strcmp(method, "POST") == 0) { + if (status == 0) + status = server_post_handler(req, q_path, + payload, p_size, &body, &b_size, &ctype); + #ifndef NO_MASTODON_API if (status == 0) status = oauth_post_handler(req, q_path, diff --git a/xs_webmention.h b/xs_webmention.h index 8415629..e177573 100644 --- a/xs_webmention.h +++ b/xs_webmention.h @@ -5,6 +5,7 @@ #define _XS_WEBMENTION_H int xs_webmention_send(const char *source, const char *target, const char *user_agent); +int xs_webmention_hook(const char *source, const char *target, const char *user_agent); #ifdef XS_IMPLEMENTATION @@ -118,6 +119,47 @@ int xs_webmention_send(const char *source, const char *target, const char *user_ } +int xs_webmention_hook(const char *source, const char *target, const char *user_agent) +/* a Webmention has been received for a target that is ours; check if the source + really contains a link to our target */ +{ + int status = 0; + + xs *ua = xs_fmt("%s (Webmention)", user_agent ? user_agent : "xs_webmention"); + xs *headers = xs_dict_new(); + headers = xs_dict_set(headers, "accept", "text/html"); + headers = xs_dict_set(headers, "user-agent", ua); + + xs *g_req = NULL; + xs *payload = NULL; + int p_size = 0; + + g_req = xs_http_request("GET", source, headers, NULL, 0, &status, &payload, &p_size, 0); + + if (status < 200 || status > 299) + return -1; + + if (!xs_is_string(payload)) + return -2; + + /* note: a "rogue" webmention can include a link to our target in commented-out HTML code */ + + xs *links = xs_regex_select(payload, "<(a +|link +)[^>]+>"); + const char *link; + + status = 0; + xs_list_foreach(links, link) { + /* if the link contains our target, it's valid */ + if (xs_str_in(link, target) != -1) { + status = 1; + break; + } + } + + return status; +} + + #endif /* XS_IMPLEMENTATION */ #endif /* _XS_WEBMENTION_H */ -- cgit From 1aa8fe8573bfc93f569bf266770e9f39a1c2da14 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 24 Jun 2025 14:08:59 +0200 Subject: "voided" unused arguments in server_post_handler(). --- httpd.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'httpd.c') diff --git a/httpd.c b/httpd.c index af78e09..9216bdc 100644 --- a/httpd.c +++ b/httpd.c @@ -380,6 +380,12 @@ int server_post_handler(const xs_dict *req, const char *q_path, { int status = 0; + (void)payload; + (void)p_size; + (void)body; + (void)b_size; + (void)ctype; + if (strcmp(q_path, "/webmention-hook") == 0) { status = HTTP_STATUS_BAD_REQUEST; -- cgit From 9403fb41effc6da7a997898b07b6c6b46f8e2ba1 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 24 Jun 2025 14:34:55 +0200 Subject: More webmention hook tweaks. --- html.c | 7 +++++++ httpd.c | 13 ++++++------- 2 files changed, 13 insertions(+), 7 deletions(-) (limited to 'httpd.c') diff --git a/html.c b/html.c index 672ea44..aeecdf5 100644 --- a/html.c +++ b/html.c @@ -3531,6 +3531,13 @@ xs_str *html_notifications(snac *user, int skip, int show) xs_html_text(L("Context")))), h); } + else + xs_html_add(entry, + xs_html_tag("p", + xs_html_text(L("Location: ")), + xs_html_tag("a", + xs_html_attr("href", id), + xs_html_text(id)))); } if (strcmp(v, n_time) > 0) { diff --git a/httpd.c b/httpd.c index 9216bdc..8f2ef4d 100644 --- a/httpd.c +++ b/httpd.c @@ -407,11 +407,6 @@ int server_post_handler(const xs_dict *req, const char *q_path, return status; } - if (!object_here(target)) { - srv_debug(0, xs_fmt("webmention-hook target %s not / no longer here", target)); - return status; - } - /* get the user */ xs *s1 = xs_replace(target, srv_baseurl, ""); @@ -419,13 +414,17 @@ int server_post_handler(const xs_dict *req, const char *q_path, const char *uid = xs_list_get(l1, 1); snac user; - if (!xs_is_string(uid) || !user_open(&user, uid)) + if (!xs_is_string(uid) || !user_open(&user, uid)) { + srv_debug(1, xs_fmt("webmention-hook cannot find user for %s", target)); return status; + } int r = xs_webmention_hook(source, target, USER_AGENT); - if (r > 0) + if (r > 0) { notify_add(&user, "Webmention", NULL, source, target, xs_stock(XSTYPE_DICT)); + timeline_touch(&user); + } srv_log(xs_fmt("webmention-hook source=%s target=%s %d", source, target, r)); -- cgit From 26502fa62ee05465e524987ab5b86078f2e64e6f Mon Sep 17 00:00:00 2001 From: grunfink Date: Thu, 10 Jul 2025 05:45:22 +0200 Subject: Added metadata to nodeinfo 2.0. --- httpd.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'httpd.c') diff --git a/httpd.c b/httpd.c index 8f2ef4d..d01eb9c 100644 --- a/httpd.c +++ b/httpd.c @@ -66,7 +66,9 @@ const char *nodeinfo_2_0_template = "" "\"services\":{\"outbound\":[],\"inbound\":[]}," "\"usage\":{\"users\":{\"total\":%d,\"activeMonth\":%d,\"activeHalfyear\":%d}," "\"localPosts\":%d}," - "\"openRegistrations\":false,\"metadata\":{}}"; + "\"openRegistrations\":false,\"metadata\":{" + "\"nodeDescription\":\"%s\",\"nodeName\":\"%s\"" + "}}"; xs_str *nodeinfo_2_0(void) /* builds a nodeinfo json object */ @@ -99,7 +101,10 @@ xs_str *nodeinfo_2_0(void) n_posts += index_len(pidxfn); } - return xs_fmt(nodeinfo_2_0_template, n_utotal, n_umonth, n_uhyear, n_posts); + const char *name = xs_dict_get_def(srv_config, "title", ""); + const char *desc = xs_dict_get_def(srv_config, "short_description", ""); + + return xs_fmt(nodeinfo_2_0_template, n_utotal, n_umonth, n_uhyear, n_posts, desc, name); } -- cgit