Censor messages by nick and text using color while still logging them.
Author: D. Bohdan
— Version: 0.3.0
— License: MIT
For WeeChat ≥ 0.3.0.
Tags: censor
Added: 2026-07-25
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 | # SPDX-FileCopyrightText: 2025-2026 D. Bohdan <dbohdan@dbohdan.com> # SPDX-License-Identifier: MIT # # Censor messages by nick and text using color while still logging them. # This is an alternative to /ignore and triggers that remove the message from logs. # # -------------------------------------------------------------------------------------- # INSTALLATION & USAGE # -------------------------------------------------------------------------------------- # This script requires WeeChat with the Python plugin and Python 3.9 or later. # # 1. Save this file with WeeChat Python scripts: # ~/.local/share/weechat/python/censor.py # # 2. Load it in WeeChat: # /python load censor.py # # 3. Configure using the following options: # plugins.var.python.censor.censor_color # plugins.var.python.censor.nick_re # plugins.var.python.censor.text_re # # The script modifies the display of censored messages # and removes mIRC color codes from them. # The message still exists in the buffer and the logs. # -------------------------------------------------------------------------------------- from __future__ import annotations import re from typing import TypedDict import_ok = True try: import weechat except ImportError: print("This script must be run under WeeChat.") print("Get WeeChat now at: https://weechat.org/") import_ok = False SCRIPT_NAME = "censor" SCRIPT_AUTHOR = "D. Bohdan <dbohdan@dbohdan.com>" SCRIPT_VERSION = "0.3.0" SCRIPT_LICENSE = "MIT" SCRIPT_DESC = "Censor messages by nick and text using color while still logging them." # --------------------------------------------------------------------------- # CONFIGURATION # --------------------------------------------------------------------------- OPTIONS_PREFIX = f"plugins.var.python.{SCRIPT_NAME}." class RegexCheck: def __init__(self, pattern: str = "") -> None: self._regex: re.Pattern | None = None self.pattern = pattern # Compile the regex. def __str__(self) -> str: return self.pattern def check(self, string: str) -> bool: return self._regex is not None and bool(self._regex.search(string)) @property def pattern(self) -> str: return "" if self._regex is None else self._regex.pattern @pattern.setter def pattern(self, pattern: str) -> None: self._regex = None if not pattern.strip(): return try: self._regex = re.compile(pattern, re.IGNORECASE) except re.error as e: weechat.prnt("", f"{SCRIPT_NAME}: invalid regex: {e}") class ScriptOptions(TypedDict): censor_color: str nick_re: RegexCheck text_re: RegexCheck SCRIPT_OPTION_DESCS = { "censor_color": "color (fg,bg) applied to censored messages", "nick_re": "case-insensitive regex matched against the nick", "text_re": "case-insensitive regex matched against the message text", } script_options: ScriptOptions = { "censor_color": "darkgray,darkgray", "nick_re": RegexCheck(), "text_re": RegexCheck(), } # --------------------------------------------------------------------------- # IMPLEMENTATION # --------------------------------------------------------------------------- def tags_nick(tags): """Extract nick from tags.""" if not tags: return "" for tag in tags.split(","): if tag.startswith("nick_"): return tag.removeprefix("nick_") return "" def set_script_option(key, value): if key == "censor_color": script_options[key] = value elif key in ("nick_re", "text_re"): script_options[key] = RegexCheck(value) def config_cb(_data, option, value): """Callback called when a script option is changed.""" key = option.removeprefix(OPTIONS_PREFIX) if key in script_options: set_script_option(key, value) return weechat.WEECHAT_RC_OK def censor_line_cb(_data, line): """hook_line callback: style censored message text.""" tags = line.get("tags", "") nick = tags_nick(tags) if not nick: return {} plain = weechat.string_remove_color(line["message"], "") if script_options["nick_re"].check(nick) or script_options["text_re"].check(plain): color = weechat.color(script_options["censor_color"]) reset = weechat.color("reset") return { "message": f"{color}{plain}{reset}", "highlight": "0", "notify_level": "-1", } return {} def main(): if not weechat.register( SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", "", ): return for key, default_value in script_options.items(): if weechat.config_is_set_plugin(key): set_script_option(key, weechat.config_get_plugin(key)) else: weechat.config_set_plugin(key, str(default_value)) weechat.config_set_desc_plugin( key, f'{SCRIPT_OPTION_DESCS[key]} (default: "{default_value}")', ) weechat.hook_config(OPTIONS_PREFIX + "*", "config_cb", "") weechat.hook_line("", "", "", "censor_line_cb", "") if __name__ == "__main__" and import_ok: main() |