From 6ad2365a41dd84ee925d7921a1c5756499d6fb17 Mon Sep 17 00:00:00 2001 From: byte Date: Mon, 12 Jan 2026 01:25:49 +0100 Subject: user-specified word mutes and matching --- html.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) (limited to 'html.c') diff --git a/html.c b/html.c index d5294cb..ebd9331 100644 --- a/html.c +++ b/html.c @@ -16,6 +16,7 @@ #include "xs_url.h" #include "xs_random.h" #include "xs_http.h" +#include "xs_list_tools.h" #include "snac.h" @@ -1864,6 +1865,38 @@ xs_html *html_top_controls(snac *user) xs_html_attr("class", "button"), xs_html_attr("value", L("Update hashtags"))))))); + xs *muted_words_action = xs_fmt("%s/admin/muted-words", user->actor); + xs *muted_words = xs_join(xs_dict_get_def(user->config, + "muted_words", xs_stock(XSTYPE_LIST)), "\n"); + + xs_html_add(top_controls, + xs_html_tag("details", + xs_html_tag("summary", + xs_html_text(L("Muted words..."))), + xs_html_tag("p", + xs_html_text(L("One word per line, partial matches count"))), + xs_html_tag("div", + xs_html_attr("class", "snac-muted-words"), + xs_html_tag("form", + xs_html_attr("autocomplete", "off"), + xs_html_attr("method", "post"), + xs_html_attr("action", muted_words_action), + xs_html_attr("enctype", "multipart/form-data"), + + xs_html_tag("textarea", + xs_html_attr("name", "muted_words"), + xs_html_attr("cols", "40"), + xs_html_attr("rows", "4"), + xs_html_attr("placeholder", "nascar\nsuperbowl\nFIFA"), + xs_html_text(muted_words)), + + xs_html_tag("br", NULL), + + xs_html_sctag("input", + xs_html_attr("type", "submit"), + xs_html_attr("class", "button"), + xs_html_attr("value", L("Update muted words"))))))); + return top_controls; } @@ -2144,6 +2177,30 @@ xs_html *html_entry_controls(snac *user, const char *actor, } +static const xs_str* words_in_content(const xs_list *words, const xs_val *content) +/* returns a word that matches any of the words in content */ +{ + if (!xs_is_list(words) || !xs_is_string(content)) { + return NULL; + } + xs *c = xs_split(content, " "); + xs *sc = xs_list_sort(c, NULL); + + const xs_str *wv; + const xs_str *cv; + xs_list_foreach(words, wv) { + xs_list_foreach(sc, cv) { + xs_tolower_i((xs_str*)cv); + if(xs_str_in(cv, wv) != -1){ + return wv; + } + } + } + + return NULL; +} + + xs_html *html_entry(snac *user, xs_dict *msg, int read_only, int level, const char *md5, int hide_children) { @@ -2438,6 +2495,17 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, xs_html_text(v), xs_html_text(L(" [SENSITIVE CONTENT]")))); } + else + if (user && + /* muted_words is all lowercase and sorted for performance */ + (v = words_in_content(xs_dict_get(user->config, "muted_words"), + xs_dict_get(msg, "content"))) != NULL) { + snac_debug(user, 1, xs_fmt("word %s muted by user preferences: %s", v, id)); + snac_content = xs_html_tag("details", + xs_html_tag("summary", + xs_html_text(L("Muted: ")), + xs_html_text(v))); + } else { snac_content = xs_html_tag("div", NULL); } @@ -5813,6 +5881,33 @@ 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/muted-words") == 0) { + const char *words = xs_dict_get(p_vars, "muted_words"); + + if (xs_is_string(words)) { + xs *new_words = xs_list_new(); + xs *l = xs_split(words, "\n"); + const char *v; + + xs_list_foreach(l, v) { + xs *s1 = xs_strip_i(xs_dup(v)); + s1 = xs_replace_i(s1, " ", ""); + + if (*s1 == '\0') + continue; + + xs *s2 = xs_utf8_to_lower(s1); + + new_words = xs_list_insert_sorted(new_words, s2); + } + + snac.config = xs_dict_set(snac.config, "muted_words", new_words); + user_persist(&snac, 0); + } + + status = HTTP_STATUS_SEE_OTHER; + } if (status == HTTP_STATUS_SEE_OTHER) { const char *hard_redir = xs_dict_get(p_vars, "hard-redir"); -- cgit v1.2.3