From 117244e16936840a8abdf4c07d33ec0b50f7d7d2 Mon Sep 17 00:00:00 2001 From: grunfink Date: Mon, 26 May 2025 07:28:00 +0200 Subject: Bumped version. --- snac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'snac.h') diff --git a/snac.h b/snac.h index 256731f..841c41e 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ -#define VERSION "2.77" +#define VERSION "2.78-dev" #define USER_AGENT "snac/" VERSION -- cgit From 631e44a64a20741b5e4716bf75caf7fa743fef82 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 27 May 2025 21:08:57 +0200 Subject: Renamed timeline_here() to timeline_here_by_md5(), as it always should have been. --- data.c | 4 ++-- html.c | 6 +++--- main.c | 2 +- mastoapi.c | 2 +- snac.h | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) (limited to 'snac.h') diff --git a/data.c b/data.c index f45e346..9eada38 100644 --- a/data.c +++ b/data.c @@ -1391,7 +1391,7 @@ xs_str *timeline_fn_by_md5(snac *snac, const char *md5) } -int timeline_here(snac *snac, const char *md5) +int timeline_here_by_md5(snac *snac, const char *md5) /* checks if an object is in the user cache */ { xs *fn = timeline_fn_by_md5(snac, md5); @@ -1515,7 +1515,7 @@ xs_list *timeline_top_level(snac *snac, const xs_list *list) break; /* well, there is a parent... but is it here? */ - if (!timeline_here(snac, line2)) + if (!timeline_here_by_md5(snac, line2)) break; /* it's here! try again with its own parent */ diff --git a/html.c b/html.c index 83dac0a..4aa0fa4 100644 --- a/html.c +++ b/html.c @@ -2117,7 +2117,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, if (!xs_is_null(parent) && *parent) { xs *md5 = xs_md5_hex(parent, strlen(parent)); - if (!timeline_here(user, md5)) { + if (!timeline_here_by_md5(user, md5)) { xs_html_add(post_header, xs_html_tag("div", xs_html_attr("class", "snac-origin"), @@ -3775,7 +3775,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, /* add the post to the timeline */ xs *md5 = xs_md5_hex(q, strlen(q)); - if (!timeline_here(&snac, md5)) + if (!timeline_here_by_md5(&snac, md5)) timeline_add(&snac, q, object); } } @@ -3917,7 +3917,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs *l = xs_split(p_path, "/"); const char *md5 = xs_list_get(l, -1); - if (md5 && *md5 && timeline_here(&snac, md5)) { + if (md5 && *md5 && timeline_here_by_md5(&snac, md5)) { xs *list0 = xs_list_append(xs_list_new(), md5); xs *list = timeline_top_level(&snac, list0); diff --git a/main.c b/main.c index 80df2d0..9cfbb0d 100644 --- a/main.c +++ b/main.c @@ -695,7 +695,7 @@ int main(int argc, char *argv[]) xs_json_dump(data, 4, stdout); enqueue_actor_refresh(&snac, xs_dict_get(data, "attributedTo"), 0); - if (!timeline_here(&snac, url)) + if (!timeline_here_by_md5(&snac, url)) timeline_add(&snac, url, data); else printf("Post %s already here\n", url); diff --git a/mastoapi.c b/mastoapi.c index 9875b12..c88fd1b 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -2510,7 +2510,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, if (xs_startswith(q, "https://")) { xs *md5 = xs_md5_hex(q, strlen(q)); - if (!timeline_here(&snac1, md5)) { + if (!timeline_here_by_md5(&snac1, md5)) { xs *object = NULL; int status; diff --git a/snac.h b/snac.h index 841c41e..621ab8a 100644 --- a/snac.h +++ b/snac.h @@ -164,7 +164,7 @@ int pending_count(snac *user); double timeline_mtime(snac *snac); int timeline_touch(snac *snac); -int timeline_here(snac *snac, const char *md5); +int timeline_here_by_md5(snac *snac, const char *md5); int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg); int timeline_del(snac *snac, const char *id); xs_str *user_index_fn(snac *user, const char *idx_name); -- cgit From 5bc451159420d5d51a507fda82a623069cfae92b Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 27 May 2025 21:14:23 +0200 Subject: New function timeline_here(). --- data.c | 8 ++++++++ html.c | 8 ++------ main.c | 2 +- mastoapi.c | 4 +--- snac.h | 1 + 5 files changed, 13 insertions(+), 10 deletions(-) (limited to 'snac.h') diff --git a/data.c b/data.c index 9eada38..224976b 100644 --- a/data.c +++ b/data.c @@ -1400,6 +1400,14 @@ int timeline_here_by_md5(snac *snac, const char *md5) } +int timeline_here(snac *user, const char *id) +{ + xs *md5 = xs_md5_hex(id, strlen(id)); + + return timeline_here_by_md5(user, md5); +} + + int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg) /* gets a message from the timeline */ { diff --git a/html.c b/html.c index 4aa0fa4..4c30355 100644 --- a/html.c +++ b/html.c @@ -2115,9 +2115,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, const char *parent = get_in_reply_to(msg); if (!xs_is_null(parent) && *parent) { - xs *md5 = xs_md5_hex(parent, strlen(parent)); - - if (!timeline_here_by_md5(user, md5)) { + if (!timeline_here(user, parent)) { xs_html_add(post_header, xs_html_tag("div", xs_html_attr("class", "snac-origin"), @@ -3773,9 +3771,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, q = url_acct; /* add the post to the timeline */ - xs *md5 = xs_md5_hex(q, strlen(q)); - - if (!timeline_here_by_md5(&snac, md5)) + if (!timeline_here(&snac, q)) timeline_add(&snac, q, object); } } diff --git a/main.c b/main.c index 9cfbb0d..80df2d0 100644 --- a/main.c +++ b/main.c @@ -695,7 +695,7 @@ int main(int argc, char *argv[]) xs_json_dump(data, 4, stdout); enqueue_actor_refresh(&snac, xs_dict_get(data, "attributedTo"), 0); - if (!timeline_here_by_md5(&snac, url)) + if (!timeline_here(&snac, url)) timeline_add(&snac, url, data); else printf("Post %s already here\n", url); diff --git a/mastoapi.c b/mastoapi.c index c88fd1b..df713a8 100644 --- a/mastoapi.c +++ b/mastoapi.c @@ -2508,9 +2508,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path, /* reply something only for offset 0; otherwise, apps like Tusky keep asking again and again */ if (xs_startswith(q, "https://")) { - xs *md5 = xs_md5_hex(q, strlen(q)); - - if (!timeline_here_by_md5(&snac1, md5)) { + if (!timeline_here(&snac1, q)) { xs *object = NULL; int status; diff --git a/snac.h b/snac.h index 621ab8a..f32a90d 100644 --- a/snac.h +++ b/snac.h @@ -165,6 +165,7 @@ int pending_count(snac *user); double timeline_mtime(snac *snac); int timeline_touch(snac *snac); int timeline_here_by_md5(snac *snac, const char *md5); +int timeline_here(snac *snac, const char *id); int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg); int timeline_del(snac *snac, const char *id); xs_str *user_index_fn(snac *user, const char *idx_name); -- cgit 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(). --- html.c | 4 ++-- httpd.c | 2 +- snac.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'snac.h') diff --git a/html.c b/html.c index 4c30355..d3d5075 100644 --- a/html.c +++ b/html.c @@ -4123,7 +4123,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs_dict_get(srv_config, "host")); xs *rss_link = xs_fmt("%s.rss", snac.actor); - *body = timeline_to_rss(&snac, elems, rss_title, rss_link, bio); + *body = rss_from_timeline(&snac, elems, rss_title, rss_link, bio); *b_size = strlen(*body); *ctype = "application/rss+xml; charset=utf-8"; status = HTTP_STATUS_OK; @@ -5022,7 +5022,7 @@ int html_post_handler(const xs_dict *req, const char *q_path, } -xs_str *timeline_to_rss(snac *user, const xs_list *timeline, +xs_str *rss_from_timeline(snac *user, const xs_list *timeline, const char *title, const char *link, const char *desc) /* converts a timeline to rss */ { 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 { diff --git a/snac.h b/snac.h index f32a90d..9a5aca0 100644 --- a/snac.h +++ b/snac.h @@ -395,7 +395,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, int html_post_handler(const xs_dict *req, const char *q_path, char *payload, int p_size, char **body, int *b_size, char **ctype); -xs_str *timeline_to_rss(snac *user, const xs_list *timeline, +xs_str *rss_from_timeline(snac *user, const xs_list *timeline, const char *title, const char *link, const char *desc); int write_default_css(void); -- cgit From 97ff66116b7633066375bbc6cc88be5b8587453b Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 28 May 2025 07:48:47 +0200 Subject: New file rss.c. --- Makefile | 9 ++--- Makefile.NetBSD | 9 ++--- html.c | 97 --------------------------------------------------- rss.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ snac.h | 5 +-- 5 files changed, 118 insertions(+), 107 deletions(-) create mode 100644 rss.c (limited to 'snac.h') diff --git a/Makefile b/Makefile index 46fdb09..bb4029c 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ CFLAGS?=-g -Wall -Wextra -pedantic all: snac snac: snac.o main.o sandbox.o data.o http.o httpd.o webfinger.o \ - activitypub.o html.o utils.o format.o upgrade.o mastoapi.o + activitypub.o html.o utils.o format.o upgrade.o mastoapi.o rss.o $(CC) $(CFLAGS) -L$(PREFIX)/lib *.o -lcurl -lcrypto $(LDFLAGS) -pthread -o $@ test: tests/smtp @@ -48,12 +48,12 @@ update-po: activitypub.o: activitypub.c xs.h xs_json.h xs_curl.h xs_mime.h \ xs_openssl.h xs_regex.h xs_time.h xs_set.h xs_match.h xs_unicode.h \ - snac.h http_codes.h + xs_webmention.h snac.h http_codes.h data.o: data.c xs.h xs_hex.h xs_io.h xs_json.h xs_openssl.h xs_glob.h \ xs_set.h xs_time.h xs_regex.h xs_match.h xs_unicode.h xs_random.h \ xs_po.h snac.h http_codes.h format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h xs_json.h \ - xs_time.h xs_match.h snac.h http_codes.h + xs_time.h xs_match.h xs_unicode.h snac.h http_codes.h html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \ xs_time.h xs_mime.h xs_match.h xs_html.h xs_curl.h xs_unicode.h xs_url.h \ xs_random.h snac.h http_codes.h @@ -66,7 +66,8 @@ main.o: main.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h xs_match.h \ snac.h http_codes.h mastoapi.o: mastoapi.c xs.h xs_hex.h xs_openssl.h xs_json.h xs_io.h \ xs_time.h xs_glob.h xs_set.h xs_random.h xs_url.h xs_mime.h xs_match.h \ - snac.h http_codes.h + xs_unicode.h snac.h http_codes.h +rss.o: rss.c xs.h xs_html.h xs_regex.h xs_time.h snac.h http_codes.h sandbox.o: sandbox.c xs.h snac.h http_codes.h snac.o: snac.c xs.h xs_hex.h xs_io.h xs_unicode_tbl.h xs_unicode.h \ xs_json.h xs_curl.h xs_openssl.h xs_socket.h xs_unix_socket.h xs_url.h \ diff --git a/Makefile.NetBSD b/Makefile.NetBSD index ecf8205..a2bf83c 100644 --- a/Makefile.NetBSD +++ b/Makefile.NetBSD @@ -6,7 +6,7 @@ LDFLAGS=-lrt all: snac snac: snac.o main.o sandbox.o data.o http.o httpd.o webfinger.o \ - activitypub.o html.o utils.o format.o upgrade.o mastoapi.o + activitypub.o html.o utils.o format.o upgrade.o mastoapi.o rss.o $(CC) $(CFLAGS) -L/usr/pkg/lib *.o -lcurl -lcrypto -pthread $(LDFLAGS) -Wl,-rpath,/usr/lib -Wl,-rpath,/usr/pkg/lib -o $@ @@ -37,12 +37,12 @@ uninstall: activitypub.o: activitypub.c xs.h xs_json.h xs_curl.h xs_mime.h \ xs_openssl.h xs_regex.h xs_time.h xs_set.h xs_match.h xs_unicode.h \ - snac.h http_codes.h + xs_webmention.h snac.h http_codes.h data.o: data.c xs.h xs_hex.h xs_io.h xs_json.h xs_openssl.h xs_glob.h \ xs_set.h xs_time.h xs_regex.h xs_match.h xs_unicode.h xs_random.h \ xs_po.h snac.h http_codes.h format.o: format.c xs.h xs_regex.h xs_mime.h xs_html.h xs_json.h \ - xs_time.h xs_match.h snac.h http_codes.h + xs_time.h xs_match.h xs_unicode.h snac.h http_codes.h html.o: html.c xs.h xs_io.h xs_json.h xs_regex.h xs_set.h xs_openssl.h \ xs_time.h xs_mime.h xs_match.h xs_html.h xs_curl.h xs_unicode.h xs_url.h \ xs_random.h snac.h http_codes.h @@ -55,7 +55,8 @@ main.o: main.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h xs_match.h \ snac.h http_codes.h mastoapi.o: mastoapi.c xs.h xs_hex.h xs_openssl.h xs_json.h xs_io.h \ xs_time.h xs_glob.h xs_set.h xs_random.h xs_url.h xs_mime.h xs_match.h \ - snac.h http_codes.h + xs_unicode.h snac.h http_codes.h +rss.o: rss.c xs.h xs_html.h xs_regex.h xs_time.h snac.h http_codes.h sandbox.o: sandbox.c xs.h snac.h http_codes.h snac.o: snac.c xs.h xs_hex.h xs_io.h xs_unicode_tbl.h xs_unicode.h \ xs_json.h xs_curl.h xs_openssl.h xs_socket.h xs_unix_socket.h xs_url.h \ diff --git a/html.c b/html.c index d3d5075..fadcfb0 100644 --- a/html.c +++ b/html.c @@ -5020,100 +5020,3 @@ int html_post_handler(const xs_dict *req, const char *q_path, return status; } - - -xs_str *rss_from_timeline(snac *user, const xs_list *timeline, - const char *title, const char *link, const char *desc) -/* converts a timeline to rss */ -{ - xs_html *rss = xs_html_tag("rss", - xs_html_attr("xmlns:content", "http:/" "/purl.org/rss/1.0/modules/content/"), - xs_html_attr("version", "2.0"), - xs_html_attr("xmlns:atom", "http:/" "/www.w3.org/2005/Atom")); - - xs_html *channel = xs_html_tag("channel", - xs_html_tag("title", - xs_html_text(title)), - xs_html_tag("language", - xs_html_text("en")), - xs_html_tag("link", - xs_html_text(link)), - xs_html_sctag("atom:link", - xs_html_attr("href", link), - xs_html_attr("rel", "self"), - xs_html_attr("type", "application/rss+xml")), - xs_html_tag("generator", - xs_html_text(USER_AGENT)), - xs_html_tag("description", - xs_html_text(desc))); - - xs_html_add(rss, channel); - - int cnt = 0; - const char *v; - - xs_list_foreach(timeline, v) { - xs *msg = NULL; - - if (user) { - if (!valid_status(timeline_get_by_md5(user, v, &msg))) - continue; - } - else { - if (!valid_status(object_get_by_md5(v, &msg))) - continue; - } - - const char *id = xs_dict_get(msg, "id"); - const char *content = xs_dict_get(msg, "content"); - const char *published = xs_dict_get(msg, "published"); - - if (user && !xs_startswith(id, user->actor)) - continue; - - if (!id || !content || !published) - continue; - - /* create a title with the first line of the content */ - xs *title = xs_replace(content, "
", "\n"); - title = xs_regex_replace_i(title, "<[^>]+>", " "); - title = xs_regex_replace_i(title, "&[^;]+;", " "); - int i; - - for (i = 0; title[i] && title[i] != '\n' && i < 50; i++); - - if (title[i] != '\0') { - title[i] = '\0'; - title = xs_str_cat(title, "..."); - } - - title = xs_strip_i(title); - - /* convert the date */ - time_t t = xs_parse_iso_date(published, 0); - xs *rss_date = xs_str_utctime(t, "%a, %d %b %Y %T +0000"); - - /* if it's the first one, add it to the header */ - if (cnt == 0) - xs_html_add(channel, - xs_html_tag("lastBuildDate", - xs_html_text(rss_date))); - - xs_html_add(channel, - xs_html_tag("item", - xs_html_tag("title", - xs_html_text(title)), - xs_html_tag("link", - xs_html_text(id)), - xs_html_tag("guid", - xs_html_text(id)), - xs_html_tag("pubDate", - xs_html_text(rss_date)), - xs_html_tag("description", - xs_html_text(content)))); - - cnt++; - } - - return xs_html_render_s(rss, "\n"); -} diff --git a/rss.c b/rss.c new file mode 100644 index 0000000..61620fa --- /dev/null +++ b/rss.c @@ -0,0 +1,105 @@ +/* snac - A simple, minimalistic ActivityPub instance */ +/* copyright (c) 2025 grunfink et al. / MIT license */ + +#include "xs.h" +#include "xs_html.h" +#include "xs_regex.h" +#include "xs_time.h" + +#include "snac.h" + +xs_str *rss_from_timeline(snac *user, const xs_list *timeline, + const char *title, const char *link, const char *desc) +/* converts a timeline to rss */ +{ + xs_html *rss = xs_html_tag("rss", + xs_html_attr("xmlns:content", "http:/" "/purl.org/rss/1.0/modules/content/"), + xs_html_attr("version", "2.0"), + xs_html_attr("xmlns:atom", "http:/" "/www.w3.org/2005/Atom")); + + xs_html *channel = xs_html_tag("channel", + xs_html_tag("title", + xs_html_text(title)), + xs_html_tag("language", + xs_html_text("en")), + xs_html_tag("link", + xs_html_text(link)), + xs_html_sctag("atom:link", + xs_html_attr("href", link), + xs_html_attr("rel", "self"), + xs_html_attr("type", "application/rss+xml")), + xs_html_tag("generator", + xs_html_text(USER_AGENT)), + xs_html_tag("description", + xs_html_text(desc))); + + xs_html_add(rss, channel); + + int cnt = 0; + const char *v; + + xs_list_foreach(timeline, v) { + xs *msg = NULL; + + if (user) { + if (!valid_status(timeline_get_by_md5(user, v, &msg))) + continue; + } + else { + if (!valid_status(object_get_by_md5(v, &msg))) + continue; + } + + const char *id = xs_dict_get(msg, "id"); + const char *content = xs_dict_get(msg, "content"); + const char *published = xs_dict_get(msg, "published"); + + if (user && !xs_startswith(id, user->actor)) + continue; + + if (!id || !content || !published) + continue; + + /* create a title with the first line of the content */ + xs *title = xs_replace(content, "
", "\n"); + title = xs_regex_replace_i(title, "<[^>]+>", " "); + title = xs_regex_replace_i(title, "&[^;]+;", " "); + int i; + + for (i = 0; title[i] && title[i] != '\n' && i < 50; i++); + + if (title[i] != '\0') { + title[i] = '\0'; + title = xs_str_cat(title, "..."); + } + + title = xs_strip_i(title); + + /* convert the date */ + time_t t = xs_parse_iso_date(published, 0); + xs *rss_date = xs_str_utctime(t, "%a, %d %b %Y %T +0000"); + + /* if it's the first one, add it to the header */ + if (cnt == 0) + xs_html_add(channel, + xs_html_tag("lastBuildDate", + xs_html_text(rss_date))); + + xs_html_add(channel, + xs_html_tag("item", + xs_html_tag("title", + xs_html_text(title)), + xs_html_tag("link", + xs_html_text(id)), + xs_html_tag("guid", + xs_html_text(id)), + xs_html_tag("pubDate", + xs_html_text(rss_date)), + xs_html_tag("description", + xs_html_text(content)))); + + cnt++; + } + + return xs_html_render_s(rss, "\n"); +} diff --git a/snac.h b/snac.h index 9a5aca0..f95ba4d 100644 --- a/snac.h +++ b/snac.h @@ -395,8 +395,6 @@ int html_get_handler(const xs_dict *req, const char *q_path, int html_post_handler(const xs_dict *req, const char *q_path, char *payload, int p_size, char **body, int *b_size, char **ctype); -xs_str *rss_from_timeline(snac *user, const xs_list *timeline, - const char *title, const char *link, const char *desc); int write_default_css(void); int snac_init(const char *_basedir); @@ -462,3 +460,6 @@ int badlogin_check(const char *user, const char *addr); void badlogin_inc(const char *user, const char *addr); 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); -- cgit From b783f287c8b3e77bbd1eb94892ea645ba05e8770 Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 28 May 2025 07:56:44 +0200 Subject: New function rss_to_timeline(). --- rss.c | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ snac.h | 2 ++ 2 files changed, 126 insertions(+) (limited to 'snac.h') diff --git a/rss.c b/rss.c index 61620fa..a3a092e 100644 --- a/rss.c +++ b/rss.c @@ -5,6 +5,8 @@ #include "xs_html.h" #include "xs_regex.h" #include "xs_time.h" +#include "xs_match.h" +#include "xs_curl.h" #include "snac.h" @@ -103,3 +105,125 @@ xs_str *rss_from_timeline(snac *user, const xs_list *timeline, return xs_html_render_s(rss, "\n"); } + + +void rss_to_timeline(snac *user, const char *url) +/* reads an RSS and inserts all ActivityPub posts into the user's timeline */ +{ + xs *hdrs = xs_dict_new(); + hdrs = xs_dict_set(hdrs, "accept", "application/rss+xml"); + hdrs = xs_dict_set(hdrs, "user-agent", USER_AGENT); + + xs *payload = NULL; + int status; + int p_size; + + xs *rsp = xs_http_request("GET", url, hdrs, NULL, 0, &status, &payload, &p_size, 0); + + if (!valid_status(status) || !xs_is_string(payload)) + return; + + /* not an RSS? done */ + const char *ctype = xs_dict_get(rsp, "content-type"); + if (!xs_is_string(ctype) || xs_str_in(ctype, "application/rss+xml") == -1) + return; + + snac_log(user, xs_fmt("parsing RSS %s", url)); + + /* yes, parsing is done with regexes (now I have two problems blah blah blah) */ + xs *links = xs_regex_select(payload, "[^<]+"); + const char *link; + + xs_list_foreach(links, link) { + xs *l = xs_replace(link, "", ""); + char *p = strchr(l, '<'); + + if (p == NULL) + continue; + *p = '\0'; + + /* skip this same URL */ + if (strcmp(l, url) == 0) + continue; + + snac_debug(user, 1, xs_fmt("RSS link: %s", l)); + + if (timeline_here(user, l)) { + snac_debug(user, 1, xs_fmt("RSS entry already in timeline %s", l)); + continue; + } + + /* special trick for Mastodon: convert from the alternate format */ + if (strchr(l, '@') != NULL) { + xs *l2 = xs_split(l, "/"); + + if (xs_list_len(l2) == 5) { + const char *uid = xs_list_get(l2, 3); + if (*uid == '@') { + xs *guessed_id = xs_fmt("https:/" "/%s/users/%s/statuses/%s", + xs_list_get(l2, 2), uid + 1, xs_list_get(l2, -1)); + + if (timeline_here(user, guessed_id)) { + snac_debug(user, 1, xs_fmt("RSS entry already in timeline (alt) %s", guessed_id)); + continue; + } + } + } + } + + xs *obj = NULL; + + if (!valid_status(object_get(l, &obj))) { + /* object is not here: bring it */ + if (!valid_status(activitypub_request(user, l, &obj))) + continue; + } + + if (xs_is_dict(obj)) { + const char *id = xs_dict_get(obj, "id"); + const char *type = xs_dict_get(obj, "type"); + const char *attr_to = get_atto(obj); + + if (!xs_is_string(id) || !xs_is_string(type) || !xs_is_string(attr_to)) + continue; + + if (!xs_match(type, POSTLIKE_OBJECT_TYPE)) + continue; + + if (timeline_here(user, id)) { + snac_debug(user, 1, xs_fmt("RSS entry already in timeline (id) %s", id)); + continue; + } + + if (!valid_status(actor_request(user, attr_to, NULL))) + continue; + + timeline_add(user, id, obj); + } + } +} + + +void rss_process(void) +/* parses all RSS from all users */ +{ + xs *list = user_list(); + const char *uid; + + xs_list_foreach(list, uid) { + snac user; + + if (user_open(&user, uid)) { + const xs_list *rss = xs_dict_get(user.config, "rss"); + + if (xs_is_list(rss)) { + const char *url; + + xs_list_foreach(rss, url) + rss_to_timeline(&user, url); + } + + user_free(&user); + } + } +} diff --git a/snac.h b/snac.h index f95ba4d..db51a1c 100644 --- a/snac.h +++ b/snac.h @@ -463,3 +463,5 @@ 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); -- 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 'snac.h') 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 c8f6e1865dca9477d0cdfb95168bdca4a62852c3 Mon Sep 17 00:00:00 2001 From: grunfink Date: Fri, 30 May 2025 11:30:37 +0200 Subject: New function enqueue_notify_webhook(). --- data.c | 19 +++++++++++++++++++ snac.h | 2 ++ 2 files changed, 21 insertions(+) (limited to 'snac.h') diff --git a/data.c b/data.c index 224976b..ddfb443 100644 --- a/data.c +++ b/data.c @@ -3166,6 +3166,8 @@ void notify_add(snac *snac, const char *type, const char *utype, pthread_mutex_unlock(&data_mutex); } + + enqueue_notify_webhook(snac, noti, 0); } @@ -3521,6 +3523,23 @@ void enqueue_webmention(const xs_dict *msg) } +void enqueue_notify_webhook(snac *user, const xs_dict *noti, int retries) +/* enqueues a notification webhook */ +{ + const char *webhook = xs_dict_get(user->config, "notify_webhook"); + + if (xs_is_string(webhook)) { + xs *qmsg = _new_qmsg("notify_webhook", noti, retries); + const char *ntid = xs_dict_get(qmsg, "ntid"); + xs *fn = xs_fmt("%s/queue/%s.json", user->basedir, ntid); + + qmsg = _enqueue_put(fn, qmsg); + + snac_debug(user, 1, xs_fmt("notify_webhook")); + } +} + + int was_question_voted(snac *user, const char *id) /* returns true if the user voted in this poll */ { diff --git a/snac.h b/snac.h index 06a36f1..b13a44a 100644 --- a/snac.h +++ b/snac.h @@ -294,6 +294,8 @@ void enqueue_object_request(snac *user, const char *id, int forward_secs); void enqueue_verify_links(snac *user); void enqueue_actor_refresh(snac *user, const char *actor, int forward_secs); void enqueue_webmention(const xs_dict *msg); +void enqueue_notify_webhook(snac *user, const xs_dict *noti, int retries); + int was_question_voted(snac *user, const char *id); xs_list *user_queue(snac *snac); -- cgit From 59987f8970fcc17eb789c7d027e68819a8db5600 Mon Sep 17 00:00:00 2001 From: grunfink Date: Mon, 2 Jun 2025 12:34:43 +0200 Subject: Version 2.78 RELEASED. --- snac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'snac.h') diff --git a/snac.h b/snac.h index b13a44a..f3cb40d 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ -#define VERSION "2.78-dev" +#define VERSION "2.78" #define USER_AGENT "snac/" VERSION -- cgit From f9151c2ffc9626a8464bfa7038fe84120734735f Mon Sep 17 00:00:00 2001 From: grunfink Date: Wed, 11 Jun 2025 05:17:20 +0200 Subject: Bumped version. --- snac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'snac.h') diff --git a/snac.h b/snac.h index f3cb40d..7f109e0 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ -#define VERSION "2.78" +#define VERSION "2.79-dev" #define USER_AGENT "snac/" VERSION -- cgit From a07458f408c304ae9d037c95bee77dbf4522f3f0 Mon Sep 17 00:00:00 2001 From: grunfink Date: Thu, 19 Jun 2025 19:19:08 +0200 Subject: New command-line option export_posts. --- main.c | 6 ++++++ snac.h | 2 ++ utils.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 60 insertions(+) (limited to 'snac.h') diff --git a/main.c b/main.c index ddb02a8..f271abc 100644 --- a/main.c +++ b/main.c @@ -59,6 +59,7 @@ int usage(const char *cmd) "verify_links {basedir} {uid} Verifies a user's links (in the metadata)\n" "search {basedir} {uid} {regex} Searches posts by content\n" "export_csv {basedir} {uid} Exports followers, lists, MUTEd and bookmarks to CSV\n" + "export_posts {basedir} {iod} Exports all posts to outbox.json\n" "alias {basedir} {uid} {account} Sets account (@user@host or actor url) as an alias\n" "migrate {basedir} {uid} Migrates to the account defined as the alias\n" "import_csv {basedir} {uid} Imports data from CSV files\n" @@ -308,6 +309,11 @@ int main(int argc, char *argv[]) return 0; } + if (strcmp(cmd, "export_posts") == 0) { /** **/ + export_posts(&snac); + return 0; + } + if (strcmp(cmd, "import_csv") == 0) { /** **/ import_csv(&snac); return 0; diff --git a/snac.h b/snac.h index 7f109e0..42fce87 100644 --- a/snac.h +++ b/snac.h @@ -434,6 +434,8 @@ void mastoapi_purge(void); void verify_links(snac *user); void export_csv(snac *user); +void export_posts(snac *user); + int migrate_account(snac *user); void import_blocked_accounts_csv(snac *user, const char *fn); diff --git a/utils.c b/utils.c index 7adbce2..5367f22 100644 --- a/utils.c +++ b/utils.c @@ -725,6 +725,58 @@ void export_csv(snac *user) } +void export_posts(snac *user) +/* exports all posts to an OrderedCollection */ +{ + xs *ifn = xs_fmt("%s/public.idx", user->basedir); + xs *index = index_list(ifn, XS_ALL); + xs *ofn = xs_fmt("%s/export/outbox.json", user->basedir); + FILE *f; + + if ((f = fopen(ofn, "w")) == NULL) { + snac_log(user, xs_fmt("Cannot create file %s", ofn)); + return; + } + + int cnt = 0; + + /* raw output */ + fprintf(f, "{\"@context\": \"https:/" "/www.w3.org/ns/activitystreams\","); + fprintf(f, "\"id\": \"outbox.json\","); + fprintf(f, "\"type\": \"OrderedCollection\","); + fprintf(f, "\"orderedItems\": ["); + + const char *md5; + + snac_log(user, xs_fmt("Creating %s...", ofn)); + + xs_list_foreach(index, md5) { + xs *obj = NULL; + + if (!valid_status(object_get_by_md5(md5, &obj))) + continue; + + const char *type = xs_dict_get(obj, "type"); + + if (!xs_is_string(type) || strcmp(type, "Note")) + continue; + + const char *atto = get_atto(obj); + + if (!xs_is_string(atto) || strcmp(atto, user->actor)) + continue; + + xs *c_msg = msg_create(user, obj); + xs_json_dump(c_msg, 0, f); + cnt++; + } + + fprintf(f, "], \"totalItems\": %d}", cnt); + + fclose(f); +} + + void import_blocked_accounts_csv(snac *user, const char *ifn) /* imports a Mastodon CSV file of blocked accounts */ { -- cgit From 9ecca1f145be9d99301ba857da9a20ac2d62a27f Mon Sep 17 00:00:00 2001 From: grunfink Date: Mon, 30 Jun 2025 09:53:02 +0200 Subject: Version 2.79 RELEASED. --- snac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'snac.h') diff --git a/snac.h b/snac.h index 42fce87..ae7b4b3 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ -#define VERSION "2.79-dev" +#define VERSION "2.79" #define USER_AGENT "snac/" VERSION -- cgit From 8ff1c54c9ea40452b899cfde2d96252ad5fc7cf0 Mon Sep 17 00:00:00 2001 From: grunfink Date: Sat, 5 Jul 2025 19:16:59 +0200 Subject: Bumped version. --- snac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'snac.h') diff --git a/snac.h b/snac.h index ae7b4b3..ea92e4d 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ -#define VERSION "2.79" +#define VERSION "2.80-dev" #define USER_AGENT "snac/" VERSION -- cgit From ed3428d9533a6076553d57d5a119cb9212641d46 Mon Sep 17 00:00:00 2001 From: grunfink Date: Mon, 7 Jul 2025 09:42:51 +0200 Subject: Version 2.80 RELEASED. --- snac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'snac.h') diff --git a/snac.h b/snac.h index ea92e4d..cc66686 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ -#define VERSION "2.80-dev" +#define VERSION "2.80" #define USER_AGENT "snac/" VERSION -- cgit From 08f03cde6b469428494d3870ac54f740fd4641c4 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 8 Jul 2025 17:30:45 +0200 Subject: Added some const here and there. --- snac.h | 2 +- webfinger.c | 2 +- xs_fcgi.h | 4 ++-- xs_httpd.h | 12 ++++++------ xs_version.h | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) (limited to 'snac.h') diff --git a/snac.h b/snac.h index cc66686..b50f345 100644 --- a/snac.h +++ b/snac.h @@ -325,7 +325,7 @@ void httpd(void); int webfinger_request_signed(snac *snac, const char *qs, xs_str **actor, xs_str **user); int webfinger_request(const char *qs, xs_str **actor, xs_str **user); int webfinger_request_fake(const char *qs, xs_str **actor, xs_str **user); -int webfinger_get_handler(xs_dict *req, const char *q_path, +int webfinger_get_handler(const xs_dict *req, const char *q_path, xs_val **body, int *b_size, char **ctype); const char *default_avatar_base64(void); diff --git a/webfinger.c b/webfinger.c index dc0855a..46b7edb 100644 --- a/webfinger.c +++ b/webfinger.c @@ -152,7 +152,7 @@ int webfinger_request_fake(const char *qs, xs_str **actor, xs_str **user) } -int webfinger_get_handler(xs_dict *req, const char *q_path, +int webfinger_get_handler(const xs_dict *req, const char *q_path, xs_val **body, int *b_size, char **ctype) /* serves webfinger queries */ { diff --git a/xs_fcgi.h b/xs_fcgi.h index 0b53dac..b3cb892 100644 --- a/xs_fcgi.h +++ b/xs_fcgi.h @@ -13,7 +13,7 @@ #define _XS_FCGI_H xs_dict *xs_fcgi_request(FILE *f, xs_str **payload, int *p_size, int *id); - void xs_fcgi_response(FILE *f, int status, xs_dict *headers, xs_str *body, int b_size, int id); + void xs_fcgi_response(FILE *f, int status, const xs_dict *headers, const xs_str *body, int b_size, int id); #ifdef XS_IMPLEMENTATION @@ -290,7 +290,7 @@ end: } -void xs_fcgi_response(FILE *f, int status, xs_dict *headers, xs_str *body, int b_size, int fcgi_id) +void xs_fcgi_response(FILE *f, int status, const xs_dict *headers, const xs_str *body, int b_size, int fcgi_id) /* writes an FCGI response */ { struct fcgi_record_header hdr = {0}; diff --git a/xs_httpd.h b/xs_httpd.h index 4cc8263..57759c4 100644 --- a/xs_httpd.h +++ b/xs_httpd.h @@ -5,7 +5,8 @@ #define _XS_HTTPD_H xs_dict *xs_httpd_request(FILE *f, xs_str **payload, int *p_size); -void xs_httpd_response(FILE *f, int status, const char *status_text, xs_dict *headers, xs_str *body, int b_size); +void xs_httpd_response(FILE *f, int status, const char *status_text, + const xs_dict *headers, const xs_val *body, int b_size); #ifdef XS_IMPLEMENTATION @@ -109,16 +110,15 @@ xs_dict *xs_httpd_request(FILE *f, xs_str **payload, int *p_size) } -void xs_httpd_response(FILE *f, int status, const char *status_text, xs_dict *headers, xs_str *body, int b_size) +void xs_httpd_response(FILE *f, int status, const char *status_text, + const xs_dict *headers, const xs_val *body, int b_size) /* sends an httpd response */ { - xs *proto; + fprintf(f, "HTTP/1.1 %d %s\r\n", status, status_text ? status_text : ""); + const xs_str *k; const xs_val *v; - proto = xs_fmt("HTTP/1.1 %d %s", status, status_text); - fprintf(f, "%s\r\n", proto); - xs_dict_foreach(headers, k, v) { fprintf(f, "%s: %s\r\n", k, v); } diff --git a/xs_version.h b/xs_version.h index 09b1bdc..466535b 100644 --- a/xs_version.h +++ b/xs_version.h @@ -1 +1 @@ -/* a32c0d513ae24ad28ffc5c6c2c1cde75bb758e09 2025-06-23T17:43:10+02:00 */ +/* 401d229ffbec89b4a5cf97793926b7afb84a4f26 2025-07-08T15:44:54+02:00 */ -- cgit From 11e9a128594f93f3e05d53de543733d04feb5555 Mon Sep 17 00:00:00 2001 From: grunfink Date: Tue, 8 Jul 2025 17:49:48 +0200 Subject: Bumped version. --- snac.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'snac.h') diff --git a/snac.h b/snac.h index b50f345..e4e190d 100644 --- a/snac.h +++ b/snac.h @@ -1,7 +1,7 @@ /* snac - A simple, minimalistic ActivityPub instance */ /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ -#define VERSION "2.80" +#define VERSION "2.81-dev" #define USER_AGENT "snac/" VERSION -- cgit