diff options
author | Johannes Löthberg <johannes@kyriasis.com> | 2014-02-02 20:35:42 +0100 |
---|---|---|
committer | Johannes Löthberg <johannes@kyriasis.com> | 2014-02-02 22:01:01 +0100 |
commit | 7e7aa928072775240ff70ca61f2dd0e8a09242d8 (patch) | |
tree | e22d0a73612befd95744dec72770e74593ab47cd /weechat/python | |
download | dotfiles-7e7aa928072775240ff70ca61f2dd0e8a09242d8.tar.xz |
(Let's pretend that this is the) initial commit
Diffstat (limited to 'weechat/python')
-rw-r--r-- | weechat/python/announce_url_title.py | 318 | ||||
-rw-r--r-- | weechat/python/anotify.py | 470 | ||||
l--------- | weechat/python/autoload/announce_url_title.py | 1 | ||||
l--------- | weechat/python/autoload/anotify.py | 1 | ||||
l--------- | weechat/python/autoload/colorize_nicks.py | 1 | ||||
l--------- | weechat/python/autoload/listbuffer.py | 1 | ||||
-rw-r--r-- | weechat/python/colorize_nicks.py | 316 | ||||
-rw-r--r-- | weechat/python/listbuffer.py | 467 |
8 files changed, 1575 insertions, 0 deletions
diff --git a/weechat/python/announce_url_title.py b/weechat/python/announce_url_title.py new file mode 100644 index 0000000..95bd796 --- /dev/null +++ b/weechat/python/announce_url_title.py @@ -0,0 +1,318 @@ +# -*- coding: utf-8 -*- +# +# Copyright (c) 2009 by xt <xt@bash.no> +# Borrowed parts from pagetitle.py by xororand +# +# 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/>. +# + +# +# +# If someone posts an URL in a configured channel +# this script will post back title + +# Explanation about ignores: +# * plugins.var.python.announce_url_title.ignore_buffers: +# Comma separated list of patterns for define ignores. +# URLs from channels where its name matches any of these patterns will be ignored. +# Wildcards '*', '?' and char groups [..] can be used. +# An ignore exception can be added by prefixing '!' in the pattern. +# +# Example: +# *ubuntu*,!#ubuntu-offtopic +# any urls from a 'ubuntu' channel will be ignored, +# except from #ubuntu-offtopic +# +# * plugins.var.python.announce_url_title.url_ignore +# simply does partial match, so specifying 'google' will ignore every url with the word google in it +# +# +# History: +# 2012-11-15, xt +# version 16: improve escaping +# 2011-09-04, Deltafire +# version 15: fix remote execution exploit due to unescaped ' character in urls; +# small bug fix for version 14 changes +# 2011-08-23, Deltafire +# version 14: ignore filtered lines +# 2011-03-11, Sebastien Helleu <flashcode@flashtux.org> +# version 13: get python 2.x binary for hook_process (fix problem when python 3.x is default python version) +# 2010-12-10, xt +# version 12: add better ignores (code based on m4v inotify.py) +# 2010-11-02, xt +# version 11: add prefix +# 2010-11-01, xt +# version 10: add ignored buffers feature +# 2010-10-29, add ignore buffers feature +# version 0.9: WeeChat user-agent option +# 2010-10-11, xt +# version 0.8: support multiple concurrent url lookups +# 2010-10-11, xt +# version 0.7: do not trigger on notices +# 2010-08-25, xt +# version 0.6: notice some buffers instead of msg +# 2009-12-08, Chaz6 +# version 0.5: only announce for specified channels +# 2009-12-08, Chaz6 <chaz@chaz6.com> +# version 0.4: add global option +# 2009-12-08, xt +# version 0.3: option for public announcing or not +# 2009-12-07, xt <xt@bash.no> +# version 0.2: don't renannounce same urls for a time +# add optional prefix and suffix +# 2009-12-02, xt +# version 0.1: initial + +import weechat +w = weechat +import re +import htmllib +from time import time as now +from fnmatch import fnmatch +from urllib import quote + +SCRIPT_NAME = "announce_url_title" +SCRIPT_AUTHOR = "xt <xt@bash.no>" +SCRIPT_VERSION = "16" +SCRIPT_LICENSE = "GPL3" +SCRIPT_DESC = "Announce URL titles to channel or locally" + +settings = { + "buffers" : 'freenode.#testing,', # comma separated list of buffers + "buffers_notice" : 'freenode.#testing,', # comma separated list of buffers + 'ignore_buffers' : 'grep,', # comma separated list of buffers to be ignored by this module + 'title_max_length': '80', + 'url_ignore' : '', # comma separated list of strings in url to ignore + 'reannounce_wait': '5', # 5 minutes delay + 'prefix': '', + 'suffix': '', + 'announce_public': 'off', # print it or msg the buffer + 'global': 'off', # whether to enable for all buffers + 'user_agent': 'WeeChat/%(version)s (http://www.weechat.org)', # user-agent format string + 'global_prefix':'url', # Prefix for when not public announcement +} + + +octet = r'(?:2(?:[0-4]\d|5[0-5])|1\d\d|\d{1,2})' +ipAddr = r'%s(?:\,.%s){3}' % (octet, octet) +# Base domain regex off RFC 1034 and 1738 +label = r'[0-9a-z][-0-9a-z]*[0-9a-z]?' +domain = r'%s(?:\.%s)*\.[a-z][-0-9a-z]*[a-z]?' % (label, label) +urlRe = re.compile(r'(\w+://(?:%s|%s)(?::\d+)?(?:/[^\])>\s]*)?)' % (domain, ipAddr), re.I) + +buffer_name = '' + +urls = {} +script_nick = 'url' +def say(s, buffer=''): + """normal msg""" + weechat.prnt(buffer, '%s\t%s' %(script_nick, s)) + +def unescape(s): + """Unescape HTML entities""" + p = htmllib.HTMLParser(None) + p.save_bgn() + p.feed(s) + return p.save_end() + +def url_print_cb(data, buffer, time, tags, displayed, highlight, prefix, message): + global buffer_name, urls, ignore_buffers + + # Do not trigger on filtered lines and notices + if displayed == '0' or prefix == '--': + return w.WEECHAT_RC_OK + + msg_buffer_name = w.buffer_get_string(buffer, "name") + # Skip ignored buffers + + #if msg_buffer_name in w.config_get_plugin('ignore_buffers').split(','): + # return w.WEECHAT_RC_OK + if msg_buffer_name in ignore_buffers: + return w.WEECHAT_RC_OK + + found = False + notice = False + if w.config_get_plugin('global') == 'on': + found = True + buffer_name = msg_buffer_name + else: + for active_buffer in w.config_get_plugin('buffers').split(','): + if active_buffer.lower() == msg_buffer_name.lower(): + found = True + buffer_name = msg_buffer_name + break + for active_buffer in w.config_get_plugin('buffers_notice').split(','): + if active_buffer.lower() == msg_buffer_name.lower(): + found = True + buffer_name = msg_buffer_name + break + + if not found: + return w.WEECHAT_RC_OK + + ignorelist = w.config_get_plugin('url_ignore').split(',') + for url in urlRe.findall(message): + + url = quote(url, ":/") # Escape URL + ignore = False + for ignore_part in ignorelist: + if ignore_part.strip(): + if ignore_part in url: + ignore = True + w.prnt('', '%s: Found %s in URL: %s, ignoring.' %(SCRIPT_NAME, ignore_part, url)) + break + + if ignore: + continue + + if url in urls: + continue + else: + urls[url] = {} + url_process_launcher() + + return w.WEECHAT_RC_OK + +def url_process_launcher(): + ''' Iterate found urls, fetch title if hasn't been launched ''' + global urls + + user_agent = w.config_get_plugin('user_agent') % {'version': w.info_get("version", "")} + for url, url_d in urls.items(): + if not url_d: # empty dict means not launched + url_d['launched'] = now() + + # Read 8192 + python2_bin = w.info_get("python2_bin", "") or "python" + cmd = python2_bin + " -c \"import urllib2; opener = urllib2.build_opener();" + cmd += "opener.addheaders = [('User-agent','%s')];" % user_agent + cmd += "print opener.open('%s').read(8192)\"" % url + + url_d['stdout'] = '' + url_d['url_hook_process'] = w.hook_process(cmd, 30 * 1000, "url_process_cb", "") + + return w.WEECHAT_RC_OK + +def url_process_cb(data, command, rc, stdout, stderr): + """ Callback parsing html for title """ + + global buffer_name, urls + + url = command.split("'")[-2] + if stdout != "": + urls[url]['stdout'] += stdout + if int(rc) >= 0: + + head = re.sub("[\r\n\t ]"," ", urls[url]['stdout']) + title = re.search('(?i)\<title\>(.*?)\</title\>', head) + if title: + title = unescape(title.group(1)) + + max_len = int(w.config_get_plugin('title_max_length')) + if len(title) > max_len: + title = "%s [...]" % title[0:max_len] + + splits = buffer_name.split('.') #FIXME bad code + server = splits[0] + buffer = '.'.join(splits[1:]) + output = w.config_get_plugin('prefix') + title + w.config_get_plugin('suffix') + announce_public = w.config_get_plugin('announce_public') + if announce_public == 'on': + found = False + for active_buffer in w.config_get_plugin('buffers').split(','): + if active_buffer.lower() == buffer_name.lower(): + w.command('', '/msg -server %s %s %s' %(server, buffer, output)) + found = True + for active_buffer in w.config_get_plugin('buffers_notice').split(','): + if active_buffer.lower() == buffer_name.lower(): + w.command('', '/notice -server %s %s %s' %(server, buffer, output)) + found = True + if found == False: + say(output,w.buffer_search('', buffer_name)) + else: + say(output,w.buffer_search('', buffer_name)) + urls[url]['stdout'] = '' + + return w.WEECHAT_RC_OK + +def purge_cb(*args): + ''' Purge the url list on configured intervals ''' + + global urls + + t_now = now() + for url, url_d in urls.items(): + if (t_now - url_d['launched']) > \ + int(w.config_get_plugin('reannounce_wait'))*60: + del urls[url] + + return w.WEECHAT_RC_OK + +class Ignores(object): + def __init__(self, ignore_type): + self.ignore_type = ignore_type + self.ignores = [] + self.exceptions = [] + self._get_ignores() + + def _get_ignores(self): + assert self.ignore_type is not None + ignores = weechat.config_get_plugin(self.ignore_type).split(',') + ignores = [ s.lower() for s in ignores if s ] + self.ignores = [ s for s in ignores if s[0] != '!' ] + self.exceptions = [ s[1:] for s in ignores if s[0] == '!' ] + + def __contains__(self, s): + s = s.lower() + for p in self.ignores: + if fnmatch(s, p): + for e in self.exceptions: + if fnmatch(s, e): + return False + return True + return False + +def ignore_update(*args): + ignore_buffers._get_ignores() + return w.WEECHAT_RC_OK + + +if __name__ == "__main__": + if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, + SCRIPT_DESC, "", ""): + + # Set default settings + for option, default_value in settings.iteritems(): + if not w.config_is_set_plugin(option): + w.config_set_plugin(option, default_value) + ignore_buffers = Ignores('ignore_buffers') + + w.hook_print("", "", "://", 1, "url_print_cb", "") + w.hook_timer(\ + int(w.config_get_plugin('reannounce_wait')) * 1000 * 60, + 0, + 0, + "purge_cb", + '') + weechat.hook_config('plugins.var.python.%s.ignore_buffers' %SCRIPT_NAME, 'ignore_update', '') + color_chat_delimiters = weechat.color('chat_delimiters') + color_chat_nick = weechat.color('chat_nick') + color_reset = weechat.color('reset') + color_chat_buffer = weechat.color('chat_buffer') + # pretty printing + script_nick = '%s[%s%s%s]%s' %(color_chat_delimiters, + color_chat_nick, + w.config_get_plugin('global_prefix'), + color_chat_delimiters, + color_reset) diff --git a/weechat/python/anotify.py b/weechat/python/anotify.py new file mode 100644 index 0000000..19e2445 --- /dev/null +++ b/weechat/python/anotify.py @@ -0,0 +1,470 @@ +# -*- coding: utf-8 -*- +# +# anotify.py +# Copyright (c) 2012 magnific0 <jacco.geul@gmail.com> +# +# based on: +# growl.py +# Copyright (c) 2011 Sorin Ionescu <sorin.ionescu@gmail.com> +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +SCRIPT_NAME = 'anotify' +SCRIPT_AUTHOR = 'magnific0' +SCRIPT_VERSION = '1.0.0' +SCRIPT_LICENSE = 'MIT' +SCRIPT_DESC = 'Sends libnotify notifications upon events.' + + +# Changelog +# 2012-09-20: v1.0.0 Forked from original and adapted for libnotify. + +# ----------------------------------------------------------------------------- +# Settings +# ----------------------------------------------------------------------------- +SETTINGS = { + 'show_public_message': 'off', + 'show_private_message': 'on', + 'show_public_action_message': 'off', + 'show_private_action_message': 'on', + 'show_notice_message': 'off', + 'show_invite_message': 'on', + 'show_highlighted_message': 'on', + 'show_server': 'on', + 'show_channel_topic': 'on', + 'show_dcc': 'on', + 'show_upgrade_ended': 'on', + 'sticky': 'off', + 'sticky_away': 'on', + 'icon': '/usr/share/pixmaps/weechat.xpm', +} + + +# ----------------------------------------------------------------------------- +# Imports +# ----------------------------------------------------------------------------- +try: + import re + import os + import weechat + import pynotify + IMPORT_OK = True +except ImportError as error: + IMPORT_OK = False + if str(error).find('weechat') != -1: + print('This script must be run under WeeChat.') + print('Get WeeChat at http://www.weechat.org.') + else: + weechat.prnt('', 'anotify: {0}'.format(error)) + +# ----------------------------------------------------------------------------- +# Globals +# ----------------------------------------------------------------------------- +TAGGED_MESSAGES = { + 'public message or action': set(['irc_privmsg', 'notify_message']), + 'private message or action': set(['irc_privmsg', 'notify_private']), + 'notice message': set(['irc_notice', 'notify_private']), + 'invite message': set(['irc_invite', 'notify_highlight']), + 'channel topic': set(['irc_topic', ]), + #'away status': set(['away_info', ]), +} + + +UNTAGGED_MESSAGES = { + 'away status': + re.compile(r'^You ((\w+).){2,3}marked as being away', re.UNICODE), + 'dcc chat request': + re.compile(r'^xfer: incoming chat request from (\w+)', re.UNICODE), + 'dcc chat closed': + re.compile(r'^xfer: chat closed with (\w+)', re.UNICODE), + 'dcc get request': + re.compile( + r'^xfer: incoming file from (\w+) [^:]+: ((?:,\w|[^,])+),', + re.UNICODE), + 'dcc get completed': + re.compile(r'^xfer: file ([^\s]+) received from \w+: OK', re.UNICODE), + 'dcc get failed': + re.compile( + r'^xfer: file ([^\s]+) received from \w+: FAILED', + re.UNICODE), + 'dcc send completed': + re.compile(r'^xfer: file ([^\s]+) sent to \w+: OK', re.UNICODE), + 'dcc send failed': + re.compile(r'^xfer: file ([^\s]+) sent to \w+: FAILED', re.UNICODE), +} + + +DISPATCH_TABLE = { + 'away status': 'set_away_status', + 'public message or action': 'notify_public_message_or_action', + 'private message or action': 'notify_private_message_or_action', + 'notice message': 'notify_notice_message', + 'invite message': 'notify_invite_message', + 'channel topic': 'notify_channel_topic', + 'dcc chat request': 'notify_dcc_chat_request', + 'dcc chat closed': 'notify_dcc_chat_closed', + 'dcc get request': 'notify_dcc_get_request', + 'dcc get completed': 'notify_dcc_get_completed', + 'dcc get failed': 'notify_dcc_get_failed', + 'dcc send completed': 'notify_dcc_send_completed', + 'dcc send failed': 'notify_dcc_send_failed', +} + + +STATE = { + 'icon': None, + 'is_away': False +} + + +# ----------------------------------------------------------------------------- +# Notifiers +# ----------------------------------------------------------------------------- +def cb_irc_server_connected(data, signal, signal_data): + '''Notify when connected to IRC server.''' + if weechat.config_get_plugin('show_server') == 'on': + a_notify( + 'Server', + 'Server Connected', + 'Connected to network {0}.'.format(signal_data)) + return weechat.WEECHAT_RC_OK + + +def cb_irc_server_disconnected(data, signal, signal_data): + '''Notify when disconnected to IRC server.''' + if weechat.config_get_plugin('show_server') == 'on': + a_notify( + 'Server', + 'Server Disconnected', + 'Disconnected from network {0}.'.format(signal_data)) + return weechat.WEECHAT_RC_OK + + +def cb_notify_upgrade_ended(data, signal, signal_data): + '''Notify on end of WeeChat upgrade.''' + if weechat.config_get_plugin('show_upgrade_ended') == 'on': + a_notify( + 'WeeChat', + 'WeeChat Upgraded', + 'WeeChat has been upgraded.') + return weechat.WEECHAT_RC_OK + + +def notify_highlighted_message(prefix, message): + '''Notify on highlighted message.''' + if weechat.config_get_plugin("show_highlighted_message") == "on": + a_notify( + 'Highlight', + 'Highlighted Message', + "{0}: {1}".format(prefix, message), + priority=pynotify.URGENCY_CRITICAL) + + +def notify_public_message_or_action(prefix, message, highlighted): + '''Notify on public message or action.''' + if prefix == ' *': + regex = re.compile(r'^(\w+) (.+)$', re.UNICODE) + match = regex.match(message) + if match: + prefix = match.group(1) + message = match.group(2) + notify_public_action_message(prefix, message, highlighted) + else: + if highlighted: + notify_highlighted_message(prefix, message) + elif weechat.config_get_plugin("show_public_message") == "on": + a_notify( + 'Public', + 'Public Message', + '{0}: {1}'.format(prefix, message)) + + +def notify_private_message_or_action(prefix, message, highlighted): + '''Notify on private message or action.''' + regex = re.compile(r'^CTCP_MESSAGE.+?ACTION (.+)$', re.UNICODE) + match = regex.match(message) + if match: + notify_private_action_message(prefix, match.group(1), highlighted) + else: + if prefix == ' *': + regex = re.compile(r'^(\w+) (.+)$', re.UNICODE) + match = regex.match(message) + if match: + prefix = match.group(1) + message = match.group(2) + notify_private_action_message(prefix, message, highlighted) + else: + if highlighted: + notify_highlighted_message(prefix, message) + elif weechat.config_get_plugin("show_private_message") == "on": + a_notify( + 'Private', + 'Private Message', + '{0}: {1}'.format(prefix, message)) + + +def notify_public_action_message(prefix, message, highlighted): + '''Notify on public action message.''' + if highlighted: + notify_highlighted_message(prefix, message) + elif weechat.config_get_plugin("show_public_action_message") == "on": + a_notify( + 'Action', + 'Public Action Message', + '{0}: {1}'.format(prefix, message), + priority=pynotify.URGENCY_NORMAL) + + +def notify_private_action_message(prefix, message, highlighted): + '''Notify on private action message.''' + if highlighted: + notify_highlighted_message(prefix, message) + elif weechat.config_get_plugin("show_private_action_message") == "on": + a_notify( + 'Action', + 'Private Action Message', + '{0}: {1}'.format(prefix, message), + priority=pynotify.URGENCY_NORMAL) + + +def notify_notice_message(prefix, message, highlighted): + '''Notify on notice message.''' + regex = re.compile(r'^([^\s]*) [^:]*: (.+)$', re.UNICODE) + match = regex.match(message) + if match: + prefix = match.group(1) + message = match.group(2) + if highlighted: + notify_highlighted_message(prefix, message) + elif weechat.config_get_plugin("show_notice_message") == "on": + a_notify( + 'Notice', + 'Notice Message', + '{0}: {1}'.format(prefix, message)) + + +def notify_invite_message(prefix, message, highlighted): + '''Notify on channel invitation message.''' + if weechat.config_get_plugin("show_invite_message") == "on": + regex = re.compile( + r'^You have been invited to ([^\s]+) by ([^\s]+)$', re.UNICODE) + match = regex.match(message) + if match: + channel = match.group(1) + nick = match.group(2) + a_notify( + 'Invite', + 'Channel Invitation', + '{0} has invited you to join {1}.'.format(nick, channel)) + + +def notify_channel_topic(prefix, message, highlighted): + '''Notify on channel topic change.''' + if weechat.config_get_plugin("show_channel_topic") == "on": + regex = re.compile( + r'^\w+ has (?:changed|unset) topic for ([^\s]+)' + + '(?:(?: from "(?:(?:"\w|[^"])+)")? to "((?:"\w|[^"])+)")?', + re.UNICODE) + match = regex.match(message) + if match: + channel = match.group(1) + topic = match.group(2) or '' + a_notify( + 'Channel', + 'Channel Topic', + "{0}: {1}".format(channel, topic)) + + +def notify_dcc_chat_request(match): + '''Notify on DCC chat request.''' + if weechat.config_get_plugin("show_dcc") == "on": + nick = match.group(1) + a_notify( + 'DCC', + 'Direct Chat Request', + '{0} wants to chat directly.'.format(nick)) + + +def notify_dcc_chat_closed(match): + '''Notify on DCC chat termination.''' + if weechat.config_get_plugin("show_dcc") == "on": + nick = match.group(1) + a_notify( + 'DCC', + 'Direct Chat Ended', + 'Direct chat with {0} has ended.'.format(nick)) + + +def notify_dcc_get_request(match): + 'Notify on DCC get request.' + if weechat.config_get_plugin("show_dcc") == "on": + nick = match.group(1) + file_name = match.group(2) + a_notify( + 'DCC', + 'File Transfer Request', + '{0} wants to send you {1}.'.format(nick, file_name)) + + +def notify_dcc_get_completed(match): + 'Notify on DCC get completion.' + if weechat.config_get_plugin("show_dcc") == "on": + file_name = match.group(1) + a_notify('DCC', 'Download Complete', file_name) + + +def notify_dcc_get_failed(match): + 'Notify on DCC get failure.' + if weechat.config_get_plugin("show_dcc") == "on": + file_name = match.group(1) + a_notify('DCC', 'Download Failed', file_name) + + +def notify_dcc_send_completed(match): + 'Notify on DCC send completion.' + if weechat.config_get_plugin("show_dcc") == "on": + file_name = match.group(1) + a_notify('DCC', 'Upload Complete', file_name) + + +def notify_dcc_send_failed(match): + 'Notify on DCC send failure.' + if weechat.config_get_plugin("show_dcc") == "on": + file_name = match.group(1) + a_notify('DCC', 'Upload Failed', file_name) + + +# ----------------------------------------------------------------------------- +# Utility +# ----------------------------------------------------------------------------- +def set_away_status(match): + status = match.group(1) + if status == 'been ': + STATE['is_away'] = True + if status == 'longer ': + STATE['is_away'] = False + + +def cb_process_message( + data, + wbuffer, + date, + tags, + displayed, + highlight, + prefix, + message +): + '''Delegates incoming messages to appropriate handlers.''' + tags = set(tags.split(',')) + functions = globals() + is_public_message = tags.issuperset( + TAGGED_MESSAGES['public message or action']) + buffer_name = weechat.buffer_get_string(wbuffer, 'name') + dcc_buffer_regex = re.compile(r'^irc_dcc\.', re.UNICODE) + dcc_buffer_match = dcc_buffer_regex.match(buffer_name) + highlighted = False + if highlight == "1": + highlighted = True + # Private DCC message identifies itself as public. + if is_public_message and dcc_buffer_match: + notify_private_message_or_action(prefix, message, highlighted) + return weechat.WEECHAT_RC_OK + # Pass identified, untagged message to its designated function. + for key, value in UNTAGGED_MESSAGES.items(): + match = value.match(message) + if match: + functions[DISPATCH_TABLE[key]](match) + return weechat.WEECHAT_RC_OK + # Pass identified, tagged message to its designated function. + for key, value in TAGGED_MESSAGES.items(): + if tags.issuperset(value): + functions[DISPATCH_TABLE[key]](prefix, message, highlighted) + return weechat.WEECHAT_RC_OK + return weechat.WEECHAT_RC_OK + + +def a_notify(notification, title, description, priority=pynotify.URGENCY_LOW): + '''Returns whether notifications should be sticky.''' + is_away = STATE['is_away'] + icon = STATE['icon'] + time_out = 5000 + if weechat.config_get_plugin('sticky') == 'on': + time_out = 0 + if weechat.config_get_plugin('sticky_away') == 'on' and is_away: + time_out = 0 + try: + pynotify.init("wee-notifier") + wn = pynotify.Notification(title, description, icon) + wn.set_urgency(priority) + wn.set_timeout(time_out) + wn.show() + except Exception as error: + weechat.prnt('', 'anotify: {0}'.format(error)) + + +# ----------------------------------------------------------------------------- +# Main +# ----------------------------------------------------------------------------- +def main(): + '''Sets up WeeChat notifications.''' + # Initialize options. + for option, value in SETTINGS.items(): + if not weechat.config_is_set_plugin(option): + weechat.config_set_plugin(option, value) + # Initialize. + name = "WeeChat" + icon = "/usr/share/pixmaps/weechat.xpm" + notifications = [ + 'Public', + 'Private', + 'Action', + 'Notice', + 'Invite', + 'Highlight', + 'Server', + 'Channel', + 'DCC', + 'WeeChat' + ] + STATE['icon'] = icon + # Register hooks. + weechat.hook_signal( + 'irc_server_connected', + 'cb_irc_server_connected', + '') + weechat.hook_signal( + 'irc_server_disconnected', + 'cb_irc_server_disconnected', + '') + weechat.hook_signal('upgrade_ended', 'cb_upgrade_ended', '') + weechat.hook_print('', '', '', 1, 'cb_process_message', '') + + +if __name__ == '__main__' and IMPORT_OK and weechat.register( + SCRIPT_NAME, + SCRIPT_AUTHOR, + SCRIPT_VERSION, + SCRIPT_LICENSE, + SCRIPT_DESC, + '', + '' +): + main() diff --git a/weechat/python/autoload/announce_url_title.py b/weechat/python/autoload/announce_url_title.py new file mode 120000 index 0000000..5417bfe --- /dev/null +++ b/weechat/python/autoload/announce_url_title.py @@ -0,0 +1 @@ +../announce_url_title.py
\ No newline at end of file diff --git a/weechat/python/autoload/anotify.py b/weechat/python/autoload/anotify.py new file mode 120000 index 0000000..1517fef --- /dev/null +++ b/weechat/python/autoload/anotify.py @@ -0,0 +1 @@ +../anotify.py
\ No newline at end of file diff --git a/weechat/python/autoload/colorize_nicks.py b/weechat/python/autoload/colorize_nicks.py new file mode 120000 index 0000000..3ee34e9 --- /dev/null +++ b/weechat/python/autoload/colorize_nicks.py @@ -0,0 +1 @@ +../colorize_nicks.py
\ No newline at end of file diff --git a/weechat/python/autoload/listbuffer.py b/weechat/python/autoload/listbuffer.py new file mode 120000 index 0000000..e7089b2 --- /dev/null +++ b/weechat/python/autoload/listbuffer.py @@ -0,0 +1 @@ +../listbuffer.py
\ No newline at end of file diff --git a/weechat/python/colorize_nicks.py b/weechat/python/colorize_nicks.py new file mode 100644 index 0000000..d3730da --- /dev/null +++ b/weechat/python/colorize_nicks.py @@ -0,0 +1,316 @@ +# -*- 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: +# 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 = "14" +SCRIPT_LICENSE = "GPL" +SCRIPT_DESC = "Use the weechat nick colors in the chat area" + +settings = { + "blacklist_channels" : '', # comma separated list of channels (use short_name) + "blacklist_nicks" : 'so,root', # comma separated list of nicks + "min_nick_length" : '2', # length + "colorize_input" : 'off', # boolean + "ignore_tags" : '', # comma separated list of tags to ignore. I.e. irc_join,irc_part,irc_quit + "greedy_matching" : 'on', # if off, then let's use lazy matching instead +} + + +VALID_NICK = r'([@~&!%+])?([-a-zA-Z0-9\[\]\\`_^\{|\}]+)' +valid_nick_re = re.compile(VALID_NICK) +PREFIX_COLORS = { + '@' : 'nicklist_prefix1', + '~' : 'nicklist_prefix1', + '&' : 'nicklist_prefix1', + '!' : 'nicklist_prefix1', + '%' : 'nicklist_prefix2', + '+' : 'nicklist_prefix3', +} +ignore_channels = [] +ignore_nicks = [] + +# Dict with every nick on every channel with its color as lookup value +colored_nicks = {} + +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 + + full_name = modifier_data.split(';')[1] + server = full_name.split('.')[0] + channel = '.'.join(full_name.split('.')[1:]) + + buffer = w.buffer_search('', full_name) + # Check if buffer has colorized nicks + if not buffer in colored_nicks: + return line + + if channel in ignore_channels: + return line + + try: + min_length = int(w.config_get_plugin('min_nick_length')) + except ValueError: + w.prnt('', '%sError with option min_nick_length, should be a integer' % weechat.prefix('error')) + w.config_set_plugin('min_nick_length', settings['min_nick_length']) + + reset = w.color('reset') + + # Don't colorize if the ignored tag is present in message + tags_line = modifier_data.rsplit(';') + if len(tags_line) >= 3: + tags_line = tags_line[2].split(',') + for i in w.config_get_plugin('ignore_tags').split(','): + if i in tags_line: + return line + + for words in valid_nick_re.findall(line): + prefix, nick = words[0], words[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] + + # Let's use greedy matching. Will check against every word in a line. + if w.config_get_plugin('greedy_matching') == "on": + for word in line.split(): + 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]: + 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) + # Let's use lazy matching for nick + elif w.config_get_plugin('greedy_matching') == "off": + if nick in colored_nicks[buffer]: + 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 str(type(match)) == "<type '_sre.SRE_Match'>": + 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 + + try: + min_length = int(w.config_get_plugin('min_nick_length')) + except ValueError: + w.prnt('', '%sError with option min_nick_length, should be a integer' % weechat.prefix('error')) + w.config_set_plugin('min_nick_length', settings['min_nick_length']) + + if w.config_get_plugin('colorize_input') == 'on': + pass + elif w.config_get_plugin('colorize_input') == 'off': + return line + else: + w.prnt('', '%sError with option colorize_input, should be on or off' % weechat.prefix('error')) + w.config_set_plugin('colorize_input', settings['colorize_input']) + return line + + buffer = w.current_buffer() + # Check if buffer has colorized nicks + if not buffer in colored_nicks: + return line + + channel = w.buffer_get_string(buffer,'name') + if channel in ignore_channels: + return line + + reset = w.color('reset') + + for words in valid_nick_re.findall(line): + prefix, nick = words[0], 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 = {} + + servers = w.infolist_get('irc_server', '', '') + while w.infolist_next(servers): + servername = w.infolist_string(servers, 'name') + colored_nicks[servername] = {} + my_nick = w.info_get('irc_nick', servername) + channels = w.infolist_get('irc_channel', '', servername) + while w.infolist_next(channels): + pointer = w.infolist_pointer(channels, 'buffer') + nicklist = w.infolist_get('nicklist', pointer, '') + channelname = w.infolist_string(channels, 'name') + + if not pointer in colored_nicks: + colored_nicks[pointer] = {} + + while w.infolist_next(nicklist): + nick = w.infolist_string(nicklist, 'name') + if nick == my_nick: + nick_color = w.color(\ + w.config_string(\ + w.config_get('weechat.color.chat_nick_self'))) + else: + nick_color = w.info_get('irc_nick_color', nick) + + colored_nicks[pointer][nick] = nick_color + + w.infolist_free(nicklist) + + w.infolist_free(channels) + + w.infolist_free(servers) + + return w.WEECHAT_RC_OK + +def add_nick(data, signal, type_data): + ''' Add nick to dict of colored nicks ''' + global colored_nicks + + pointer, nick = type_data.split(',') + if not pointer in colored_nicks: + colored_nicks[pointer] = {} + + servername = w.buffer_get_string(pointer, 'localvar_server') + my_nick = w.buffer_get_string(pointer, 'localvar_nick') + + if nick == my_nick: + nick_color = w.color(\ + w.config_string(\ + w.config_get('weechat.color.chat_nick_self'))) + else: + nick_color = w.info_get('irc_nick_color', 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 + + pointer, nick = type_data.split(',') + + 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): + global ignore_channels, ignore_nicks + if w.config_get_plugin('blacklist_channels'): + ignore_channels = w.config_get_plugin('blacklist_channels').split(',') + ignore_nicks = w.config_get_plugin('blacklist_nicks').split(',') + return w.WEECHAT_RC_OK + +if __name__ == "__main__": + if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, + SCRIPT_DESC, "", ""): + # Set default settings +# for option, default_value in settings.iteritems(): + for option, default_value in list(settings.items()): + if not w.config_is_set_plugin(option): + w.config_set_plugin(option, default_value) + + for key, value in list(PREFIX_COLORS.items()): +# for key, value in PREFIX_COLORS.iteritems(): + PREFIX_COLORS[key] = w.color(w.config_string(w.config_get('weechat.look.%s'%value))) + + update_blacklist() # Set blacklist + populate_nicks() # Run it once to get data ready + 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', '') + # 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('plugins.var.python.%s.blacklist*' %SCRIPT_NAME, 'update_blacklist', '') diff --git a/weechat/python/listbuffer.py b/weechat/python/listbuffer.py new file mode 100644 index 0000000..227a3ba --- /dev/null +++ b/weechat/python/listbuffer.py @@ -0,0 +1,467 @@ +# -*- coding: utf-8 -*- +# +# ListBuffer, version 0.8.1 for WeeChat version 0.3 +# Latest development version: https://github.com/FiXato/listbuffer +# +# Show /list results in a common buffer and interact with them. +# +# This script allows you to easily join channels from the /list output. +# It will open a common buffer for the /list result, through which you +# browse with your cursor keys, and join with the meta-enter keys. +# Adjust sorting with meta->, meta-< and meta-/ keybindings. +# +## History: +### 2011-09-08: FiXato: +# +# * version 0.1: initial release. +# * added a common buffer for /list results +# * added highlighting for currently selected line +# * added /join support via enter key +# * added scroll_top and scroll_bottom support +# +# * version 0.2: /list format bugfix +# * added support for /list results without modes +# * some servers don't send 321 (/list start). Taken into account. +# +# * version 0.3: Sorting support +# * Added some basic sorting support. Scroll through sort options +# with meta-> and meta-< (users, channel, topic, modes) +# +### 2011-09-19: FiXato +# +# * version 0.4: +# * Case-insensitive buffer lookup fix. +# * Removed default enter keybind +# +### 2011-12-28: troydm: +# +# * version 0.5: It's an upside-down-world +# * Added inverted sorting support provided by Dmitry "troydm" Geurkov +# Use meta-/ to switch between inverted and regular sorting. +# +### 2012-02-10: FiXato: +# +# * version 0.6: Stop shoving that buffer in my face! +# * The listbuffer should no longer pop up by itself when you load the script. +# It should only pop up now when you actually do a /list query. +# +# * version 0.7: .. but please pop it up in my current window when I ask for it +# * Added setting plugins.var.python.listbuffer.autofocus +# This will autofocus the listbuffer in the current window if another window isn't +# already showing it, and of course only when the user issues /list +# +### 2012-07-10: FiXato: +# +# * version 0.7.1: Forgetful bugfix +# * Made sure lb_curline global variable is defined +# +### 2013-03-19: FiXato: +# +# * version 0.8: Sorted out the sorting +# * Added automatically updating options for sorting: +# * plugins.var.python.listbuffer.sort_inverted +# * plugins.var.python.listbuffer.sort_order +# * version 0.8.1: Pad it baby! +# * Channel modes are equally padded even when there are no channel modes. +# * Added padding options: +# * plugins.var.python.listbuffer.modes_min_width +# * plugins.var.python.listbuffer.channel_min_width +# * plugins.var.python.listbuffer.users_min_width +# +## Acknowledgements: +# * Dmitry "troydm" Geurkov, for providing the inverse-sorting patch to the project. +# * Sebastien "Flashcode" Helleu, for developing the kick-ass IRC client WeeChat +# and the iset.pl script which inspired me to this script. +# * Nils "nils_2" Görs, for his contributions to iset.pl which served as +# example code. +# * David "drubin" Rubin, for his urlgrab.py script, which also served +# as example code. +# * ArZa, whose listsort.pl script helped me getting started with +# grabbing the /list results. Parts of his code have been shamelessly +# copied and ported to Python. +# * Khaled Mardam-Bey, for making me yearn for similar /list support in +# WeeChat as mIRC already offered. :P +# * mave_, for pointing out that sort orders weren't remembered. +# +## TODO: +# - Auto-scroll selected line upon window scroll. +# - Add option to hide already joined channels. +# - Improve sorting methods +# - Add auto-join support +# - Detect if channel is already in auto-join +# - Allow automatically switching to the listbuffer +# - Add support for ALIS (/squery alis LIST * -mix 100 (IRCNet) +# - Make colours configurable +# - Limit number of channels to parse +# - Add filter support a la iset +# - Allow selecting multiple channels +# - Add optional command redirection. +# +## Copyright (c) 2011,2012,2013 Filip H.F. "FiXato" Slagter, +# <FiXato [at] Gmail [dot] com> +# http://profile.fixato.org +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +# +SCRIPT_NAME = "listbuffer" +SCRIPT_AUTHOR = "Filip H.F. 'FiXato' Slagter <fixato [at] gmail [dot] com>" +SCRIPT_VERSION = "0.8.1" +SCRIPT_LICENSE = "MIT" +SCRIPT_DESC = "A common buffer for /list output." +SCRIPT_COMMAND = "listbuffer" + +import_ok = True + +try: + import weechat +except ImportError: + print "This script must be run under WeeChat." + import_ok = False + +import re + +lb_settings = ( + ("autofocus", "on", "Focus the listbuffer in the current window if it isn't already displayed by a window."), + ("sort_order", "users", "Last used sort order for the channel list."), + ("sort_inverted", "on", "Invert the sort order for the channel list."), + ("modes_min_width", "8", "The minimum width used for modes in the channel list. If a channel has less modes than this amount, the column will be padded with spaces."), + ("channel_min_width", "25", "The minimum width used for the channel name in the channel list. If a channelname is shorter than this amount, the column will be padded with spaces."), + ("users_min_width", "8", "The minimum width used for the usercount in the channel list. If the usercount has less digits than this amount, the column will be padded with spaces."), +) +lb_buffer = None +lb_curline = 0 +lb_channels = [] +lb_network = None +lb_list_started = False +lb_current_sort = None +lb_sort_inverted = False +lb_sort_options = ( + 'channel', + 'users', + 'modes', + 'topic', +) + +# server numeric Nick Chan Users Modes Topic +lb_channel_list_expression = '(:\S+) (\d{3}) (\S+) (\S+) (\d+) :(\[(.*?)\] )?(.*)' + +# Create listbuffer. +def lb_create_buffer(): + global lb_buffer, lb_curline + + if not lb_buffer: + lb_buffer = weechat.buffer_new("listbuffer", "lb_input_cb", \ + "", "lb_close_cb", "") + lb_set_buffer_title() + # Sets notify to 0 as this buffer does not need to be in hotlist. + weechat.buffer_set(lb_buffer, "notify", "0") + weechat.buffer_set(lb_buffer, "nicklist", "0") + weechat.buffer_set(lb_buffer, "type", "free") + weechat.buffer_set(lb_buffer, "key_bind_ctrl-L", "/listbuffer **refresh") + weechat.buffer_set(lb_buffer, "key_bind_meta2-A", "/listbuffer **up") + weechat.buffer_set(lb_buffer, "key_bind_meta2-B", "/listbuffer **down") + weechat.buffer_set(lb_buffer, "key_bind_meta2-1~", "/listbuffer **scroll_top") + weechat.buffer_set(lb_buffer, "key_bind_meta2-4~", "/listbuffer **scroll_bottom") + weechat.buffer_set(lb_buffer, "key_bind_meta-ctrl-J", "/listbuffer **enter") + weechat.buffer_set(lb_buffer, "key_bind_meta-ctrl-M", "/listbuffer **enter") + weechat.buffer_set(lb_buffer, "key_bind_meta->", "/listbuffer **sort_next") + weechat.buffer_set(lb_buffer, "key_bind_meta-<", "/listbuffer **sort_previous") + weechat.buffer_set(lb_buffer, "key_bind_meta-/", "/listbuffer **sort_invert") + lb_curline = 0 + if weechat.config_get_plugin("autofocus") == "on": + if not weechat.window_search_with_buffer(lb_buffer): + weechat.command("", "/buffer " + weechat.buffer_get_string(lb_buffer,"name")) + +def lb_set_buffer_title(): + global lb_buffer, lb_current_sort + ascdesc = '(v)' if lb_sort_inverted else '(^)' + weechat.buffer_set(lb_buffer, "title", lb_line_format({ + 'channel': 'Channel name%s' % (ascdesc if lb_current_sort == 'channel' else ''), + 'users': 'Users%s' % (ascdesc if lb_current_sort == 'users' else ''), + 'modes': 'Modes%s' % (ascdesc if lb_current_sort == 'modes' else ''), + 'topic': 'Topic%s' % (ascdesc if lb_current_sort == 'topic' else ''), + 'nomodes': None, + })) + +def lb_list_start(data, signal, message): + lb_initialise_list + + return weechat.WEECHAT_RC_OK + +def lb_initialise_list(signal): + global lb_channels, lb_network, lb_list_started + + lb_create_buffer() + lb_channels = [] + lb_network = signal.split(',')[0] + lb_list_started = True + return + + +def lb_list_chan(data, signal, message): + global lb_channels, lb_buffer, lb_list_started + + # Work-around for IRCds which don't send 321 Numeric (/List start) + if not lb_list_started: + lb_initialise_list(signal) + + for chan_data in re.findall(lb_channel_list_expression,message): + lb_channels.append({ + 'server': chan_data[0][1:-1], + 'numeric': chan_data[1], + 'nick': chan_data[2], + 'channel': chan_data[3], + 'users': chan_data[4], + 'nomodes': chan_data[5] == '', + 'modes': chan_data[6], + 'topic': weechat.hook_modifier_exec("irc_color_decode", "1", chan_data[7]) + }) + return weechat.WEECHAT_RC_OK + +def lb_list_end(data, signal, message): + global lb_list_started + + # Work-around for IRCds which don't send 321 Numeric (/List start) + if not lb_list_started: + lb_initialise_list(signal) + + lb_list_started = False + if lb_current_sort: + lb_sort() + lb_refresh() + return weechat.WEECHAT_RC_OK + +def keyEvent (data, buffer, args): + global lb_options + lb_options[args]() + +def lb_input_cb(data, buffer, input_data): + global lb_options, lb_curline + lb_options[input_data]() + return weechat.WEECHAT_RC_OK + +def lb_refresh(): + global lb_channels, lb_buffer + weechat.buffer_clear(lb_buffer) + + y = 0 + for list_data in lb_channels: + lb_refresh_line(y) + y += 1 + return + +def lb_refresh_line(y): + global lb_buffer, lb_curline, lb_channels + if y >= 0 and y < len(lb_channels): + formatted_line = lb_line_format(lb_channels[y], y == lb_curline) + weechat.prnt_y(lb_buffer, y, formatted_line) + +def lb_refresh_curline(): + global lb_curline + lb_refresh_line(lb_curline-1) + lb_refresh_line(lb_curline) + lb_refresh_line(lb_curline+1) + return + +def lb_line_format(list_data,curr=False): + str = "" + if (curr): + str += weechat.color("yellow,red") + channel_text = list_data['channel'].ljust(int(weechat.config_get_plugin('channel_min_width'))) + users_text = "(%s)" % list_data['users'] + padded_users_text = users_text.rjust(int(weechat.config_get_plugin('users_min_width')) + 2) + str += "%s%s %s " % (weechat.color("bold"), channel_text, padded_users_text) + if not list_data['nomodes']: + modes = "[%s]" % list_data['modes'] + else: + modes = "[]" + str += "%s: " % modes.rjust(int(weechat.config_get_plugin('modes_min_width')) + 2) + str += "%s" % list_data['topic'] + return str + +def lb_line_up(): + global lb_curline + if lb_curline <= 0: + return + lb_curline -= 1 + lb_refresh_curline() + lb_check_outside_window() + return + +def lb_line_down(): + global lb_curline, lb_channels + if lb_curline+1 >= len(lb_channels): + return + lb_curline += 1 + lb_refresh_curline() + lb_check_outside_window() + return + +def lb_line_run(): + global lb_channels, lb_curline, lb_network + buff = weechat.info_get("irc_buffer", lb_network) + channel = lb_channels[lb_curline]['channel'] + command = "/join %s" % channel + weechat.command(buff, command) + return + +def lb_line_select(): + return + +def lb_scroll_top(): + global lb_curline + old_y = lb_curline + lb_curline = 0 + lb_refresh_curline() + lb_refresh_line(old_y) + weechat.command(lb_buffer, "/window scroll_top") + return + +def lb_scroll_bottom(): + global lb_curline, lb_channels + old_y = lb_curline + lb_curline = len(lb_channels)-1 + lb_refresh_curline() + lb_refresh_line(old_y) + weechat.command(lb_buffer, "/window scroll_bottom") + return + +def lb_check_outside_window(): + global lb_buffer, lb_curline + if (lb_buffer): + infolist = weechat.infolist_get("window", "", "current") + if (weechat.infolist_next(infolist)): + start_line_y = weechat.infolist_integer(infolist, "start_line_y") + chat_height = weechat.infolist_integer(infolist, "chat_height") + if(start_line_y > lb_curline): + weechat.command(lb_buffer, "/window scroll -%i" %(start_line_y - lb_curline)) + elif(start_line_y <= lb_curline - chat_height): + weechat.command(lb_buffer, "/window scroll +%i"%(lb_curline - start_line_y - chat_height + 1)) + weechat.infolist_free(infolist) + +def lb_sort_next(): + global lb_current_sort, lb_sort_options + if lb_current_sort: + new_index = lb_sort_options.index(lb_current_sort) + 1 + else: + new_index = 0 + + if len(lb_sort_options) <= new_index: + new_index = 0 + + lb_set_current_sort_order(lb_sort_options[new_index]) + lb_sort() + +def lb_set_current_sort_order(value): + global lb_current_sort + lb_current_sort = value + weechat.config_set_plugin('sort_order', lb_current_sort) + +def lb_set_invert_sort_order(value): + global lb_sort_inverted + lb_sort_inverted = value + weechat.config_set_plugin('sort_inverted', ('on' if lb_sort_inverted else 'off')) + +def lb_sort_previous(): + global lb_current_sort, lb_sort_options + if lb_current_sort: + new_index = lb_sort_options.index(lb_current_sort) - 1 + else: + new_index = 0 + + if new_index < 0: + new_index = len(lb_sort_options) - 1 + + lb_set_current_sort_order(lb_sort_options[new_index]) + lb_sort() + +def lb_sort(sort_key=None): + global lb_channels, lb_current_sort, lb_sort_inverted + if sort_key: + lb_set_current_sort_order(sort_key) + if lb_current_sort == 'users': + lb_channels = sorted(lb_channels, key=lambda chan_data: int(chan_data[lb_current_sort])) + else: + lb_channels = sorted(lb_channels, key=lambda chan_data: chan_data[lb_current_sort]) + if lb_sort_inverted: + lb_channels.reverse() + lb_set_buffer_title() + lb_refresh() + +def lb_sort_invert(): + global lb_current_sort, lb_sort_inverted + if lb_current_sort: + lb_set_invert_sort_order(not lb_sort_inverted) + lb_sort() + +def lb_close_cb(*kwargs): + """ A callback for buffer closing. """ + global lb_buffer + + lb_buffer = None + return weechat.WEECHAT_RC_OK + +lb_options = { + 'refresh' : lb_refresh, + 'up' : lb_line_up, + 'down' : lb_line_down, + 'enter' : lb_line_run, + 'space' : lb_line_select, + 'scroll_top' : lb_scroll_top, + 'scroll_bottom': lb_scroll_bottom, + 'sort_next' : lb_sort_next, + 'sort_previous': lb_sort_previous, + 'sort_invert': lb_sort_invert +} + +def lb_command_main(data, buffer, args): + if args[0:2] == "**": + keyEvent(data, buffer, args[2:]) + return weechat.WEECHAT_RC_OK + +def lb_set_default_settings(): + global lb_settings + # Set default settings + for option, default_value, description in lb_settings: + if not weechat.config_is_set_plugin(option): + weechat.config_set_plugin(option, default_value) + version = weechat.info_get("version_number", "") or 0 + if int(version) >= 0x00030500: + weechat.config_set_desc_plugin(option, description) + +def lb_reset_stored_sort_order(): + global lb_current_sort, lb_sort_inverted + lb_current_sort = weechat.config_get_plugin('sort_order') + lb_sort_inverted = (True if weechat.config_get_plugin('sort_inverted') == 'on' else False) + +if __name__ == "__main__" and import_ok: + if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, + SCRIPT_LICENSE, SCRIPT_DESC, "lb_close_cb", ""): + lb_set_default_settings() + lb_reset_stored_sort_order() + lb_buffer = weechat.buffer_search("python", "listbuffer") + + weechat.hook_signal("*,irc_in_321", "lb_list_start", "") + weechat.hook_signal("*,irc_in_322", "lb_list_chan", "") + weechat.hook_signal("*,irc_in_323", "lb_list_end", "") + weechat.hook_command(SCRIPT_COMMAND, + "List Buffer", + "", "", "", + "lb_command_main", "") |