From 56ca23c68c76fc1225f64b3d19e7c23954b2e8a3 Mon Sep 17 00:00:00 2001 From: byte Date: Sun, 18 Jan 2026 22:13:21 +0000 Subject: notifications filter --- html.c | 211 +++++++++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 132 insertions(+), 79 deletions(-) (limited to 'html.c') diff --git a/html.c b/html.c index 66ce738..c862195 100644 --- a/html.c +++ b/html.c @@ -492,7 +492,7 @@ void html_note_render_visibility(snac* user, xs_html *form, const int scope) xs_html *paragraph = xs_html_tag("p", xs_html_text(L("Visibility: "))); const int* to_render = scopes[scope]; - for( int i = 0; to_render[i] != -1; i++ ){ + for( int i = 0; to_render[i] != -1; i++ ) { const int scope_i = to_render[i]; const char* value = scopes_tags[scope_i]; const char* name = scopes_names[scope_i]; @@ -1422,6 +1422,20 @@ static xs_html *html_user_body(snac *user, int read_only) } +xs_html *html_checkbox(const char *form_name, const char *label, int flag) +/* helper for checkbox rendering */ +{ + return xs_html_tag("p", + xs_html_sctag("input", + xs_html_attr("type", "checkbox"), + xs_html_attr("name", form_name), + xs_html_attr(flag ? "checked" : "", NULL)), + xs_html_tag("label", + xs_html_attr("for", form_name), + xs_html_text(label))); +} + + xs_html *html_top_controls(snac *user) /* generates the top controls */ { @@ -1745,70 +1759,21 @@ xs_html *html_top_controls(snac *user) xs_html_attr("type", "number"), xs_html_attr("name", "purge_days"), xs_html_attr("value", purge_days))), - xs_html_tag("p", - xs_html_sctag("input", - xs_html_attr("type", "checkbox"), - xs_html_attr("name", "drop_dm_from_unknown"), - xs_html_attr("id", "drop_dm_from_unknown"), - xs_html_attr(xs_type(d_dm_f_u) == XSTYPE_TRUE ? "checked" : "", NULL)), - xs_html_tag("label", - xs_html_attr("for", "drop_dm_from_unknown"), - xs_html_text(L("Drop direct messages from people you don't follow")))), - xs_html_tag("p", - xs_html_sctag("input", - xs_html_attr("type", "checkbox"), - xs_html_attr("name", "bot"), - xs_html_attr("id", "bot"), - xs_html_attr(xs_type(bot) == XSTYPE_TRUE ? "checked" : "", NULL)), - xs_html_tag("label", - xs_html_attr("for", "bot"), - xs_html_text(L("This account is a bot")))), - xs_html_tag("p", - xs_html_sctag("input", - xs_html_attr("type", "checkbox"), - xs_html_attr("name", "auto_boost"), - xs_html_attr("id", "auto_boost"), - xs_html_attr(xs_is_true(auto_boost) ? "checked" : "", NULL)), - xs_html_tag("label", - xs_html_attr("for", "auto_boost"), - xs_html_text(L("Auto-boost all mentions to this account")))), - xs_html_tag("p", - xs_html_sctag("input", - xs_html_attr("type", "checkbox"), - xs_html_attr("name", "private"), - xs_html_attr("id", "private"), - xs_html_attr(xs_type(a_private) == XSTYPE_TRUE ? "checked" : "", NULL)), - xs_html_tag("label", - xs_html_attr("for", "private"), - xs_html_text(L("This account is private " - "(posts are not shown through the web)")))), - xs_html_tag("p", - xs_html_sctag("input", - xs_html_attr("type", "checkbox"), - xs_html_attr("name", "collapse_threads"), - xs_html_attr("id", "collapse_threads"), - xs_html_attr(xs_is_true(coll_thrds) ? "checked" : "", NULL)), - xs_html_tag("label", - xs_html_attr("for", "collapse_threads"), - xs_html_text(L("Collapse top threads by default")))), - xs_html_tag("p", - xs_html_sctag("input", - xs_html_attr("type", "checkbox"), - xs_html_attr("name", "approve_followers"), - xs_html_attr("id", "approve_followers"), - xs_html_attr(xs_is_true(pending) ? "checked" : "", NULL)), - xs_html_tag("label", - xs_html_attr("for", "approve_followers"), - xs_html_text(L("Follow requests must be approved")))), - xs_html_tag("p", - xs_html_sctag("input", - xs_html_attr("type", "checkbox"), - xs_html_attr("name", "show_contact_metrics"), - xs_html_attr("id", "show_contact_metrics"), - xs_html_attr(xs_is_true(show_foll) ? "checked" : "", NULL)), - xs_html_tag("label", - xs_html_attr("for", "show_contact_metrics"), - xs_html_text(L("Publish follower and following metrics")))), + html_checkbox("drop_dm_from_unknown", L("Drop direct messages from people you don't follow"), + xs_is_true(d_dm_f_u)), + html_checkbox("bot", L("This account is a bot"), + xs_is_true(bot)), + html_checkbox("auto_boost", L("Auto-boost all mentions to this account"), + xs_is_true(auto_boost)), + html_checkbox("private", L("This account is private " + "(posts are not shown through the web)"), + xs_is_true(a_private)), + html_checkbox("collapse_threads", L("Collapse top threads by default"), + xs_is_true(coll_thrds)), + html_checkbox("approve_followers", L("Follow requests must be approved"), + xs_is_true(pending)), + html_checkbox("show_contact_metrics", L("Publish follower and following metrics"), + xs_is_true(show_foll)), xs_html_tag("p", xs_html_text(L("Current location:")), xs_html_sctag("br", NULL), @@ -2256,7 +2221,7 @@ static const xs_str* words_in_content(const xs_list *words, const xs_val *conten /* returns a word that matches any of the words in content */ { if (!xs_is_list(words) || !xs_is_string(content)) { - return NULL; + return NULL; } xs *c = xs_split(content, " "); xs *sc = xs_list_sort(c, NULL); @@ -2266,9 +2231,8 @@ static const xs_str* words_in_content(const xs_list *words, const xs_val *conten xs_list_foreach(words, wv) { xs_list_foreach(sc, cv) { xs_tolower_i((xs_str*)cv); - if(xs_str_in(cv, wv) != -1){ + if(xs_str_in(cv, wv) != -1) return wv; - } } } @@ -3634,7 +3598,7 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, continue; const int scope = get_msg_visibility(msg); - if (user != NULL && scope != SCOPE_PUBLIC){ + if (user != NULL && scope != SCOPE_PUBLIC) { /* is this message a non-public reply? */ const char *irt = get_in_reply_to(msg); @@ -3651,13 +3615,11 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only, } } /* hide non-public posts from /instance view */ - if (page != NULL && strcmp(page, "/instance") == 0 && scope != SCOPE_PUBLIC){ + if (page != NULL && strcmp(page, "/instance") == 0 && scope != SCOPE_PUBLIC) continue; - } /* hide non-public posts viewed from outside */ - if (read_only && (scope != SCOPE_PUBLIC && scope != SCOPE_UNLISTED)) { + if (read_only && (scope != SCOPE_PUBLIC && scope != SCOPE_UNLISTED)) continue; - } xs_html *entry = html_entry(user, msg, read_only, 0, v, (user && !hide_children) ? 0 : 1); @@ -4112,6 +4074,32 @@ xs_str *html_people_one(snac *user, const char *actor, const xs_list *list, return xs_html_render_s(html, "\n"); } +void notify_filter(snac *user, const xs_dict *p_vars) +/* sets filter for notifications */ +{ + const char *v; + int likes_on = (v = xs_dict_get(p_vars, "likes_on")) ? strcmp(v, "on") == 0 : 0; + int reacts_on = (v = xs_dict_get(p_vars, "reacts_on")) ? strcmp(v, "on") == 0 : 0; + int ments_on = (v = xs_dict_get(p_vars, "mentions_on")) ? strcmp(v, "on") == 0 : 0; + int ann_on = (v = xs_dict_get(p_vars, "announces_on")) ? strcmp(v, "on") == 0 : 0; + int foll_on = (v = xs_dict_get(p_vars, "follows_on")) ? strcmp(v, "on") == 0 : 0; + int unfoll_on = (v = xs_dict_get(p_vars, "unfollows_on")) ? strcmp(v, "on") == 0 : 0; + int folreq_on = (v = xs_dict_get(p_vars, "folreqs_on")) ? strcmp(v, "on") == 0 : 0; + int blocks_on = (v = xs_dict_get(p_vars, "blocks_on")) ? strcmp(v, "on") == 0 : 0; + int polls_on = (v = xs_dict_get(p_vars, "polls_on")) ? strcmp(v, "on") == 0 : 0; + xs_dict *filter = xs_dict_new(); + filter = xs_dict_set(filter, "likes", xs_stock(likes_on ? XSTYPE_TRUE : XSTYPE_FALSE)); + filter = xs_dict_set(filter, "reacts", xs_stock(reacts_on ? XSTYPE_TRUE : XSTYPE_FALSE)); + filter = xs_dict_set(filter, "mentions", xs_stock(ments_on ? XSTYPE_TRUE : XSTYPE_FALSE)); + filter = xs_dict_set(filter, "announces", xs_stock(ann_on ? XSTYPE_TRUE : XSTYPE_FALSE)); + filter = xs_dict_set(filter, "follows", xs_stock(foll_on ? XSTYPE_TRUE : XSTYPE_FALSE)); + filter = xs_dict_set(filter, "unfollows", xs_stock(unfoll_on ? XSTYPE_TRUE : XSTYPE_FALSE)); + filter = xs_dict_set(filter, "folreqs", xs_stock(folreq_on ? XSTYPE_TRUE : XSTYPE_FALSE)); + filter = xs_dict_set(filter, "blocks", xs_stock(blocks_on ? XSTYPE_TRUE : XSTYPE_FALSE)); + filter = xs_dict_set(filter, "polls", xs_stock(polls_on ? XSTYPE_TRUE : XSTYPE_FALSE)); + user->config = xs_dict_set(user->config, "notify_filter", filter); +} + xs_str *html_notifications(snac *user, int skip, int show) { const char *proxy = NULL; @@ -4119,15 +4107,66 @@ xs_str *html_notifications(snac *user, int skip, int show) if (xs_is_true(xs_dict_get(srv_config, "proxy_media"))) proxy = user->actor; - xs *n_list = notify_list(user, skip, show); + xs *n_list_unfilt = notify_list(user, skip, show); xs *n_time = notify_check_time(user, 0); xs_html *body = html_user_body(user, 0); + const xs_dict *n_filter = xs_dict_get(user->config, "notify_filter"); + if (!n_filter) { + user->config = xs_dict_set(user->config, "notify_filter", xs_dict_new()); + n_filter = xs_dict_get(user->config, "notify_filter"); + } + xs *n_list = notify_filter_list(user, n_list_unfilt); + /* all filters are true by default */ + const xs_val *n_def = xs_stock( XSTYPE_TRUE ); + int n_likes_on = xs_is_true(xs_dict_get_def(n_filter, "likes", n_def)); + int n_reacts_on = xs_is_true(xs_dict_get_def(n_filter, "reacts", n_def)); + int n_ments_on = xs_is_true(xs_dict_get_def(n_filter, "mentions", n_def)); + int n_ann_on = xs_is_true(xs_dict_get_def(n_filter, "announces", n_def)); + int n_fol_on = xs_is_true(xs_dict_get_def(n_filter, "follows", n_def)); + int n_unfol_on = xs_is_true(xs_dict_get_def(n_filter, "unfollows", n_def)); + int n_folreq_on = xs_is_true(xs_dict_get_def(n_filter, "folreqs", n_def)); + int n_blocks_on = xs_is_true(xs_dict_get_def(n_filter, "blocks", n_def)); + int n_polls_on = xs_is_true(xs_dict_get_def(n_filter, "polls", n_def)); xs_html *html = xs_html_tag("html", html_user_head(user, NULL, NULL), body); + xs *filter_notifs_action = xs_fmt("%s/admin/filter-notifications", user->actor); + xs_html *notifs_form = xs_html_tag("form", + xs_html_attr("autocomplete", "off"), + xs_html_attr("method", "post"), + xs_html_attr("action", filter_notifs_action), + xs_html_attr("enctype", "multipart/form-data"), + xs_html_attr("id", "filter"), + xs_html_sctag("input", + xs_html_attr("type", "hidden"), + xs_html_attr("name", "hard-redir"), + xs_html_attr("value", xs_fmt("%s/notifications", user->actor))), + html_checkbox("likes_on", L("Likes"), n_likes_on), + html_checkbox("reacts_on", L("Emoji reacts"), n_reacts_on), + html_checkbox("mentions_on", L("Mentions"), n_ments_on), + html_checkbox("announces_on", L("Announces"), n_ann_on), + html_checkbox("follows_on", L("Follows"), n_fol_on), + html_checkbox("unfollows_on", L("Unollows"), n_unfol_on), + html_checkbox("folreqs_on", L("Follow requests"), n_folreq_on), + html_checkbox("blocks_on", L("Blocks"), n_blocks_on), + html_checkbox("polls_on", L("Polls"), n_polls_on), + xs_html_sctag("input", + xs_html_attr("type", "submit"), + xs_html_attr("class", "button"), + xs_html_attr("value", L("Save")))); + + xs_html_add(body, + xs_html_tag("p", + xs_html_tag("div", + xs_html_attr("class", "snac-notify-filter"), + xs_html_tag("details", + xs_html_tag("summary", + xs_html_text(L("Notifications filter"))), + notifs_form)))); + xs *clear_all_action = xs_fmt("%s/admin/clear-notifications", user->actor); xs_html_add(body, @@ -4205,14 +4244,17 @@ xs_str *html_notifications(snac *user, int skip, int show) xs *label_sanitized = sanitize(type); const char *label = label_sanitized; - if (strcmp(type, "Create") == 0) + if (strcmp(type, "Create") == 0) { label = L("Mention"); + } else - if (strcmp(type, "Update") == 0 && strcmp(utype, "Question") == 0) + if (strcmp(type, "Update") == 0 && strcmp(utype, "Question") == 0) { label = L("Finished poll"); + } else - if (strcmp(type, "Undo") == 0 && strcmp(utype, "Follow") == 0) + if (strcmp(type, "Undo") == 0 && strcmp(utype, "Follow") == 0) { label = L("Unfollow"); + } else if (strcmp(type, "EmojiReact") == 0 || strcmp(type, "Like") == 0) { const char *content = xs_dict_get_path(noti, "msg.content"); @@ -4227,14 +4269,19 @@ xs_str *html_notifications(snac *user, int skip, int show) isEmoji = 1; if (xs_type(content) == XSTYPE_STRING) { + if (!isEmoji && !n_likes_on) + continue; xs *emoji = replace_shortnames(xs_dup(content), xs_dict_get_path(noti, "msg.tag"), 1, proxy); wrk = xs_fmt("%s (%s️)", isEmoji ? "EmojiReact" : "Like", emoji); label = wrk; } } else - if (strcmp(type, "Follow") == 0 && pending_check(user, actor_id)) - label = L("Follow Request"); + if (strcmp(type, "Follow") == 0) { + if (pending_check(user, actor_id)) { + label = L("Follow Request"); + } + } xs *s_date = html_date_label(user, date); @@ -5835,6 +5882,12 @@ int html_post_handler(const xs_dict *req, const char *q_path, status = HTTP_STATUS_SEE_OTHER; } else + if (p_path && strcmp(p_path, "admin/filter-notifications") == 0) { /** **/ + notify_filter(&snac, p_vars); + user_persist(&snac, 0); + status = HTTP_STATUS_SEE_OTHER; + } + else if (p_path && strcmp(p_path, "admin/clear-notifications") == 0) { /** **/ notify_clear(&snac); timeline_touch(&snac); -- cgit v1.2.3