aboutsummaryrefslogtreecommitdiffstats
path: root/weechat/python
diff options
context:
space:
mode:
authorJohannes Löthberg <johannes@kyriasis.com>2016-10-05 11:06:16 +0200
committerJohannes Löthberg <johannes@kyriasis.com>2017-06-01 18:37:33 +0200
commitc06e64a6556f1703f9455ad91299c521ad8b3ed8 (patch)
treef8ad6e602df47c837251c6525f4dfb4a8b04ab1b /weechat/python
parentbca24f634795b670883348f7aaca95f987e0c388 (diff)
downloaddotfiles-c06e64a6556f1703f9455ad91299c521ad8b3ed8.tar.xz
weechat: I give up
Signed-off-by: Johannes Löthberg <johannes@kyriasis.com>
Diffstat (limited to 'weechat/python')
-rw-r--r--weechat/python/autojoin_on_invite.py102
-rw-r--r--weechat/python/chanop.py3236
-rw-r--r--weechat/python/colorize_nicks.py14
-rw-r--r--weechat/python/go.py10
-rw-r--r--weechat/python/infolist.py195
-rw-r--r--weechat/python/listbuffer.py467
-rw-r--r--weechat/python/nsb.py113
7 files changed, 896 insertions, 3241 deletions
diff --git a/weechat/python/autojoin_on_invite.py b/weechat/python/autojoin_on_invite.py
new file mode 100644
index 0000000..cdaeaf0
--- /dev/null
+++ b/weechat/python/autojoin_on_invite.py
@@ -0,0 +1,102 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2009 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 requires WeeChat 0.3.0 or newer)
+#
+# History:
+# 2013-12-21, Sebastien Helleu <flashcode@flashtux.org>
+# version 0.5: fix parsing of INVITE message
+# 2013-11-28, sakkemo <scajanus@gmail.com>
+# version 0.4: add whitelist for nicks/channels
+# 2009-11-09, xt <xt@bash.no>
+# version 0.3: add ignore option for channels
+# 2009-10-29, xt <xt@bash.no>
+# version 0.2: add ignore option
+# 2009-10-28, xt <xt@bash.no>
+# version 0.1: initial release
+
+import weechat as w
+import re
+
+SCRIPT_NAME = "autojoin_on_invite"
+SCRIPT_AUTHOR = "xt <xt@bash.no>"
+SCRIPT_VERSION = "0.5"
+SCRIPT_LICENSE = "GPL3"
+SCRIPT_DESC = "Auto joins channels when invited"
+
+# script options
+settings = {
+ 'whitelist_nicks': '', # comma separated list of nicks,
+ # overrides ignore_nicks
+ 'whitelist_channels': '', # comma separated list of channels,
+ # overrides ignore_channels
+ 'ignore_nicks': '', # comma separated list of nicks
+ #that we will not accept auto invite from
+ 'ignore_channels': '', # comma separated list of channels to not join
+}
+
+if w.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE,
+ SCRIPT_DESC, "", ""):
+ for option, default_value in settings.iteritems():
+ if not w.config_is_set_plugin(option):
+ w.config_set_plugin(option, default_value)
+
+ w.hook_signal('*,irc_in2_invite', 'invite_cb', '')
+
+
+def invite_cb(data, signal, signal_data):
+ server = signal.split(',')[0] # EFNet,irc_in_INVITE
+ channel = signal_data.split()[-1].lstrip(':') # :nick!ident@host.name INVITE yournick :#channel
+ from_nick = re.match(':(?P<nick>.+)!', signal_data).groups()[0]
+
+ if len(w.config_get_plugin('whitelist_nicks')) > 0 and len(w.config_get_plugin('whitelist_channels')) > 0: # if there's two whitelists, accept both
+ if from_nick in w.config_get_plugin('whitelist_nicks').split(',') or channel in w.config_get_plugin('whitelist_channels').split(','):
+ w.prnt('', 'Automatically joining %s on server %s, invitation from %s (whitelist).' \
+ %(channel, server, from_nick))
+ w.command('', '/quote -server %s JOIN %s' % (server, channel))
+ else:
+ w.prnt('', 'Ignoring invite from %s to channel %s. Neither inviter nor channel in whitelist.' %(from_nick, channel))
+
+ elif len(w.config_get_plugin('whitelist_nicks')) > 0: # if there's a whitelist, accept nicks in it
+ if from_nick in w.config_get_plugin('whitelist_nicks').split(','):
+ w.prnt('', 'Automatically joining %s on server %s, invitation from %s (whitelist).' \
+ %(channel, server, from_nick))
+ w.command('', '/quote -server %s JOIN %s' % (server, channel))
+ else:
+ w.prnt('', 'Ignoring invite from %s to channel %s. Inviter not in whitelist.' %(from_nick, channel))
+
+ elif len(w.config_get_plugin('whitelist_channels')) > 0: # if there's a whitelist, accept channels in it
+ if channel in w.config_get_plugin('whitelist_channels').split(','):
+ w.prnt('', 'Automatically joining %s on server %s, invitation from %s (whitelist).' \
+ %(channel, server, from_nick))
+ w.command('', '/quote -server %s JOIN %s' % (server, channel))
+ else:
+ w.prnt('', 'Ignoring invite from %s to channel %s. Channel not in whitelist.' %(from_nick, channel))
+
+ else: # use the ignore lists to make the decision
+ if from_nick in w.config_get_plugin('ignore_nicks').split(','):
+ w.prnt('', 'Ignoring invite from %s to channel %s. Invite from ignored inviter.' %(from_nick, channel))
+ elif channel in w.config_get_plugin('ignore_channels').split(','):
+ w.prnt('', 'Ignoring invite from %s to channel %s. Invite to ignored channel.' %(from_nick, channel))
+ else:
+ w.prnt('', 'Automatically joining %s on server %s, invitation from %s.' \
+ %(channel, server, from_nick))
+ w.command('', '/quote -server %s JOIN %s' % (server, channel))
+
+ return w.WEECHAT_RC_OK
diff --git a/weechat/python/chanop.py b/weechat/python/chanop.py
deleted file mode 100644
index 848837e..0000000
--- a/weechat/python/chanop.py
+++ /dev/null
@@ -1,3236 +0,0 @@
-# -*- coding: utf-8 -*-
-###
-# Copyright (c) 2009-2013 by Elián Hanisch <lambdae2@gmail.com>
-#
-# 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/>.
-###
-
-###
-# Helper script for IRC Channel Operators
-#
-# Inspired by auto_bleh.pl (irssi) and chanserv.py (xchat) scripts.
-#
-# Networks like Freenode and some channels encourage operators to not stay
-# permanently with +o privileges and only use it when needed. This script
-# works along those lines, requesting op, kick/ban/etc and deop
-# automatically with a single command.
-# Still this script is very configurable and its behaviour can be configured
-# in a per server or per channel basis so it can fit most needs without
-# changing its code.
-#
-# Features several completions for ban/quiet masks and a memory for channel
-# masks and users (so users that parted are still bannable by nick).
-#
-#
-# Commands (see detailed help with /help in WeeChat):
-# * /oop: Request or give op.
-# * /odeop: Drop or remove op.
-# * /okick: Kick user (or users).
-# * /oban: Apply ban mask.
-# * /ounban: Remove ban mask.
-# * /oquiet: Apply quiet mask.
-# * /ounquiet: Remove quiet mask.
-# * /obankick: Ban and kick user (or users)
-# * /otopic: Change channel topic
-# * /omode: Change channel modes
-# * /olist: List cached masks (bans or quiets)
-# * /ovoice: Give voice to user
-# * /odevoice: Remove voice from user
-#
-#
-# Settings:
-# Most configs (unless noted otherwise) can be defined for a server or a
-# channel in particular, so it is possible to request op in different
-# networks, stay always op'ed in one channel while
-# auto-deop in another.
-#
-# For define an option for a specific server use:
-# /set plugins.var.python.chanop.<option>.<server> "value"
-# For define it in a specific channel use:
-# /set plugins.var.python.chanop.<option>.<server>.<#channel> "value"
-#
-# * plugins.var.python.chanop.op_command:
-# Here you define the command the script must run for request op, normally
-# is a /msg to a bot, like chanserv in freenode or Q in quakenet.
-# It accepts the special vars $server, $channel and $nick
-#
-# By default it ask op to chanserv, if your network doesn't use chanserv,
-# then you must change it.
-#
-# Examples:
-# /set plugins.var.python.chanop.op_command
-# "/msg chanserv op $channel $nick"
-# (globally for all servers, like freenode and oftc)
-# /set plugins.var.python.chanop.op_command.quakenet
-# "/msg q op $channel $nick"
-# (for quakenet only)
-#
-# * plugins.var.python.chanop.autodeop:
-# Enables auto-deop'ing after using any of the ban or kick commands.
-# Note that if you got op manually (like with /oop) then the script won't
-# deop you.
-# Valid values: 'on', 'off' Default: 'on'
-#
-# * plugins.var.python.chanop.autodeop_delay:
-# Time it must pass (without using any commands) before auto-deop, in
-# seconds. Using zero causes to deop immediately.
-# Default: 180
-#
-# * plugins.var.python.chanop.default_banmask:
-# List of keywords separated by comas. Defines default banmask, when using
-# /oban, /obankick or /oquiet
-# You can use several keywords for build a banmask, each keyword defines how
-# the banmask will be generated for a given hostmask, see /help oban.
-# Valid keywords are: nick, user, host and exact.
-# Default: 'host'
-#
-# Examples:
-# /set plugins.var.python.chanop.default_banmask host
-# (bans with *!*@host)
-# /set plugins.var.python.chanop.default_banmask host,user
-# (bans with *!user@host)
-#
-# * plugins.var.python.chanop.kick_reason:
-# Default kick reason if none was given in the command.
-#
-# * plugins.var.python.chanop.enable_remove:
-# If enabled, it will use "/quote remove" command instead of /kick, enable
-# it only in networks that support it, like freenode.
-# Valid values: 'on', 'off' Default: 'off'
-#
-# Example:
-# /set plugins.var.python.chanop.enable_remove.freenode on
-#
-# * plugins.var.python.chanop.display_affected:
-# Whenever a new ban is set, chanop will show the users affected by it.
-# This is intended for help operators to see if their ban is too wide or
-# point out clones in the channel.
-# Valid values: 'on', 'off' Default: 'off'
-#
-#
-# The following configs are global and can't be defined per server or channel.
-#
-# * plugins.var.python.chanop.enable_multi_kick:
-# Enables kicking multiple users with /okick command.
-# Be careful with this as you can kick somebody by accident if
-# you're not careful when writting the kick reason.
-#
-# This also applies to /obankick command, multiple bankicks would be enabled.
-# Valid values: 'on', 'off' Default: 'off'
-#
-# * plugins.var.python.chanop.enable_bar:
-# This will enable a pop-up bar for displaying chanop messages that would
-# otherwise be printed in the buffer. This bar also shows in realtime the
-# users affected by a ban you're about to set.
-# Valid values: 'on', 'off' Default: 'on'
-#
-#
-# The following configs are defined per server and are updated by the script only.
-#
-# * plugins.var.python.chanop.watchlist:
-# Indicates to chanop which channels should watch and keep track of users and
-# masks. This config is automatically updated when you use any command that needs
-# op, so manual setting shouldn't be needed.
-#
-# * plugins.var.python.chanop.isupport:
-# Only used in WeeChat versions prior to 0.3.3 which lacked support for
-# irc_005 messages. These aren't meant to be set manually.
-#
-#
-# Completions:
-# Chanop has several completions, documented here. Some aren't used by chanop
-# itself, but can be used in aliases with custom completions.
-# Examples:
-# apply exemptions with mask autocompletion
-# /alias -completion %(chanop_ban_mask) exemption /mode $channel +e
-# if you use grep.py script, grep with host autocompletion, for look clones.
-# /alias -completion %(chanop_hosts) ogrep /grep
-#
-# * chanop_unban_mask (used in /ounban)
-# Autocompletes with banmasks set in current channel, requesting them if needed.
-# Supports patterns for autocomplete several masks: *<tab> for all bans, or
-# *192.168*<tab> for bans with '192.168' string.
-#
-# * chanop_unquiet (used in /ounquiet)
-# Same as chanop_unban_mask, but with masks for q channel mode.
-#
-# * chanop_ban_mask (used in /oban and /oquiet)
-# Given a partial IRC hostmask, it will try to complete with hostmasks of current
-# users: *!*@192<tab> will try to complete with matching users, like
-# *!*@192.168.0.1
-#
-# * chanop_nicks (used in most commands)
-# Autocompletes nicks, same as WeeChat's completer, but using chanop's user
-# cache, so nicks from users that parted the channel will be still be completed.
-#
-# * chanop_users (not used by chanop)
-# Same as chanop_nicks, but with the usename part of the hostmask.
-#
-# * chanop_hosts (not used by chanop)
-# Same as chanop_nicks, but with the host part of the hostmask (includes previously used
-# hostnames).
-#
-#
-# TODO
-# * use dedicated config file like in urlgrab.py?
-# * ban expire time
-# * save ban.mask and ban.hostmask across reloads
-# * allow to override quiet command (for quiet with ChanServ)
-# * freenode:
-# - support for bans with channel forward
-# - support for extbans (?)
-# * Sort completions by user activity
-#
-#
-# History:
-# 2013-05-24
-# version 0.3.1: bug fixes
-# * fix exceptions while fetching bans with /mode
-# * fix crash with /olist command in networks that don't support +q channel masks.
-#
-# 2013-04-14
-# version 0.3:
-# * cycle between different banmasks in /oban /oquiet commands.
-# * added pop-up bar for show information.
-# * save ban mask information (date and operator)
-# * remove workarounds for < 0.3.2 weechat versions
-# * python 3.0 compatibility (not tested)
-#
-# 2013-01-02
-# version 0.2.7: bug fixes:
-# * fix /obankick, don't deop before kicking.
-#
-# 2011-09-18
-# version 0.2.6: bug fixes:
-# * update script to work with freenode's new quiet messages.
-# * /omode wouldn't work with several modes.
-#
-# 2011-05-31
-# version 0.2.5: bug fixes:
-# * /omode -o nick wouldn't work due to the deopNow switch.
-# * unban_completer could fetch the same masks several times.
-# * removing ban forwards falied when using exact mask.
-# * user nick wasn't updated in every call.
-#
-# 2011-02-02
-# version 0.2.4: fix python 2.5 compatibility
-#
-# 2011-01-09
-# version 0.2.3: bug fixes.
-#
-# 2010-12-23
-# version 0.2.2: bug fixes.
-#
-# 2010-10-28
-# version 0.2.1: refactoring mostly
-# * deop_command option removed
-# * removed --webchat switch, freenode's updates made it superfluous.
-# * if WeeChat doesn't know a hostmask, use /userhost or /who if needed.
-# * /oban and /oquiet without arguments show ban/quiet list.
-# * most commands allows '-o' option, that forces immediate deop (without configured delay).
-# * updated for WeeChat 0.3.4 (irc_nick infolist changes)
-#
-# 2010-09-20
-# version 0.2: major update
-# * fixed quiets for ircd-seven (freenode)
-# * implemented user and mask cache.
-# * added commands:
-# - /ovoice /odevoice for de/voice users.
-# - /omode for change channel modes.
-# - /olist for list bans/quiets on cache.
-# * changed /omute and /ounmute commands to /oquiet and /ounquiet, as q masks
-# is refered as a quiet rather than a mute.
-# * autocompletions:
-# - for bans set on a channel.
-# - for make new bans.
-# - for nicks/usernames/hostnames.
-# * /okban renamed to /obankick. This is because /okban is too similar to
-# /okick and bankicking somebody due to tab fail was too easy.
-# * added display_affected feature.
-# * added --webchat ban option.
-# * config options removed:
-# - merge_bans: superseded by isupport methods.
-# - enable_mute: superseded by isupport methods.
-# - invert_kickban_order: now is fixed to "ban, then kick"
-# * Use WeeChat isupport infos.
-# * /oop and /odeop can op/deop other users.
-#
-# 2009-11-9
-# version 0.1.1: fixes
-# * script renamed to 'chanop' because it was causing conflicts with python
-# 'operator' module
-# * added /otopic command
-#
-# 2009-10-31
-# version 0.1: Initial release
-###
-
-WEECHAT_VERSION = (0x30200, '0.3.2')
-
-SCRIPT_NAME = "chanop"
-SCRIPT_AUTHOR = "Elián Hanisch <lambdae2@gmail.com>"
-SCRIPT_VERSION = "0.3.1"
-SCRIPT_LICENSE = "GPL3"
-SCRIPT_DESC = "Helper script for IRC Channel Operators"
-
-# default settings
-settings = {
-'op_command' :'/msg chanserv op $channel $nick',
-'autodeop' :'on',
-'autodeop_delay' :'180',
-'default_banmask' :'host',
-'enable_remove' :'off',
-'kick_reason' :'',
-'enable_multi_kick' :'off',
-'display_affected' :'on',
-'enable_bar' :'on',
-}
-
-try:
- import weechat
- from weechat import WEECHAT_RC_OK, prnt
- import_ok = True
-except ImportError:
- print("This script must be run under WeeChat.")
- print("Get WeeChat now at: http://www.weechat.org/")
- import_ok = False
-
-import os
-import re
-import time
-import string
-import getopt
-from collections import defaultdict
-from shelve import DbfilenameShelf as Shelf
-
-chars = string.maketrans('', '')
-
-# -----------------------------------------------------------------------------
-# Messages
-
-script_nick = SCRIPT_NAME
-def error(s, buffer=''):
- """Error msg"""
- prnt(buffer, '%s%s %s' % (weechat.prefix('error'), script_nick, s))
- value = weechat.config_get_plugin('debug')
- if value and boolDict[value]:
- import traceback
- if traceback.sys.exc_type:
- trace = traceback.format_exc()
- prnt('', trace)
-
-def say(s, buffer=''):
- """normal msg"""
- prnt(buffer, '%s\t%s' %(script_nick, s))
-
-def _no_debug(*args):
- pass
-
-debug = _no_debug
-
-# -----------------------------------------------------------------------------
-# Config
-
-# TODO Need to refactor all this too
-
-boolDict = {'on':True, 'off':False, True:'on', False:'off'}
-def get_config_boolean(config, get_function=None, **kwargs):
- if get_function and callable(get_function):
- value = get_function(config, **kwargs)
- else:
- value = weechat.config_get_plugin(config)
- try:
- return boolDict[value]
- except KeyError:
- default = settings[config]
- error("Error while fetching config '%s'. Using default value '%s'." %(config, default))
- error("'%s' is invalid, allowed: 'on', 'off'" %value)
- return boolDict[default]
-
-def get_config_int(config, get_function=None):
- if get_function and callable(get_function):
- value = get_function(config)
- else:
- value = weechat.config_get_plugin(config)
- try:
- return int(value)
- except ValueError:
- default = settings[config]
- error("Error while fetching config '%s'. Using default value '%s'." %(config, default))
- error("'%s' is not a number." %value)
- return int(default)
-
-valid_banmask = set(('nick', 'user', 'host', 'exact'))
-def get_config_banmask(config='default_banmask', get_function=None):
- if get_function and callable(get_function):
- value = get_function(config)
- else:
- value = weechat.config_get_plugin(config)
- values = value.lower().split(',')
- for value in values:
- if value not in valid_banmask:
- default = settings[config]
- error("Error while fetching config '%s'. Using default value '%s'." %(config, default))
- error("'%s' is an invalid value, allowed: %s." %(value, ', '.join(valid_banmask)))
- return default
- #debug("default banmask: %s" %values)
- return values
-
-def get_config_list(config):
- value = weechat.config_get_plugin(config)
- if value:
- return value.split(',')
- else:
- return []
-
-def get_config_specific(config, server='', channel=''):
- """Gets config defined for either server or channel."""
- value = None
- if server and channel:
- string = '%s.%s.%s' %(config, server, channel)
- value = weechat.config_get_plugin(string)
- if server and not value:
- string = '%s.%s' %(config, server)
- value = weechat.config_get_plugin(string)
- if not value:
- value = weechat.config_get_plugin(config)
- return value
-
-# -----------------------------------------------------------------------------
-# Utils
-
-now = lambda: int(time.time())
-
-def time_elapsed(elapsed, ret=None, level=2):
- time_hour = 3600
- time_day = 86400
- time_year = 31536000
-
- if ret is None:
- ret = []
-
- if not elapsed:
- return ''
-
- if elapsed > time_year:
- years, elapsed = elapsed // time_year, elapsed % time_year
- ret.append('%s%s' %(years, 'y'))
- elif elapsed > time_day:
- days, elapsed = elapsed // time_day, elapsed % time_day
- ret.append('%s%s' %(days, 'd'))
- elif elapsed > time_hour:
- hours, elapsed = elapsed // time_hour, elapsed % time_hour
- ret.append('%s%s' %(hours, 'h'))
- elif elapsed > 60:
- mins, elapsed = elapsed // 60, elapsed % 60
- ret.append('%s%s' %(mins, 'm'))
- else:
- secs, elapsed = elapsed, 0
- ret.append('%s%s' %(secs, 's'))
-
- if len(ret) >= level or not elapsed:
- return ' '.join(ret)
-
- ret = time_elapsed(elapsed, ret, level)
- return ret
-
-# -----------------------------------------------------------------------------
-# IRC utils
-
-_hostmaskRe = re.compile(r':?\S+!\S+@\S+') # poor but good enough
-def is_hostmask(s):
- """Returns whether or not the string s starts with something like a hostmask."""
- return _hostmaskRe.match(s) is not None
-
-def is_ip(s):
- """Returns whether or not a given string is an IPV4 address."""
- import socket
- try:
- return bool(socket.inet_aton(s))
- except socket.error:
- return False
-
-_reCache = {}
-def cachedPattern(f):
- """Use cached regexp object or compile a new one from pattern."""
- def getRegexp(pattern, *arg):
- try:
- regexp = _reCache[pattern]
- except KeyError:
- s = '^'
- for c in pattern:
- if c == '*':
- s += '.*'
- elif c == '?':
- s += '.'
- elif c in '[{':
- s += r'[\[{]'
- elif c in ']}':
- s += r'[\]}]'
- elif c in '|\\':
- s += r'[|\\]'
- else:
- s += re.escape(c)
- s += '$'
- regexp = re.compile(s, re.I)
- _reCache[pattern] = regexp
- return f(regexp, *arg)
- return getRegexp
-
-def hostmaskPattern(f):
- """Check if pattern is for match a hostmask and remove ban forward if there's one."""
- def checkPattern(pattern, arg):
- # XXX this needs a refactor
- if is_hostmask(pattern):
- # nick!user@host$#channel
- if '$' in pattern:
- pattern = pattern.partition('$')[0]
- if isinstance(arg, list):
- arg = [ s for s in arg if is_hostmask(s) ]
- elif not is_hostmask(arg):
- return ''
-
- rt = f(pattern, arg)
- # this doesn't match any mask in args with a channel forward
- pattern += '$*'
- if isinstance(arg, list):
- rt.extend(f(pattern, arg))
- elif not rt:
- rt = f(pattern, arg)
- return rt
-
- return ''
- return checkPattern
-
-match_string = lambda r, s: r.match(s) is not None
-match_list = lambda r, L: [ s for s in L if r.match(s) is not None ]
-
-pattern_match = cachedPattern(match_string)
-pattern_match_list = cachedPattern(match_list)
-hostmask_match = hostmaskPattern(pattern_match)
-hostmask_match_list = hostmaskPattern(pattern_match_list)
-
-def get_nick(s):
- """':nick!user@host' => 'nick'"""
- return weechat.info_get('irc_nick_from_host', s)
-
-def get_user(s, trim=False):
- """'nick!user@host' => 'user'"""
- assert is_hostmask(s), "Invalid hostmask: %s" % s
- s = s[s.find('!') + 1:s.find('@')]
- if trim:
- # remove the stuff not part of the username.
- if s[0] == '~':
- return s[1:]
- elif s[:2] in ('i=', 'n='):
- return s[2:]
- return s
-
-def get_host(s):
- """'nick!user@host' => 'host'"""
- assert is_hostmask(s), "Invalid hostmask: %s" % s
- if ' ' in s:
- return s[s.find('@') + 1:s.find(' ')]
- return s[s.find('@') + 1:]
-
-def is_channel(s):
- return weechat.info_get('irc_is_channel', s)
-
-def is_nick(s):
- return weechat.info_get('irc_is_nick', s)
-
-def irc_buffer(buffer):
- """Returns pair (server, channel) or None if buffer isn't an irc channel"""
- get_string = weechat.buffer_get_string
- if get_string(buffer, 'plugin') == 'irc' \
- and get_string(buffer, 'localvar_type') == 'channel':
- channel = get_string(buffer, 'localvar_channel')
- server = get_string(buffer, 'localvar_server')
- return (server, channel)
-
-# -----------------------------------------------------------------------------
-# WeeChat classes
-
-class InvalidIRCBuffer(Exception):
- pass
-
-def catchExceptions(f):
- def function(*args, **kwargs):
- try:
- return f(*args, **kwargs)
- except Exception as e:
- error(e)
- return function
-
-def callback(method):
- """This function will take a bound method or function and make it a callback."""
- # try to create a descriptive and unique name.
- func = method.func_name
- try:
- im_self = method.im_self
- try:
- inst = im_self.__name__
- except AttributeError:
- try:
- inst = im_self.name
- except AttributeError:
- raise Exception("Instance of %s has no __name__ attribute" %type(im_self))
- cls = type(im_self).__name__
- name = '_'.join((cls, inst, func))
- except AttributeError:
- # not a bound method
- name = func
-
- method = catchExceptions(method)
-
- # set our callback
- import __main__
- setattr(__main__, name, method)
- return name
-
-def weechat_command(buffer, cmd):
- """I want to always keep debug() calls for this"""
- if buffer:
- debug("%ssending: %r buffer: %s", COLOR_WHITE, cmd, buffer)
- else:
- debug("%ssending: %r", COLOR_WHITE, cmd)
- weechat.command(buffer, cmd)
-
-class Infolist(object):
- """Class for reading WeeChat's infolists."""
-
- fields = {
- 'name' :'string',
- 'option_name' :'string',
- 'value' :'string',
- 'host' :'string',
- 'flags' :'integer',
- 'prefixes' :'string',
- 'is_connected':'integer',
- 'buffer' :'pointer',
- }
-
- _use_flags = False
-
- def __init__(self, name, args='', pointer=''):
- self.cursor = 0
- #debug('Generating infolist %r %r', name, args)
- self.pointer = weechat.infolist_get(name, pointer, args)
- if self.pointer == '':
- raise Exception("Infolist initialising failed (name:'%s' args:'%s')" %(name, args))
-
- def __len__(self):
- """True False evaluation."""
- if self.pointer:
- return 1
- else:
- return 0
-
- def __del__(self):
- """Purge infolist if is no longer referenced."""
- self.free()
-
- def __getitem__(self, name):
- """Implement the evaluation of self[name]."""
- if self._use_flags and name == 'prefixes':
- name = 'flags'
- value = getattr(weechat, 'infolist_%s' %self.fields[name])(self.pointer, name)
- if self._use_flags and name == 'flags':
- value = self._flagsAsString(value)
- return value
-
- def _flagsAsString(self, n):
- s = ''
- if n & 32:
- s += '+'
- if n & 8:
- s += '@'
- return s
-
- def __iter__(self):
- def generator():
- while self.next():
- yield self
- return generator()
-
- def next(self):
- self.cursor = weechat.infolist_next(self.pointer)
- return self.cursor
-
- def prev(self):
- self.cursor = weechat.infolist_prev(self.pointer)
- return self.cursor
-
- def reset(self):
- """Moves cursor to beginning of infolist."""
- if self.cursor == 1: # only if we aren't in the beginning already
- while self.prev():
- pass
-
- def free(self):
- if self.pointer:
- weechat.infolist_free(self.pointer)
- self.pointer = ''
-
-def nick_infolist(server, channel):
- try:
- return Infolist('irc_nick', '%s,%s' % (server, channel))
- except:
- raise InvalidIRCBuffer('%s.%s' % (server, channel))
-
-class NoArguments(Exception):
- pass
-
-class ArgumentError(Exception):
- pass
-
-class Command(object):
- """Class for hook WeeChat commands."""
- description, usage, help = "WeeChat command.", "[define usage template]", "detailed help here"
- command = ''
- completion = ''
-
- def __init__(self):
- assert self.command, "No command defined"
- self.__name__ = self.command
- self._pointer = ''
- self._callback = ''
-
- def __call__(self, *args):
- return self.callback(*args)
-
- def callback(self, data, buffer, args):
- """Called by WeeChat when /command is used."""
- self.data, self.buffer, self.args = data, buffer, args
- debug("%s[%s] data: \"%s\" buffer: \"%s\" args: \"%s\"", COLOR_DARKGRAY,
- self.command,
- data,
- buffer,
- args)
- try:
- self.parser(args) # argument parsing
- except ArgumentError as e:
- error('Argument error, %s' %e)
- except NoArguments:
- pass
- else:
- self.execute()
- return WEECHAT_RC_OK
-
- def parser(self, args):
- """Argument parsing, override if needed."""
- pass
-
- def execute(self):
- """This method is called when the command is run, override this."""
- pass
-
- def hook(self):
- assert not self._pointer, \
- "There's already a hook pointer, unhook first (%s)" %self.command
- self._callback = callback(self.callback)
- pointer = weechat.hook_command(self.command,
- self.description,
- self.usage,
- self.help,
- self.completion,
- self._callback, '')
- if pointer == '':
- raise Exception("hook_command failed: %s %s" % (SCRIPT_NAME, self.command))
- self._pointer = pointer
-
- def unhook(self):
- if self._pointer:
- weechat.unhook(self._pointer)
- self._pointer = ''
- self._callback = ''
-
-class Bar(object):
- def __init__(self, name, hidden=False, items=''):
- self.name = name
- self.hidden = hidden
- self._pointer = ''
- self._items = items
-
- def new(self):
- assert not self._pointer, "Bar %s already created" % self.name
- pointer = weechat.bar_search(self.name)
- if not pointer:
- pointer = weechat.bar_new(self.name, boolDict[self.hidden], '0', 'window',
- 'active', 'bottom', 'horizontal', 'vertical',
- '0', '1', 'default', 'cyan', 'blue', 'off',
- self._items)
- if not pointer:
- raise Exception("bar_new failed: %s %s" % (SCRIPT_NAME, self.name))
-
- self._pointer = pointer
-
- def getPointer(self):
- return weechat.bar_search(self.name)
-
- def show(self):
- pointer = self.getPointer()
- if pointer and self.hidden:
- weechat.bar_set(pointer, 'hidden', 'off')
- self.hidden = False
- return pointer
-
- def hide(self):
- pointer = self.getPointer()
- if pointer and not self.hidden:
- weechat.bar_set(pointer, 'hidden', 'on')
- self.hidden = True
-
- def remove(self):
- pointer = self.getPointer()
- if pointer:
- weechat.bar_remove(pointer)
- self._pointer = ''
-
- def __len__(self):
- """True False evaluation."""
- if self.getPointer():
- return 1
- else:
- return 0
-
-class PopupBar(Bar):
- _timer_hook = ''
- popup_mode = False
-
- def popup(self, delay=10):
- if self.show():
- if self._timer_hook:
- weechat.unhook(self._timer_hook)
- self._timer_hook = weechat.hook_timer(delay * 1000, 0, 1, callback(self._timer), '')
-
- def _timer(self, data, counter):
- self.hide()
- self._timer_hook = ''
- return WEECHAT_RC_OK
-
-# -----------------------------------------------------------------------------
-# Per buffer variables
-
-class BufferVariables(dict):
- """Keeps variables and objects of a specific buffer."""
- def __init__(self, buffer):
- self['buffer'] = buffer
- self['irc'] = IrcCommands(buffer)
- self['autodeop'] = True
- self['deopHook'] = self.opHook = self.opTimeout = None
- self['server'] = weechat.buffer_get_string(buffer, 'localvar_server')
- self['channel'] = weechat.buffer_get_string(buffer, 'localvar_channel')
- self['nick'] = weechat.info_get('irc_nick', self.server)
-
- def __getattr__(self, k):
- return self[k]
-
- def __setattr__(self, k, v):
- debug(' -- buffer[%s] %s ... %s => %s', self.buffer, k, self.get(k), v)
- self[k] = v
-
-class ChanopBuffers(object):
- """Keeps track of BuffersVariables instances in chanop."""
- buffer = ''
- _buffer = {} # must be shared across instances
- def __getattr__(self, k):
- return self._buffer[self.buffer][k]
-
- def setup(self, buffer):
- self.buffer = buffer
- if buffer not in self._buffer:
- self._buffer[buffer] = BufferVariables(buffer)
- else:
- # update nick, it might have changed.
- self.vars.nick = weechat.info_get('irc_nick', self.vars.server)
-
- @property
- def vars(self):
- return self._buffer[self.buffer]
-
- def varsOf(self, buffer):
- return self._buffer[buffer]
-
- def replace_vars(self, s):
- try:
- return weechat.buffer_string_replace_local_var(self.buffer, s)
- except AttributeError:
- if '$channel' in s:
- s = s.replace('$channel', self.channel)
- if '$nick' in s:
- s = s.replace('$nick', self.nick)
- if '$server' in s:
- s = s.replace('$server', self.server)
- return s
-
- def get_config(self, config):
- #debug('config: %s' %config)
- return get_config_specific(config, self.server, self.channel)
-
- def get_config_boolean(self, config):
- return get_config_boolean(config, self.get_config)
-
- def get_config_int(self, config):
- return get_config_int(config, self.get_config)
-
-# -----------------------------------------------------------------------------
-# IRC messages queue
-
-class Message(ChanopBuffers):
- command = None
- args = ()
- wait = 0
- def __init__(self, cmd=None, args=(), wait=0):
- if cmd: self.command = cmd
- if args: self.args = args
- if wait: self.wait = wait
-
- def payload(self):
- cmd = self.command
- if cmd[0] != '/':
- cmd = '/' + cmd
- if self.args:
- cmd += ' ' + ' '.join(self.args)
- if self.wait:
- cmd = '/wait %s ' %self.wait + cmd
- return cmd
-
- def register(self, buffer):
- self.buffer = buffer
-
- def __call__(self):
- cmd = self.payload()
- if cmd:
- self.send(cmd)
-
- def send(self, cmd):
- weechat_command(self.buffer, cmd)
-
- def __repr__(self):
- return '<Message(%s, %s)>' %(self.command, self.args)
-
-class IrcCommands(ChanopBuffers):
- """Class that manages and sends the script's commands to WeeChat."""
-
- # Special message classes
- class OpMessage(Message):
- def send(self, cmd):
- if self.irc.checkOp():
- # nothing to do
- return
-
- self.irc.interrupt = True
- Message.send(self, cmd)
-
- def modeOpCallback(buffer, signal, signal_data):
- vars = self.varsOf(buffer)
- data = 'MODE %s +o %s' % (vars.channel, vars.nick)
- signal = signal_data.split(None, 1)[1]
- if signal == data:
- debug('GOT OP')
- # add this channel to our watchlist
- config = 'watchlist.%s' % vars.server
- channels = CaseInsensibleSet(get_config_list(config))
- if vars.channel not in channels:
- channels.add(vars.channel)
- value = ','.join(channels)
- weechat.config_set_plugin(config, value)
- weechat.unhook(vars.opHook)
- weechat.unhook(vars.opTimeout)
- vars.opTimeout = vars.opHook = None
- vars.irc.interrupt = False
- vars.irc.run()
- return WEECHAT_RC_OK
-
- def timeoutCallback(buffer, count):
- vars = self.varsOf(buffer)
- error("Couldn't get op in '%s', purging command queue..." % vars.channel)
- weechat.unhook(vars.opHook)
- if vars.deopHook:
- weechat.unhook(vars.deopHook)
- vars.deopHook = None
- vars.opTimeout = vars.opHook = None
- vars.irc.interrupt = False
- vars.irc.clear()
- return WEECHAT_RC_OK
-
- # wait for a while before timing out.
- self.vars.opTimeout = weechat.hook_timer(30*1000, 0, 1, callback(timeoutCallback),
- self.buffer)
-
- self.vars.opHook = weechat.hook_signal('%s,irc_in2_MODE' %self.server,
- callback(modeOpCallback), self.buffer)
-
- class UserhostMessage(Message):
- def send(self, cmd):
- self.irc.interrupt = True
- Message.send(self, cmd)
-
- def msgCallback(buffer, modifier, modifier_data, string):
- vars = self.varsOf(buffer)
- if vars.server != modifier_data:
- return string
- nick, host = string.rsplit(None, 1)[1].split('=')
- nick, host = nick.strip(':*'), host[1:]
- hostmask = '%s!%s' % (nick, host)
- debug('USERHOST: %s %s', nick, hostmask)
- userCache.remember(modifier_data, nick, hostmask)
- weechat.unhook(vars.msgHook)
- weechat.unhook(vars.msgTimeout)
- vars.msgTimeout = vars.msgHook = None
- vars.irc.interrupt = False
- vars.irc.run()
- return ''
-
- def timeoutCallback(buffer, count):
- vars = self.varsOf(buffer)
- weechat.unhook(vars.msgHook)
- vars.msgTimeout = vars.msgHook = None
- vars.irc.interrupt = False
- vars.irc.clear()
- return WEECHAT_RC_OK
-
- # wait for a while before timing out.
- self.vars.msgTimeout = \
- weechat.hook_timer(30*1000, 0, 1, callback(timeoutCallback), self.buffer)
-
- self.vars.msgHook = weechat.hook_modifier('irc_in_302',
- callback(msgCallback), self.buffer)
-
-
- class ModeMessage(Message):
- command = 'mode'
- def __init__(self, char=None, args=None, **kwargs):
- self.chars = [ char ]
- self.charargs = [ args ]
- self.args = (char, args)
- Message.__init__(self, **kwargs)
-
- def payload(self):
- args = []
- modeChar = []
- prefix = ''
- for m, a in zip(self.chars, self.charargs):
- if a:
- if callable(a):
- a = a()
- if not a:
- continue
- args.append(a)
- if m[0] != prefix:
- prefix = m[0]
- modeChar.append(prefix)
- modeChar.append(m[1])
- args.insert(0, ''.join(modeChar))
- if args:
- self.args = args
- return Message.payload(self)
-
-
- class DeopMessage(ModeMessage):
- def send(self, cmd):
- if self.irc.checkOp():
- Message.send(self, cmd)
-
- # IrcCommands methods
- def __init__(self, buffer):
- self.interrupt = False
- self.commands = []
- self.buffer = buffer
-
- def checkOp(self):
- infolist = nick_infolist(self.server, self.channel)
- while infolist.next():
- if infolist['name'] == self.nick:
- return '@' in infolist['prefixes']
- return False
-
- def Op(self):
- if self.opHook and self.opTimeout:
- # already send command, wait for timeout
- return
-
- value = self.replace_vars(self.get_config('op_command'))
- if not value:
- raise Exception("No command defined for get op.")
- msg = self.OpMessage(value)
- self.queue(msg, insert=True)
-
- def Deop(self):
- msg = self.DeopMessage('-o', self.nick)
- self.queue(msg)
-
- def Mode(self, mode, args=None, wait=0):
- msg = self.ModeMessage(mode, args, wait=wait)
- self.queue(msg)
-
- def Kick(self, nick, reason=None, wait=0):
- if not reason:
- reason = self.get_config('kick_reason')
- if self.get_config_boolean('enable_remove'):
- cmd = '/quote remove %s %s :%s' %(self.channel, nick, reason)
- msg = Message(cmd, wait=wait)
- else:
- msg = Message('kick', (nick, reason), wait=wait)
- self.queue(msg)
-
- def Voice(self, nick):
- self.Mode('+v', nick)
-
- def Devoice(self, nick):
- self.Mode('-v', nick)
-
- def Userhost(self, nick):
- msg = self.UserhostMessage('USERHOST', (nick, ))
- self.queue(msg, insert=True) # USERHOST should be sent first
-
- def queue(self, message, insert=False):
- debug('queuing: %s', message)
- # merge /modes
- if self.commands and message.command == 'mode':
- max_modes = supported_maxmodes(self.server)
- msg = self.commands[-1]
- if msg.command == 'mode' and len(msg.chars) < max_modes:
- msg.chars.append(message.chars[0])
- msg.charargs.append(message.charargs[0])
- return
- if insert:
- self.commands.insert(0, message)
- else:
- self.commands.append(message)
-
- # it happened once and it wasn't pretty
- def safe_check(f):
- def abort_if_too_many_commands(self):
- if len(self.commands) > 10:
- error("Limit of 10 commands in queue reached, aborting.")
- self.clear()
- else:
- f(self)
- return abort_if_too_many_commands
-
- @safe_check
- def run(self):
- while self.commands and not self.interrupt:
- msg = self.commands.pop(0)
- msg.register(self.buffer)
- msg()
- if self.interrupt:
- #debug("Interrupting queue")
- break
-
- def clear(self):
- debug('clear queue (%s messages)', len(self.commands))
- self.commands = []
-
- def __repr__(self):
- return '<IrcCommands(%s)>' % ', '.join(map(repr, self.commands))
-
-# -----------------------------------------------------------------------------
-# User/Mask classes
-
-_rfc1459trans = string.maketrans(string.ascii_uppercase + r'\[]',
- string.ascii_lowercase + r'|{}')
-def IRClower(s):
- return s.translate(_rfc1459trans)
-
-class CaseInsensibleString(str):
- def __init__(self, s=''):
- self.lowered = IRClower(s)
-
- lower = lambda self: self.lowered
- translate = lambda self, trans: self.lowered
- __eq__ = lambda self, s: self.lowered == IRClower(s)
- __ne__ = lambda self, s: not self == s
- __hash__ = lambda self: hash(self.lowered)
-
-def caseInsensibleKey(k):
- if isinstance(k, str):
- return CaseInsensibleString(k)
- elif isinstance(k, tuple):
- return tuple(map(caseInsensibleKey, k))
- return k
-
-class CaseInsensibleDict(dict):
- key = staticmethod(caseInsensibleKey)
-
- def __init__(self, **kwargs):
- for k, v in kwargs.items():
- self[k] = v
-
- def __setitem__(self, k, v):
- dict.__setitem__(self, self.key(k), v)
-
- def __getitem__(self, k):
- return dict.__getitem__(self, self.key(k))
-
- def __delitem__(self, k):
- dict.__delitem__(self, self.key(k))
-
- def __contains__(self, k):
- return dict.__contains__(self, self.key(k))
-
- def pop(self, k):
- return dict.pop(self, self.key(k))
-
-class CaseInsensibleDefaultDict(defaultdict, CaseInsensibleDict):
- pass
-
-class CaseInsensibleSet(set):
- normalize = staticmethod(caseInsensibleKey)
-
- def __init__(self, iterable=()):
- iterable = map(self.normalize, iterable)
- set.__init__(self, iterable)
-
- def __contains__(self, v):
- return set.__contains__(self, self.normalize(v))
-
- def update(self, L):
- set.update(self, map(self.normalize, L))
-
- def add(self, v):
- set.add(self, self.normalize(v))
-
- def remove(self, v):
- set.remove(self, self.normalize(v))
-
-class ChannelWatchlistSet(CaseInsensibleSet):
- _updated = False
- def __contains__(self, v):
- if not self._updated:
- self.__updateFromConfig()
- return CaseInsensibleSet.__contains__(self, v)
-
- def __updateFromConfig(self):
- self._updated = True
- infolist = Infolist('option', 'plugins.var.python.%s.watchlist.*' %SCRIPT_NAME)
- n = len('python.%s.watchlist.' %SCRIPT_NAME)
- while infolist.next():
- name = infolist['option_name']
- value = infolist['value']
- server = name[n:]
- if value:
- channels = value.split(',')
- else:
- channels = []
- self.update([ (server, channel) for channel in channels ])
-
-chanopChannels = ChannelWatchlistSet()
-
-class ServerChannelDict(CaseInsensibleDict):
- def getChannels(self, server, item=None):
- """Return a list of channels that match server and has item if given"""
- if item:
- return [ chan for serv, chan in self if serv == server and item in self[serv, chan] ]
- else:
- return [ chan for serv, chan in self if serv == server ]
-
- def purge(self):
- for key in self.keys():
- if key not in chanopChannels:
- debug('removing %s mask list, not in watchlist.', key)
- del self[key]
- for data in self.values():
- data.purge()
-
-# -----------------------------------------------------------------------------
-# Channel Modes (bans)
-
-class MaskObject(object):
- def __init__(self, mask, hostmask=[], operator='', date=0, expires=0):
- self.mask = mask
- self.operator = operator
- if date:
- date = int(date)
- else:
- date = now()
- self.date = date
- if isinstance(hostmask, str):
- hostmask = [ hostmask ]
- self.hostmask = hostmask
- self.expires = int(expires)
-
- def serialize(self):
- data = ';'.join([ self.operator,
- str(self.date),
- str(self.expires),
- ','.join(self.hostmask) ])
- return data
-
- def deserialize(self, data):
- op, date, expires, hostmasks = data.split(';')
- assert op and date, "Error reading chanmask option %s, missing operator or date" % self.mask
- if not is_hostmask(op):
- raise Exception('Error reading chanmask option %s, invalid usermask %r' \
- % (self.mask, op))
-
- self.operator = op
- try:
- self.date = int(date)
- except ValueError:
- self.date = int(time.mktime(time.strptime(date,'%Y-%m-%d %H:%M:%S')))
- if expires:
- self.expires = int(expires)
- else:
- self.expires = 0
- if hostmasks:
- hostmasks = hostmasks.split(',')
- if not all(map(is_hostmask, hostmasks)):
- raise Exception('Error reading chanmask option %s, a hostmask is invalid: %s' \
- % (self.mask, hostmasks))
-
- self.hostmask = hostmasks
-
- def __repr__(self):
- return "<MaskObject(%s)>" % self.mask
-
-class MaskList(CaseInsensibleDict):
- """Single list of masks"""
- def __init__(self, server, channel):
- self.synced = 0
-
- def add(self, mask, **kwargs):
- if mask in self:
- # mask exists, update it
- ban = self[mask]
- for attr, value in kwargs.items():
- if value and not getattr(ban, attr):
- setattr(ban, attr, value)
- else:
- ban = self[mask] = MaskObject(mask, **kwargs)
- return ban
-
-# def searchByNick(self, nick):
-# try:
-# hostmask = userCache.getHostmask(nick, self.server, self.channel)
-# return self.searchByHostmask(hostmask)
-# except KeyError:
-# return []
-
- def search(self, pattern, reverseMatch=False):
- if reverseMatch:
- L = [ mask for mask in self if hostmask_match(mask, pattern) ]
- else:
- L = pattern_match_list(pattern, self.keys())
- return L
-
- def purge(self):
- pass
-
-class MaskCache(ServerChannelDict):
- """Keeps a cache of masks for different channels."""
- def add(self, server, channel, mask, **kwargs):
- """Adds a ban to (server, channel) banlist."""
- key = (server, channel)
- if key not in self:
- self[key] = MaskList(*key)
- ban = self[key].add(mask, **kwargs)
- return ban
-
- def remove(self, server, channel, mask=None):#, hostmask=None):
- key = (server, channel)
- try:
- if mask is None:
- del self[key]
- else:
- del self[key][mask]
- #debug("removing ban: %s" %banmask)
- except KeyError:
- pass
-
-class ChanopCache(Shelf):
- def __init__(self, filename):
- path = os.path.join(weechat.info_get('weechat_dir', ''), filename)
- Shelf.__init__(self, path, writeback=True)
-
-class ModeCache(ChanopCache):
- """class for store channel modes lists."""
- def __init__(self, filename):
- ChanopCache.__init__(self, filename)
- self.modes = set()
- self.map = CaseInsensibleDict()
-
- # reset all sync timers
- for cache in self.values():
- for masklist in cache.values():
- masklist.synced = 0
-
- def registerMode(self, mode, *args):
- if mode not in self:
- cache = MaskCache()
- self[mode] = cache
-
- if mode not in self.modes:
- self.modes.add(mode)
-
- self.map[mode] = mode
- for name in args:
- self.map[name] = mode
-
- def __getitem__(self, mode):
- try:
- return ChanopCache.__getitem__(self, mode)
- except KeyError:
- return ChanopCache.__getitem__(self, self.map[mode])
-
- def add(self, server, channel, mode, mask, **kwargs):
- assert mode in self.modes
- self[mode].add(server, channel, mask, **kwargs)
-
- def remove(self, server, channel, mode, mask):
- self[mode].remove(server, channel, mask)
-
- def purge(self):
- for cache in self.values():
- cache.purge()
-
-class MaskSync(object):
- """Class for fetch and sync bans of any channel and mode."""
- __name__ = ''
- _hide_msg = False
-
- _hook_mask = ''
- _hook_end = ''
-
- # freenode new signals for list quiet messages
- _hook_quiet_mask = ''
- _hook_quiet_end = ''
-
- # sync queue stuff
- queue = []
- _maskbuffer = CaseInsensibleDefaultDict(list)
- _callback = CaseInsensibleDict()
-
- def hook(self):
- # 367 - ban mask
- # 368 - end of ban list
- # 728 - quiet mask
- # 729 - end of quiet list
- self.unhook()
- self._hook_mask = \
- weechat.hook_modifier('irc_in_367', callback(self._maskCallback), '')
- self._hook_end = \
- weechat.hook_modifier('irc_in_368', callback(self._endCallback), '')
- self._hook_quiet_mask = \
- weechat.hook_modifier('irc_in_728', callback(self._maskCallback), '')
- self._hook_quiet_end = \
- weechat.hook_modifier('irc_in_729', callback(self._endCallback), '')
-
- def unhook(self):
- for hook in ('_hook_mask',
- '_hook_end',
- '_hook_quiet_mask',
- '_hook_quiet_end'):
- attr = getattr(self, hook)
- if attr:
- weechat.unhook(attr)
- setattr(self, hook, '')
-
- def fetch(self, server, channel, mode, callback=None):
- """Fetches masks for a given server and channel."""
- buffer = weechat.buffer_search('irc', 'server.%s' %server)
- if not buffer or not weechat.info_get('irc_is_channel', channel):
- # invalid server or channel
- return
-
- # check modes
- if mode not in supported_modes(server):
- return
- maskCache = modeCache[mode]
- key = (server, channel)
- # check the last time we did this
- try:
- masklist = maskCache[key]
- if (now() - masklist.synced) < 60:
- # don't fetch again
- return
- except KeyError:
- pass
-
- if not self.queue:
- self.queue.append((server, channel, mode))
- self._fetch(server, channel, mode)
- elif (server, channel, mode) not in self.queue:
- self.queue.append((server, channel, mode))
-
- if callback:
- self._callback[server, channel] = callback
-
- def _fetch(self, server, channel, mode):
- buffer = weechat.buffer_search('irc', 'server.%s' %server)
- if not buffer:
- return
- cmd = '/mode %s %s' %(channel, mode)
- self._hide_msg = True
- weechat_command(buffer, cmd)
-
- def _maskCallback(self, data, modifier, modifier_data, string):
- """callback for store a single mask."""
- #debug("MASK %s: %s %s", modifier, modifier_data, string)
- args = string.split()
- if self.queue:
- server, channel, _ = self.queue[0]
- else:
- server, channel = modifier_data, args[3]
-
- if modifier == 'irc_in_367':
- try:
- mask, op, date = args[4:]
- except IndexError:
- mask = args[4]
- op = date = None
- elif modifier == 'irc_in_728':
- mask, op, date = args[5:]
-
- # store temporally until "end list" msg
- self._maskbuffer[server, channel].append((mask, op, date))
- if self._hide_msg:
- string = ''
- return string
-
- def _endCallback(self, data, modifier, modifier_data, string):
- """callback for end of channel's mask list."""
- #debug("MASK END %s: %s %s", modifier, modifier_data, string)
- if self.queue:
- server, channel, mode = self.queue.pop(0)
- else:
- args = string.split()
- server, channel = modifier_data, args[3]
- if modifier == 'irc_in_368':
- mode = args[7]
- elif modifier == 'irc_in_729':
- mode = args[4]
- else:
- return string
-
- maskCache = modeCache[mode]
-
- # delete old masks in cache
- if (server, channel) in maskCache:
- masklist = maskCache[server, channel]
- banmasks = [ L[0] for L in self._maskbuffer[server, channel] ]
- for mask in masklist.keys():
- if mask not in banmasks:
- del masklist[mask]
-
- for banmask, op, date in self._maskbuffer[server, channel]:
- maskCache.add(server, channel, banmask, operator=op, date=date)
- del self._maskbuffer[server, channel]
- try:
- maskList = maskCache[server, channel]
- except KeyError:
- maskList = maskCache[server, channel] = MaskList(server, channel)
- maskList.synced = now()
-
- # run hooked functions if any
- if (server, channel) in self._callback:
- self._callback[server, channel]()
- del self._callback[server, channel]
-
- if self._hide_msg:
- string = ''
- if self.queue:
- next = self.queue[0]
- self._fetch(*next)
- else:
- assert not self._maskbuffer, "mask buffer not empty: %s" % self._maskbuffer.keys()
- self._hide_msg = False
- return string
-
-maskSync = MaskSync()
-
-# -----------------------------------------------------------------------------
-# User cache
-
-class UserObject(object):
- def __init__(self, nick, hostmask=None):
- self.nick = nick
- if hostmask:
- self._hostmask = [ hostmask ]
- else:
- self._hostmask = []
- self.seen = now()
- self._channels = 0
-
- @property
- def hostmask(self):
- try:
- return self._hostmask[-1]
- except IndexError:
- return ''
-
- def update(self, hostmask=None):
- if hostmask and hostmask != self.hostmask:
- if hostmask in self._hostmask:
- del self._hostmask[self._hostmask.index(hostmask)]
- self._hostmask.append(hostmask)
- self.seen = now()
-
- def __len__(self):
- return len(self.hostmask)
-
- def __repr__(self):
- return '<UserObject(%s)>' %(self.hostmask or self.nick)
-
-class ServerUserList(CaseInsensibleDict):
- def __init__(self, server):
- self.server = server
- buffer = weechat.buffer_search('irc', 'server.%s' %server)
- self.irc = IrcCommands(buffer)
- self._purge_time = 3600*4 # 4 hours
-
- def getHostmask(self, nick):
- user = self[nick]
- return user.hostmask
-
- def purge(self):
- """Purge old nicks"""
- n = now()
- for nick, user in self.items():
- if user._channels < 1 and (n - user.seen) > self._purge_time:
- #debug('purging old user: %s' % nick)
- del self[nick]
-
-class UserList(ServerUserList):
- def __init__(self, server, channel):
- self.server = server
- self.channel = channel
- self._purge_list = CaseInsensibleDict()
- self._purge_time = 3600*2 # 2 hours
-
- def __setitem__(self, nick, user):
- #debug('%s %s: join, %s', self.server, self.channel, nick)
- if nick not in self:
- user._channels += 1
- if nick in self._purge_list:
- #debug(' - removed from purge list')
- del self._purge_list[nick]
- ServerUserList.__setitem__(self, nick, user)
-
- def part(self, nick):
- try:
- #debug('%s %s: part, %s', self.server, self.channel, nick)
- user = self[nick]
- self._purge_list[nick] = user
- except KeyError:
- pass
-
- def values(self):
- if not all(ServerUserList.values(self)):
- userCache.who(self.server, self.channel)
- return sorted(ServerUserList.values(self), key=lambda x:x.seen, reverse=True)
-
- def hostmasks(self, sorted=False, all=False):
- if sorted:
- users = self.values()
- else:
- users = ServerUserList.values(self)
- if all:
- # return all known hostmasks
- return [ hostmask for user in users for hostmask in user._hostmask ]
- else:
- # only current hostmasks
- return [ user.hostmask for user in users if user._hostmask ]
-
- def nicks(self, *args, **kwargs):
-# if not all(self.itervalues()):
-# userCache.who(self.server, self.channel)
- L = list(self.items())
- L.sort(key=lambda x:x[1].seen)
- return reversed([x[0] for x in L])
-
- def getHostmask(self, nick):
- try:
- user = self[nick]
- except KeyError:
- user = userCache[self.server][nick]
- return user.hostmask
-
- def purge(self):
- """Purge old nicks"""
- n = now()
- for nick, user in self._purge_list.items():
- if (n - user.seen) > self._purge_time:
- #debug('%s %s: forgeting about %s', self.server, self.channel, nick)
- user._channels -= 1
- try:
- del self._purge_list[nick]
- del self[nick]
- except KeyError:
- pass
-
-class UserCache(ServerChannelDict):
- __name__ = ''
- servercache = CaseInsensibleDict()
- _hook_who = _hook_who_end = None
- _channels = CaseInsensibleSet()
-
- def generateCache(self, server, channel):
- debug('* building cache: %s %s', server, channel)
- users = UserList(server, channel)
- try:
- infolist = nick_infolist(server, channel)
- except:
- # better to fail silently
- #debug('invalid buffer')
- return users
-
- while infolist.next():
- nick = infolist['name']
- host = infolist['host']
- if host:
- hostmask = '%s!%s' %(nick, host)
- else:
- hostmask = ''
- user = self.remember(server, nick, hostmask)
- users[nick] = user
- self[server, channel] = users
- debug("new cache of %s users", len(users))
- return users
-
- def remember(self, server, nick, hostmask):
- cache = self[server]
- try:
- user = cache[nick]
- if hostmask:
- user.update(hostmask)
- except KeyError:
- #debug("%s: new user %s %s", server, nick, hostmask)
- user = UserObject(nick, hostmask)
- cache[nick] = user
- return user
-
- def __getitem__(self, k):
- if isinstance(k, tuple):
- try:
- return ServerChannelDict.__getitem__(self, k)
- except KeyError:
- return self.generateCache(*k)
- elif isinstance(k, str):
- try:
- return self.servercache[k]
- except KeyError:
- cache = self.servercache[k] = ServerUserList(k)
- return cache
-
- def __delitem__(self, k):
- # when we delete a channel, we need to reduce user._channels count
- # so they can be purged later.
- #debug('forgeting about %s', k)
- for user in self[k].values():
- user._channels -= 1
- ServerChannelDict.__delitem__(self, k)
-
- def getHostmask(self, nick, server, channel=None):
- """Returns hostmask of nick."""
- if channel:
- return self[server, channel].getHostmask(nick)
- return self[server].getHostmask(nick)
-
- def who(self, server, channel):
- if self._hook_who:
- return
-
- if (server, channel) in self._channels:
- return
-
- self._channels.add((server, channel))
-
- key = ('%s.%s' %(server, channel)).lower()
- self._hook_who = weechat.hook_modifier(
- 'irc_in_352', callback(self._whoCallback), key)
- self._hook_who_end = weechat.hook_modifier(
- 'irc_in_315', callback(self._endWhoCallback), key)
-
- buffer = weechat.buffer_search('irc', 'server.%s' %server)
- weechat_command(buffer, '/who %s' % channel)
-
- def _whoCallback(self, data, modifier, modifier_data, string):
- #debug('%s %s %s', modifier, modifier_data, string)
- args = string.split()
- server, channel = modifier_data, args[3]
- key = ('%s.%s' %(server, channel)).lower()
- if key != data:
- return string
-
- nick, user, host = args[7], args[4], args[5]
- hostmask = '%s!%s@%s' %(nick, user, host)
- debug('WHO: %s', hostmask)
- self.remember(server, nick, hostmask)
- return ''
-
- def _endWhoCallback(self, data, modifier, modifier_data, string):
- args = string.split()
- server, channel = modifier_data, args[3]
- key = ('%s.%s' %(server, channel)).lower()
- if key != data:
- return string
-
- debug('WHO: end.')
- weechat.unhook(self._hook_who)
- weechat.unhook(self._hook_who_end)
- self._hook_who = self._hook_who_end = None
- return ''
-
- def purge(self):
- ServerChannelDict.purge(self)
- for cache in self.servercache.values():
- cache.purge()
-
-userCache = UserCache()
-
-# -----------------------------------------------------------------------------
-# Chanop Command Classes
-
-# Base classes for chanop commands
-class CommandChanop(Command, ChanopBuffers):
- """Base class for our commands, with config and general functions."""
- infolist = None
-
- def parser(self, args):
- if not args:
- weechat_command('', '/help %s' % self.command)
- raise NoArguments
- self.setup(self.buffer)
-
- def execute(self):
- self.users = userCache[self.server, self.channel]
- try:
- self.execute_chanop() # call our command and queue messages for WeeChat
- self.irc.run() # run queued messages
- except InvalidIRCBuffer as e:
- error('Not in a IRC channel (%s)' % e)
- self.irc.clear()
- self.infolist = None # free irc_nick infolist
-
- def execute_chanop(self):
- pass
-
- def nick_infolist(self):
- # reuse the same infolist instead of creating it many times
- if not self.infolist:
- self.infolist = nick_infolist(self.server, self.channel)
- else:
- self.infolist.reset()
- return self.infolist
-
- def has_op(self, nick):
- nicks = self.nick_infolist()
- while nicks.next():
- if nicks['name'] == nick:
- return '@' in nicks['prefixes']
-
- def has_voice(self, nick):
- nicks = self.nick_infolist()
- while nicks.next():
- if nicks['name'] == nick:
- return '+' in nicks['prefixes']
-
- def isUser(self, nick):
- return nick in self.users
-
- def inChannel(self, nick):
- return CaseInsensibleString(nick) in [ nick['name'] for nick in self.nick_infolist() ]
-
- def getHostmask(self, name):
- try:
- hostmask = self.users.getHostmask(name)
- if not hostmask:
- self.irc.Userhost(name)
- user = userCache[self.server][name]
- return lambda: user.hostmask or user.nick
- return hostmask
- except KeyError:
- pass
-
- def set_mode(self, *nicks):
- mode = self.prefix + self.mode
- for nick in nicks:
- self.irc.Mode(mode, nick)
-
-class CommandWithOp(CommandChanop):
- """Base class for all the commands that requires op status for work."""
- _enable_deopNow = True
- deop_delay = 0
-
- def __init__(self, *args, **kwargs):
- CommandChanop.__init__(self, *args, **kwargs)
- # update help so it adds --deop option
- if self._enable_deopNow:
- if self.usage:
- self.usage += " "
- if self.help:
- self.help += "\n"
- self.usage += "[--deop]"
- self.help += " -o --deop: Forces deop immediately, without configured delay"\
- " (option must be the last argument)."
-
- def setup(self, buffer):
- self.deopNow = False
- CommandChanop.setup(self, buffer)
-
- def parser(self, args):
- CommandChanop.parser(self, args)
- args = args.split()
- if self._enable_deopNow and args[-1] in ('-o', '--deop'):
- self.deopNow = True
- del args[-1]
- self.args = ' '.join(args)
- if not self.args:
- raise NoArguments
-
- def execute_chanop(self, *args):
- self.execute_op(*args)
-
- if not self.irc.commands:
- # nothing in queue, no reason to op.
- return
-
- self.irc.Op()
- if (self.autodeop and self.get_config_boolean('autodeop')) or self.deopNow:
- if self.deopNow:
- delay = self.deop_delay
- else:
- delay = self.get_config_int('autodeop_delay')
- if delay > 0:
- if self.deopHook:
- weechat.unhook(self.deopHook)
- self.vars.deopHook = weechat.hook_timer(delay * 1000, 0, 1,
- callback(self.deopCallback), self.buffer)
- elif self.irc.commands: # only Deop if there are msgs in queue
- self.irc.Deop()
-
- def execute_op(self, *args):
- """Commands in this method will be run with op privileges."""
- pass
-
- def deopCallback(self, buffer, count):
- #debug('deop %s', buffer)
- vars = self.varsOf(buffer)
- if vars.autodeop:
- if vars.irc.commands:
- # there are commands in queue yet, wait some more
- vars.deopHook = weechat.hook_timer(1000, 0, 1,
- callback(self.deopCallback), buffer)
- return WEECHAT_RC_OK
- else:
- vars.irc.Deop()
- vars.irc.run()
- vars.deopHook = None
- return WEECHAT_RC_OK
-
-# Chanop commands
-class Op(CommandChanop):
- description, usage = "Request operator privileges or give it to users.", "[nick [nick ... ]]",
- help = \
- "The command used for ask op is defined globally in plugins.var.python.%(name)s.op_command\n"\
- "It can be defined per server or per channel in:\n"\
- " plugins.var.python.%(name)s.op_command.<server>\n"\
- " plugins.var.python.%(name)s.op_command.<server>.<#channel>\n"\
- "\n"\
- "After using this command, you won't be autodeoped." %{'name':SCRIPT_NAME}
- command = 'oop'
- completion = '%(nicks)'
-
- prefix = '+'
- mode = 'o'
-
- def parser(self, args):
- # dont show /help if no args
- self.setup(self.buffer)
-
- def execute_chanop(self):
- self.irc.Op()
- # /oop was used, we assume that the user wants
- # to stay opped permanently
- self.vars.autodeop = False
- if self.args:
- for nick in self.args.split():
- if self.inChannel(nick) and not self.has_op(nick):
- self.set_mode(nick)
-
-class Deop(Op, CommandWithOp):
- description, usage, help = \
- "Removes operator privileges from yourself or users.", "[nick [nick ... ]]", ""
- command = 'odeop'
- completion = '%(nicks)'
-
- prefix = '-'
- _enable_deopNow = False
-
- def execute_chanop(self):
- if self.args:
- nicks = []
- for nick in self.args.split():
- if self.inChannel(nick) and self.has_op(nick):
- nicks.append(nick)
- if nicks:
- CommandWithOp.execute_chanop(self, nicks)
- else:
- self.vars.autodeop = True
- if self.has_op(self.nick):
- self.irc.Deop()
-
- def execute_op(self, nicks):
- self.set_mode(*nicks)
-
-class Kick(CommandWithOp):
- description, usage = "Kick nick.", "<nick> [<reason>]"
- help = \
- "On freenode, you can set this command to use /remove instead of /kick, users"\
- " will see it as if the user parted and it can bypass autojoin-on-kick scripts."\
- " See plugins.var.python.%s.enable_remove config option." %SCRIPT_NAME
- command = 'okick'
- completion = '%(nicks)'
-
- def execute_op(self):
- nick, s, reason = self.args.partition(' ')
- if self.inChannel(nick):
- self.irc.Kick(nick, reason)
- else:
- say("Nick not in %s (%s)" % (self.channel, nick), self.buffer)
- self.irc.clear()
-
-class MultiKick(Kick):
- description = "Kick one or more nicks."
- usage = "<nick> [<nick> ... ] [:] [<reason>]"
- help = Kick.help + "\n\n"\
- "Note: Is not needed, but use ':' as a separator between nicks and "\
- "the reason. Otherwise, if there's a nick in the channel matching the "\
- "first word in reason it will be kicked."
- completion = '%(nicks)|%*'
-
- def execute_op(self):
- args = self.args.split()
- nicks = []
- nicks_parted = []
- #debug('multikick: %s' %str(args))
- while(args):
- nick = args[0]
- if nick[0] == ':' or not self.isUser(nick):
- break
- nick = args.pop(0)
- if self.inChannel(nick):
- nicks.append(nick)
- else:
- nicks_parted.append(nick)
-
- #debug('multikick: %s, %s' %(nicks, args))
- reason = ' '.join(args).lstrip(':')
- if nicks_parted:
- say("Nick(s) not in %s (%s)" % (self.channel, ', '.join(nicks_parted)), self.buffer)
- elif not nicks:
- say("Unknown nick (%s)" % nick, self.buffer)
- if nicks:
- for nick in nicks:
- self.irc.Kick(nick, reason)
- else:
- self.irc.clear()
-
-ban_help = \
-"Mask options:\n"\
-" -h --host: Match hostname (*!*@host)\n"\
-" -n --nick: Match nick (nick!*@*)\n"\
-" -u --user: Match username (*!user@*)\n"\
-" -e --exact: Use exact hostmask.\n"\
-"\n"\
-"If no mask options are supplied, configured defaults are used.\n"\
-"\n"\
-"Completer:\n"\
-"%(script)s will attempt to guess a complete banmask from current\n"\
-"users when using <tab> in an incomplete banmask. Using <tab> in a\n"\
-"complete banmask will generate variations of it. \n"\
-"\n"\
-"Examples:\n"\
-" /%(cmd)s somebody --user --host\n"\
-" will ban with *!user@hostname mask.\n"\
-" /%(cmd)s nick!*@<tab>\n"\
-" will autocomple with 'nick!*@host'.\n"\
-" /%(cmd)s nick!*@*<tab>\n"\
-" will cycle through different banmask variations for the same user.\n"
-
-class Ban(CommandWithOp):
- description = "Ban user or hostmask."
- usage = \
- "<nick|mask> [<nick|mask> ... ] [ [--host] [--user] [--nick] | --exact ]"
- command = 'oban'
- help = ban_help % {'script': SCRIPT_NAME, 'cmd': command}
- completion = '%(chanop_nicks)|%(chanop_ban_mask)|%*'
-
- banmask = []
- mode = 'b'
- prefix = '+'
-
- def __init__(self):
- self.maskCache = modeCache[self.mode]
- CommandWithOp.__init__(self)
-
- def parser(self, args):
- if not args:
- showBans.callback(self.data, self.buffer, self.mode)
- raise NoArguments
- CommandWithOp.parser(self, args)
- self._parser(self.args)
-
- def _parser(self, args):
- args = args.split()
- try:
- (opts, args) = getopt.gnu_getopt(args, 'hune', ('host', 'user', 'nick', 'exact'))
- except getopt.GetoptError as e:
- raise ArgumentError(e)
- self.banmask = []
- for k, v in opts:
- if k in ('-h', '--host'):
- self.banmask.append('host')
- elif k in ('-u', '--user'):
- self.banmask.append('user')
- elif k in ('-n', '--nick'):
- self.banmask.append('nick')
- elif k in ('-e', '--exact'):
- self.banmask = ['exact']
- break
- if not self.banmask:
- self.banmask = self.get_default_banmask()
- self.args = ' '.join(args)
-
- def get_default_banmask(self):
- return get_config_banmask(get_function=self.get_config)
-
- def make_banmask(self, hostmask):
- assert self.banmask
- template = self.banmask
-
- def banmask(s):
- if not is_hostmask(s):
- return s
- if 'exact' in template:
- return s
- nick = user = host = '*'
- if 'nick' in template:
- nick = get_nick(s)
- if 'user' in template:
- user = get_user(s)
- if 'host' in template:
- host = get_host(s)
- # check for freenode's webchat, and use a better mask.
- if host.startswith('gateway/web/freenode'):
- ip = host.partition('.')[2]
- if is_ip(ip):
- host = '*%s' % ip
- s = '%s!%s@%s' %(nick, user, host)
- assert is_hostmask(s), "Invalid hostmask: %s" % s
- return s
-
- if callable(hostmask):
- return lambda: banmask(hostmask())
- return banmask(hostmask)
-
- def execute_op(self):
- args = self.args.split()
- banmasks = []
- for arg in args:
- if is_nick(arg):
- hostmask = self.getHostmask(arg)
- if not hostmask:
- say("Unknown nick (%s)" % arg, self.buffer)
- continue
- mask = self.make_banmask(hostmask)
- if self.has_voice(arg):
- self.irc.Devoice(arg)
- else:
- # probably an extban
- mask = arg
- banmasks.append(mask)
- banmasks = set(banmasks) # remove duplicates
- self.ban(*banmasks)
-
- def mode_is_supported(self):
- return self.mode in supported_modes(self.server)
-
- def ban(self, *banmasks, **kwargs):
- if self.mode != 'b' and not self.mode_is_supported():
- error("%s doesn't seem to support channel mode '%s', using regular ban." %(self.server,
- self.mode))
- mode = 'b'
- else:
- mode = self.mode
- mode = self.prefix + mode
- for mask in banmasks:
- self.irc.Mode(mode, mask, **kwargs)
-
-class UnBan(Ban):
- description, usage = "Remove bans.", "<nick|mask> [<nick|mask> ... ]"
- command = 'ounban'
- help = \
- "Autocompletion will use channel's bans, patterns allowed for autocomplete multiple"\
- " bans.\n"\
- "\n"\
- "Example:\n"\
- "/%(cmd)s *192.168*<tab>\n"\
- " Will autocomplete with all bans matching *192.168*" %{'cmd':command}
- completion = '%(chanop_unban_mask)|%(chanop_nicks)|%*'
- prefix = '-'
-
- def search_masks(self, hostmask, **kwargs):
- try:
- masklist = self.maskCache[self.server, self.channel]
- except KeyError:
- return []
-
- if callable(hostmask):
- def banmask():
- L = masklist.search(hostmask(), **kwargs)
- if L: return L[0]
-
- return [ banmask ]
- return masklist.search(hostmask, **kwargs)
-
- def execute_op(self):
- args = self.args.split()
- banmasks = []
- for arg in args:
- if is_hostmask(arg):
- banmasks.extend(self.search_masks(arg))
- elif is_nick(arg):
- hostmask = self.getHostmask(arg)
- if hostmask:
- banmasks.extend(self.search_masks(hostmask, reverseMatch=True))
- else:
- # nick unknown to chanop
- say("Unknown nick (%s)" % arg, self.buffer)
- else:
- banmasks.append(arg)
- self.ban(*banmasks)
-
-class Quiet(Ban):
- description = "Silence user or hostmask."
- command = 'oquiet'
- help = "This command is only for networks that support channel mode 'q'.\n\n" \
- + ban_help % {'script': SCRIPT_NAME, 'cmd': command}
- completion = '%(chanop_nicks)|%(chanop_ban_mask)|%*'
-
- mode = 'q'
-
-class UnQuiet(UnBan):
- command = 'ounquiet'
- description = "Remove quiets."
- help = "Works exactly like /ounban, but only for quiets. See /help ounban"
- completion = '%(chanop_unquiet_mask)|%(chanop_nicks)|%*'
-
- mode = 'q'
-
-class BanKick(Ban, Kick):
- description = "Bankicks nick."
- usage = "<nick> [<reason>] [ [--host] [--user] [--nick] | --exact ]"
- help = "Combines /oban and /okick commands. See /help oban and /help okick."
- command = 'obankick'
- completion = '%(chanop_nicks)'
- deop_delay = 2
-
- def execute_op(self):
- nick, s, reason = self.args.partition(' ')
- if not self.isUser(nick):
- say("Unknown nick (%s)" % nick, self.buffer)
- self.irc.clear()
- return
-
- hostmask = self.getHostmask(nick)
- # we already checked that nick is valid, so hostmask shouldn't be None
- banmask = self.make_banmask(hostmask)
- self.ban(banmask)
- if self.inChannel(nick):
- self.irc.Kick(nick, reason, wait=1)
-
-class MultiBanKick(BanKick):
- description = "Bankicks one or more nicks."
- usage = \
- "<nick> [<nick> ... ] [:] [<reason>] [ [--host)] [--user] [--nick] | --exact ]"
- completion = '%(chanop_nicks)|%*'
-
- def execute_op(self):
- args = self.args.split()
- nicks = []
- while(args):
- nick = args[0]
- if nick[0] == ':' or not self.isUser(nick):
- break
- nicks.append(args.pop(0))
- reason = ' '.join(args).lstrip(':')
- if not nicks:
- say("Unknown nick (%s)" % nick, self.buffer)
- self.irc.clear()
- return
-
- for nick in nicks:
- hostmask = self.getHostmask(nick)
- banmask = self.make_banmask(hostmask)
- self.ban(banmask)
-
- self.deop_delay = 1
- for nick in nicks:
- if self.inChannel(nick):
- self.deop_delay += 1
- self.irc.Kick(nick, reason, wait=1)
-
-class Topic(CommandWithOp):
- description, usage = "Changes channel topic.", "[-delete | topic]"
- help = "Clear topic if '-delete' is the new topic."
- command = 'otopic'
- completion = '%(irc_channel_topic)||-delete'
-
- def execute_op(self):
- self.irc.queue(Message('/topic %s' %self.args))
-
-class Voice(CommandWithOp):
- description, usage, help = "Gives voice to somebody.", "nick [nick ... ]", ""
- command = 'ovoice'
- completion = '%(nicks)|%*'
-
- prefix = '+'
- mode = 'v'
-
- def execute_op(self):
- for nick in self.args.split():
- if self.inChannel(nick) and not self.has_voice(nick):
- self.set_mode(nick)
-
-class DeVoice(Voice):
- description = "Removes voice from somebody."
- command = 'odevoice'
-
- prefix = '-'
-
- def has_voice(self, nick):
- return not Voice.has_voice(self, nick)
-
-class Mode(CommandWithOp):
- description, usage, help = "Changes channel modes.", "<channel modes>", ""
- command = 'omode'
-
- def execute_op(self):
- args = self.args.split()
- modes = args.pop(0)
- L = []
- p = ''
- for c in modes:
- if c in '+-':
- p = c
- elif args:
- L.append((p + c, args.pop(0)))
- else:
- L.append((p + c, None))
- if not L:
- return
-
- for mode, arg in L:
- self.irc.Mode(mode, arg)
-
-class ShowBans(CommandChanop):
- description, usage, help = "Lists bans or quiets of a channel.", "(bans|quiets) [channel]", ""
- command = 'olist'
- completion = 'bans|quiets %(irc_server_channels)'
- showbuffer = ''
-
- padding = 40
-
- def parser(self, args):
- server = weechat.buffer_get_string(self.buffer, 'localvar_server')
- channel = weechat.buffer_get_string(self.buffer, 'localvar_channel')
- if server:
- self.server = server
- if channel:
- self.channel = channel
- type, _, args = args.partition(' ')
- if not type:
- raise ValueError('missing argument')
- try:
- mode = modeCache.map[type]
- except KeyError:
- raise ValueError('incorrect argument')
-
- self.mode = mode
- # fix self.type so is "readable" (ie, 'bans' instead of 'b')
- if mode == 'b':
- self.type = 'bans'
- elif mode == 'q':
- self.type = 'quiets'
- args = args.strip()
- if args:
- self.channel = args
-
- def get_buffer(self):
- if self.showbuffer:
- return self.showbuffer
-
- buffer = weechat.buffer_search('python', SCRIPT_NAME)
- if not buffer:
- buffer = weechat.buffer_new(SCRIPT_NAME, '', '', '', '')
- weechat.buffer_set(buffer, 'localvar_set_no_log', '1')
- weechat.buffer_set(buffer, 'time_for_each_line', '0')
- self.showbuffer = buffer
- return buffer
-
- def prnt(self, s):
- weechat.prnt(self.get_buffer(), s)
-
- def prnt_ban(self, banmask, op, when, hostmask=None):
- padding = self.padding - len(banmask)
- if padding < 0:
- padding = 0
- self.prnt('%s%s%s %sset by %s%s%s %s' %(color_mask,
- banmask,
- color_reset,
- '.'*padding,
- color_chat_nick,
- op,
- color_reset,
- self.formatTime(when)))
- if hostmask:
- hostmasks = ' '.join(hostmask)
- self.prnt(' %s%s' % (color_chat_host, hostmasks))
-
- def clear(self):
- b = self.get_buffer()
- weechat.buffer_clear(b)
- weechat.buffer_set(b, 'display', '1')
- weechat.buffer_set(b, 'title', '%s' %SCRIPT_NAME)
-
- def set_title(self, s):
- weechat.buffer_set(self.get_buffer(), 'title', s)
-
- def formatTime(self, t):
- t = now() - int(t)
- elapsed = time_elapsed(t, level=3)
- return '%s ago' %elapsed
-
- def execute(self):
- self.showbuffer = ''
- if self.mode not in supported_modes(self.server):
- self.clear()
- self.prnt("\n%sNetwork '%s' doesn't support %s" % (color_channel,
- self.server,
- self.type))
- return
-
- maskCache = modeCache[self.mode]
- key = (self.server, self.channel)
- try:
- masklist = maskCache[key]
- except KeyError:
- if not (weechat.info_get('irc_is_channel', key[1]) and self.server):
- error("Command /%s must be used in an IRC buffer." % self.command)
- return
-
- masklist = None
- self.clear()
- mask_count = 0
- if masklist:
- mask_count = len(masklist)
- self.prnt('\n%s[%s %s]' %(color_channel, key[0], key[1]))
- masks = [ m for m in masklist.values() ]
- masks.sort(key=lambda x: x.date)
- for ban in masks:
- op = self.server
- if ban.operator:
- try:
- op = get_nick(ban.operator)
- except:
- pass
- self.prnt_ban(ban.mask, op, ban.date, ban.hostmask)
- else:
- self.prnt('No known %s for %s.%s' %(self.type, key[0], key[1]))
- if masklist is None or not masklist.synced:
- self.prnt("\n%sList not synced, please wait ..." %color_channel)
- maskSync.fetch(key[0], key[1], self.mode, lambda: self.execute())
- self.set_title('List of %s known by chanop in %s.%s (total: %s)' %(self.type,
- key[0],
- key[1],
- mask_count))
-
-# -----------------------------------------------------------------------------
-# Script callbacks
-
-# Decorators
-def signal_parse(f):
- @catchExceptions
- def decorator(data, signal, signal_data):
- server = signal[:signal.find(',')]
- channel = signal_data.split()[2]
- if channel[0] == ':':
- channel = channel[1:]
- if (server, channel) not in chanopChannels:
- # signals only processed for channels in watchlist
- return WEECHAT_RC_OK
- nick = get_nick(signal_data)
- hostmask = signal_data[1:signal_data.find(' ')]
- #debug('%s %s', signal, signal_data)
- return f(server, channel, nick, hostmask, signal_data)
- decorator.func_name = f.func_name
- return decorator
-
-def signal_parse_no_channel(f):
- @catchExceptions
- def decorator(data, signal, signal_data):
- server = signal[:signal.find(',')]
- nick = get_nick(signal_data)
- channels = userCache.getChannels(server, nick)
- if channels:
- hostmask = signal_data[1:signal_data.find(' ')]
- #debug('%s %s', signal, signal_data)
- return f(server, channels, nick, hostmask, signal_data)
- return WEECHAT_RC_OK
- decorator.func_name = f.func_name
- return decorator
-
-isupport = {}
-def get_isupport_value(server, feature):
- #debug('isupport %s %s', server, feature)
- try:
- return isupport[server][feature]
- except KeyError:
- if not server:
- return ''
- elif server not in isupport:
- isupport[server] = {}
- v = weechat.info_get('irc_server_isupport_value', '%s,%s' %(server, feature.upper()))
- if v:
- isupport[server][feature] = v
- else:
- # old api
- v = weechat.config_get_plugin('isupport.%s.%s' %(server, feature))
- if not v:
- # lets do a /VERSION (it should be done only once.)
- if '/VERSION' in isupport[server]:
- return ''
- buffer = weechat.buffer_search('irc', 'server.%s' %server)
- weechat_command(buffer, '/VERSION')
- isupport[server]['/VERSION'] = True
- return v
-
-_supported_modes = set('bq') # the script only support b,q masks
-def supported_modes(server):
- """Returns modes supported by server."""
- modes = get_isupport_value(server, 'chanmodes')
- if not modes:
- return 'b'
- modes = modes.partition(',')[0] # we only care about the first type
- modes = ''.join(_supported_modes.intersection(modes))
- return modes
-
-def supported_maxmodes(server):
- """Returns max modes number supported by server."""
- max = get_isupport_value(server, 'modes')
- try:
- max = int(max)
- if max <= 0:
- max = 1
- except ValueError:
- return 1
- return max
-
-def isupport_cb(data, signal, signal_data):
- """Callback used for catch isupport msg if current version of WeeChat doesn't
- support it."""
- data = signal_data.split(' ', 3)[-1]
- data, s, s = data.rpartition(' :')
- data = data.split()
- server = signal.partition(',')[0]
- d = {}
- #debug(data)
- for s in data:
- if '=' in s:
- k, v = s.split('=')
- else:
- k, v = s, True
- k = k.lower()
- if k in ('chanmodes', 'modes', 'prefix'):
- config = 'isupport.%s.%s' %(server, k)
- weechat.config_set_plugin(config, v)
- d[k] = v
- isupport[server] = d
- return WEECHAT_RC_OK
-
-def print_affected_users(buffer, *hostmasks):
- """Print a list of users, max 8 hostmasks"""
- def format_user(hostmask):
- nick, host = hostmask.split('!', 1)
- return '%s%s%s(%s%s%s)' %(color_chat_nick,
- nick,
- color_delimiter,
- color_chat_host,
- host,
- color_delimiter)
-
- max = 8
- count = len(hostmasks)
- if count > max:
- hostmasks = hostmasks[:max]
- say('Affects (%s): %s%s' %(count, ' '.join(map(format_user,
- hostmasks)), count > max and ' %s...' %color_reset or ''), buffer=buffer)
-
-# Masks list tracking
-@signal_parse
-def mode_cb(server, channel, nick, opHostmask, signal_data):
- """Keep the banmask list updated when somebody changes modes"""
- #:m4v!~znc@unaffiliated/m4v MODE #test -bo+v asd!*@* m4v dude
- pair = signal_data.split(' ', 4)[3:]
- if len(pair) != 2:
- # modes without argument, not interesting.
- return WEECHAT_RC_OK
- modes, args = pair
-
- # check if there are interesting modes
- servermodes = supported_modes(server)
- s = modes.translate(chars, '+-') # remove + and -
- if not set(servermodes).intersection(s):
- return WEECHAT_RC_OK
-
- # check if channel is in watchlist
- key = (server, channel)
- allkeys = CaseInsensibleSet()
- for maskCache in modeCache.values():
- allkeys.update(maskCache)
- if key not in allkeys and key not in chanopChannels:
- # from a channel we're not tracking
- return WEECHAT_RC_OK
-
- prefix = get_isupport_value(server, 'prefix')
- chanmodes = get_isupport_value(server, 'chanmodes')
- if not prefix or not chanmodes:
- # we don't have ISUPPORT data, can't continue
- return WEECHAT_RC_OK
-
- # split chanmodes into tuples like ('+', 'b', 'asd!*@*')
- action = ''
- chanmode_list = []
- args = args.split()
-
- # user channel mode, such as +v or +o, get only the letters and not the prefixes
- usermodes = ''.join(map(lambda c: c.isalpha() and c or '', prefix))
- chanmodes = chanmodes.split(',')
- # modes not supported by script, like +e +I
- notsupported = chanmodes[0].translate(chars, servermodes)
- modes_with_args = chanmodes[1] + usermodes + notsupported
- modes_with_args_when_set = chanmodes[2]
- for c in modes:
- if c in '+-':
- action = c
- elif c in servermodes:
- chanmode_list.append((action, c, args.pop(0)))
- elif c in modes_with_args:
- del args[0]
- elif c in modes_with_args_when_set and action == '+':
- del args[0]
-
- affected_users = []
- # update masks
- for action, mode, mask in chanmode_list:
- debug('MODE: %s%s %s %s', action, mode, mask, opHostmask)
- if action == '+':
- hostmask = hostmask_match_list(mask, userCache[key].hostmasks())
- if hostmask:
- affected_users.extend(hostmask)
- if mask != '*!*@*':
- # sending this signal with a *!*@* is annoying
- weechat.hook_signal_send("%s,chanop_mode_%s" % (server, mode),
- weechat.WEECHAT_HOOK_SIGNAL_STRING,
- "%s %s %s %s" % (opHostmask, channel,
- mask, ','.join(hostmask)))
- modeCache.add(server, channel, mode, mask, operator=opHostmask, hostmask=hostmask)
- elif action == '-':
- modeCache.remove(server, channel, mode, mask)
-
- if affected_users and get_config_boolean('display_affected',
- get_function=get_config_specific, server=server, channel=channel):
- buffer = weechat.buffer_search('irc', '%s.%s' %key)
- print_affected_users(buffer, *set(affected_users))
- return WEECHAT_RC_OK
-
-# User cache
-@signal_parse
-def join_cb(server, channel, nick, hostmask, signal_data):
- if weechat.info_get('irc_nick', server) == nick:
- # we're joining the channel, the cache is no longer valid
- #userCache.generateCache(server, channel)
- try:
- del userCache[server, channel]
- except KeyError:
- pass
- return WEECHAT_RC_OK
- user = userCache.remember(server, nick, hostmask)
- userCache[server, channel][nick] = user
- return WEECHAT_RC_OK
-
-@signal_parse
-def part_cb(server, channel, nick, hostmask, signal_data):
- userCache.remember(server, nick, hostmask)
- userCache[server, channel].part(nick)
- return WEECHAT_RC_OK
-
-@signal_parse_no_channel
-def quit_cb(server, channels, nick, hostmask, signal_data):
- userCache.remember(server, nick, hostmask)
- for channel in channels:
- userCache[server, channel].part(nick)
- return WEECHAT_RC_OK
-
-@signal_parse_no_channel
-def nick_cb(server, channels, oldNick, oldHostmask, signal_data):
- newNick = signal_data[signal_data.rfind(' ') + 2:]
- newHostmask = '%s!%s' % (newNick, oldHostmask[oldHostmask.find('!') + 1:])
- userCache.remember(server, oldNick, oldHostmask)
- user = userCache.remember(server, newNick, newHostmask)
- for channel in channels:
- userCache[server, channel].part(oldNick)
- userCache[server, channel][newNick] = user
- return WEECHAT_RC_OK
-
-# Garbage collector
-def garbage_collector_cb(data, counter):
- """This takes care of purging users and masks from channels not in watchlist, and
- expired users that parted.
- """
- debug('* flushing caches')
- modeCache.purge()
- userCache.purge()
-
- if weechat.config_get_plugin('debug'):
- # extra check that everything is right.
- for serv, chan in userCache:
- for nick in [ nick['name'] for nick in nick_infolist(serv, chan) ]:
- if nick not in userCache[serv, chan]:
- error('User cache out of sync, unknown nick. (%s - %s.%s)' % (nick, serv, chan))
-
- return WEECHAT_RC_OK
-
-# -----------------------------------------------------------------------------
-# Config callbacks
-
-def enable_multi_kick_conf_cb(data, config, value):
- global cmd_kick, cmd_bankick
- cmd_kick.unhook()
- cmd_bankick.unhook()
- if boolDict[value]:
- cmd_kick = MultiKick()
- cmd_bankick = MultiBanKick()
- else:
- cmd_kick = Kick()
- cmd_bankick = BanKick()
- cmd_kick.hook()
- cmd_bankick.hook()
- return WEECHAT_RC_OK
-
-def update_chanop_watchlist_cb(data, config, value):
- #debug('CONFIG: %s' %(' '.join((data, config, value))))
- server = config[config.rfind('.')+1:]
- if value:
- L = value.split(',')
- else:
- L = []
- for serv, chan in list(chanopChannels):
- if serv == server:
- chanopChannels.remove((serv, chan))
- chanopChannels.update([ (server, channel) for channel in L ])
- return WEECHAT_RC_OK
-
-def enable_bar_cb(data, config, value):
- if boolDict[value]:
- chanop_bar.new()
- weechat.bar_item_new('chanop_ban_matches', 'item_ban_matches_cb', '')
- weechat.bar_item_new('chanop_status', 'item_status_cb', '')
- weechat.hook_modifier('input_text_content', 'input_content_cb', '')
- else:
- chanop_bar.remove()
- return WEECHAT_RC_OK
-
-def enable_debug_cb(data, config, value):
- global debug
- if value and boolDict[value]:
- try:
- # custom debug module I use, allows me to inspect script's objects.
- import pybuffer
- debug = pybuffer.debugBuffer(globals(), '%s_debug' % SCRIPT_NAME)
- weechat.buffer_set(debug._getBuffer(), 'localvar_set_no_log', '0')
- except:
- def debug(s, *args):
- if not isinstance(s, str):
- s = str(s)
- if args:
- s = s % args
- prnt('', '%s\t%s' % (script_nick, s))
- else:
- try:
- if hasattr(debug, 'close'):
- debug.close()
- except NameError:
- pass
-
- debug = _no_debug
-
- return WEECHAT_RC_OK
-
-# -----------------------------------------------------------------------------
-# Completers
-
-def cmpl_get_irc_users(f):
- """Check if completion is done in a irc channel, and pass the buffer's user list."""
- @catchExceptions
- def decorator(data, completion_item, buffer, completion):
- key = irc_buffer(buffer)
- if not key:
- return WEECHAT_RC_OK
- users = userCache[key]
- return f(users, data, completion_item, buffer, completion)
- return decorator
-
-def unban_mask_cmpl(mode, completion_item, buffer, completion):
- """Completion for applied banmasks, for commands like /ounban /ounquiet"""
- maskCache = modeCache[mode]
- key = irc_buffer(buffer)
- if not key:
- return WEECHAT_RC_OK
- server, channel = key
-
- def cmpl_unban(masklist):
- input = weechat.buffer_get_string(buffer, 'input')
- if input[-1] != ' ':
- input, _, pattern = input.rpartition(' ')
- else:
- pattern = ''
- #debug('%s %s', repr(input), repr(pattern))
- if pattern and not is_nick(pattern): # FIXME nick completer interferes.
- # NOTE masklist no longer accepts nicks.
- L = masklist.search(pattern)
- #debug('unban pattern %s => %s', pattern, L)
- if L:
- input = '%s %s ' % (input, ' '.join(L))
- weechat.buffer_set(buffer, 'input', input)
- weechat.buffer_set(buffer, 'input_pos', str(len(input)))
- return
- elif not masklist:
- return
- for mask in masklist.keys():
- #debug('unban mask: %s', mask)
- weechat.hook_completion_list_add(completion, mask, 0, weechat.WEECHAT_LIST_POS_END)
-
- if key not in maskCache or not maskCache[key].synced:
- # do completion after fetching marks
- if not maskSync.queue:
- def callback():
- masklist = maskCache[key]
- if chanop_bar:
- global chanop_bar_status
- if masklist:
- chanop_bar_status = 'Got %s +%s masks.' % (len(masklist), mode)
- else:
- chanop_bar_status = 'No +%s masks found.' % mode
- chanop_bar.popup()
- weechat.bar_item_update('chanop_status')
- else:
- if masklist:
- say('Got %s +%s masks.' % (len(masklist), mode), buffer)
- else:
- say('No +%s masks found.' % mode, buffer)
- cmpl_unban(masklist)
-
- maskSync.fetch(server, channel, mode, callback)
- if chanop_bar:
- global chanop_bar_status
- chanop_bar_status = 'Fetching +%s masks in %s, please wait...' %(mode, channel)
- weechat.bar_item_update('chanop_status')
- chanop_bar.popup()
- else:
- say('Fetching +%s masks in %s, please wait...' %(mode, channel), buffer)
- else:
- # mask list is up to date, do completion
- cmpl_unban(maskCache[key])
- return WEECHAT_RC_OK
-
-banmask_cmpl_list = []
-@cmpl_get_irc_users
-def ban_mask_cmpl(users, data, completion_item, buffer, completion):
- """Completion for banmasks, for commands like /oban /oquiet"""
- input = weechat.buffer_get_string(buffer, 'input')
- if input[-1] == ' ':
- # no pattern, return
- return WEECHAT_RC_OK
-
- input, _, pattern = input.rpartition(' ')
-
- global banmask_cmpl_list
- if is_hostmask(pattern):
- if not banmask_cmpl_list:
- maskList = pattern_match_list(pattern, users.hostmasks(sorted=True, all=True))
- if maskList:
- banmask_cmpl_list = [ pattern ]
-
- def add(mask):
- if mask not in banmask_cmpl_list:
- banmask_cmpl_list.append(mask)
-
- for mask in maskList:
- #debug('ban_mask_cmpl: Generating variations for %s', mask)
- host = get_host(mask)
- add('*!*@%s' % host)
- add('%s!*@%s' % (get_nick(mask), host))
- if host.startswith('gateway/web/freenode'):
- ip = host.partition('.')[2]
- if is_ip(ip):
- add('*!*@*%s' % ip)
- elif is_ip(host):
- user = get_user(mask)
- iprange = host.rsplit('.', 2)[0]
- add('*!%s@%s.*' % (user, iprange))
- add('*!*@%s.*' % iprange)
- #debug('ban_mask_cmpl: variations: %s', banmask_cmpl_list)
-
- if pattern in banmask_cmpl_list:
- i = banmask_cmpl_list.index(pattern) + 1
- if i == len(banmask_cmpl_list):
- i = 0
- mask = banmask_cmpl_list[i]
- input = '%s %s' % (input, mask)
- weechat.buffer_set(buffer, 'input', input)
- weechat.buffer_set(buffer, 'input_pos', str(len(input)))
- return WEECHAT_RC_OK
-
- banmask_cmpl_list = []
-
- if pattern[-1] != '*':
- search_pattern = pattern + '*'
- else:
- search_pattern = pattern
-
- if '@' in pattern:
- # complete *!*@hostname
- prefix = pattern[:pattern.find('@')]
- make_mask = lambda mask: '%s@%s' %(prefix, mask[mask.find('@') + 1:])
- get_list = users.hostmasks
- elif '!' in pattern:
- # complete *!username@*
- prefix = pattern[:pattern.find('!')]
- make_mask = lambda mask: '%s!%s@*' %(prefix, mask[mask.find('!') + 1:mask.find('@')])
- get_list = users.hostmasks
- else:
- # complete nick!*@*
- make_mask = lambda mask: '%s!*@*' %mask
- get_list = users.nicks
-
- for mask in pattern_match_list(search_pattern, get_list(sorted=True, all=True)):
- mask = make_mask(mask)
- weechat.hook_completion_list_add(completion, mask, 0, weechat.WEECHAT_LIST_POS_END)
- return WEECHAT_RC_OK
-
-# Completions for nick, user and host parts of a usermask
-@cmpl_get_irc_users
-def nicks_cmpl(users, data, completion_item, buffer, completion):
- for nick in users.nicks():
- weechat.hook_completion_list_add(completion, nick, 0, weechat.WEECHAT_LIST_POS_END)
- return WEECHAT_RC_OK
-
-@cmpl_get_irc_users
-def hosts_cmpl(users, data, completion_item, buffer, completion):
- for hostmask in users.hostmasks(sorted=True, all=True):
- weechat.hook_completion_list_add(completion, get_host(hostmask), 0,
- weechat.WEECHAT_LIST_POS_SORT)
- return WEECHAT_RC_OK
-
-@cmpl_get_irc_users
-def users_cmpl(users, data, completion_item, buffer, completion):
- for hostmask in users.hostmasks(sorted=True, all=True):
- user = get_user(hostmask)
- weechat.hook_completion_list_add(completion, user, 0, weechat.WEECHAT_LIST_POS_END)
- return WEECHAT_RC_OK
-
-# info hooks
-def info_hostmask_from_nick(data, info_name, arguments):
- #debug('INFO: %s %s', info_name, arguments)
- args = arguments.split(',')
- channel = None
- try:
- nick, server, channel = args
- except ValueError:
- try:
- nick, server = args
- except ValueError:
- return ''
- try:
- hostmask = userCache.getHostmask(nick, server, channel)
- except KeyError:
- return ''
- return hostmask
-
-def info_pattern_match(data, info_name, arguments):
- #debug('INFO: %s %s', info_name, arguments)
- pattern, string = arguments.split(',')
- if pattern_match(pattern, string):
- return '1'
- return ''
-
-# -----------------------------------------------------------------------------
-# Chanop bar callbacks
-
-chanop_bar_current_buffer = ''
-
-@catchExceptions
-def item_ban_matches_cb(data, item, window):
- #debug('ban matches item: %s %s', item, window)
- global chanop_bar_current_buffer
- buffer = chanop_bar_current_buffer
- if not buffer:
- return ''
-
- input = weechat.buffer_get_string(buffer, 'input')
- if not input:
- return ''
-
- command, _, content = input.partition(' ')
-
- if command[1:] not in ('oban', 'oquiet'):
- return ''
-
- def format(s):
- return '%s affects: %s' % (command, s)
-
- channel = weechat.buffer_get_string(buffer, 'localvar_channel')
- if not channel or not is_channel(channel):
- return format('(not an IRC channel)')
-
- server = weechat.buffer_get_string(buffer, 'localvar_server')
- users = userCache[server, channel]
- content = content.split()
- masks = [ mask for mask in content if is_hostmask(mask) or is_nick(mask) ]
- if not masks:
- return format('(no valid user mask or nick)')
-
- #debug('ban matches item: %s', masks)
-
- affected = []
- hostmasks = users.hostmasks(all=True)
- for mask in masks:
- if is_hostmask(mask):
- affected.extend(hostmask_match_list(mask, hostmasks))
- elif mask in users:
- affected.append(mask)
- #debug('ban matches item: %s', affected)
-
- if not affected:
- return format('(nobody)')
-
- L = set([ get_nick(h) for h in affected ])
- return format('(%s) %s' % (len(L), ' '.join(L)))
-
-chanop_bar_status = ''
-def item_status_cb(data, item, window):
- global chanop_bar_status
- if chanop_bar_status:
- return "%s[%s%s%s]%s %s" % (COLOR_BAR_DELIM,
- COLOR_BAR_FG,
- SCRIPT_NAME,
- COLOR_BAR_DELIM,
- color_reset,
- chanop_bar_status)
- else:
- return "%s[%s%s%s]" % (COLOR_BAR_DELIM,
- COLOR_BAR_FG,
- SCRIPT_NAME,
- COLOR_BAR_DELIM)
-
-@catchExceptions
-def input_content_cb(data, modifier, modifier_data, string):
- #debug('input_content_cb: %s %s %r', modifier, modifier_data, string)
- global chanop_bar_current_buffer, chanop_bar_status
- if not chanop_bar:
- return string
-
- if string and not weechat.string_input_for_buffer(string):
- command, _, content = string.partition(' ')
- content = content.strip()
- if content and command[1:] in ('oban', 'oquiet'):
- chanop_bar.show()
- chanop_bar_current_buffer = modifier_data
- weechat.bar_item_update('chanop_ban_matches')
- if chanop_bar_status:
- chanop_bar_status = ''
- weechat.bar_item_update('chanop_bar_status')
- return string
-
- if not chanop_bar._timer_hook:
- chanop_bar.hide()
- return string
-
-# -----------------------------------------------------------------------------
-# Main
-
-def unload_chanop():
- if chanop_bar:
- # we don't remove it, so custom options configs aren't lost
- chanop_bar.hide()
- bar_item = weechat.bar_item_search('chanop_ban_matches')
- if bar_item:
- weechat.bar_item_remove(bar_item)
- return WEECHAT_RC_OK
-
-# Register script
-if __name__ == '__main__' and import_ok and \
- weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE,
- SCRIPT_DESC, 'unload_chanop', ''):
-
- # colors
- color_delimiter = weechat.color('chat_delimiters')
- color_chat_nick = weechat.color('chat_nick')
- color_chat_host = weechat.color('chat_host')
- color_mask = weechat.color('white')
- color_channel = weechat.color('lightred')
- color_reset = weechat.color('reset')
- COLOR_WHITE = weechat.color('white')
- COLOR_DARKGRAY = weechat.color('darkgray')
- COLOR_BAR_DELIM = weechat.color('bar_delim')
- COLOR_BAR_FG = weechat.color('bar_fg')
-
- # pretty [chanop]
- script_nick = '%s[%s%s%s]%s' %(color_delimiter,
- color_chat_nick,
- SCRIPT_NAME,
- color_delimiter,
- color_reset)
-
- # -------------------------------------------------------------------------
- # Debug
-
- enable_debug_cb('', '', weechat.config_get_plugin('debug'))
- weechat.hook_config('plugins.var.python.%s.debug' % SCRIPT_NAME, 'enable_debug_cb', '')
-
- # -------------------------------------------------------------------------
- # Init
-
- # check weechat version
- try:
- version = int(weechat.info_get('version_number', ''))
- except:
- version = 0
- if version < WEECHAT_VERSION[0]:
- error("This version of WeeChat isn't supported. Use %s or later." % WEECHAT_VERSION[1])
- raise Exception('unsupported weechat version')
- if version < 0x30300: # prior to 0.3.3 didn't have support for ISUPPORT msg
- error('WeeChat < 0.3.3: using ISUPPORT workaround.')
- weechat.hook_signal('*,irc_in_005', 'isupport_cb', '')
- if version < 0x30400: # irc_nick flags changed in 0.3.4
- error('WeeChat < 0.3.4: using irc_nick infolist workaround.')
- Infolist._use_flags = True
-
- for opt, val in settings.items():
- if not weechat.config_is_set_plugin(opt):
- weechat.config_set_plugin(opt, val)
-
- modeCache = ModeCache('chanop_mode_cache.dat')
- modeCache.registerMode('b', 'ban', 'bans')
- modeCache.registerMode('q', 'quiet', 'quiets')
-
- # -------------------------------------------------------------------------
- # remove old chanmask config and save them in shelf
-
- prefix = 'python.%s.chanmask' % SCRIPT_NAME
- infolist = Infolist('option', 'plugins.var.%s.*' % prefix)
- n = len(prefix)
- while infolist.next():
- option = infolist['option_name'][n + 1:]
- server, channel, mode, mask = option.split('.', 3)
- if mode in modeCache:
- cache = modeCache[mode]
- if (server, channel) in cache:
- masklist = cache[server, channel]
- else:
- masklist = cache[server, channel] = MaskList(server, channel)
- if mask in masklist:
- masklist[mask].deserialize(infolist['value'])
- else:
- obj = masklist[mask] = MaskObject(mask)
- obj.deserialize(infolist['value'])
- weechat.config_unset_plugin('chanmask.%s.%s.%s.%s' \
- % (server, channel, mode, mask))
- del infolist
-
- # hook /oop /odeop
- Op().hook()
- Deop().hook()
- # hook /okick /obankick
- if get_config_boolean('enable_multi_kick'):
- cmd_kick = MultiKick()
- cmd_bankick = MultiBanKick()
- else:
- cmd_kick = Kick()
- cmd_bankick = BanKick()
- cmd_kick.hook()
- cmd_bankick.hook()
- # hook /oban /ounban /olist
- Ban().hook()
- UnBan().hook()
- showBans = ShowBans()
- showBans.hook()
- # hook /oquiet /ounquiet
- Quiet().hook()
- UnQuiet().hook()
- # hook /otopic /omode /ovoive /odevoice
- Topic().hook()
- Mode().hook()
- Voice().hook()
- DeVoice().hook()
-
- maskSync.hook()
-
- weechat.hook_config('plugins.var.python.%s.enable_multi_kick' % SCRIPT_NAME,
- 'enable_multi_kick_conf_cb', '')
- weechat.hook_config('plugins.var.python.%s.watchlist.*' % SCRIPT_NAME,
- 'update_chanop_watchlist_cb', '')
- weechat.hook_config('plugins.var.python.%s.enable_bar' % SCRIPT_NAME,
- 'enable_bar_cb', '')
-
- weechat.hook_completion('chanop_unban_mask', 'channelmode b masks', 'unban_mask_cmpl', 'b')
- weechat.hook_completion('chanop_unquiet_mask', 'channelmode q masks', 'unban_mask_cmpl', 'q')
- weechat.hook_completion('chanop_ban_mask', 'completes partial mask', 'ban_mask_cmpl', '')
- weechat.hook_completion('chanop_nicks', 'nicks in cache', 'nicks_cmpl', '')
- weechat.hook_completion('chanop_users', 'usernames in cache', 'users_cmpl', '')
- weechat.hook_completion('chanop_hosts', 'hostnames in cache', 'hosts_cmpl', '')
-
- weechat.hook_signal('*,irc_in_join', 'join_cb', '')
- weechat.hook_signal('*,irc_in_part', 'part_cb', '')
- weechat.hook_signal('*,irc_in_quit', 'quit_cb', '')
- weechat.hook_signal('*,irc_in_nick', 'nick_cb', '')
- weechat.hook_signal('*,irc_in_mode', 'mode_cb', '')
-
- # run our cleaner function every 30 min.
- weechat.hook_timer(1000 * 60 * 30, 0, 0, 'garbage_collector_cb', '')
-
- chanop_bar = PopupBar('chanop_bar', hidden=True,
- items='chanop_status,chanop_ban_matches')
- if get_config_boolean('enable_bar'):
- chanop_bar.new()
- weechat.bar_item_new('chanop_ban_matches', 'item_ban_matches_cb', '')
- weechat.bar_item_new('chanop_status', 'item_status_cb', '')
- weechat.hook_modifier('input_text_content', 'input_content_cb', '')
- else:
- chanop_bar.remove()
-
- weechat.hook_info("chanop_hostmask_from_nick",
- "Returns nick's hostmask if is known. Returns '' otherwise.",
- "nick,server[,channel]", "info_hostmask_from_nick", "")
- weechat.hook_info("chanop_pattern_match",
- "Test if pattern matches text, is case insensible with IRC case rules.",
- "pattern,text", "info_pattern_match", "")
-
-
-# vim:set shiftwidth=4 tabstop=4 softtabstop=4 expandtab textwidth=100:
diff --git a/weechat/python/colorize_nicks.py b/weechat/python/colorize_nicks.py
index 5978253..03dac1d 100644
--- a/weechat/python/colorize_nicks.py
+++ b/weechat/python/colorize_nicks.py
@@ -21,6 +21,8 @@
#
#
# History:
+# 2015-07-28, xt
+# version 21: fix problems with nicks with commas in them
# 2015-04-19, xt
# version 20: fix ignore of nicks in URLs
# 2015-04-18, xt
@@ -73,7 +75,7 @@ w = weechat
SCRIPT_NAME = "colorize_nicks"
SCRIPT_AUTHOR = "xt <xt@bash.no>"
-SCRIPT_VERSION = "20"
+SCRIPT_VERSION = "21"
SCRIPT_LICENSE = "GPL"
SCRIPT_DESC = "Use the weechat nick colors in the chat area"
@@ -287,7 +289,10 @@ def add_nick(data, signal, type_data):
''' Add nick to dict of colored nicks '''
global colored_nicks
- pointer, nick = type_data.split(',')
+ # Nicks can have , in them in some protocols
+ splitted = type_data.split(',')
+ pointer = splitted[0]
+ nick = ",".join(splitted[1:])
if pointer not in colored_nicks:
colored_nicks[pointer] = {}
@@ -302,7 +307,10 @@ def remove_nick(data, signal, type_data):
''' Remove nick from dict with colored nicks '''
global colored_nicks
- pointer, nick = type_data.split(',')
+ # Nicks can have , in them in some protocols
+ splitted = type_data.split(',')
+ pointer = splitted[0]
+ nick = ",".join(splitted[1:])
if pointer in colored_nicks and nick in colored_nicks[pointer]:
del colored_nicks[pointer][nick]
diff --git a/weechat/python/go.py b/weechat/python/go.py
index 0e5b6bd..476b824 100644
--- a/weechat/python/go.py
+++ b/weechat/python/go.py
@@ -21,6 +21,10 @@
#
# History:
#
+# 2015-11-12, nils_2 <weechatter@arcor.de>
+# version 2.1: fix problem with buffer short_name "weechat", using option
+# "use_core_instead_weechat", see:
+# https://github.com/weechat/weechat/issues/574
# 2014-05-12, Sébastien Helleu <flashcode@flashtux.org>:
# version 2.0: add help on options, replace option "sort_by_activity" by
# "sort" (add sort by name and first match at beginning of
@@ -80,7 +84,7 @@ from __future__ import print_function
SCRIPT_NAME = 'go'
SCRIPT_AUTHOR = 'Sébastien Helleu <flashcode@flashtux.org>'
-SCRIPT_VERSION = '2.0'
+SCRIPT_VERSION = '2.1'
SCRIPT_LICENSE = 'GPL3'
SCRIPT_DESC = 'Quick jump to buffers'
@@ -279,7 +283,9 @@ def go_matching_buffers(strinput):
name = weechat.infolist_string(infolist, 'short_name')
else:
name = weechat.infolist_string(infolist, 'name')
- if name == 'weechat' and go_option_enabled('use_core_instead_weechat'):
+ if name == 'weechat' \
+ and go_option_enabled('use_core_instead_weechat') \
+ and weechat.infolist_string(infolist, 'plugin_name') == 'core':
name = 'core'
number = weechat.infolist_integer(infolist, 'number')
full_name = weechat.infolist_string(infolist, 'full_name')
diff --git a/weechat/python/infolist.py b/weechat/python/infolist.py
new file mode 100644
index 0000000..471f63c
--- /dev/null
+++ b/weechat/python/infolist.py
@@ -0,0 +1,195 @@
+#
+# Copyright (C) 2008-2012 Sebastien Helleu <flashcode@flashtux.org>
+#
+# 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/>.
+#
+
+# Display infolist in a buffer.
+#
+# History:
+# 2012-10-02, nils_2 <freenode.#weechat>:
+# version 0.5: switch to infolist buffer (if exists) when command /infolist
+# is called with arguments, add some examples to help page
+# 2012-01-03, Sebastien Helleu <flashcode@flashtux.org>:
+# version 0.4: make script compatible with Python 3.x
+# 2010-01-23, m4v <lambdae2@gmail.com>:
+# version 0.3: user can give a pointer as argument
+# 2010-01-18, Sebastien Helleu <flashcode@flashtux.org>:
+# version 0.2: use tag "no_filter" for lines displayed, fix display bug
+# when infolist is empty
+# 2009-11-30, Sebastien Helleu <flashcode@flashtux.org>:
+# version 0.1: first version
+# 2008-12-12, Sebastien Helleu <flashcode@flashtux.org>:
+# script creation
+
+SCRIPT_NAME = "infolist"
+SCRIPT_AUTHOR = "Sebastien Helleu <flashcode@flashtux.org>"
+SCRIPT_VERSION = "0.5"
+SCRIPT_LICENSE = "GPL3"
+SCRIPT_DESC = "Display infolist in a buffer"
+
+import_ok = True
+
+try:
+ import weechat
+except:
+ print("This script must be run under WeeChat.")
+ print("Get WeeChat now at: http://www.weechat.org/")
+ import_ok = False
+
+infolist_buffer = ""
+infolist_var_type = { "i": "int",
+ "s": "str",
+ "p": "ptr",
+ "t": "tim",
+ "b": "buf",
+ }
+
+
+def infolist_buffer_set_title(buffer):
+ # get list of infolists available
+ list = ""
+ infolist = weechat.infolist_get("hook", "", "infolist")
+ while weechat.infolist_next(infolist):
+ list += " %s" % weechat.infolist_string(infolist, "infolist_name")
+ weechat.infolist_free(infolist)
+
+ # set buffer title
+ weechat.buffer_set(buffer, "title",
+ "%s %s | Infolists:%s" % (SCRIPT_NAME, SCRIPT_VERSION, list))
+
+def infolist_display(buffer, args):
+ global infolist_var_type
+
+ items = args.split(" ", 1)
+ infolist_args = ""
+ infolist_pointer = ""
+ if len(items) >= 2:
+ infolist_args = items[1]
+ if infolist_args[:2] == "0x":
+ infolist_pointer, sep, infolist_args = infolist_args.partition(" ")
+ elif infolist_args[:3] == "\"\" ":
+ infolist_args = infolist_args[3:]
+
+ infolist = weechat.infolist_get(items[0], infolist_pointer, infolist_args)
+ if infolist == "":
+ weechat.prnt_date_tags(buffer, 0, "no_filter",
+ "%sInfolist '%s' not found."
+ % (weechat.prefix("error"), items[0]))
+ return weechat.WEECHAT_RC_OK
+
+ item_count = 0
+ weechat.buffer_clear(buffer)
+ weechat.prnt_date_tags(buffer, 0, "no_filter",
+ "Infolist '%s', with pointer '%s' and arguments '%s':" % (items[0],
+ infolist_pointer, infolist_args))
+ weechat.prnt(buffer, "")
+ count = 0
+ while weechat.infolist_next(infolist):
+ item_count += 1
+ if item_count > 1:
+ weechat.prnt(buffer, "")
+
+ fields = weechat.infolist_fields(infolist).split(",")
+ prefix = "%s[%s%d%s]\t" % (weechat.color("chat_delimiters"),
+ weechat.color("chat_buffer"),
+ item_count,
+ weechat.color("chat_delimiters"))
+ for field in fields:
+ (type, name) = field.split(":", 1)
+ value = ""
+ quote = ""
+ if type == "i":
+ value = weechat.infolist_integer(infolist, name)
+ elif type == "s":
+ value = weechat.infolist_string(infolist, name)
+ quote = "'"
+ elif type == "p":
+ value = weechat.infolist_pointer(infolist, name)
+ elif type == "t":
+ value = weechat.infolist_time(infolist, name)
+ name_end = "." * (30 - len(name))
+ weechat.prnt_date_tags(buffer, 0, "no_filter",
+ "%s%s%s: %s%s%s %s%s%s%s%s%s" %
+ (prefix, name, name_end,
+ weechat.color("brown"), infolist_var_type[type],
+ weechat.color("chat"),
+ weechat.color("chat"), quote,
+ weechat.color("cyan"), value,
+ weechat.color("chat"), quote))
+ prefix = ""
+ count += 1
+ if count == 0:
+ weechat.prnt_date_tags(buffer, 0, "no_filter", "Empty infolist.")
+ weechat.infolist_free(infolist)
+ return weechat.WEECHAT_RC_OK
+
+def infolist_buffer_input_cb(data, buffer, input_data):
+ if input_data == "q" or input_data == "Q":
+ weechat.buffer_close(buffer)
+ else:
+ infolist_display(buffer, input_data)
+ return weechat.WEECHAT_RC_OK
+
+def infolist_buffer_close_cb(data, buffer):
+ global infolist_buffer
+
+ infolist_buffer = ""
+ return weechat.WEECHAT_RC_OK
+
+def infolist_buffer_new():
+ global infolist_buffer
+
+ infolist_buffer = weechat.buffer_search("python", "infolist")
+ if infolist_buffer == "":
+ infolist_buffer = weechat.buffer_new("infolist",
+ "infolist_buffer_input_cb", "",
+ "infolist_buffer_close_cb", "")
+ if infolist_buffer != "":
+ infolist_buffer_set_title(infolist_buffer)
+ weechat.buffer_set(infolist_buffer, "localvar_set_no_log", "1")
+ weechat.buffer_set(infolist_buffer, "time_for_each_line", "0")
+ weechat.buffer_set(infolist_buffer, "display", "1")
+
+def infolist_cmd(data, buffer, args):
+ global infolist_buffer
+
+ if infolist_buffer == "":
+ infolist_buffer_new()
+ if infolist_buffer != "" and args != "":
+ infolist_display(infolist_buffer, args)
+ weechat.buffer_set(infolist_buffer, "display", "1");
+
+ return weechat.WEECHAT_RC_OK
+
+if __name__ == "__main__" and import_ok:
+ if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE,
+ SCRIPT_DESC, "", ""):
+ weechat.hook_command("infolist", "Display infolist in a buffer",
+ "[infolist [pointer] [arguments]]",
+ " infolist: name of infolist\n"
+ " pointer: optional pointer for infolist (\"\" for none)\n"
+ "arguments: optional arguments for infolist\n\n"
+ "Command without argument will open buffer used "
+ "to display infolists.\n\n"
+ "On infolist buffer, you can enter name of an "
+ "infolist, with optional arguments.\n"
+ "Enter 'q' to close infolist buffer.\n\n"
+ "Examples:\n"
+ " Show information about nick \"FlashCode\" in channel \"#weechat\" on server \"freenode\":\n"
+ " /infolist irc_nick freenode,#weechat,FlashCode\n"
+ " Show nicklist from a specific buffer:\n"
+ " /infolist nicklist <buffer pointer>"
+ "",
+ "%(infolists)", "infolist_cmd", "")
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", "")
diff --git a/weechat/python/nsb.py b/weechat/python/nsb.py
new file mode 100644
index 0000000..d4c9fc8
--- /dev/null
+++ b/weechat/python/nsb.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+import weechat
+
+operation = {}
+hooks = {}
+
+weechat.register('nsb', 'Johannes Löthberg', '0.0.1', 'ISC', 'Ban users by NickServ nick', 'unload', 'UTF-8')
+hooks['command'] = weechat.hook_command("nsb", "ban nick by ns account name",
+ "ban <nick> || unban <nick>",
+ " ban: Ban a user\nunban: Unban a user\n",
+ "ban %(nick) || unban %(nick)",
+ "ns_ban_cb", "")
+
+infolist_buffer = ""
+
+def infolist_display(buffer, args):
+ items = args.split(" ", 1)
+ infolist_args = ""
+ infolist_pointer = ""
+ if len(items) >= 2:
+ infolist_args = items[1]
+ if infolist_args[:2] == "0x":
+ infolist_pointer, sep, infolist_args = infolist_args.partition(" ")
+ elif infolist_args[:3] == "\"\" ":
+ infolist_args = infolist_args[3:]
+
+ infolist = weechat.infolist_get(items[0], infolist_pointer, infolist_args)
+ if infolist == "":
+ weechat.prnt_date_tags(buffer, 0, "no_filter",
+ "%sInfolist '%s' not found."
+ % (weechat.prefix("error"), items[0]))
+ return weechat.WEECHAT_RC_OK
+
+ item_count = 0
+ weechat.buffer_clear(buffer)
+ weechat.prnt(buffer, "")
+ count = 0
+ while weechat.infolist_next(infolist):
+ item_count += 1
+ if item_count > 1:
+ weechat.prnt(buffer, "")
+
+ fields = weechat.infolist_fields(infolist).split(",")
+ for field in fields:
+ (type, name) = field.split(":", 1)
+ if name != 'host':
+ continue
+ value = weechat.infolist_string(infolist, name)
+ name_end = "." * (30 - len(name))
+ weechat.prnt(buffer, "%s: %s%s" %
+ (name, weechat.color("cyan"), value))
+ prefix = ""
+ count += 1
+ if count == 0:
+ weechat.prnt_date_tags(buffer, 0, "no_filter", "Empty infolist.")
+ weechat.infolist_free(infolist)
+ return weechat.WEECHAT_RC_OK
+
+def ns_ban_cb(data, buffer, args):
+ args = args.split()
+ oper = args[0]
+ nick = args[1]
+
+ channel = weechat.buffer_get_string(buffer, 'localvar_channel')
+ server = weechat.buffer_get_string(buffer, 'localvar_server')
+
+ if oper == 'ban':
+ found = False
+ infolist = weechat.infolist_get("irc_nick", "", "{},{},{}".format(server, channel, nick))
+ while weechat.infolist_next(infolist):
+ found = True
+ account = weechat.infolist_string(infolist, "account")
+ if account:
+ weechat.command("", '/mode +b $a:{}'.format(account))
+
+ weechat.infolist_free(infolist)
+
+ if not found:
+ # TODO: Handle numeric 315 too, ‘End of /WHO list’
+ hooks['who'] = weechat.hook_modifier("irc_in_354", "who_mod_cb", "")
+ weechat.command("", "/who %s n%%an" % args[1])
+ operation[nick] = oper
+
+ else:
+ # TODO: Handle numeric 315 too, ‘End of /WHO list’
+ hooks['who'] = weechat.hook_modifier("irc_in_354", "who_mod_cb", "")
+ weechat.command("", "/who %s n%%an" % args[1])
+
+ operation[nick] = oper
+
+ return weechat.WEECHAT_RC_OK
+
+def who_mod_cb(data, modifier, modifier_data, string):
+ ns_name = string.split()[-1]
+ nick = string.split()[-2]
+ mode = operation.pop(nick)
+
+ weechat.unhook(hooks.pop('who'))
+
+ if mode == 'ban':
+ weechat.command("", '/mode +b $a:{}'.format(ns_name))
+ elif mode == 'unban':
+ weechat.command("", '/mode -b $a:{}'.format(ns_name))
+ elif not mode:
+ weechat.prnt('', 'nsb: "mode" variable not set in who_cb')
+ else:
+ weechat.prnt('', 'nsb: err, something bork')
+
+ return ""
+
+def unload():
+ weechat.unhook(hooks['command'])
+ return weechat.WEECHAT_RC_OK