diff options
| author | Ruben Beltran del Rio <rbdr@bravo-i.local> | 2026-04-15 16:59:19 +0200 |
|---|---|---|
| committer | Ruben Beltran del Rio <rbdr@bravo-i.local> | 2026-04-15 16:59:19 +0200 |
| commit | 6fc748933ad263ba0e8c78eb9ae1e085a2b0dfb2 (patch) | |
| tree | e82d7cf345ed9c0e4b40e6156c509eb48aef13ee | |
| parent | 5e1dbb58904ad39f20cd8e90c5d8dded2a6ca2ad (diff) | |
Add neighborhood featuremaster
| -rw-r--r-- | Makefile | 6 | ||||
| -rw-r--r-- | data.c | 8 | ||||
| -rw-r--r-- | html.c | 27 | ||||
| -rw-r--r-- | httpd.c | 12 | ||||
| -rw-r--r-- | neighborhood.c | 90 | ||||
| -rw-r--r-- | snac.h | 10 |
6 files changed, 152 insertions, 1 deletions
@@ -5,7 +5,8 @@ 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 rss.o + activitypub.o html.o utils.o format.o upgrade.o mastoapi.o rss.o \ + neighborhood.o $(CC) $(CFLAGS) -L$(PREFIX)/lib *.o -lcurl -lcrypto $(LDFLAGS) -pthread -o $@ test: tests/smtp @@ -81,3 +82,6 @@ utils.o: utils.c xs.h xs_io.h xs_json.h xs_time.h xs_openssl.h \ xs_list_tools.h xs_set.h snac.h webfinger.o: webfinger.c xs.h xs_json.h xs_curl.h xs_mime.h xs_http.h \ xs_http_codes.h snac.h +# BEGIN neighborhood +neighborhood.o: neighborhood.c xs.h xs_set.h snac.h +# END neighborhood @@ -1543,6 +1543,14 @@ void timeline_update_indexes(snac *snac, const char *id) { object_user_cache_add(snac, id, "private"); + /* BEGIN neighborhood */ + { + xs *n_msg = NULL; + if (valid_status(object_get(id, &n_msg))) + neighborhood_update_indexes(id, n_msg); + } + /* END neighborhood */ + if (is_msg_mine(snac, id)) { xs *msg = NULL; @@ -1015,6 +1015,20 @@ static xs_html *html_instance_body(void) xs_html_text(handle))))); } + /* BEGIN neighborhood */ + if (xs_type(xs_dict_get(srv_config, "neighborhood")) == XSTYPE_LIST) { + xs *nurl = xs_fmt("%s/neighborhood", srv_baseurl); + xs_html_add(dl, + xs_html_tag("di", + xs_html_tag("dt", + xs_html_text(L("Neighborhood"))), + xs_html_tag("dd", + xs_html_tag("a", + xs_html_attr("href", nurl), + xs_html_text(L("View federated neighborhood feed")))))); + } + /* END neighborhood */ + return body; } @@ -1207,6 +1221,18 @@ static xs_html *html_user_body(snac *user, int read_only) xs *people_url = xs_fmt("%s/people", user->actor); xs *instance_url = xs_fmt("%s/instance", user->actor); + /* BEGIN neighborhood */ + xs_html *neighborhood_nav = xs_html_text(""); + if (xs_type(xs_dict_get(srv_config, "neighborhood")) == XSTYPE_LIST) { + xs *nurl = xs_fmt("%s/neighborhood", srv_baseurl); + neighborhood_nav = xs_html_container( + xs_html_text(" - "), + xs_html_tag("a", + xs_html_attr("href", nurl), + xs_html_text(L("neighborhood")))); + } + /* END neighborhood */ + xs_html_add(top_nav, xs_html_tag("a", xs_html_attr("href", user->actor), @@ -1229,6 +1255,7 @@ static xs_html *html_user_body(snac *user, int read_only) xs_html_tag("a", xs_html_attr("href", instance_url), xs_html_text(L("instance"))), + neighborhood_nav, xs_html_text(" "), xs_html_tag("form", xs_html_attr("style", "display: inline!important"), @@ -280,6 +280,18 @@ int server_get_handler(xs_dict *req, const char *q_path, *body = xs_base64_dec(default_avatar_base64(), b_size); *ctype = "image/png"; } + /* BEGIN neighborhood */ + else + if (strcmp(q_path, "/neighborhood") == 0) { + if (xs_type(xs_dict_get(srv_config, "neighborhood")) == XSTYPE_LIST) { + xs *tl = timeline_neighborhood_list(0, 30); + *body = html_timeline(NULL, tl, 0, 0, 0, 0, + L("Neighborhood"), NULL, 0, NULL, 0); + if (*body) + status = HTTP_STATUS_OK; + } + } + /* END neighborhood */ else if (strcmp(q_path, "/.well-known/nodeinfo") == 0) { status = HTTP_STATUS_OK; diff --git a/neighborhood.c b/neighborhood.c new file mode 100644 index 0000000..964a633 --- /dev/null +++ b/neighborhood.c @@ -0,0 +1,90 @@ +/* snac - A simple, minimalistic ActivityPub instance */ +/* copyright (c) 2022 - 2026 grunfink et al. / MIT license */ + +/* BEGIN neighborhood */ +/* neighborhood feed: feed of public posts from servers listed in server.json + under the "neighborhood" key. */ + +#include "xs.h" +#include "xs_set.h" + +#include "snac.h" + +xs_str *neighborhood_index_fn(void) +/* returns the filename of the neighborhood index */ +{ + return xs_fmt("%s/neighborhood.idx", srv_basedir); +} + + +int neighborhood_contains_actor(const char *actor) +/* 1 if the actor's host is in srv_config["neighborhood"], 0 otherwise */ +{ + if (!xs_is_string(actor) || *actor == '\0') + return 0; + + const xs_list *neighborhoods = xs_dict_get(srv_config, "neighborhood"); + if (xs_type(neighborhoods) != XSTYPE_LIST) + return 0; + + /* extract host: third segment of "https://host/..." */ + xs *parts = xs_split(actor, "/"); + const char *raw_host = xs_list_get(parts, 2); + if (!xs_is_string(raw_host) || *raw_host == '\0') + return 0; + + xs *host = xs_tolower_i(xs_dup(raw_host)); + + const xs_str *v; + int c = 0; + while (xs_list_next(neighborhoods, &v, &c)) { + if (!xs_is_string(v)) + continue; + xs *candidate = xs_tolower_i(xs_dup(v)); + if (strcmp(host, candidate) == 0) + return 1; + } + + return 0; +} + + +void neighborhood_update_indexes(const char *id, const xs_dict *msg) +/* adds id to neighborhood.idx when the author's host is allowlisted and the post is public */ +{ + if (!xs_is_string(id) || msg == NULL) + return; + + if (xs_type(xs_dict_get(srv_config, "neighborhood")) != XSTYPE_LIST) + return; + + if (get_msg_visibility(msg) != SCOPE_PUBLIC) + return; + + const char *atto = get_atto(msg); + if (!neighborhood_contains_actor(atto)) + return; + + xs *fn = neighborhood_index_fn(); + if (!index_in(fn, id)) + index_add(fn, id); +} + + +xs_list *timeline_neighborhood_list(int skip, int show) +/* returns the timeline for the neighborhood */ +{ + xs *idx = neighborhood_index_fn(); + xs *lst = index_list_desc(idx, skip, show); + + /* make the list unique */ + xs_set rep; + xs_set_init(&rep); + const char *md5; + + xs_list_foreach(lst, md5) + xs_set_add(&rep, md5); + + return xs_set_result(&rep); +} +/* END neighborhood */ @@ -200,6 +200,16 @@ xs_list *local_list(snac *snac, int max); xs_str *instance_index_fn(void); xs_list *timeline_instance_list(int skip, int show); +/* BEGIN neighborhood */ +xs_str *neighborhood_index_fn(void); +int neighborhood_contains_actor(const char *actor); +void neighborhood_update_indexes(const char *id, const xs_dict *msg); +xs_list *timeline_neighborhood_list(int skip, int show); + +int index_in(const char *fn, const char *id); +int index_in_md5(const char *fn, const char *md5); +/* END neighborhood */ + int following_add(snac *snac, const char *actor, const xs_dict *msg); int following_del(snac *snac, const char *actor); int following_check(snac *snac, const char *actor); |