aboutsummaryrefslogtreecommitdiffstats
path: root/weechat/python
diff options
context:
space:
mode:
authorJohannes Löthberg <johannes@kyriasis.com>2014-02-02 20:35:42 +0100
committerJohannes Löthberg <johannes@kyriasis.com>2014-02-02 22:01:01 +0100
commit7e7aa928072775240ff70ca61f2dd0e8a09242d8 (patch)
treee22d0a73612befd95744dec72770e74593ab47cd /weechat/python
downloaddotfiles-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.py318
-rw-r--r--weechat/python/anotify.py470
l---------weechat/python/autoload/announce_url_title.py1
l---------weechat/python/autoload/anotify.py1
l---------weechat/python/autoload/colorize_nicks.py1
l---------weechat/python/autoload/listbuffer.py1
-rw-r--r--weechat/python/colorize_nicks.py316
-rw-r--r--weechat/python/listbuffer.py467
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", "")