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', '')