mirror of
https://github.com/jlu5/SupyPlugins.git
synced 2025-04-26 13:01:07 -05:00
825 lines
37 KiB
Python
825 lines
37 KiB
Python
###
|
|
# Copyright (c) 2010, quantumlemur
|
|
# Copyright (c) 2011, Valentin Lorentz
|
|
# Copyright (c) 2013-2014, James Lu (GLolol)
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright notice,
|
|
# this list of conditions, and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
|
# this list of conditions, and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
# * Neither the name of the author of this software nor the name of
|
|
# contributors to this software may be used to endorse or promote products
|
|
# derived from this software without specific prior written consent.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
###
|
|
|
|
import re
|
|
import copy
|
|
import itertools
|
|
import supybot.log as log
|
|
import supybot.conf as conf
|
|
import supybot.utils as utils
|
|
import supybot.world as world
|
|
from supybot.commands import *
|
|
import supybot.irclib as irclib
|
|
import supybot.ircmsgs as ircmsgs
|
|
import supybot.ircutils as ircutils
|
|
import supybot.registry as registry
|
|
import supybot.callbacks as callbacks
|
|
from supybot.utils.structures import TimeoutQueue
|
|
try:
|
|
from supybot.i18n import PluginInternationalization
|
|
from supybot.i18n import internationalizeDocstring
|
|
_ = PluginInternationalization('RelayLink')
|
|
except:
|
|
# This are useless functions that's allow to run the plugin on a bot
|
|
# without the i18n plugin
|
|
_ = lambda x:x
|
|
internationalizeDocstring = lambda x:x
|
|
|
|
@internationalizeDocstring
|
|
class RelayLink(callbacks.Plugin):
|
|
threaded = True
|
|
|
|
class Relay():
|
|
def __init__(self, sourceChannel, sourceNetwork, targetChannel,
|
|
targetNetwork, channelRegex, networkRegex, messageRegex):
|
|
self.sourceChannel = sourceChannel
|
|
self.sourceNetwork = sourceNetwork
|
|
self.targetChannel = targetChannel
|
|
self.targetNetwork = targetNetwork
|
|
self.channelRegex = channelRegex
|
|
self.networkRegex = networkRegex
|
|
self.messageRegex = messageRegex
|
|
self.hasTargetIRC = False
|
|
self.hasSourceIRCChannels = False
|
|
|
|
def __init__(self, irc):
|
|
self.__parent = super(RelayLink, self)
|
|
self.__parent.__init__(irc)
|
|
self._loadFromConfig()
|
|
self.ircstates = {}
|
|
for IRC in world.ircs:
|
|
self.addIRC(IRC)
|
|
floodProtectTimeout = conf.supybot.plugins.RelayLink.antiflood.seconds
|
|
self.floodCounter = TimeoutQueue(floodProtectTimeout)
|
|
self.floodActivated = False
|
|
try:
|
|
conf.supybot.plugins.RelayLink.substitutes.addCallback(
|
|
self._loadFromConfig)
|
|
conf.supybot.plugins.RelayLink.relays.addCallback(
|
|
self._loadFromConfig)
|
|
except registry.NonExistentRegistryEntry:
|
|
log.error("Your version of Supybot is not compatible with "
|
|
"configuration hooks. So, RelayLink won't be able "
|
|
"to reload the configuration if you use the Config "
|
|
"plugin.")
|
|
|
|
def _loadFromConfig(self, name=None):
|
|
self.relays = []
|
|
for relay in self.registryValue('relays').split(' || '):
|
|
if relay.endswith('|'):
|
|
relay += ' '
|
|
relay = relay.split(' | ')
|
|
if not len(relay) == 5:
|
|
continue
|
|
try:
|
|
self.relays.append(self.Relay(relay[0],
|
|
relay[1],
|
|
relay[2],
|
|
relay[3],
|
|
re.compile('^%s$' % relay[0], re.I),
|
|
re.compile('^%s$' % relay[1], re.I),
|
|
re.compile(relay[4])))
|
|
except:
|
|
log.error('Failed adding relay: %r' % relay)
|
|
|
|
self.nickSubstitutions = {}
|
|
for substitute in self.registryValue('substitutes').split(' || '):
|
|
if substitute.endswith('|'):
|
|
substitute += ' '
|
|
substitute = substitute.split(' | ')
|
|
if not len(substitute) == 2:
|
|
continue
|
|
self.nickSubstitutions[substitute[0]] = substitute[1]
|
|
|
|
def simpleHash(self, s):
|
|
colors = ["05", "04", "03", "09", "02", "12",
|
|
"06", "13", "10", "11", "07"]
|
|
num = 0
|
|
for i in s:
|
|
num += ord(i)
|
|
num = num % 11
|
|
return colors[num]
|
|
|
|
def getPrivmsgData(self, channel, nick, text, colored):
|
|
color = self.simpleHash(nick)
|
|
nickprefix = ''
|
|
if nick in self.nickSubstitutions:
|
|
nick = self.nickSubstitutions[nick]
|
|
if not self.registryValue('nicks', channel):
|
|
nick = ''
|
|
elif self.registryValue('noHighlight', channel):
|
|
nickprefix = '-'
|
|
if re.match('^\x01ACTION .*\x01$', text):
|
|
text = text.strip('\x01')
|
|
text = text[7:]
|
|
if colored:
|
|
return ('%(network)s* %(nickprefix)s\x03%(color)s%(nick)s\x03 %(text)s',
|
|
{'nick': nick, 'color': color, 'text': text,
|
|
'nickprefix': nickprefix})
|
|
else:
|
|
return ('%(network)s* %(nickprefix)s%(nick)s %(text)s',
|
|
{'nick': nick, 'text': text, 'nickprefix': nickprefix})
|
|
else:
|
|
if colored:
|
|
return ('%(network)s<%(nickprefix)s\x03%(color)s%(nick)s\x03> %(text)s',
|
|
{'color': color, 'nick': nick, 'text': text,
|
|
'nickprefix': nickprefix})
|
|
else:
|
|
return ('%(network)s<%(nickprefix)s%(nick)s> %(text)s',
|
|
{'nick': nick, 'text': text, 'nickprefix': nickprefix})
|
|
return s
|
|
|
|
@internationalizeDocstring
|
|
def list(self, irc, msg, args):
|
|
"""takes no arguments
|
|
|
|
Returns all the defined relay links."""
|
|
if irc.nested:
|
|
irc.error('This command cannot be nested.', Raise=True)
|
|
elif not self.relays:
|
|
irc.reply(_('This is no relay enabled. Use "RelayLink add" or "RelayLink'
|
|
' addall" to add one.'))
|
|
return
|
|
replies = []
|
|
for relay in self.relays:
|
|
if relay.hasTargetIRC:
|
|
hasIRC = '\x0309Link healthy!\x03'
|
|
else:
|
|
hasIRC = ('\x0312IRC object not refreshed yet. Reloading the plugin may '
|
|
'help.\x03')
|
|
s = '\x02%s\x02 on \x02%s\x02 => \x02%s\x02 on \x02%s\x02 [%s]'
|
|
if not self.registryValue('color', msg.args[0]):
|
|
hasIRC, s = map(ircutils.stripFormatting, (hasIRC, s))
|
|
replies.append(s %
|
|
(relay.sourceChannel,
|
|
relay.sourceNetwork,
|
|
relay.targetChannel,
|
|
relay.targetNetwork,
|
|
hasIRC))
|
|
irc.replies(replies)
|
|
|
|
def doPrivmsg(self, irc, msg):
|
|
self.addIRC(irc)
|
|
channel = msg.args[0]
|
|
s = msg.args[1]
|
|
s, args = self.getPrivmsgData(channel, msg.nick, s,
|
|
self.registryValue('color', channel))
|
|
if channel not in irc.state.channels: # in private
|
|
# cuts off the end of commands, so that passwords
|
|
# won't be revealed in relayed PM's
|
|
if callbacks.addressed(irc.nick, msg):
|
|
if self.registryValue('color', channel):
|
|
color = '\x0314'
|
|
match = '(>\017 \w+) .*'
|
|
else:
|
|
color = ''
|
|
match = '(> \w+) .*'
|
|
s = re.sub(match, '\\1 %s[%s]' % (color, _('truncated')), s)
|
|
s = '(via PM) %s' % s
|
|
self.sendToOthers(irc, channel, s, args, isPrivmsg=True)
|
|
|
|
def outFilter(self, irc, msg):
|
|
if msg.command == 'PRIVMSG':
|
|
if not msg.relayedMsg:
|
|
if msg.args[0] in irc.state.channels:
|
|
s, args = self.getPrivmsgData(msg.args[0], irc.nick, msg.args[1],
|
|
self.registryValue('color', msg.args[0]))
|
|
self.sendToOthers(irc, msg.args[0], s, args, isPrivmsg=True)
|
|
return msg
|
|
|
|
def doPing(self, irc, msg):
|
|
self.addIRC(irc)
|
|
|
|
def doMode(self, irc, msg):
|
|
self.addIRC(irc)
|
|
args = {'nick': msg.nick, 'channel': msg.args[0],
|
|
'mode': ' '.join(msg.args[1:]), 'userhost': ''}
|
|
if self.registryValue("noHighlight", msg.args[0]):
|
|
args['nick'] = '-'+msg.nick
|
|
if self.registryValue('color', msg.args[0]):
|
|
args['nick'] = '\x03%s%s\x03' % (self.simpleHash(msg.nick), args['nick'])
|
|
if self.registryValue('hostmasks', msg.args[0]) and "." not in \
|
|
msg.nick:
|
|
args['userhost'] = ' (%s@%s)' % (msg.user, msg.host)
|
|
s = ('%(network)s%(nick)s%(userhost)s set mode %(mode)s on'
|
|
' %(channel)s')
|
|
self.sendToOthers(irc, msg.args[0], s, args)
|
|
|
|
def doJoin(self, irc, msg):
|
|
args = {'nick': msg.nick, 'channel': msg.args[0], 'userhost': ''}
|
|
if self.registryValue("noHighlight", msg.args[0]):
|
|
args['nick'] = '-'+msg.nick
|
|
if irc.nick == msg.nick:
|
|
if self.registryValue('color'):
|
|
s = '%(network)s\x0309*** Relay joined to %(channel)s'
|
|
else:
|
|
s = '%(network)s*** Relay joined to %(channel)s'
|
|
else:
|
|
if self.registryValue('color', msg.args[0]):
|
|
args['nick'] = '\x03%s%s\x03' % (self.simpleHash(msg.nick), args['nick'])
|
|
if self.registryValue('hostmasks', msg.args[0]):
|
|
args['userhost'] = ' (%s@%s)' % (msg.user, msg.host)
|
|
s = '%(network)s%(nick)s%(userhost)s has joined %(channel)s'
|
|
self.addIRC(irc)
|
|
self.sendToOthers(irc, msg.args[0], s, args)
|
|
|
|
def doPart(self, irc, msg):
|
|
args = {'nick': msg.nick, 'channel': msg.args[0], 'message': '',
|
|
'userhost': ''}
|
|
if msg.nick == irc.nick:
|
|
if self.registryValue('color'):
|
|
s = '%(network)s\x0308*** Relay parted from %(channel)s'
|
|
else:
|
|
s = '%(network)s*** Relay parted from %(channel)s'
|
|
else:
|
|
if self.registryValue("noHighlight", msg.args[0]):
|
|
args['nick'] = '-'+msg.nick
|
|
self.addIRC(irc)
|
|
if self.registryValue('color', msg.args[0]):
|
|
args['nick'] = '\x03%s%s\x03' % (self.simpleHash(msg.nick), args['nick'])
|
|
if self.registryValue('hostmasks', msg.args[0]):
|
|
args['userhost'] = ' (%s@%s)' % (msg.user, msg.host)
|
|
try:
|
|
args['message'] = ' (%s)' % (msg.args[1])
|
|
except IndexError: pass
|
|
s = '%(network)s%(nick)s%(userhost)s has parted %(channel)s%(message)s'
|
|
self.sendToOthers(irc, msg.args[0], s, args)
|
|
|
|
def doKick(self, irc, msg):
|
|
self.addIRC(irc)
|
|
args = {'kicked': msg.args[1], 'channel': msg.args[0],
|
|
'kicker': msg.nick, 'message': msg.args[2], 'userhost': ''}
|
|
if args['kicked'] == irc.nick:
|
|
if self.registryValue('color', msg.args[0]):
|
|
s = '%(network)s\x0308*** Relay kicked from %(channel)s'
|
|
else:
|
|
s = '%(network)s*** Relay kicked from %(channel)s'
|
|
else:
|
|
if self.registryValue('color', msg.args[0]):
|
|
args['kicked'] = '\x03%s%s\x03' % (self.simpleHash(msg.args[1]), args['kicked'])
|
|
if self.registryValue('hostmasks', msg.args[0]):
|
|
# The IRC protocol only sends the hostmask of the kicker, so we'll need
|
|
# to use an alternate method to fetch the host of the person being
|
|
# kicked. (in this case, using ircutils)
|
|
h = ircutils.splitHostmask(irc.state.nickToHostmask(msg.args[1]))
|
|
args['userhost'] = ' (%s@%s)' % (h[1], h[2])
|
|
s = ('%(network)s%(kicked)s%(userhost)s has been kicked from '
|
|
'%(channel)s by %(kicker)s (%(message)s)')
|
|
self.sendToOthers(irc, msg.args[0], s, args)
|
|
|
|
def doNick(self, irc, msg):
|
|
self.addIRC(irc)
|
|
if self.registryValue("noHighlight"):
|
|
args = {'oldnick': '-'+msg.nick, 'newnick': '-'+msg.args[0]}
|
|
else:
|
|
args = {'oldnick': msg.nick, 'newnick': msg.args[0]}
|
|
if self.registryValue('color'):
|
|
args['oldnick'] = '\x03%s%s\x03' % (self.simpleHash(msg.nick), args['oldnick'])
|
|
args['newnick'] = '\x03%s%s\x03' % (self.simpleHash(msg.args[0]), args['newnick'])
|
|
s = '%(network)s%(oldnick)s is now known as %(newnick)s'
|
|
try:
|
|
chandict = irc.state.channels.iteritems()
|
|
except AttributeError: # Python 3 compatibility
|
|
chandict = irc.state.channels.items()
|
|
for (channel, c) in chandict:
|
|
if msg.args[0] in c.users:
|
|
self.sendToOthers(irc, channel, s, args)
|
|
|
|
def doQuit(self, irc, msg):
|
|
args = {'nick': msg.nick, 'message': msg.args[0]}
|
|
if self.registryValue("noHighlight"): args['nick'] = '-' + msg.nick
|
|
if msg.nick == irc.nick: # It's us.
|
|
if self.registryValue('color'):
|
|
s = '%(network)s\x0304*** ERROR: Relay disconnected...'
|
|
else:
|
|
s = '%(network)s*** ERROR: Relay disconnected...'
|
|
else:
|
|
if self.registryValue('color'):
|
|
args['nick'] = '\x03%s%s\x03' % (self.simpleHash(msg.nick), args['nick'])
|
|
s = '%(network)s%(nick)s has quit (%(message)s)'
|
|
self.sendToOthers(irc, None, s, args, msg.nick)
|
|
self.addIRC(irc)
|
|
|
|
def sendToOthers(self, irc, channel, s, args, nick=None, isPrivmsg=False):
|
|
assert channel is not None or nick is not None
|
|
def format_(relay, s, args):
|
|
if 'network' not in args:
|
|
if self.registryValue('includeNetwork', relay.targetChannel):
|
|
if self.registryValue('color', relay.targetChannel):
|
|
args['network'] = "\x02[\x03%s%s\x03]\x02 " % \
|
|
(self.simpleHash(irc.network), irc.network)
|
|
else:
|
|
args['network'] = "[%s] " % irc.network
|
|
if not isPrivmsg and not self.registryValue("noHighlight", channel):
|
|
args['network'] += "- "
|
|
else:
|
|
args['network'] = ''
|
|
return s % args
|
|
def send(s):
|
|
if not relay.hasTargetIRC:
|
|
self.log.debug('RelayLink: IRC %s not yet scraped.',
|
|
relay.targetNetwork)
|
|
elif relay.targetIRC.zombie:
|
|
self.log.debug('RelayLink: IRC %s appears to be a zombie',
|
|
relay.targetNetwork)
|
|
elif irc.isChannel(relay.targetChannel) and \
|
|
relay.targetChannel not in relay.targetIRC.state.channels:
|
|
self.log.debug('RelayLink: I\'m not in %s on %s.',
|
|
relay.targetChannel, relay.targetNetwork)
|
|
else:
|
|
if isPrivmsg or \
|
|
self.registryValue('nonPrivmsgs', channel) == 'privmsg':
|
|
msg = ircmsgs.privmsg(relay.targetChannel, s)
|
|
elif self.registryValue('nonPrivmsgs', channel) == 'notice':
|
|
msg = ircmsgs.notice(relay.targetChannel, s)
|
|
else:
|
|
return
|
|
msg.tag('relayedMsg')
|
|
relay.targetIRC.sendMsg(msg)
|
|
|
|
msgs = self.registryValue("antiflood.messages")
|
|
if self.registryValue("antiflood.enable") and msgs and \
|
|
len(self.floodCounter) > msgs:
|
|
if not self.floodActivated:
|
|
secs = self.registryValue("antiflood.seconds")
|
|
limit = "({} messages in {} seconds)".format(msgs,secs)
|
|
self.log.info("RelayLink: flood protection triggered on %s %s", irc.network, limit)
|
|
s = ("%(network)s*** Flood detected {}. Not relaying messages for {} seconds!".format(limit, secs))
|
|
self.floodActivated = True
|
|
if self.registryValue('antiflood.announce'):
|
|
for relay in self.relays:
|
|
new_s = format_(relay, s, args)
|
|
if relay.channelRegex.match(channel) and \
|
|
relay.networkRegex.match(irc.network) and \
|
|
relay.messageRegex.search(new_s):
|
|
send(new_s)
|
|
return
|
|
else: self.floodActivated = False
|
|
|
|
if channel is None:
|
|
for relay in self.relays:
|
|
if not relay.hasSourceIRCChannels:
|
|
continue
|
|
for channel in relay.sourceIRCChannels:
|
|
new_s = format_(relay, s, args)
|
|
if nick in relay.sourceIRCChannels[channel].users and \
|
|
relay.channelRegex.match(channel) and \
|
|
relay.networkRegex.match(irc.network)and \
|
|
relay.messageRegex.search(new_s):
|
|
if nick != irc.nick: self.floodCounter.enqueue(0)
|
|
send(new_s)
|
|
else:
|
|
for relay in self.relays:
|
|
new_s = format_(relay, s, args)
|
|
if relay.channelRegex.match(channel) and \
|
|
relay.networkRegex.match(irc.network) and \
|
|
relay.messageRegex.search(new_s):
|
|
if nick != irc.nick:
|
|
if isPrivmsg: self.floodCounter.enqueue(0)
|
|
else: self.floodCounter.enqueue(0)
|
|
send(new_s)
|
|
|
|
def addIRC(self, irc):
|
|
match = False
|
|
for relay in self.relays:
|
|
if relay.sourceNetwork == irc.network:
|
|
relay.sourceIRCChannels = copy.deepcopy(irc.state.channels)
|
|
relay.hasSourceIRCChannels = True
|
|
if relay.targetNetwork == irc.network and not relay.hasTargetIRC:
|
|
relay.targetIRC = irc
|
|
relay.hasTargetIRC = True
|
|
|
|
@internationalizeDocstring
|
|
def nicks(self, irc, msg, args, channel, optlist):
|
|
"""[<channel>] [--count]
|
|
|
|
Returns the nicks of the people in the linked channels.
|
|
<channel> is only necessary if the message
|
|
isn't sent on the channel itself.
|
|
If --count is specified, only the amount of """
|
|
keys = [option for (option, arg) in optlist]
|
|
if irc.nested and 'count' not in keys:
|
|
irc.error('This command cannot be nested.', Raise=True)
|
|
try:
|
|
c = irc.state.channels[channel]
|
|
except KeyError:
|
|
irc.error("Unknown channel '%s'." % channel, Raise=True)
|
|
if msg.nick not in c.users:
|
|
self.log.warning('RelayLink: %s on %s attempted to view'
|
|
' nicks in %s without being in it.', msg.nick, irc.network, channel)
|
|
irc.error(('You are not in %s.' % channel), Raise=True)
|
|
# Include the local channel for nicks output
|
|
totalUsers = len(c.users)
|
|
totalChans = 1
|
|
users = []
|
|
for s in c.users:
|
|
s = s.strip()
|
|
if not s:
|
|
continue
|
|
if s in c.ops:
|
|
users.append('@%s' % s)
|
|
elif s in c.halfops:
|
|
users.append('%%%s' % s)
|
|
elif s in c.voices:
|
|
users.append('+%s' % s)
|
|
else:
|
|
users.append(s)
|
|
s = _('%d users in %s on %s: %s') % (totalUsers,
|
|
channel, irc.network,
|
|
utils.str.commaAndify(users))
|
|
if 'count' not in keys:
|
|
irc.reply(s, private=True)
|
|
for relay in self.relays:
|
|
if relay.sourceChannel == channel and \
|
|
relay.sourceNetwork.lower() == irc.network.lower():
|
|
totalChans += 1
|
|
if not relay.hasTargetIRC:
|
|
irc.reply(_("Relays for network %s hasn't been refreshed yet, "
|
|
"try reloading the plugin or waiting for a minute.") % \
|
|
relay.targetNetwork)
|
|
else:
|
|
users = []
|
|
ops = []
|
|
halfops = []
|
|
voices = []
|
|
normals = []
|
|
numUsers = 0
|
|
target = relay.targetChannel
|
|
|
|
channels = relay.targetIRC.state.channels
|
|
found = False
|
|
for key, channel_ in channels.items():
|
|
if ircutils.toLower(relay.targetChannel) \
|
|
== ircutils.toLower(key):
|
|
found = True
|
|
break
|
|
if not found:
|
|
continue
|
|
|
|
for s in channel_.users:
|
|
s = s.strip()
|
|
if not s:
|
|
continue
|
|
numUsers += 1
|
|
totalUsers += 1
|
|
if s in channel_.ops:
|
|
users.append('@%s' % s)
|
|
elif s in channel_.halfops:
|
|
users.append('%%%s' % s)
|
|
elif s in channel_.voices:
|
|
users.append('+%s' % s)
|
|
else:
|
|
users.append(s)
|
|
#utils.sortBy(ircutils.toLower, ops)
|
|
#utils.sortBy(ircutils.toLower, halfops)
|
|
#utils.sortBy(ircutils.toLower, voices)
|
|
#utils.sortBy(ircutils.toLower, normals)
|
|
users.sort()
|
|
msg.tag('relayedMsg')
|
|
s = _('%d users in %s on %s: %s') % (numUsers,
|
|
relay.targetChannel,
|
|
relay.targetNetwork,
|
|
utils.str.commaAndify(users))
|
|
if 'count' not in keys: irc.reply(s, private=True)
|
|
if not irc.nested:
|
|
irc.reply("Total users across %d channels: %d. " % \
|
|
(totalChans, totalUsers), private=False if 'count' in keys else True)
|
|
else:
|
|
irc.reply(totalUsers)
|
|
irc.noReply()
|
|
nicks = wrap(nicks, ['Channel', getopts({'count':''})])
|
|
|
|
# The following functions handle configuration
|
|
def _writeToConfig(self, from_, to, regexp, add):
|
|
from_, to = from_.split('@'), to.split('@')
|
|
args = from_
|
|
args.extend(to)
|
|
args.append(regexp)
|
|
s = ' | '.join(args)
|
|
|
|
currentConfig = self.registryValue('relays')
|
|
config = list(map(ircutils.IrcString, currentConfig.split(' || ')))
|
|
if add:
|
|
if s in config:
|
|
return False
|
|
if currentConfig == '':
|
|
self.setRegistryValue('relays', value=s)
|
|
else:
|
|
self.setRegistryValue('relays',
|
|
value=' || '.join((currentConfig, s)))
|
|
else:
|
|
if s not in config:
|
|
return False
|
|
config.remove(s)
|
|
self.setRegistryValue('relays', value=' || '.join(config))
|
|
return True
|
|
|
|
def _parseOptlist(self, irc, msg, tupleOptlist, batchadd=False):
|
|
optlist = {}
|
|
for key, value in tupleOptlist:
|
|
optlist.update({key: value})
|
|
if not batchadd:
|
|
if 'from' not in optlist and 'to' not in optlist:
|
|
irc.error(_('You must give at least --from or --to.'))
|
|
return
|
|
for name in ('from', 'to'):
|
|
if name not in optlist:
|
|
optlist.update({name: '%s@%s' % (msg.args[0], irc.network)})
|
|
if 'reciprocal' in optlist:
|
|
optlist.update({'reciprocal': True})
|
|
else:
|
|
optlist.update({'reciprocal': False})
|
|
if not len(optlist['from'].split('@')) == 2:
|
|
irc.error(_('--from should be like "--from #channel@network"'))
|
|
return
|
|
if not len(optlist['to'].split('@')) == 2:
|
|
irc.error(_('--to should be like "--to #channel@network"'))
|
|
return
|
|
if 'regexp' not in optlist:
|
|
optlist.update({'regexp': ''})
|
|
return optlist
|
|
|
|
@internationalizeDocstring
|
|
def add(self, irc, msg, args, optlist):
|
|
"""[--from <channel>@<network>] [--to <channel>@<network>] [--regexp <regexp>] [--reciprocal]
|
|
|
|
Adds a relay to the list. You must give at least --from or --to; if
|
|
one of them is not given, it defaults to the current channel@network.
|
|
Only messages matching <regexp> will be relayed; if <regexp> is not
|
|
given, everything is relayed.
|
|
If --reciprocal is given, another relay will be added automatically,
|
|
in the opposite direction."""
|
|
optlist = self._parseOptlist(irc, msg, optlist)
|
|
if optlist is None:
|
|
return
|
|
|
|
failedWrites = 0
|
|
if not self._writeToConfig(optlist['from'], optlist['to'],
|
|
optlist['regexp'], True):
|
|
failedWrites += 1
|
|
if optlist['reciprocal']:
|
|
if not self._writeToConfig(optlist['to'], optlist['from'],
|
|
optlist['regexp'], True):
|
|
failedWrites += 1
|
|
|
|
self._loadFromConfig()
|
|
if failedWrites == 0:
|
|
irc.replySuccess()
|
|
else:
|
|
irc.error(_('One (or more) relay(s) already exists and has not '
|
|
'been added.'))
|
|
add = wrap(add, [('checkCapability', 'admin'),
|
|
getopts({'from': 'something',
|
|
'to': 'something',
|
|
'regexp': 'something',
|
|
'reciprocal': ''})])
|
|
|
|
def _fail(self, c):
|
|
if self.registryValue('logFailedChanges'):
|
|
self.log.warning("RelayLink: failed to batch remove relay: %s -> %s", c[0], c[1])
|
|
|
|
def addall(self, irc, msg, args, optlist, channels):
|
|
"""[--regexp <regexp>] <channel1@network1> <channel2@network2> [<channel3@network3>] ...
|
|
|
|
Batch adds all the relays/reciprocals between the channels defined. Useful if you are
|
|
relaying to more than 2 networks/channels with one bot, as a large amount of
|
|
reciprocals easily becomes a mess.
|
|
Only messages matching <regexp> will be relayed; if <regexp> is not
|
|
given, everything is relayed."""
|
|
optlist = self._parseOptlist(irc, msg, optlist, batchadd=True)
|
|
channels = channels.split()
|
|
if len(channels) < 2:
|
|
irc.error('Not enough channels specified to relay! (needs at least 2)', Raise=True)
|
|
if len(channels) > self.registryValue('addall.max'):
|
|
irc.error('Too many channels specified, aborting. (see config plugins.RelayLink.addall.max)', Raise=True)
|
|
for ch in channels:
|
|
if len(ch.split("@")) != 2:
|
|
irc.error("Channels must be specified in the format #channel@network", Raise=True)
|
|
failedWrites = writes = 0
|
|
# Get all the channel combinations and try to add them one by one
|
|
p = itertools.permutations(channels, 2)
|
|
for c in p:
|
|
if not self._writeToConfig(c[0], c[1],
|
|
optlist['regexp'], True):
|
|
failedWrites += 1
|
|
self._fail(c)
|
|
writes += 1
|
|
self._loadFromConfig()
|
|
if failedWrites == 0:
|
|
irc.replySuccess()
|
|
else:
|
|
irc.reply('Finished, though {} out of {} relays failed to be added.'.format(failedWrites, writes))
|
|
addall = wrap(addall, [('checkCapability', 'admin'),
|
|
getopts({'regexp': 'something'}), 'text'])
|
|
|
|
def removeall(self, irc, msg, args, optlist, channels):
|
|
"""[--regexp <regexp>] <channel1@network1> [<channel2@network2>] [<channel3@network3>] ...
|
|
|
|
Batch removes relays. If only one channel@network is given, removes all
|
|
relays going to and from the channel.
|
|
Otherwise, removes all relays going between the channels defined (similar to addall)."""
|
|
optlist = self._parseOptlist(irc, msg, optlist, batchadd=True)
|
|
channels = channels.split()
|
|
if len(channels) > self.registryValue('addall.max'):
|
|
irc.error('Too many channels specified, aborting. (see config plugins.RelayLink.addall.max)', Raise=True)
|
|
failedWrites = writes = 0
|
|
for ch in channels:
|
|
if len(ch.split("@")) != 2:
|
|
irc.error("Channels must be specified in the format #channel@network", Raise=True)
|
|
if len(channels) == 1:
|
|
# Only one channel@net specified, so we'll try to remove all relays going to
|
|
# or from this channel
|
|
c = channels[0].split('@')
|
|
for relay in self.relays:
|
|
# I wish I knew a better way to do this...
|
|
if c[0] == relay.sourceChannel and c[1] == relay.sourceNetwork:
|
|
s = '@'.join((relay.targetChannel, relay.targetNetwork))
|
|
if not self._writeToConfig(channels[0], s,
|
|
optlist['regexp'], False):
|
|
failedWrites += 1
|
|
self._fail(('@'.join(c), s))
|
|
writes += 1
|
|
elif c[0] == relay.targetChannel and c[1] == relay.targetNetwork:
|
|
s = '@'.join((relay.sourceChannel, relay.sourceNetwork))
|
|
if not self._writeToConfig(s, channels[0],
|
|
optlist['regexp'], False):
|
|
failedWrites += 1
|
|
self._fail(('@'.join(c), s))
|
|
writes += 1
|
|
self._loadFromConfig()
|
|
if writes == 0:
|
|
irc.error("No matching relays for %s found." % channels[0], Raise=True)
|
|
elif len(channels) >= 2:
|
|
# Get all the channel combinations and try to remove them one by one
|
|
p = itertools.permutations(channels, 2)
|
|
for c in p:
|
|
if not self._writeToConfig(c[0], c[1],
|
|
optlist['regexp'], False):
|
|
failedWrites += 1
|
|
self._fail(c)
|
|
writes += 1
|
|
self._loadFromConfig()
|
|
if failedWrites == 0:
|
|
irc.replySuccess()
|
|
else:
|
|
irc.reply('Finished, though {} out of {} relays failed to be removed.'.format(failedWrites, writes))
|
|
removeall = wrap(removeall, [('checkCapability', 'admin'),
|
|
getopts({'regexp': 'something'}), 'text'])
|
|
|
|
@internationalizeDocstring
|
|
def remove(self, irc, msg, args, optlist):
|
|
"""[--from <channel>@<network>] [--to <channel>@<network>] [--regexp <regexp>] [--reciprocal]
|
|
|
|
Remove a relay from the list. You must give at least --from or --to; if
|
|
one of them is not given, it defaults to the current channel@network.
|
|
Only messages matching <regexp> will be relayed; if <regexp> is not
|
|
given, everything is relayed.
|
|
If --reciprocal is given, another relay will be removed automatically,
|
|
in the opposite direction."""
|
|
optlist = self._parseOptlist(irc, msg, optlist)
|
|
if optlist is None:
|
|
return
|
|
|
|
failedWrites = 0
|
|
if not self._writeToConfig(optlist['from'], optlist['to'],
|
|
optlist['regexp'], False):
|
|
failedWrites += 1
|
|
if optlist['reciprocal']:
|
|
if not self._writeToConfig(optlist['to'], optlist['from'],
|
|
optlist['regexp'], False):
|
|
failedWrites +=1
|
|
|
|
self._loadFromConfig()
|
|
if failedWrites == 0:
|
|
irc.replySuccess()
|
|
else:
|
|
irc.error(_('One (or more) relay(s) did not exist and has not '
|
|
'been removed.'))
|
|
remove = wrap(remove, [('checkCapability', 'admin'),
|
|
getopts({'from': 'something',
|
|
'to': 'something',
|
|
'regexp': 'something',
|
|
'reciprocal': ''})])
|
|
|
|
def _getSubstitutes(self):
|
|
# Get a list of strings
|
|
substitutes = self.registryValue('substitutes').split(' || ')
|
|
if substitutes == ['']:
|
|
return {}
|
|
# Convert it to a list of tuples
|
|
substitutes = [tuple(x.split(' | ')) for x in substitutes]
|
|
# Finally, make a dictionnary
|
|
substitutes = dict(substitutes)
|
|
|
|
return substitutes
|
|
|
|
def _setSubstitutes(self, substitutes):
|
|
# Get a list of tuples from the dictionnary
|
|
substitutes = substitutes.items()
|
|
# Make it a list of strings
|
|
substitutes = ['%s | %s' % (x,y) for x,y in substitutes]
|
|
# Finally, get a string
|
|
substitutes = ' || '.join(substitutes)
|
|
|
|
self.setRegistryValue('substitutes', value=substitutes)
|
|
|
|
@internationalizeDocstring
|
|
def substitute(self, irc, msg, args, regexp, to):
|
|
"""<regexp> <replacement>
|
|
|
|
Replaces all nicks that matches the <regexp> by the <replacement>
|
|
string."""
|
|
substitutes = self._getSubstitutes()
|
|
# Don't check if it is already in the config: if will be overriden
|
|
# automatically and that is a good thing.
|
|
substitutes.update({regexp: to})
|
|
self._setSubstitutes(substitutes)
|
|
self._loadFromConfig()
|
|
irc.replySuccess()
|
|
substitute = wrap(substitute, [('checkCapability', 'admin'),
|
|
'something',
|
|
'text'])
|
|
|
|
@internationalizeDocstring
|
|
def nosubstitute(self, irc, msg, args, regexp):
|
|
"""<regexp>
|
|
|
|
Undo a substitution."""
|
|
substitutes = self._getSubstitutes()
|
|
if regexp not in substitutes:
|
|
irc.error(_('This regexp was not in the nick substitutions '
|
|
'database'))
|
|
return
|
|
# Don't check if it is already in the config: if will be overriden
|
|
# automatically and that is a good thing.
|
|
substitutes.pop(regexp)
|
|
self._setSubstitutes(substitutes)
|
|
self._loadFromConfig()
|
|
irc.replySuccess()
|
|
nosubstitute = wrap(nosubstitute, [('checkCapability', 'admin'),
|
|
'something'])
|
|
def rpm(self, irc, msg, args, remoteuser, otherIrc, text):
|
|
"""<remoteUser> <network> <text>
|
|
|
|
Sends a private message to a user on a remote network."""
|
|
found_targetnet = found_sourcenet = False
|
|
if not self.registryValue("remotepm.enable"):
|
|
irc.error("This command is not enabled; please set 'config plugins.relaylink.remotepm.enable' "
|
|
"to True.", Raise=True)
|
|
for relay in self.relays:
|
|
# Find if the remote user exists in any known relay channel on the target net
|
|
channels = otherIrc.state.channels
|
|
for key, channel_ in channels.items():
|
|
if ircutils.toLower(relay.targetChannel) \
|
|
== ircutils.toLower(key) and remoteuser in channel_.users:
|
|
found_targetnet = True
|
|
break
|
|
# Find if the caller has at least one source channel in common with the target
|
|
for ch in irc.state.channels:
|
|
if ircutils.toLower(relay.sourceChannel) == \
|
|
ircutils.toLower(ch) and msg.nick in irc.state.channels[ch].users:
|
|
found_sourcenet = True
|
|
break
|
|
if found_targetnet and found_sourcenet:
|
|
prefix = msg.prefix if self.registryValue("remotepm.useHostmasks") else msg.nick
|
|
if self.registryValue("remotepm.useNotice"):
|
|
otherIrc.queueMsg(ircmsgs.notice(remoteuser, "Message from %s on %s: %s" % (prefix, irc.network, text)))
|
|
else:
|
|
otherIrc.queueMsg(ircmsgs.privmsg(remoteuser, "Message from %s on %s: %s" % (prefix, irc.network, text)))
|
|
else:
|
|
irc.error("User '%s' does not exist on %s or you are not sharing "
|
|
"a channel with them." % (remoteuser, otherIrc.network), Raise=True)
|
|
rpm = wrap(rpm, ['nick', ('networkIrc', True), 'text'])
|
|
|
|
Class = RelayLink
|
|
|
|
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:
|