diff options
-rw-r--r-- | RELEASE_NOTES.md | 4 | ||||
-rw-r--r-- | TODO.md | 14 | ||||
-rw-r--r-- | activitypub.c | 19 | ||||
-rw-r--r-- | data.c | 6 | ||||
-rw-r--r-- | html.c | 83 | ||||
-rw-r--r-- | httpd.c | 6 | ||||
-rw-r--r-- | snac.h | 7 |
7 files changed, 95 insertions, 44 deletions
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index b8ee714..72e1d77 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -2,12 +2,16 @@ ## 2.48 +New instance page, that show all posts by users in the same instance (like the public instance timeline, but interactive). This will help building communities. + Follower-only replies to unknown users are not shown in timelines. Added verification of metadata links: if the linked page contains a link back to the snac user with a rel="me" attribute, it's marked as verified. Added a profile-page relation to links in webfinger responses (contributed by khm). +Fixed some regressions and a crash. + ## 2.47 Added pagination to the notification page. @@ -14,14 +14,16 @@ Important: deleting a follower should do more that just delete the object, see h ## Wishlist -Add support for rel="me" links, see https://codeberg.org/grunfink/snac2/issues/124 and https://streetpass.social - -Hide followers-only replies to unknown accounts, see https://codeberg.org/grunfink/snac2/issues/123 - Integrate "Ability to federate with hidden networks" see https://codeberg.org/grunfink/snac2/issues/93 Integrate "Added handling for International Domain Names" PR https://codeberg.org/grunfink/snac2/pulls/104 +Consider implementing the rejection of activities from recently-created accounts to mitigate spam, see https://akkoma.dev/AkkomaGang/akkoma/src/branch/develop/lib/pleroma/web/activity_pub/mrf/reject_newly_created_account_note_policy.ex + +Consider discarding posts by content using string or regex to mitigate spam. + +Consider adding milter-like support to reject posts to mitigate spam. + Do something about Akkoma and Misskey's quoted replies (they use the `quoteUrl` field instead of `inReplyTo`). Add more CSS classes according to https://comam.es/snac/grunfink/p/1705598619.090050 @@ -301,3 +303,7 @@ Add a flag to make accounts private, i.e., they don't expose any content from th Fix duplicate mentions, see https://codeberg.org/grunfink/snac2/issues/115 (2024-02-14T09:51:01+0100). Change HTML metadata information to the post info instead of user info, see https://codeberg.org/grunfink/snac2/issues/116 (2024-02-14T09:51:22+0100). + +Add support for rel="me" links, see https://codeberg.org/grunfink/snac2/issues/124 and https://streetpass.social (2024-02-22T12:40:58+0100). + +Hide followers-only replies to unknown accounts, see https://codeberg.org/grunfink/snac2/issues/123 (2024-02-22T12:40:58+0100). diff --git a/activitypub.c b/activitypub.c index e389915..d8f748e 100644 --- a/activitypub.c +++ b/activitypub.c @@ -1955,9 +1955,12 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) if (xs_type(object) == XSTYPE_DICT) object = xs_dict_get(object, "id"); - timeline_admire(snac, object, actor, 1); - snac_log(snac, xs_fmt("new 'Like' %s %s", actor, object)); - do_notify = 1; + if (timeline_admire(snac, object, actor, 1) == 201) { + snac_log(snac, xs_fmt("new 'Like' %s %s", actor, object)); + do_notify = 1; + } + else + snac_log(snac, xs_fmt("repeated 'Like' from %s to %s", actor, object)); } else if (strcmp(type, "Announce") == 0) { /** **/ @@ -1983,9 +1986,13 @@ int process_input_message(snac *snac, xs_dict *msg, xs_dict *req) xs *who_o = NULL; if (valid_status(actor_request(snac, who, &who_o))) { - timeline_admire(snac, object, actor, 0); - snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object)); - do_notify = 1; + if (timeline_admire(snac, object, actor, 0) == 201) { + snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object)); + do_notify = 1; + } + else + snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s", + actor, object)); } else snac_debug(snac, 1, xs_fmt("dropped 'Announce' on actor request error %s", who)); @@ -1107,7 +1107,7 @@ int timeline_add(snac *snac, const char *id, const xs_dict *o_msg) } -void timeline_admire(snac *snac, const char *id, const char *admirer, int like) +int timeline_admire(snac *snac, const char *id, const char *admirer, int like) /* updates a timeline entry with a new admiration */ { /* if we are admiring this, add to both timelines */ @@ -1116,10 +1116,12 @@ void timeline_admire(snac *snac, const char *id, const char *admirer, int like) object_user_cache_add(snac, id, "private"); } - object_admire(id, admirer, like); + int ret = object_admire(id, admirer, like); snac_debug(snac, 1, xs_fmt("timeline_admire (%s) %s %s", like ? "Like" : "Announce", id, admirer)); + + return ret; } @@ -646,7 +646,7 @@ xs_html *html_user_head(snac *user, char *desc) } -static xs_html *html_user_body(snac *user, int local) +static xs_html *html_user_body(snac *user, int read_only) { xs_html *body = xs_html_tag("body", NULL); @@ -667,7 +667,7 @@ static xs_html *html_user_body(snac *user, int local) xs_html_attr("class", "snac-avatar"), xs_html_attr("alt", ""))); - if (local) { + if (read_only) { xs *rss_url = xs_fmt("%s.rss", user->actor); xs *admin_url = xs_fmt("%s/admin", user->actor); @@ -698,6 +698,8 @@ static xs_html *html_user_body(snac *user, int local) xs *admin_url = xs_fmt("%s/admin", user->actor); xs *notify_url = xs_fmt("%s/notifications", user->actor); xs *people_url = xs_fmt("%s/people", user->actor); + xs *instance_url = xs_fmt("%s/instance", user->actor); + xs_html_add(top_nav, xs_html_tag("a", xs_html_attr("href", user->actor), @@ -714,7 +716,11 @@ static xs_html *html_user_body(snac *user, int local) xs_html_text(" - "), xs_html_tag("a", xs_html_attr("href", people_url), - xs_html_text(L("people")))); + xs_html_text(L("people"))), + xs_html_text(" - "), + xs_html_tag("a", + xs_html_attr("href", instance_url), + xs_html_text(L("instance")))); } xs_html_add(body, @@ -724,7 +730,7 @@ static xs_html *html_user_body(snac *user, int local) xs_html *top_user = xs_html_tag("div", xs_html_attr("class", "h-card snac-top-user")); - if (local) { + if (read_only) { char *header = xs_dict_get(user->config, "header"); if (header && *header) { xs_html_add(top_user, @@ -749,7 +755,7 @@ static xs_html *html_user_body(snac *user, int local) xs_html_attr("class", "snac-top-user-id"), xs_html_text(handle))); - if (local) { + if (read_only) { xs *es1 = encode_html(xs_dict_get(user->config, "bio")); xs *bio1 = not_really_markdown(es1, NULL); xs *tags = xs_list_new(); @@ -1306,7 +1312,7 @@ xs_html *html_entry_controls(snac *snac, char *actor, const xs_dict *msg, const } -xs_html *html_entry(snac *user, xs_dict *msg, int local, +xs_html *html_entry(snac *user, xs_dict *msg, int read_only, int level, char *md5, int hide_children) { char *id = xs_dict_get(msg, "id"); @@ -1315,7 +1321,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local, char *v; /* do not show non-public messages in the public timeline */ - if ((local || !user) && !is_msg_public(msg)) + if ((read_only || !user) && !is_msg_public(msg)) return NULL; /* hidden? do nothing more for this conversation */ @@ -1334,7 +1340,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local, xs_html_tag("div", xs_html_attr("class", "snac-origin"), xs_html_text(L("follows you"))), - html_msg_icon(local ? NULL : user, xs_dict_get(msg, "actor"), msg))); + html_msg_icon(read_only ? NULL : user, xs_dict_get(msg, "actor"), msg))); } else if (!xs_match(type, "Note|Question|Page|Article|Video")) { @@ -1446,7 +1452,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local, if (!xs_is_null(name)) { xs *href = NULL; - if (!local && user != NULL) + if (!read_only && user != NULL) href = xs_fmt("%s/people#%s", user->actor, p); else href = xs_dup(xs_dict_get(actor_r, "id")); @@ -1482,7 +1488,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local, } xs_html_add(post_header, - html_msg_icon(local ? NULL : user, actor, msg)); + html_msg_icon(read_only ? NULL : user, actor, msg)); /** post content **/ @@ -1510,7 +1516,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local, /* only show it when not in the public timeline and the config setting is "open" */ char *cw = xs_dict_get(user->config, "cw"); - if (xs_is_null(cw) || local) + if (xs_is_null(cw) || read_only) cw = ""; snac_content = xs_html_tag("details", @@ -1574,7 +1580,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local, xs_html *poll = xs_html_tag("div", NULL); - if (local) + if (read_only) closed = 1; /* non-identified page; show as closed */ else if (xs_dict_get(msg, "closed")) @@ -1795,7 +1801,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local, /** controls **/ - if (!local && user) { + if (!read_only && user) { xs_html_add(entry, html_entry_controls(user, actor, msg, md5)); } @@ -1839,7 +1845,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int local, object_get_by_md5(cmd5, &chd); if (chd != NULL && xs_is_null(xs_dict_get(chd, "name"))) { - xs_html *che = html_entry(user, chd, local, level + 1, cmd5, hide_children); + xs_html *che = html_entry(user, chd, read_only, level + 1, cmd5, hide_children); if (che != NULL) { if (left > 3) @@ -1879,8 +1885,9 @@ xs_html *html_footer(void) } -xs_str *html_timeline(snac *user, const xs_list *list, int local, - int skip, int show, int show_more, char *tag) +xs_str *html_timeline(snac *user, const xs_list *list, int read_only, + int skip, int show, int show_more, + char *tag, char *page, int utl) /* returns the HTML for the timeline */ { xs_list *p = (xs_list *)list; @@ -1903,7 +1910,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int local, if (user) { head = html_user_head(user, desc); - body = html_user_body(user, local); + body = html_user_body(user, read_only); } else { head = html_instance_head(); @@ -1914,7 +1921,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int local, head, body); - if (user && !local) + if (user && !read_only) xs_html_add(body, html_top_controls(user)); @@ -1932,7 +1939,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int local, xs *msg = NULL; int status; - if (user && !is_pinned_by_md5(user, v)) + if (utl && user && !is_pinned_by_md5(user, v)) status = timeline_get_by_md5(user, v, &msg); else status = object_get_by_md5(v, &msg); @@ -1954,14 +1961,15 @@ xs_str *html_timeline(snac *user, const xs_list *list, int local, } } - xs_html *entry = html_entry(user, msg, local, 0, v, user ? 0 : 1); + xs_html *entry = html_entry(user, msg, read_only, 0, v, user ? 0 : 1); if (entry != NULL) xs_html_add(posts, entry); } - if (list && user && local) { + if (list && user && read_only) { + /** history **/ if (xs_type(xs_dict_get(srv_config, "disable_history")) != XSTYPE_TRUE) { xs_html *ul = xs_html_tag("ul", NULL); @@ -2003,12 +2011,15 @@ xs_str *html_timeline(snac *user, const xs_list *list, int local, xs *m = NULL; xs *ss = xs_fmt("skip=%d&show=%d", skip + show, show); + xs *url = page == NULL || user == NULL ? + xs_dup(srv_baseurl) : xs_fmt("%s%s", user->actor, page); + if (tag) { - t = xs_fmt("%s?t=%s", srv_baseurl, tag); + t = xs_fmt("%s?t=%s", url, tag); m = xs_fmt("%s&%s", t, ss); } else { - t = xs_fmt("%s%s", user ? user->actor : srv_baseurl, local ? "" : "/admin"); + t = xs_dup(url); m = xs_fmt("%s?%s", t, ss); } @@ -2401,7 +2412,8 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs *h = xs_str_localtime(0, "%Y-%m.html"); if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE) { - *body = html_timeline(&snac, NULL, 1, 0, 0, 0, NULL); + /** empty public timeline for private users **/ + *body = html_timeline(&snac, NULL, 1, 0, 0, 0, NULL, "", 1); *b_size = strlen(*body); status = 200; } @@ -2419,7 +2431,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs *pins = pinned_list(&snac); pins = xs_list_cat(pins, list); - *body = html_timeline(&snac, pins, 1, skip, show, xs_list_len(next), NULL); + *body = html_timeline(&snac, pins, 1, skip, show, xs_list_len(next), NULL, "", 1); *b_size = strlen(*body); status = 200; @@ -2456,7 +2468,8 @@ int html_get_handler(const xs_dict *req, const char *q_path, xs *pins = pinned_list(&snac); pins = xs_list_cat(pins, list); - *body = html_timeline(&snac, pins, 0, skip, show, xs_list_len(next), NULL); + *body = html_timeline(&snac, pins, 0, skip, show, + xs_list_len(next), NULL, "/admin", 1); *b_size = strlen(*body); status = 200; @@ -2491,6 +2504,22 @@ int html_get_handler(const xs_dict *req, const char *q_path, } } else + if (strcmp(p_path, "instance") == 0) { /** instance timeline **/ + if (!login(&snac, req)) { + *body = xs_dup(uid); + status = 401; + } + else { + xs *list = timeline_instance_list(skip, show); + xs *next = timeline_instance_list(skip + show, 1); + + *body = html_timeline(&snac, list, 0, skip, show, + xs_list_len(next), NULL, "/instance", 0); + *b_size = strlen(*body); + status = 200; + } + } + else if (xs_startswith(p_path, "p/")) { /** a timeline with just one entry **/ if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE) return 403; @@ -2504,7 +2533,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, list = xs_list_append(list, md5); - *body = html_timeline(&snac, list, 1, 0, 0, 0, NULL); + *body = html_timeline(&snac, list, 1, 0, 0, 0, NULL, "", 1); *b_size = strlen(*body); status = 200; } @@ -177,6 +177,7 @@ int server_get_handler(xs_dict *req, const char *q_path, char *t = NULL; if (xs_type(q_vars) == XSTYPE_DICT && (t = xs_dict_get(q_vars, "t"))) { + /** search by tag **/ int skip = 0; int show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries")); char *v; @@ -194,12 +195,13 @@ int server_get_handler(xs_dict *req, const char *q_path, more = 1; } - *body = html_timeline(NULL, tl, 0, skip, show, more, t); + *body = html_timeline(NULL, tl, 0, skip, show, more, t, NULL, 0); } else if (xs_type(xs_dict_get(srv_config, "show_instance_timeline")) == XSTYPE_TRUE) { + /** instance timeline **/ xs *tl = timeline_instance_list(0, 30); - *body = html_timeline(NULL, tl, 0, 0, 0, 0, NULL); + *body = html_timeline(NULL, tl, 0, 0, 0, 0, NULL, NULL, 0); } else *body = greeting_html(); @@ -133,7 +133,7 @@ int timeline_del(snac *snac, char *id); xs_list *timeline_simple_list(snac *snac, const char *idx_name, int skip, int show); xs_list *timeline_list(snac *snac, const char *idx_name, int skip, int show); int timeline_add(snac *snac, const char *id, const xs_dict *o_msg); -void timeline_admire(snac *snac, const char *id, const char *admirer, int like); +int timeline_admire(snac *snac, const char *id, const char *admirer, int like); xs_list *timeline_top_level(snac *snac, xs_list *list); xs_list *local_list(snac *snac, int max); @@ -296,8 +296,9 @@ xs_str *not_really_markdown(const char *content, xs_list **attach); xs_str *sanitize(const char *content); xs_str *encode_html(const char *str); -xs_str *html_timeline(snac *user, const xs_list *list, int local, - int skip, int show, int show_more, char *tag); +xs_str *html_timeline(snac *user, const xs_list *list, int read_only, + int skip, int show, int show_more, + char *tag, char *page, int utl); int html_get_handler(const xs_dict *req, const char *q_path, char **body, int *b_size, char **ctype, xs_str **etag); |