aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile6
-rw-r--r--data.c8
-rw-r--r--html.c27
-rw-r--r--httpd.c12
-rw-r--r--neighborhood.c90
-rw-r--r--snac.h10
6 files changed, 152 insertions, 1 deletions
diff --git a/Makefile b/Makefile
index ec239f4..c7d90c2 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/data.c b/data.c
index 992a42f..4521f17 100644
--- a/data.c
+++ b/data.c
@@ -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;
diff --git a/html.c b/html.c
index a3d2c61..4484fc2 100644
--- a/html.c
+++ b/html.c
@@ -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"),
diff --git a/httpd.c b/httpd.c
index 248f5fa..b79aa87 100644
--- a/httpd.c
+++ b/httpd.c
@@ -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 */
diff --git a/snac.h b/snac.h
index 9f6e534..96b083d 100644
--- a/snac.h
+++ b/snac.h
@@ -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);