weechat- me personal weechat setup 🔵🟢 |
git clone git://git.acid.vegas/weechat.git |
Log | Files | Refs | Archive | README |
colorize_nicks.py (16767B)
1 # -*- coding: utf-8 -*- 2 # 3 # Copyright (c) 2010 by xt <xt@bash.no> 4 # 5 # This program is free software; you can redistribute it and/or modify 6 # it under the terms of the GNU General Public License as published by 7 # the Free Software Foundation; either version 3 of the License, or 8 # (at your option) any later version. 9 # 10 # This program is distributed in the hope that it will be useful, 11 # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 # GNU General Public License for more details. 14 # 15 # You should have received a copy of the GNU General Public License 16 # along with this program. If not, see <http://www.gnu.org/licenses/>. 17 # 18 19 # This script colors nicks in IRC channels in the actual message 20 # not just in the prefix section. 21 # 22 # 23 # History: 24 # 2022-11-07: mva 25 # version 30: add ":" and "," to VALID_NICK regexp, 26 # to don't reset colorization in input_line 27 # 2022-07-11: ncfavier 28 # version 29: check nick for exclusion *after* stripping 29 # decrease minimum min_nick_length to 1 30 # 2020-11-29: jess 31 # version 28: fix ignore_tags having been broken by weechat 2.9 changes 32 # 2020-05-09: Sébastien Helleu <flashcode@flashtux.org> 33 # version 27: add compatibility with new weechat_print modifier data 34 # (WeeChat >= 2.9) 35 # 2018-04-06: Joey Pabalinas <joeypabalinas@gmail.com> 36 # version 26: fix freezes with too many nicks in one line 37 # 2018-03-18: nils_2 38 # version 25: fix unable to run function colorize_config_reload_cb() 39 # 2017-06-20: lbeziaud <louis.beziaud@ens-rennes.fr> 40 # version 24: colorize utf8 nicks 41 # 2017-03-01, arza <arza@arza.us> 42 # version 23: don't colorize nicklist group names 43 # 2016-05-01, Simmo Saan <simmo.saan@gmail.com> 44 # version 22: invalidate cached colors on hash algorithm change 45 # 2015-07-28, xt 46 # version 21: fix problems with nicks with commas in them 47 # 2015-04-19, xt 48 # version 20: fix ignore of nicks in URLs 49 # 2015-04-18, xt 50 # version 19: new option ignore nicks in URLs 51 # 2015-03-03, xt 52 # version 18: iterate buffers looking for nicklists instead of servers 53 # 2015-02-23, holomorph 54 # version 17: fix coloring in non-channel buffers (#58) 55 # 2014-09-17, holomorph 56 # version 16: use weechat config facilities 57 # clean unused, minor linting, some simplification 58 # 2014-05-05, holomorph 59 # version 15: fix python2-specific re.search check 60 # 2013-01-29, nils_2 61 # version 14: make script compatible with Python 3.x 62 # 2012-10-19, ldvx 63 # version 13: Iterate over every word to prevent incorrect colorization of 64 # nicks. Added option greedy_matching. 65 # 2012-04-28, ldvx 66 # version 12: added ignore_tags to avoid colorizing nicks if tags are present 67 # 2012-01-14, nesthib 68 # version 11: input_text_display hook and modifier to colorize nicks in input bar 69 # 2010-12-22, xt 70 # version 10: hook config option for updating blacklist 71 # 2010-12-20, xt 72 # version 0.9: hook new config option for weechat 0.3.4 73 # 2010-11-01, nils_2 74 # version 0.8: hook_modifier() added to communicate with rainbow_text 75 # 2010-10-01, xt 76 # version 0.7: changes to support non-irc-plugins 77 # 2010-07-29, xt 78 # version 0.6: compile regexp as per patch from Chris quigybo@hotmail.com 79 # 2010-07-19, xt 80 # version 0.5: fix bug with incorrect coloring of own nick 81 # 2010-06-02, xt 82 # version 0.4: update to reflect API changes 83 # 2010-03-26, xt 84 # version 0.3: fix error with exception 85 # 2010-03-24, xt 86 # version 0.2: use ignore_channels when populating to increase performance. 87 # 2010-02-03, xt 88 # version 0.1: initial (based on ruby script by dominikh) 89 # 90 # Known issues: nicks will not get colorized if they begin with a character 91 # such as ~ (which some irc networks do happen to accept) 92 93 import weechat 94 import re 95 w = weechat 96 97 SCRIPT_NAME = "colorize_nicks" 98 SCRIPT_AUTHOR = "xt <xt@bash.no>" 99 SCRIPT_VERSION = "30" 100 SCRIPT_LICENSE = "GPL" 101 SCRIPT_DESC = "Use the weechat nick colors in the chat area" 102 103 # Based on the recommendations in RFC 7613. A valid nick is composed 104 # of anything but " ,*?.!@". 105 VALID_NICK = r'([@~&!%+-])?([^\s,\*?\.!@:,]+)' 106 valid_nick_re = re.compile(VALID_NICK) 107 ignore_channels = [] 108 ignore_nicks = [] 109 110 # Dict with every nick on every channel with its color as lookup value 111 colored_nicks = {} 112 113 CONFIG_FILE_NAME = "colorize_nicks" 114 115 # config file and options 116 colorize_config_file = "" 117 colorize_config_option = {} 118 119 def colorize_config_init(): 120 ''' 121 Initialization of configuration file. 122 Sections: look. 123 ''' 124 global colorize_config_file, colorize_config_option 125 colorize_config_file = weechat.config_new(CONFIG_FILE_NAME, 126 "", "") 127 if colorize_config_file == "": 128 return 129 130 # section "look" 131 section_look = weechat.config_new_section( 132 colorize_config_file, "look", 0, 0, "", "", "", "", "", "", "", "", "", "") 133 if section_look == "": 134 weechat.config_free(colorize_config_file) 135 return 136 colorize_config_option["blacklist_channels"] = weechat.config_new_option( 137 colorize_config_file, section_look, "blacklist_channels", 138 "string", "Comma separated list of channels", "", 0, 0, 139 "", "", 0, "", "", "", "", "", "") 140 colorize_config_option["blacklist_nicks"] = weechat.config_new_option( 141 colorize_config_file, section_look, "blacklist_nicks", 142 "string", "Comma separated list of nicks", "", 0, 0, 143 "so,root", "so,root", 0, "", "", "", "", "", "") 144 colorize_config_option["min_nick_length"] = weechat.config_new_option( 145 colorize_config_file, section_look, "min_nick_length", 146 "integer", "Minimum length nick to colorize", "", 147 1, 20, "2", "2", 0, "", "", "", "", "", "") 148 colorize_config_option["colorize_input"] = weechat.config_new_option( 149 colorize_config_file, section_look, "colorize_input", 150 "boolean", "Whether to colorize input", "", 0, 151 0, "off", "off", 0, "", "", "", "", "", "") 152 colorize_config_option["ignore_tags"] = weechat.config_new_option( 153 colorize_config_file, section_look, "ignore_tags", 154 "string", "Comma separated list of tags to ignore; i.e. irc_join,irc_part,irc_quit", "", 0, 0, 155 "", "", 0, "", "", "", "", "", "") 156 colorize_config_option["greedy_matching"] = weechat.config_new_option( 157 colorize_config_file, section_look, "greedy_matching", 158 "boolean", "If off, then use lazy matching instead", "", 0, 159 0, "on", "on", 0, "", "", "", "", "", "") 160 colorize_config_option["match_limit"] = weechat.config_new_option( 161 colorize_config_file, section_look, "match_limit", 162 "integer", "Fall back to lazy matching if greedy matches exceeds this number", "", 163 20, 1000, "", "", 0, "", "", "", "", "", "") 164 colorize_config_option["ignore_nicks_in_urls"] = weechat.config_new_option( 165 colorize_config_file, section_look, "ignore_nicks_in_urls", 166 "boolean", "If on, don't colorize nicks inside URLs", "", 0, 167 0, "off", "off", 0, "", "", "", "", "", "") 168 169 def colorize_config_read(): 170 ''' Read configuration file. ''' 171 global colorize_config_file 172 return weechat.config_read(colorize_config_file) 173 174 def colorize_nick_color(nick, my_nick): 175 ''' Retrieve nick color from weechat. ''' 176 if nick == my_nick: 177 return w.color(w.config_string(w.config_get('weechat.color.chat_nick_self'))) 178 else: 179 return w.info_get('nick_color', nick) 180 181 def colorize_cb(data, modifier, modifier_data, line): 182 ''' Callback that does the colorizing, and returns new line if changed ''' 183 184 global ignore_nicks, ignore_channels, colored_nicks 185 186 if modifier_data.startswith('0x'): 187 # WeeChat >= 2.9 188 buffer, tags = modifier_data.split(';', 1) 189 else: 190 # WeeChat <= 2.8 191 plugin, buffer_name, tags = modifier_data.split(';', 2) 192 buffer = w.buffer_search(plugin, buffer_name) 193 194 channel = w.buffer_get_string(buffer, 'localvar_channel') 195 tags = tags.split(',') 196 197 # Check if buffer has colorized nicks 198 if buffer not in colored_nicks: 199 return line 200 201 if channel and channel in ignore_channels: 202 return line 203 204 min_length = w.config_integer(colorize_config_option['min_nick_length']) 205 reset = w.color('reset') 206 207 # Don't colorize if the ignored tag is present in message 208 tag_ignores = w.config_string(colorize_config_option['ignore_tags']).split(',') 209 for tag in tags: 210 if tag in tag_ignores: 211 return line 212 213 for words in valid_nick_re.findall(line): 214 nick = words[1] 215 216 # If the matched word is not a known nick, we try to match the 217 # word without its first or last character (if not a letter). 218 # This is necessary as "foo:" is a valid nick, which could be 219 # adressed as "foo::". 220 if nick not in colored_nicks[buffer]: 221 if not nick[-1].isalpha() and not nick[0].isalpha(): 222 if nick[1:-1] in colored_nicks[buffer]: 223 nick = nick[1:-1] 224 elif not nick[0].isalpha(): 225 if nick[1:] in colored_nicks[buffer]: 226 nick = nick[1:] 227 elif not nick[-1].isalpha(): 228 if nick[:-1] in colored_nicks[buffer]: 229 nick = nick[:-1] 230 231 # Check that nick is not ignored and longer than minimum length 232 if len(nick) < min_length or nick in ignore_nicks: 233 continue 234 235 # Check that nick is in the dictionary colored_nicks 236 if nick in colored_nicks[buffer]: 237 nick_color = colored_nicks[buffer][nick] 238 239 try: 240 # Let's use greedy matching. Will check against every word in a line. 241 if w.config_boolean(colorize_config_option['greedy_matching']): 242 cnt = 0 243 limit = w.config_integer(colorize_config_option['match_limit']) 244 245 for word in line.split(): 246 cnt += 1 247 assert cnt < limit 248 # if cnt > limit: 249 # raise RuntimeError('Exceeded colorize_nicks.look.match_limit.'); 250 251 if w.config_boolean(colorize_config_option['ignore_nicks_in_urls']) and \ 252 word.startswith(('http://', 'https://')): 253 continue 254 255 if nick in word: 256 # Is there a nick that contains nick and has a greater lenght? 257 # If so let's save that nick into var biggest_nick 258 biggest_nick = "" 259 for i in colored_nicks[buffer]: 260 cnt += 1 261 assert cnt < limit 262 263 if nick in i and nick != i and len(i) > len(nick): 264 if i in word: 265 # If a nick with greater len is found, and that word 266 # also happens to be in word, then let's save this nick 267 biggest_nick = i 268 # If there's a nick with greater len, then let's skip this 269 # As we will have the chance to colorize when biggest_nick 270 # iterates being nick. 271 if len(biggest_nick) > 0 and biggest_nick in word: 272 pass 273 elif len(word) < len(biggest_nick) or len(biggest_nick) == 0: 274 new_word = word.replace(nick, '%s%s%s' % (nick_color, nick, reset)) 275 line = line.replace(word, new_word) 276 277 # Switch to lazy matching 278 else: 279 raise AssertionError 280 281 except AssertionError: 282 # Let's use lazy matching for nick 283 nick_color = colored_nicks[buffer][nick] 284 # The two .? are in case somebody writes "nick:", "nick,", etc 285 # to address somebody 286 regex = r"(\A|\s).?(%s).?(\Z|\s)" % re.escape(nick) 287 match = re.search(regex, line) 288 if match is not None: 289 new_line = line[:match.start(2)] + nick_color+nick+reset + line[match.end(2):] 290 line = new_line 291 292 return line 293 294 def colorize_input_cb(data, modifier, modifier_data, line): 295 ''' Callback that does the colorizing in input ''' 296 297 global ignore_nicks, ignore_channels, colored_nicks 298 299 min_length = w.config_integer(colorize_config_option['min_nick_length']) 300 301 if not w.config_boolean(colorize_config_option['colorize_input']): 302 return line 303 304 buffer = w.current_buffer() 305 # Check if buffer has colorized nicks 306 if buffer not in colored_nicks: 307 return line 308 309 channel = w.buffer_get_string(buffer, 'name') 310 if channel and channel in ignore_channels: 311 return line 312 313 reset = w.color('reset') 314 315 for words in valid_nick_re.findall(line): 316 nick = words[1] 317 # Check that nick is not ignored and longer than minimum length 318 if len(nick) < min_length or nick in ignore_nicks: 319 continue 320 if nick in colored_nicks[buffer]: 321 nick_color = colored_nicks[buffer][nick] 322 line = line.replace(nick, '%s%s%s' % (nick_color, nick, reset)) 323 324 return line 325 326 def populate_nicks(*args): 327 ''' Fills entire dict with all nicks weechat can see and what color it has 328 assigned to it. ''' 329 global colored_nicks 330 331 colored_nicks = {} 332 333 buffers = w.infolist_get('buffer', '', '') 334 while w.infolist_next(buffers): 335 buffer_ptr = w.infolist_pointer(buffers, 'pointer') 336 my_nick = w.buffer_get_string(buffer_ptr, 'localvar_nick') 337 nicklist = w.infolist_get('nicklist', buffer_ptr, '') 338 while w.infolist_next(nicklist): 339 if buffer_ptr not in colored_nicks: 340 colored_nicks[buffer_ptr] = {} 341 342 if w.infolist_string(nicklist, 'type') != 'nick': 343 continue 344 345 nick = w.infolist_string(nicklist, 'name') 346 nick_color = colorize_nick_color(nick, my_nick) 347 348 colored_nicks[buffer_ptr][nick] = nick_color 349 350 w.infolist_free(nicklist) 351 352 w.infolist_free(buffers) 353 354 return w.WEECHAT_RC_OK 355 356 def add_nick(data, signal, type_data): 357 ''' Add nick to dict of colored nicks ''' 358 global colored_nicks 359 360 # Nicks can have , in them in some protocols 361 splitted = type_data.split(',') 362 pointer = splitted[0] 363 nick = ",".join(splitted[1:]) 364 if pointer not in colored_nicks: 365 colored_nicks[pointer] = {} 366 367 my_nick = w.buffer_get_string(pointer, 'localvar_nick') 368 nick_color = colorize_nick_color(nick, my_nick) 369 370 colored_nicks[pointer][nick] = nick_color 371 372 return w.WEECHAT_RC_OK 373 374 def remove_nick(data, signal, type_data): 375 ''' Remove nick from dict with colored nicks ''' 376 global colored_nicks 377 378 # Nicks can have , in them in some protocols 379 splitted = type_data.split(',') 380 pointer = splitted[0] 381 nick = ",".join(splitted[1:]) 382 383 if pointer in colored_nicks and nick in colored_nicks[pointer]: 384 del colored_nicks[pointer][nick] 385 386 return w.WEECHAT_RC_OK 387 388 def update_blacklist(*args): 389 ''' Set the blacklist for channels and nicks. ''' 390 global ignore_channels, ignore_nicks 391 ignore_channels = w.config_string(colorize_config_option['blacklist_channels']).split(',') 392 ignore_nicks = w.config_string(colorize_config_option['blacklist_nicks']).split(',') 393 return w.WEECHAT_RC_OK 394 395 if __name__ == "__main__": 396 if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, 397 SCRIPT_DESC, "", ""): 398 colorize_config_init() 399 colorize_config_read() 400 401 # Run once to get data ready 402 update_blacklist() 403 populate_nicks() 404 405 w.hook_signal('nicklist_nick_added', 'add_nick', '') 406 w.hook_signal('nicklist_nick_removed', 'remove_nick', '') 407 w.hook_modifier('weechat_print', 'colorize_cb', '') 408 # Hook config for changing colors 409 w.hook_config('weechat.color.chat_nick_colors', 'populate_nicks', '') 410 w.hook_config('weechat.look.nick_color_hash', 'populate_nicks', '') 411 # Hook for working togheter with other scripts (like colorize_lines) 412 w.hook_modifier('colorize_nicks', 'colorize_cb', '') 413 # Hook for modifying input 414 w.hook_modifier('250|input_text_display', 'colorize_input_cb', '') 415 # Hook for updating blacklist (this could be improved to use fnmatch) 416 weechat.hook_config('%s.look.blacklist*' % SCRIPT_NAME, 'update_blacklist', '')