commit 6ad2365

byte  ·  2026-01-12 00:25:49 +0000 UTC
parent 493a09a
user-specified word mutes and matching
2 files changed,  +96, -1
M html.c
+1, -1
1@@ -2958,7 +2958,7 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req)
2         if (xs_is_null(object))
3             snac_log(snac, xs_fmt("malformed message: no 'id' field"));
4         else
5-        if (timeline_admire(snac, object, actor, 1, xs_dup(msg)) == HTTP_STATUS_CREATED)
6+        if (timeline_admire(snac, object, actor, 1, msg) == HTTP_STATUS_CREATED)
7             snac_log(snac, xs_fmt("new '%s' %s %s", type, actor, object));
8         else
9             snac_log(snac, xs_fmt("repeated '%s' from %s to %s", type, actor, object));
M html.c
+95, -0
  1@@ -16,6 +16,7 @@
  2 #include "xs_url.h"
  3 #include "xs_random.h"
  4 #include "xs_http.h"
  5+#include "xs_list_tools.h"
  6 
  7 #include "snac.h"
  8 
  9@@ -1864,6 +1865,38 @@ xs_html *html_top_controls(snac *user)
 10                     xs_html_attr("class", "button"),
 11                     xs_html_attr("value", L("Update hashtags")))))));
 12 
 13+    xs *muted_words_action = xs_fmt("%s/admin/muted-words", user->actor);
 14+    xs *muted_words = xs_join(xs_dict_get_def(user->config,
 15+                        "muted_words", xs_stock(XSTYPE_LIST)), "\n");
 16+
 17+    xs_html_add(top_controls,
 18+        xs_html_tag("details",
 19+        xs_html_tag("summary",
 20+            xs_html_text(L("Muted words..."))),
 21+        xs_html_tag("p",
 22+            xs_html_text(L("One word per line, partial matches count"))),
 23+        xs_html_tag("div",
 24+            xs_html_attr("class", "snac-muted-words"),
 25+            xs_html_tag("form",
 26+                xs_html_attr("autocomplete", "off"),
 27+                xs_html_attr("method", "post"),
 28+                xs_html_attr("action", muted_words_action),
 29+                xs_html_attr("enctype", "multipart/form-data"),
 30+
 31+                xs_html_tag("textarea",
 32+                    xs_html_attr("name", "muted_words"),
 33+                    xs_html_attr("cols", "40"),
 34+                    xs_html_attr("rows", "4"),
 35+                    xs_html_attr("placeholder", "nascar\nsuperbowl\nFIFA"),
 36+                    xs_html_text(muted_words)),
 37+
 38+                xs_html_tag("br", NULL),
 39+
 40+                xs_html_sctag("input",
 41+                    xs_html_attr("type", "submit"),
 42+                    xs_html_attr("class", "button"),
 43+                    xs_html_attr("value", L("Update muted words")))))));
 44+
 45     return top_controls;
 46 }
 47 
 48@@ -2144,6 +2177,30 @@ xs_html *html_entry_controls(snac *user, const char *actor,
 49 }
 50 
 51 
 52+static const xs_str* words_in_content(const xs_list *words, const xs_val *content)
 53+/* returns a word that matches any of the words in content */
 54+{
 55+    if (!xs_is_list(words) || !xs_is_string(content)) {
 56+	    return NULL;
 57+    }
 58+    xs *c = xs_split(content, " ");
 59+    xs *sc = xs_list_sort(c, NULL);
 60+
 61+    const xs_str *wv;
 62+    const xs_str *cv;
 63+    xs_list_foreach(words, wv) {
 64+        xs_list_foreach(sc, cv) {
 65+            xs_tolower_i((xs_str*)cv);
 66+            if(xs_str_in(cv, wv) != -1){
 67+                return wv;
 68+            }
 69+        }
 70+    }
 71+
 72+    return NULL;
 73+}
 74+
 75+
 76 xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
 77                    int level, const char *md5, int hide_children)
 78 {
 79@@ -2438,6 +2495,17 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
 80                 xs_html_text(v),
 81                 xs_html_text(L(" [SENSITIVE CONTENT]"))));
 82     }
 83+    else
 84+    if (user &&
 85+        /* muted_words is all lowercase and sorted for performance */
 86+        (v = words_in_content(xs_dict_get(user->config, "muted_words"),
 87+                              xs_dict_get(msg, "content"))) != NULL) {
 88+        snac_debug(user, 1, xs_fmt("word %s muted by user preferences: %s", v, id));
 89+        snac_content = xs_html_tag("details",
 90+            xs_html_tag("summary",
 91+                xs_html_text(L("Muted: ")),
 92+                xs_html_text(v)));
 93+    }
 94     else {
 95         snac_content = xs_html_tag("div", NULL);
 96     }
 97@@ -5813,6 +5881,33 @@ int html_post_handler(const xs_dict *req, const char *q_path,
 98 
 99         status = HTTP_STATUS_SEE_OTHER;
100     }
101+    else
102+    if (p_path && strcmp(p_path, "admin/muted-words") == 0) {
103+        const char *words = xs_dict_get(p_vars, "muted_words");
104+
105+        if (xs_is_string(words)) {
106+            xs *new_words = xs_list_new();
107+            xs *l = xs_split(words, "\n");
108+            const char *v;
109+
110+            xs_list_foreach(l, v) {
111+                xs *s1 = xs_strip_i(xs_dup(v));
112+                s1 = xs_replace_i(s1, " ", "");
113+
114+                if (*s1 == '\0')
115+                    continue;
116+
117+                xs *s2 = xs_utf8_to_lower(s1);
118+
119+                new_words = xs_list_insert_sorted(new_words, s2);
120+            }
121+
122+            snac.config = xs_dict_set(snac.config, "muted_words", new_words);
123+            user_persist(&snac, 0);
124+        }
125+
126+        status = HTTP_STATUS_SEE_OTHER;
127+    }
128 
129     if (status == HTTP_STATUS_SEE_OTHER) {
130         const char *hard_redir = xs_dict_get(p_vars, "hard-redir");