Use the weechat nick colors in the chat area and command line.
Author: xt
— Version: 32
— License: GPL-3.0-or-later
For WeeChat ≥ 0.3.0.
Tags: irc, color, py2, py3
Added: 2010-03-05
— Updated: 2023-10-30
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 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 | # -*- coding: utf-8 -*- # # Copyright (c) 2010 by xt <xt@bash.no> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. # # This script colors nicks in IRC channels in the actual message # not just in the prefix section. # # # History: # 2023-10-30: Sébastien Helleu <flashcode@flashtux.org> # version 32: revert to info "nick_color" with WeeChat >= 4.1.1 # 2023-10-16: Sébastien Helleu <flashcode@flashtux.org> # version 31: use info "irc_nick_color" on IRC buffers with WeeChat >= 4.1.0 # 2022-11-07: mva # version 30: add ":" and "," to VALID_NICK regexp, # to don't reset colorization in input_line # 2022-07-11: ncfavier # version 29: check nick for exclusion *after* stripping # decrease minimum min_nick_length to 1 # 2020-11-29: jess # version 28: fix ignore_tags having been broken by weechat 2.9 changes # 2020-05-09: Sébastien Helleu <flashcode@flashtux.org> # version 27: add compatibility with new weechat_print modifier data # (WeeChat >= 2.9) # 2018-04-06: Joey Pabalinas <joeypabalinas@gmail.com> # version 26: fix freezes with too many nicks in one line # 2018-03-18: nils_2 # version 25: fix unable to run function colorize_config_reload_cb() # 2017-06-20: lbeziaud <louis.beziaud@ens-rennes.fr> # version 24: colorize utf8 nicks # 2017-03-01, arza <arza@arza.us> # version 23: don't colorize nicklist group names # 2016-05-01, Simmo Saan <simmo.saan@gmail.com> # version 22: invalidate cached colors on hash algorithm change # 2015-07-28, xt # version 21: fix problems with nicks with commas in them # 2015-04-19, xt # version 20: fix ignore of nicks in URLs # 2015-04-18, xt # version 19: new option ignore nicks in URLs # 2015-03-03, xt # version 18: iterate buffers looking for nicklists instead of servers # 2015-02-23, holomorph # version 17: fix coloring in non-channel buffers (#58) # 2014-09-17, holomorph # version 16: use weechat config facilities # clean unused, minor linting, some simplification # 2014-05-05, holomorph # version 15: fix python2-specific re.search check # 2013-01-29, nils_2 # version 14: make script compatible with Python 3.x # 2012-10-19, ldvx # version 13: Iterate over every word to prevent incorrect colorization of # nicks. Added option greedy_matching. # 2012-04-28, ldvx # version 12: added ignore_tags to avoid colorizing nicks if tags are present # 2012-01-14, nesthib # version 11: input_text_display hook and modifier to colorize nicks in input bar # 2010-12-22, xt # version 10: hook config option for updating blacklist # 2010-12-20, xt # version 0.9: hook new config option for weechat 0.3.4 # 2010-11-01, nils_2 # version 0.8: hook_modifier() added to communicate with rainbow_text # 2010-10-01, xt # version 0.7: changes to support non-irc-plugins # 2010-07-29, xt # version 0.6: compile regexp as per patch from Chris quigybo@hotmail.com # 2010-07-19, xt # version 0.5: fix bug with incorrect coloring of own nick # 2010-06-02, xt # version 0.4: update to reflect API changes # 2010-03-26, xt # version 0.3: fix error with exception # 2010-03-24, xt # version 0.2: use ignore_channels when populating to increase performance. # 2010-02-03, xt # version 0.1: initial (based on ruby script by dominikh) # # Known issues: nicks will not get colorized if they begin with a character # such as ~ (which some irc networks do happen to accept) import weechat import re w = weechat SCRIPT_NAME = "colorize_nicks" SCRIPT_AUTHOR = "xt <xt@bash.no>" SCRIPT_VERSION = "32" SCRIPT_LICENSE = "GPL" SCRIPT_DESC = "Use the weechat nick colors in the chat area" # Based on the recommendations in RFC 7613. A valid nick is composed # of anything but " ,*?.!@". VALID_NICK = r'([@~&!%+-])?([^\s,\*?\.!@:,]+)' valid_nick_re = re.compile(VALID_NICK) ignore_channels = [] ignore_nicks = [] # Dict with every nick on every channel with its color as lookup value colored_nicks = {} CONFIG_FILE_NAME = "colorize_nicks" # config file and options colorize_config_file = "" colorize_config_option = {} def colorize_config_init(): ''' Initialization of configuration file. Sections: look. ''' global colorize_config_file, colorize_config_option colorize_config_file = weechat.config_new(CONFIG_FILE_NAME, "", "") if colorize_config_file == "": return # section "look" section_look = weechat.config_new_section( colorize_config_file, "look", 0, 0, "", "", "", "", "", "", "", "", "", "") if section_look == "": weechat.config_free(colorize_config_file) return colorize_config_option["blacklist_channels"] = weechat.config_new_option( colorize_config_file, section_look, "blacklist_channels", "string", "Comma separated list of channels", "", 0, 0, "", "", 0, "", "", "", "", "", "") colorize_config_option["blacklist_nicks"] = weechat.config_new_option( colorize_config_file, section_look, "blacklist_nicks", "string", "Comma separated list of nicks", "", 0, 0, "so,root", "so,root", 0, "", "", "", "", "", "") colorize_config_option["min_nick_length"] = weechat.config_new_option( colorize_config_file, section_look, "min_nick_length", "integer", "Minimum length nick to colorize", "", 1, 20, "2", "2", 0, "", "", "", "", "", "") colorize_config_option["colorize_input"] = weechat.config_new_option( colorize_config_file, section_look, "colorize_input", "boolean", "Whether to colorize input", "", 0, 0, "off", "off", 0, "", "", "", "", "", "") colorize_config_option["ignore_tags"] = weechat.config_new_option( colorize_config_file, section_look, "ignore_tags", "string", "Comma separated list of tags to ignore; i.e. irc_join,irc_part,irc_quit", "", 0, 0, "", "", 0, "", "", "", "", "", "") colorize_config_option["greedy_matching"] = weechat.config_new_option( colorize_config_file, section_look, "greedy_matching", "boolean", "If off, then use lazy matching instead", "", 0, 0, "on", "on", 0, "", "", "", "", "", "") colorize_config_option["match_limit"] = weechat.config_new_option( colorize_config_file, section_look, "match_limit", "integer", "Fall back to lazy matching if greedy matches exceeds this number", "", 20, 1000, "", "", 0, "", "", "", "", "", "") colorize_config_option["ignore_nicks_in_urls"] = weechat.config_new_option( colorize_config_file, section_look, "ignore_nicks_in_urls", "boolean", "If on, don't colorize nicks inside URLs", "", 0, 0, "off", "off", 0, "", "", "", "", "", "") def colorize_config_read(): ''' Read configuration file. ''' global colorize_config_file return weechat.config_read(colorize_config_file) def colorize_nick_color(buffer, nick, my_nick): ''' Retrieve nick color from weechat. ''' if nick == my_nick: return w.color(w.config_string(w.config_get('weechat.color.chat_nick_self'))) else: version = int(w.info_get('version_number', '') or 0) if w.buffer_get_string(buffer, 'plugin') == 'irc' and version == 0x4010000: server = w.buffer_get_string(buffer, 'localvar_server') return w.info_get('irc_nick_color', '%s,%s' % (server, nick)) return w.info_get('nick_color', nick) def colorize_cb(data, modifier, modifier_data, line): ''' Callback that does the colorizing, and returns new line if changed ''' global ignore_nicks, ignore_channels, colored_nicks if modifier_data.startswith('0x'): # WeeChat >= 2.9 buffer, tags = modifier_data.split(';', 1) else: # WeeChat <= 2.8 plugin, buffer_name, tags = modifier_data.split(';', 2) buffer = w.buffer_search(plugin, buffer_name) channel = w.buffer_get_string(buffer, 'localvar_channel') tags = tags.split(',') # Check if buffer has colorized nicks if buffer not in colored_nicks: return line if channel and channel in ignore_channels: return line min_length = w.config_integer(colorize_config_option['min_nick_length']) reset = w.color('reset') # Don't colorize if the ignored tag is present in message tag_ignores = w.config_string(colorize_config_option['ignore_tags']).split(',') for tag in tags: if tag in tag_ignores: return line for words in valid_nick_re.findall(line): nick = words[1] # If the matched word is not a known nick, we try to match the # word without its first or last character (if not a letter). # This is necessary as "foo:" is a valid nick, which could be # adressed as "foo::". if nick not in colored_nicks[buffer]: if not nick[-1].isalpha() and not nick[0].isalpha(): if nick[1:-1] in colored_nicks[buffer]: nick = nick[1:-1] elif not nick[0].isalpha(): if nick[1:] in colored_nicks[buffer]: nick = nick[1:] elif not nick[-1].isalpha(): if nick[:-1] in colored_nicks[buffer]: nick = nick[:-1] # Check that nick is not ignored and longer than minimum length if len(nick) < min_length or nick in ignore_nicks: continue # Check that nick is in the dictionary colored_nicks if nick in colored_nicks[buffer]: nick_color = colored_nicks[buffer][nick] try: # Let's use greedy matching. Will check against every word in a line. if w.config_boolean(colorize_config_option['greedy_matching']): cnt = 0 limit = w.config_integer(colorize_config_option['match_limit']) for word in line.split(): cnt += 1 assert cnt < limit # if cnt > limit: # raise RuntimeError('Exceeded colorize_nicks.look.match_limit.'); if w.config_boolean(colorize_config_option['ignore_nicks_in_urls']) and \ word.startswith(('http://', 'https://')): continue if nick in word: # Is there a nick that contains nick and has a greater lenght? # If so let's save that nick into var biggest_nick biggest_nick = "" for i in colored_nicks[buffer]: cnt += 1 assert cnt < limit if nick in i and nick != i and len(i) > len(nick): if i in word: # If a nick with greater len is found, and that word # also happens to be in word, then let's save this nick biggest_nick = i # If there's a nick with greater len, then let's skip this # As we will have the chance to colorize when biggest_nick # iterates being nick. if len(biggest_nick) > 0 and biggest_nick in word: pass elif len(word) < len(biggest_nick) or len(biggest_nick) == 0: new_word = word.replace(nick, '%s%s%s' % (nick_color, nick, reset)) line = line.replace(word, new_word) # Switch to lazy matching else: raise AssertionError except AssertionError: # Let's use lazy matching for nick nick_color = colored_nicks[buffer][nick] # The two .? are in case somebody writes "nick:", "nick,", etc # to address somebody regex = r"(\A|\s).?(%s).?(\Z|\s)" % re.escape(nick) match = re.search(regex, line) if match is not None: new_line = line[:match.start(2)] + nick_color+nick+reset + line[match.end(2):] line = new_line return line def colorize_input_cb(data, modifier, modifier_data, line): ''' Callback that does the colorizing in input ''' global ignore_nicks, ignore_channels, colored_nicks min_length = w.config_integer(colorize_config_option['min_nick_length']) if not w.config_boolean(colorize_config_option['colorize_input']): return line buffer = w.current_buffer() # Check if buffer has colorized nicks if buffer not in colored_nicks: return line channel = w.buffer_get_string(buffer, 'name') if channel and channel in ignore_channels: return line reset = w.color('reset') for words in valid_nick_re.findall(line): nick = words[1] # Check that nick is not ignored and longer than minimum length if len(nick) < min_length or nick in ignore_nicks: continue if nick in colored_nicks[buffer]: nick_color = colored_nicks[buffer][nick] line = line.replace(nick, '%s%s%s' % (nick_color, nick, reset)) return line def populate_nicks(*args): ''' Fills entire dict with all nicks weechat can see and what color it has assigned to it. ''' global colored_nicks colored_nicks = {} buffers = w.infolist_get('buffer', '', '') while w.infolist_next(buffers): buffer_ptr = w.infolist_pointer(buffers, 'pointer') my_nick = w.buffer_get_string(buffer_ptr, 'localvar_nick') nicklist = w.infolist_get('nicklist', buffer_ptr, '') while w.infolist_next(nicklist): if buffer_ptr not in colored_nicks: colored_nicks[buffer_ptr] = {} if w.infolist_string(nicklist, 'type') != 'nick': continue nick = w.infolist_string(nicklist, 'name') nick_color = colorize_nick_color(buffer_ptr, nick, my_nick) colored_nicks[buffer_ptr][nick] = nick_color w.infolist_free(nicklist) w.infolist_free(buffers) return w.WEECHAT_RC_OK def add_nick(data, signal, type_data): ''' Add nick to dict of colored nicks ''' global colored_nicks # Nicks can have , in them in some protocols splitted = type_data.split(',') pointer = splitted[0] nick = ",".join(splitted[1:]) if pointer not in colored_nicks: colored_nicks[pointer] = {} my_nick = w.buffer_get_string(pointer, 'localvar_nick') nick_color = colorize_nick_color(pointer, nick, my_nick) colored_nicks[pointer][nick] = nick_color return w.WEECHAT_RC_OK def remove_nick(data, signal, type_data): ''' Remove nick from dict with colored nicks ''' global colored_nicks # Nicks can have , in them in some protocols splitted = type_data.split(',') pointer = splitted[0] nick = ",".join(splitted[1:]) if pointer in colored_nicks and nick in colored_nicks[pointer]: del colored_nicks[pointer][nick] return w.WEECHAT_RC_OK def update_blacklist(*args): ''' Set the blacklist for channels and nicks. ''' global ignore_channels, ignore_nicks ignore_channels = w.config_string(colorize_config_option['blacklist_channels']).split(',') ignore_nicks = w.config_string(colorize_config_option['blacklist_nicks']).split(',') return w.WEECHAT_RC_OK if __name__ == "__main__": if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, "", ""): colorize_config_init() colorize_config_read() # Run once to get data ready update_blacklist() populate_nicks() w.hook_signal('nicklist_nick_added', 'add_nick', '') w.hook_signal('nicklist_nick_removed', 'remove_nick', '') w.hook_modifier('weechat_print', 'colorize_cb', '') # Hook config for changing colors w.hook_config('weechat.color.chat_nick_colors', 'populate_nicks', '') w.hook_config('weechat.look.nick_color_hash', 'populate_nicks', '') # Hook for working togheter with other scripts (like colorize_lines) w.hook_modifier('colorize_nicks', 'colorize_cb', '') # Hook for modifying input w.hook_modifier('250|input_text_display', 'colorize_input_cb', '') # Hook for updating blacklist (this could be improved to use fnmatch) weechat.hook_config('%s.look.blacklist*' % SCRIPT_NAME, 'update_blacklist', '') |