Repository navigation
Expand file tree
/
Copy pathcensor.py
More file actions
187 lines (141 loc) · 5.13 KB
/
Copy pathcensor.py
File metadata and controls
187 lines (141 loc) · 5.13 KB
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()