Merge branch 'netconf-and-ircmsgs-channel' into testing

This commit is contained in:
Valentin Lorentz 2019-09-14 12:10:47 +02:00
commit 573921c00f
57 changed files with 1026 additions and 549 deletions

View File

@ -49,9 +49,9 @@ def getChannel(irc, msg, args):
If the channel was given in args, args is modified (the channel is
removed).
"""
if args and irc.isChannel(msg.args[0]):
if args and msg.channel:
if conf.supybot.reply.requireChannelCommandsToBeSentInChannel():
if args[0] != msg.args[0]:
if args[0] != msg.channel:
s = 'Channel commands must be sent in the channel to which ' \
'they apply; if this is not the behavior you desire, ' \
'ask the bot\'s administrator to change the registry ' \
@ -60,8 +60,8 @@ def getChannel(irc, msg, args):
'to False.'
raise callbacks.Error(s)
return args.pop(0)
elif irc.isChannel(msg.args[0]):
return msg.args[0]
elif msg.channel:
return msg.channel
else:
raise callbacks.Error('Command must be sent in a channel or ' \
'include a channel in its arguments.')

View File

@ -50,17 +50,20 @@ class Anonymous(callbacks.Plugin):
supybot.plugins.Anonymous.requireRegistration.
"""
def _preCheck(self, irc, msg, target, action):
if self.registryValue('requireRegistration', target):
if self.registryValue('requireRegistration', target, irc.network):
try:
foo = ircdb.users.getUser(msg.prefix)
except KeyError:
irc.errorNotRegistered(Raise=True)
capability = self.registryValue('requireCapability', target)
capability = self.registryValue('requireCapability',
target, irc.network)
if capability:
if not ircdb.checkCapability(msg.prefix, capability):
irc.errorNoCapability(capability, Raise=True)
if action != 'tell':
if self.registryValue('requirePresenceInChannel', target) and \
require_presence = self.registryValue('requirePresenceInChannel',
target, irc.network)
if require_presence and \
msg.nick not in irc.state.channels[target].users:
irc.error(format(_('You must be in %s to %q in there.'),
target, action), Raise=True)

View File

@ -48,12 +48,13 @@ class AutoMode(callbacks.Plugin):
"""This plugin, when configured, allows the bot to automatically set modes
on users when they join."""
def doJoin(self, irc, msg):
channel = msg.args[0]
channel = msg.channel
network = irc.network
if ircutils.strEqual(irc.nick, msg.nick):
return
if not self.registryValue('enable', channel):
if not self.registryValue('enable', channel, network):
return
fallthrough = self.registryValue('fallthrough', channel)
fallthrough = self.registryValue('fallthrough', channel, network)
def do(type):
cap = ircdb.makeChannelCapability(channel, type)
cap_auto = ircdb.makeChannelCapability(channel, 'auto'+type)
@ -63,7 +64,7 @@ class AutoMode(callbacks.Plugin):
ignoreChannelOp=True, ignoreDefaultAllow=True)
except KeyError:
apply_mode = False
if self.registryValue('alternativeCapabilities', channel):
if self.registryValue('alternativeCapabilities', channel, network):
try:
override = ircdb.checkCapability(msg.prefix, cap_auto,
ignoreOwner=not self.registryValue('owner'),
@ -73,9 +74,9 @@ class AutoMode(callbacks.Plugin):
else:
override = False
if apply_mode or override:
if override or self.registryValue(type, channel):
self.log.info('Scheduling auto-%s of %s in %s.',
type, msg.prefix, channel)
if override or self.registryValue(type, channel, network):
self.log.info('Scheduling auto-%s of %s in %s @ %s.',
type, msg.prefix, channel, network)
def dismiss():
"""Determines whether or not a mode has already
been applied."""
@ -87,8 +88,9 @@ class AutoMode(callbacks.Plugin):
raise Continue # Even if fallthrough, let's only do one.
elif not fallthrough:
self.log.debug('%s has %s, but supybot.plugins.AutoMode.%s'
' is not enabled in %s, refusing to fall '
'through.', msg.prefix, cap, type, channel)
' is not enabled in %s @ %s, refusing to '
'fall through.',
msg.prefix, cap, type, channel, network)
raise Continue
def schedule_msg(msg, dismiss):
def f():
@ -96,7 +98,7 @@ class AutoMode(callbacks.Plugin):
irc.queueMsg(msg)
else:
self.log.info('Dismissing auto-mode for %s.', msg.args[2])
delay = self.registryValue('delay', channel)
delay = self.registryValue('delay', channel, network)
if delay:
schedule.addEvent(f, time.time() + delay)
else:
@ -107,7 +109,7 @@ class AutoMode(callbacks.Plugin):
except KeyError:
return
pattern = re.compile('-|\+')
for item in self.registryValue('extra', channel):
for item in self.registryValue('extra', channel, network):
try:
username, modes = pattern.split(item, maxsplit=1)
modes = item[len(username)] + modes
@ -118,8 +120,8 @@ class AutoMode(callbacks.Plugin):
if username != user.name:
continue
else:
self.log.info('Scheduling auto-modes %s of %s in %s.',
modes, msg.prefix, channel)
self.log.info('Scheduling auto-modes %s of %s in %s @ %s.',
modes, msg.prefix, channel, network)
modes = [modes] + \
([msg.nick]*len(pattern.sub('', modes)))
schedule_msg(ircmsgs.mode(channel, modes), lambda :False)
@ -133,8 +135,9 @@ class AutoMode(callbacks.Plugin):
finally:
extra_modes()
c = ircdb.channels.getChannel(channel)
if c.checkBan(msg.prefix) and self.registryValue('ban', channel):
period = self.registryValue('ban.period', channel)
if c.checkBan(msg.prefix) and self.registryValue('ban',
channel, network):
period = self.registryValue('ban.period', channel, network)
if period:
def unban():
try:

View File

@ -72,10 +72,11 @@ class BadWords(callbacks.Privmsg):
# We need to check for bad words here rather than in doPrivmsg because
# messages don't get to doPrivmsg if the user is ignored.
if msg.command == 'PRIVMSG' and self.words():
channel = msg.args[0]
self.updateRegexp(channel)
channel = msg.channel
self.updateRegexp(channel, irc.network)
s = ircutils.stripFormatting(msg.args[1])
if irc.isChannel(channel) and self.registryValue('kick', channel):
if irc.isChannel(channel) \
and self.registryValue('kick', channel, irc.network):
if self.regexp.search(s):
c = irc.state.channels[channel]
cap = ircdb.makeChannelCapability(channel, 'op')
@ -86,22 +87,23 @@ class BadWords(callbacks.Privmsg):
"they are halfop+ or can't be "
"kicked.", msg.nick, channel)
else:
message = self.registryValue('kick.message', channel)
message = self.registryValue('kick.message',
channel, irc.network)
irc.queueMsg(ircmsgs.kick(channel, msg.nick, message))
else:
self.log.warning('Should kick %s from %s, but not opped.',
msg.nick, channel)
return msg
def updateRegexp(self, channel):
def updateRegexp(self, channel, network):
if self.lastModified < self.words.lastModified:
self.makeRegexp(self.words(), channel)
self.makeRegexp(self.words(), channel, network)
self.lastModified = time.time()
def outFilter(self, irc, msg):
if self.filtering and msg.command == 'PRIVMSG' and self.words():
channel = msg.args[0]
self.updateRegexp(channel)
channel = msg.channel
self.updateRegexp(channel, irc.network)
s = msg.args[1]
if self.registryValue('stripFormatting'):
s = ircutils.stripFormatting(s)
@ -110,9 +112,9 @@ class BadWords(callbacks.Privmsg):
msg = ircmsgs.privmsg(msg.args[0], t, msg=msg)
return msg
def makeRegexp(self, iterable, channel):
def makeRegexp(self, iterable, channel, network):
s = '(%s)' % '|'.join(map(re.escape, iterable))
if self.registryValue('requireWordBoundaries', channel):
if self.registryValue('requireWordBoundaries', channel, network):
s = r'\b%s\b' % s
self.regexp = re.compile(s, re.I)

View File

@ -54,24 +54,26 @@ class Channel(callbacks.Plugin):
self.invites = {}
def doKick(self, irc, msg):
channel = msg.args[0]
channel = msg.channel
network = irc.network
if msg.args[1] == irc.nick:
if self.registryValue('alwaysRejoin', channel):
delay = self.registryValue('rejoinDelay', channel)
if self.registryValue('alwaysRejoin', channel, network):
delay = self.registryValue('rejoinDelay', channel, network)
networkGroup = conf.supybot.networks.get(irc.network)
if delay:
def f():
irc.sendMsg(networkGroup.channels.join(channel))
schedule.addEvent(f, time.time() + delay)
self.log.info('Kicked from %s by %s. Rejoining after %s '
'seconds.', channel, msg.prefix, delay)
self.log.info('Kicked from %s @ %s by %s. '
'Rejoining after %s seconds.',
channel, network, msg.prefix, delay)
else:
self.log.info('Kicked from %s by %s. Rejoining.',
channel, msg.prefix)
self.log.info('Kicked from %s @ %s by %s. Rejoining.',
channel, network, msg.prefix)
irc.sendMsg(networkGroup.channels.join(channel))
else:
self.log.info('Kicked from %s by %s. Not auto-rejoining.',
channel, msg.prefix)
self.log.info('Kicked from %s @ %s by %s. Not auto-rejoining.',
channel, network, msg.prefix)
def _sendMsg(self, irc, msg):
irc.queueMsg(msg)
@ -274,7 +276,7 @@ class Channel(callbacks.Plugin):
supybot.plugins.Channel.partMsg will be used. No part message will be
used if neither a cycle reason nor a default part message is given.
"""
reason = (reason or self.registryValue("partMsg", channel))
reason = (reason or self.registryValue("partMsg", channel, irc.network))
reason = ircutils.standardSubstitute(irc, msg, reason)
self._sendMsg(irc, ircmsgs.part(channel, reason))
networkGroup = conf.supybot.networks.get(irc.network)
@ -907,12 +909,20 @@ class Channel(callbacks.Plugin):
nicks if --count option is provided.
"""
# Make sure we don't elicit information about private channels to
# people or channels that shouldn't know
# people or channels that shouldn't know. Someone is allowed if
# any of these are true:
# * the channel is not secret (mode +s),
# * the request is sent to the channel itself (FIXME: what about
# channels without +n?),
# * the requester is op,
# * the request is not sent to a channel (or else everyone in the
# channel would see the response) and the requester is in the
# channel themselves
capability = ircdb.makeChannelCapability(channel, 'op')
if 's' in irc.state.channels[channel].modes and \
msg.args[0] != channel and \
msg.channel != channel and \
not ircdb.checkCapability(msg.prefix, capability) and \
(irc.isChannel(msg.args[0]) or \
(msg.channel or \
msg.nick not in irc.state.channels[channel].users):
irc.error(_('You don\'t have access to that information.'),
Raise=True)
@ -920,7 +930,7 @@ class Channel(callbacks.Plugin):
keys = [option for (option, arg) in optlist]
if 'count' not in keys:
utils.sortBy(str.lower, L)
private = self.registryValue("nicksInPrivate", channel)
private = self.registryValue("nicksInPrivate", channel, irc.network)
irc.reply(utils.str.commaAndify(L), private=private)
else:
irc.reply(str(len(L)))
@ -961,11 +971,9 @@ class Channel(callbacks.Plugin):
supybot.plugins.Channel.partMsg will be used. No part message will be
used if no default is configured.
"""
if channel is None:
if irc.isChannel(msg.args[0]):
channel = msg.args[0]
else:
irc.error(Raise=True)
channel = channel or msg.channel
if not channel:
irc.error(Raise=True)
capability = ircdb.makeChannelCapability(channel, 'op')
if not ircdb.checkCapabilities(msg.prefix, [capability, 'admin']):
irc.errorNoCapability(capability, Raise=True)
@ -976,7 +984,7 @@ class Channel(callbacks.Plugin):
pass
if channel not in irc.state.channels:
irc.error(_('I\'m not in %s.') % channel, Raise=True)
reason = (reason or self.registryValue("partMsg", channel))
reason = (reason or self.registryValue("partMsg", channel, irc.network))
reason = ircutils.standardSubstitute(irc, msg, reason)
irc.queueMsg(ircmsgs.part(channel, reason))
if msg.nick in irc.state.channels[channel].users:

View File

@ -90,13 +90,13 @@ class ChannelLogger(callbacks.Plugin):
if e.args[0] != 'I/O operation on a closed file':
self.log.exception('Odd exception:')
def logNameTimestamp(self, channel):
format = self.registryValue('filenameTimestamp', channel)
def logNameTimestamp(self, network, channel):
format = self.registryValue('filenameTimestamp', channel, network)
return time.strftime(format)
def getLogName(self, channel):
if self.registryValue('rotateLogs', channel):
name = '%s.%s.log' % (channel, self.logNameTimestamp(channel))
def getLogName(self, network, channel):
if self.registryValue('rotateLogs', channel, network):
return '%s.%s.log' % (channel, self.logNameTimestamp(network, channel))
else:
name = '%s.log' % channel
return utils.file.sanitizeName(name)
@ -120,8 +120,8 @@ class ChannelLogger(callbacks.Plugin):
def checkLogNames(self):
for (irc, logs) in self.logs.items():
for (channel, log) in list(logs.items()):
if self.registryValue('rotateLogs', channel):
name = self.getLogName(channel)
if self.registryValue('rotateLogs', channel, irc.network):
name = self.getLogName(irc.network, channel)
if name != os.path.basename(log.name):
log.close()
del logs[channel]
@ -137,7 +137,7 @@ class ChannelLogger(callbacks.Plugin):
return logs[channel]
else:
try:
name = self.getLogName(channel)
name = self.getLogName(irc.network, channel)
logDir = self.getLogDir(irc, channel)
log = open(os.path.join(logDir, name), encoding='utf-8', mode='a')
logs[channel] = log
@ -158,14 +158,14 @@ class ChannelLogger(callbacks.Plugin):
return ircutils.toLower(channel)
def doLog(self, irc, channel, s, *args):
if not self.registryValue('enable', channel):
if not self.registryValue('enable', channel, irc.network):
return
s = format(s, *args)
channel = self.normalizeChannel(irc, channel)
log = self.getLog(irc, channel)
if self.registryValue('timestamp', channel):
if self.registryValue('timestamp', channel, irc.network):
self.timestamp(log)
if self.registryValue('stripFormatting', channel):
if self.registryValue('stripFormatting', channel, irc.network):
s = ircutils.stripFormatting(s)
if minisix.PY2:
s = s.decode('utf8', 'ignore')
@ -177,7 +177,8 @@ class ChannelLogger(callbacks.Plugin):
(recipients, text) = msg.args
for channel in recipients.split(','):
if irc.isChannel(channel):
noLogPrefix = self.registryValue('noLogPrefix', channel)
noLogPrefix = self.registryValue('noLogPrefix',
channel, irc.network)
cap = ircdb.makeChannelCapability(channel, 'logChannelMessages')
try:
logChannelMessages = ircdb.checkCapability(msg.prefix, cap,
@ -219,7 +220,7 @@ class ChannelLogger(callbacks.Plugin):
def doJoin(self, irc, msg):
for channel in msg.args[0].split(','):
if(self.registryValue('showJoinParts', channel)):
if(self.registryValue('showJoinParts', channel, irc.network)):
self.doLog(irc, channel,
'*** %s <%s> has joined %s\n',
msg.nick, msg.prefix, channel)
@ -244,7 +245,7 @@ class ChannelLogger(callbacks.Plugin):
else:
reason = ""
for channel in msg.args[0].split(','):
if(self.registryValue('showJoinParts', channel)):
if(self.registryValue('showJoinParts', channel, irc.network)):
self.doLog(irc, channel,
'*** %s <%s> has left %s%s\n',
msg.nick, msg.prefix, channel, reason)
@ -270,7 +271,7 @@ class ChannelLogger(callbacks.Plugin):
else:
reason = ""
for channel in msg.tagged('channels'):
if(self.registryValue('showJoinParts', channel)):
if(self.registryValue('showJoinParts', channel, irc.network)):
self.doLog(irc, channel,
'*** %s <%s> has quit IRC%s\n',
msg.nick, msg.prefix, reason)

View File

@ -142,8 +142,8 @@ class StatsDB(plugins.ChannelUserDB):
return UserStat(*L)
def addMsg(self, irc, msg, id=None):
if msg.args and irc.isChannel(msg.args[0]):
channel = plugins.getChannel(msg.args[0])
if msg.channel:
channel = plugins.getChannel(msg.channel)
if (channel, 'channelStats') not in self:
self[channel, 'channelStats'] = ChannelStat()
self[channel, 'channelStats'].addMsg(msg)
@ -186,8 +186,8 @@ class ChannelStats(callbacks.Plugin):
def outFilter(self, irc, msg):
if msg.command == 'PRIVMSG':
if irc.isChannel(msg.args[0]):
if self.registryValue('selfStats', msg.args[0]):
if msg.channel:
if self.registryValue('selfStats', msg.channel, irc.network):
try:
self.outFiltering = True
self.db.addMsg(irc, msg, 0)

View File

@ -151,10 +151,10 @@ class Config(callbacks.Plugin):
def _list(self, irc, group):
L = []
for (vname, v) in group._children.items():
if hasattr(group, 'channelValue') and group.channelValue and \
if hasattr(group, '_channelValue') and group._channelValue and \
irc.isChannel(vname) and not v._children:
continue
if hasattr(v, 'channelValue') and v.channelValue:
if hasattr(v, '_channelValue') and v._channelValue:
vname = '#' + vname
if v._added and not all(irc.isChannel, v._added):
vname = '@' + vname
@ -198,13 +198,33 @@ class Config(callbacks.Plugin):
irc.reply(_('There were no matching configuration variables.'))
search = wrap(search, ['lowered']) # XXX compose with withoutSpaces?
def _getValue(self, irc, msg, group, addChannel=False):
def _getValue(self, irc, msg, group, network=None, channel=None, addGlobal=False):
global_group = group
global_value = str(group) or ' '
group = group.getSpecific(
network=network.network, channel=channel, check=False)
value = str(group) or ' '
if addChannel and irc.isChannel(msg.args[0]) and not irc.nested:
s = str(group.get(msg.args[0]))
value = _('Global: %s; %s: %s') % (value, msg.args[0], s)
if hasattr(group, 'value'):
if not group._private:
if addGlobal and not irc.nested:
if global_group._channelValue and channel:
# TODO: also show the network value when relevant
value = _(
'Global: %(global_value)s; '
'%(channel_name)s @ %(network_name)s: %(channel_value)s') % {
'global_value': global_value,
'channel_name': msg.channel,
'network_name': irc.network,
'channel_value': value,
}
elif global_group._networkValue and network:
value = _(
'Global: %(global_value)s; '
'%(network_name)s: %(network_value)s') % {
'global_value': global_value,
'network_name': irc.network,
'network_value': value,
}
if hasattr(global_group, 'value'):
if not global_group._private:
return (value, None)
else:
capability = getCapability(irc, group._name)
@ -215,7 +235,7 @@ class Config(callbacks.Plugin):
else:
irc.error(_('That registry variable has no value. Use the list '
'command in this plugin to see what variables are '
'available in this group.'))
'available in this group.'), Raise=True)
def _setValue(self, irc, msg, group, value):
if isReadOnly(group._name):
@ -230,28 +250,44 @@ class Config(callbacks.Plugin):
irc.errorNoCapability(capability, Raise=True)
@internationalizeDocstring
def channel(self, irc, msg, args, channels, group, value):
"""[<channel>] <name> [<value>]
def channel(self, irc, msg, args, network, channels, group, value):
"""[<network>] [<channel>] <name> [<value>]
If <value> is given, sets the channel configuration variable for <name>
to <value> for <channel>. Otherwise, returns the current channel
to <value> for <channel> on the <network>.
Otherwise, returns the current channel
configuration value of <name>. <channel> is only necessary if the
message isn't sent in the channel itself. More than one channel may
be given at once by separating them with commas."""
if not group.channelValue:
be given at once by separating them with commas.
<network> defaults to the current network."""
if not group._channelValue:
irc.error(_('That configuration variable is not a channel-specific '
'configuration variable.'))
return
if value is not None:
for channel in channels:
assert irc.isChannel(channel)
# Sets the non-network-specific value, for forward
# compatibility, ie. this will work even if the owner rolls
# back Limnoria to an older version.
# It's also an easy way to support plugins which are not
# network-aware.
self._setValue(irc, msg, group.get(channel), value)
if network != '*':
# Set the network-specific value
self._setValue(irc, msg, group.get(':' + network.network).get(channel), value)
irc.replySuccess()
else:
if network == '*':
network = None
values = []
private = None
for channel in channels:
(value, private_value) = self._getValue(irc, msg, group.get(channel))
(value, private_value) = \
self._getValue(irc, msg, group, network, channel)
values.append((channel, value))
if private_value:
private = True
@ -261,7 +297,32 @@ class Config(callbacks.Plugin):
private=private)
else:
irc.reply(values[0][1], private=private)
channel = wrap(channel, ['channels', 'settableConfigVar',
channel = wrap(channel, [optional(first(('literal', '*'), 'networkIrc')),
'channels', 'settableConfigVar',
additional('text')])
def network(self, irc, msg, args, network, group, value):
"""[<network>] <name> [<value>]
If <value> is given, sets the network configuration variable for <name>
to <value> for <network>.
Otherwise, returns the current network configuration value of <name>.
<network> defaults to the current network."""
if not group._networkValue:
irc.error(_('That configuration variable is not a network-specific '
'configuration variable.'))
return
if value is not None:
self._setValue(irc, msg, group.get(':' + network.network), value)
irc.replySuccess()
else:
values = []
private = None
(value, private) = \
self._getValue(irc, msg, group, network)
irc.reply(value, private=private)
network = wrap(network, ['networkIrc', 'settableConfigVar',
additional('text')])
@internationalizeDocstring
@ -276,7 +337,10 @@ class Config(callbacks.Plugin):
self._setValue(irc, msg, group, value)
irc.replySuccess()
else:
(value, private) = self._getValue(irc, msg, group, addChannel=group.channelValue)
(value, private) = self._getValue(
irc, msg, group, network=irc,
channel=msg.channel,
addGlobal=group._channelValue or group._networkValue)
irc.reply(value, private=private)
config = wrap(config, ['settableConfigVar', additional('text')])
@ -290,11 +354,10 @@ class Config(callbacks.Plugin):
s = group.help()
if s:
if hasattr(group, 'value') and not group._private:
channel = msg.args[0]
if irc.isChannel(channel) and \
channel in group._children:
if msg.channel and \
msg.channel in group._children:
globvalue = str(group)
chanvalue = str(group.get(channel))
chanvalue = str(group.get(msg.channel))
if chanvalue != globvalue:
s += _(' (Current global value: %s; '
'current channel value: %s)') % \

View File

@ -133,13 +133,13 @@ class ConfigTestCase(ChannelPluginTestCase):
'^Completely: Error: ',
frm=self.prefix3)
self.assertResponse('config plugins.Config.%s' % var_name,
'Global: 0; #test: 0')
'Global: 0; #test @ test: 0')
self.assertNotRegexp('config channel plugins.Config.%s 1' % var_name,
'^Completely: Error: ',
frm=self.prefix3)
self.assertResponse('config plugins.Config.%s' % var_name,
'Global: 0; #test: 1')
'Global: 0; #test @ test: 1')
def testOpNonEditable(self):
var_name = 'testOpNonEditable' + random_string()
@ -154,18 +154,18 @@ class ConfigTestCase(ChannelPluginTestCase):
'^Completely: Error: ',
frm=self.prefix3)
self.assertResponse('config plugins.Config.%s' % var_name,
'Global: 0; #test: 0')
'Global: 0; #test @ test: 0')
self.assertRegexp('config channel plugins.Config.%s 1' % var_name,
'^Completely: Error: ',
frm=self.prefix3)
self.assertResponse('config plugins.Config.%s' % var_name,
'Global: 0; #test: 0')
'Global: 0; #test @ test: 0')
self.assertNotRegexp('config channel plugins.Config.%s 1' % var_name,
'^Completely: Error: ')
self.assertResponse('config plugins.Config.%s' % var_name,
'Global: 0; #test: 1')
'Global: 0; #test @ test: 1')
def testChannel(self):
self.assertResponse('config reply.whenAddressedBy.strings ^',
@ -181,6 +181,77 @@ class ConfigTestCase(ChannelPluginTestCase):
self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings', '.')
self.assertResponse('config channel #testchan2 reply.whenAddressedBy.strings', '.')
def testNetwork(self):
getTestIrc('testnet1')
getTestIrc('testnet2')
self.assertResponse('config reply.whenAddressedBy.strings ^',
'The operation succeeded.')
self.assertResponse('config network reply.whenAddressedBy.strings @',
'The operation succeeded.')
self.assertResponse('config network reply.whenAddressedBy.strings', '@')
self.assertNotError('config network reply.whenAddressedBy.strings $')
self.assertResponse('config network testnet1 reply.whenAddressedBy.strings', '^')
self.assertResponse('config network testnet2 reply.whenAddressedBy.strings', '^')
self.assertResponse('config network reply.whenAddressedBy.strings', '$')
self.assertResponse('config network testnet1 reply.whenAddressedBy.strings', '^')
self.assertResponse('config network testnet2 reply.whenAddressedBy.strings', '^')
self.assertNotError('config network testnet1 reply.whenAddressedBy.strings =')
self.assertResponse('config network testnet1 reply.whenAddressedBy.strings', '=')
self.assertResponse('config network testnet2 reply.whenAddressedBy.strings', '^')
def testChannelNetwork(self):
irc = self.irc
irc1 = getTestIrc('testnet1')
irc2 = getTestIrc('testnet2')
irc3 = getTestIrc('testnet3')
conf.supybot.reply.whenAddressedBy.strings.get('#test')._wasSet = False
# 1. Set global
self.assertResponse('config reply.whenAddressedBy.strings ^',
'The operation succeeded.')
# 2. Set for current net + #testchan1
self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings @',
'The operation succeeded.')
# Exact match for #2:
self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings', '@')
# 3: Set for #testchan1 for all nets:
self.assertNotError('config channel * #testchan1 reply.whenAddressedBy.strings $')
# Still exact match for #2:
self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings', '@')
# Inherit from *:
self.assertResponse('config channel testnet1 #testchan1 reply.whenAddressedBy.strings', '$')
self.assertResponse('config channel testnet2 #testchan1 reply.whenAddressedBy.strings', '$')
# 4: Set for testnet1 for #testchan1 and #testchan2:
self.assertNotError('config channel testnet1 #testchan1,#testchan2 reply.whenAddressedBy.strings .')
# 5: Set for testnet2 for #testchan1:
self.assertNotError('config channel testnet2 #testchan1 reply.whenAddressedBy.strings :')
# Inherit from global value (nothing was set of current net or current
# chan):
(old_channel, self.channel) = (self.channel, '#iejofjfozifk')
try:
self.assertResponse('config channel reply.whenAddressedBy.strings', '^')
finally:
self.channel = old_channel
# Still exact match for #2:
self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings', '@')
self.assertResponse('config channel %s #testchan1 reply.whenAddressedBy.strings' % irc.network, '@')
# Exact match for #4:
self.assertResponse('config channel testnet1 #testchan1 reply.whenAddressedBy.strings', '.')
self.assertResponse('config channel testnet1 #testchan2 reply.whenAddressedBy.strings', '.')
# Inherit from #5, which set for #testchan1 on all nets
self.assertResponse('config channel testnet3 #testchan1 reply.whenAddressedBy.strings', ':')
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -93,13 +93,13 @@ class Dict(callbacks.Plugin):
if words[0] in dbs:
dictionary = words.pop(0)
else:
default = self.registryValue('default', msg.args[0])
default = self.registryValue('default', msg.channel, irc.network)
if default in dbs:
dictionary = default
else:
if default:
self.log.info('Default dict for %s is not a supported '
'dictionary: %s.', msg.args[0], default)
self.log.info('Default dict for %s @ %s is not a supported '
'dictionary: %s.', msg.channel, irc.network, default)
dictionary = '*'
if not words:
irc.error(_('You must give a word to define.'), Raise=True)
@ -123,7 +123,7 @@ class Dict(callbacks.Plugin):
L.append('%s: %s' % (db, s))
utils.sortBy(len, L)
if dictionary == '*' and len(dbs) > 1 and \
self.registryValue("showDictName", msg.args[0]):
self.registryValue("showDictName", msg.channel, irc.network):
s = format(_('%L responded: %s'), list(dbs), '; '.join(L))
else:
s = '; '.join(L)

View File

@ -42,12 +42,12 @@ class Dunno(plugins.ChannelIdDatabasePlugin):
callAfter = ['MoobotFactoids', 'Factoids', 'Infobot']
def invalidCommand(self, irc, msg, tokens):
channel = msg.args[0]
if irc.isChannel(channel):
dunno = self.db.random(channel)
if msg.channel:
dunno = self.db.random(msg.channel)
if dunno is not None:
dunno = dunno.text
prefixNick = self.registryValue('prefixNick', channel)
prefixNick = self.registryValue('prefixNick',
msg.channel, irc.network)
env = {'command': tokens[0]}
self.log.info('Issuing "dunno" answer, %s is not a command.',
tokens[0])

View File

@ -53,7 +53,8 @@ from supybot.utils.seq import dameraulevenshtein
def getFactoid(irc, msg, args, state):
assert not state.channel
callConverter('channel', irc, msg, args, state)
separator = state.cb.registryValue('learnSeparator', state.channel)
separator = state.cb.registryValue('learnSeparator',
state.channel, irc.network)
try:
i = args.index(separator)
except ValueError:
@ -137,7 +138,8 @@ class FactoidsCallback(httpserver.SupyHTTPServerCallback):
{'title': 'Factoids - not a channel',
'error': 'This is not a channel'})
return
if not self._plugin.registryValue('web.channel', channel):
if not self._plugin.registryValue('web.channel',
channel, irc.network):
self.send_response(403)
self.send_header('Content-type', 'text/html; charset=utf-8')
self.end_headers()
@ -252,13 +254,16 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler):
method = self.getCommandMethod(command)
if method.__func__.__name__ == 'learn':
chan = None
network = None
if dynamic.msg is not None:
chan = dynamic.msg.args[0]
s = self.registryValue('learnSeparator', chan)
chan = dynamic.msg.channel
if dynamic.irc is not None:
network = dynamic.irc.network
s = self.registryValue('learnSeparator', chan, network)
help = callbacks.getHelp
if simpleSyntax is None:
simpleSyntax = conf.get(conf.supybot.reply.showSimpleSyntax,
chan)
simpleSyntax = conf.supybot.reply.showSimpleSyntax.getSpecific(
dynamic.irc.network, chan)()
if simpleSyntax:
help = callbacks.getSyntax
return help(method,
@ -276,7 +281,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler):
return (keyresults, factresults,)
def learn(self, irc, msg, args, channel, key, factoid):
if self.registryValue('requireVoice', channel) and \
if self.registryValue('requireVoice', channel, irc.network) and \
not irc.state.channels[channel].isVoicePlus(msg.nick):
irc.error(_('You have to be at least voiced to teach factoids.'), Raise=True)
@ -366,8 +371,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler):
return []
def _updateRank(self, channel, factoids):
if self.registryValue('keepRankInfo', channel):
def _updateRank(self, network, channel, factoids):
if self.registryValue('keepRankInfo', channel, network):
db = self.getDb(channel)
cursor = db.cursor()
for (fact,factid,relationid) in factoids:
@ -391,7 +396,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler):
if number:
try:
irc.reply(format_fact(factoids[number-1][0]))
self._updateRank(channel, [factoids[number-1]])
self._updateRank(irc.network, channel, [factoids[number-1]])
except IndexError:
irc.error(_('That\'s not a valid number for that key.'))
return
@ -399,7 +404,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler):
env = {'key': key}
def prefixer(v):
env['value'] = v
formatter = self.registryValue('format', msg.args[0])
formatter = self.registryValue('format',
msg.channel, irc.network)
return ircutils.standardSubstitute(irc, msg,
formatter, env)
if len(factoids) == 1:
@ -413,7 +419,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler):
counter += 1
irc.replies(factoidsS, prefixer=prefixer,
joiner=', or ', onlyPrefixFirst=True)
self._updateRank(channel, factoids)
self._updateRank(irc.network, channel, factoids)
elif error:
irc.error(_('No factoid matches that key.'))
@ -429,9 +435,9 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler):
irc.error('No factoid matches that key.')
def invalidCommand(self, irc, msg, tokens):
if irc.isChannel(msg.args[0]):
channel = msg.args[0]
if self.registryValue('replyWhenInvalidCommand', channel):
channel = msg.channel
if channel:
if self.registryValue('replyWhenInvalidCommand', channel, irc.network):
key = ' '.join(tokens)
factoids = self._lookupFactoid(channel, key)
if factoids:
@ -556,7 +562,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler):
itself.
"""
if not number:
number = self.registryValue('rankListLength', channel)
number = self.registryValue('rankListLength', channel, irc.network)
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT keys.key, relations.usage_count
@ -655,7 +661,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler):
<channel> is only necessary if
the message isn't sent in the channel itself.
"""
if self.registryValue('requireVoice', channel) and \
if self.registryValue('requireVoice', channel, irc.network) and \
not irc.state.channels[channel].isVoicePlus(msg.nick):
irc.error(_('You have to be at least voiced to remove factoids.'), Raise=True)
number = None
@ -834,7 +840,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler):
if cursor.rowcount == 0:
irc.reply(_('No keys matched that query.'))
elif cursor.rowcount == 1 and \
self.registryValue('showFactoidIfOnlyOneMatch', channel):
self.registryValue('showFactoidIfOnlyOneMatch',
channel, irc.network):
self.whatis(irc, msg, [channel, cursor.fetchone()[0]])
elif cursor.rowcount > 100:
irc.reply(_('More than 100 keys matched that query; '
@ -843,7 +850,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler):
if len(results) == 0:
irc.reply(_('No keys matched that query.'))
elif len(results) == 1 and \
self.registryValue('showFactoidIfOnlyOneMatch', channel):
self.registryValue('showFactoidIfOnlyOneMatch',
channel, irc.network):
self.whatis(irc, msg, [channel, results[0][0]])
elif len(results) > 100:
irc.reply(_('More than 100 keys matched that query; '

View File

@ -63,12 +63,12 @@ class Filter(callbacks.Plugin):
def outFilter(self, irc, msg):
if msg.command in ('PRIVMSG', 'NOTICE'):
if msg.args[0] in self.outFilters:
if msg.channel in self.outFilters:
if ircmsgs.isAction(msg):
s = ircmsgs.unAction(msg)
else:
s = msg.args[1]
methods = self.outFilters[msg.args[0]]
methods = self.outFilters[msg.channel]
for filtercommand in methods:
myIrc = MyFilterProxy()
filtercommand(myIrc, msg, [s])
@ -651,7 +651,8 @@ class Filter(callbacks.Plugin):
"internationalization" becomes "i18n").
"""
L = []
minimum = self.registryValue('shrink.minimum', msg.args[0])
minimum = self.registryValue('shrink.minimum',
msg.channel, irc.network)
r = re.compile(r'[A-Za-z]{%s,}' % minimum)
def shrink(m):
s = m.group(0)

View File

@ -130,7 +130,7 @@ class Games(callbacks.Plugin):
self._rouletteBullet = random.randrange(0, 6)
irc.reply(_('*SPIN* Are you feeling lucky?'), prefixNick=False)
return
channel = msg.args[0]
channel = msg.channel
if self._rouletteChamber == self._rouletteBullet:
self._rouletteBullet = random.randrange(0, 6)
self._rouletteChamber = random.randrange(0, 6)

View File

@ -69,7 +69,7 @@ class Google(callbacks.PluginRegexp):
_googleRe = re.compile(r'\b(google)\b', re.I)
def outFilter(self, irc, msg):
if msg.command == 'PRIVMSG' and \
self.registryValue('colorfulFilter', msg.args[0]):
self.registryValue('colorfulFilter', msg.channel, irc.network):
s = msg.args[1]
s = re.sub(self._googleRe, self._getColorGoogle, s)
msg = ircmsgs.privmsg(msg.args[0], s, msg=msg)
@ -88,7 +88,7 @@ class Google(callbacks.PluginRegexp):
_gsearchUrl = 'https://www.google.com/search'
def search(self, query, channel, options={}):
def search(self, query, channel, network, options={}):
"""search("search phrase", options={})
Valid options are:
@ -121,11 +121,11 @@ class Google(callbacks.PluginRegexp):
opts['safe'] = v
elif k == 'language':
opts['hl'] = v
defLang = self.registryValue('defaultLanguage', channel)
defLang = self.registryValue('defaultLanguage', channel, network)
if 'hl' not in opts and defLang:
opts['hl'] = defLang.strip('lang_')
if 'safe' not in opts:
opts['safe'] = self.registryValue('searchFilter', dynamic.channel)
opts['safe'] = self.registryValue('searchFilter', channel, network)
if 'rsz' not in opts:
opts['rsz'] = 'large'
@ -169,7 +169,8 @@ class Google(callbacks.PluginRegexp):
If option --snippet is given, returns also the page text snippet.
"""
opts = dict(opts)
data = self.search(text, msg.args[0], {'smallsearch': True})
data = self.search(text, msg.channel, irc.network,
{'smallsearch': True})
data = self.decode(data)
if data:
url = data[0]['url']
@ -195,13 +196,13 @@ class Google(callbacks.PluginRegexp):
if 'language' in optlist and optlist['language'].lower() not in \
conf.supybot.plugins.Google.safesearch.validStrings:
irc.errorInvalid('language')
data = self.search(text, msg.args[0], dict(optlist))
bold = self.registryValue('bold', msg.args[0])
max = self.registryValue('maximumResults', msg.args[0])
data = self.search(text, msg.channel, irc.network, dict(optlist))
bold = self.registryValue('bold', msg.channel, irc.network)
max = self.registryValue('maximumResults', msg.channel, irc.network)
# We don't use supybot.reply.oneToOne here, because you generally
# do not want @google to echo ~20 lines of results, even if you
# have reply.oneToOne enabled.
onetoone = self.registryValue('oneToOne', msg.args[0])
onetoone = self.registryValue('oneToOne', msg.channel, irc.network)
for result in self.formatData(data,
bold=bold, max=max, onetoone=onetoone):
irc.reply(result)
@ -215,7 +216,7 @@ class Google(callbacks.PluginRegexp):
Returns a link to the cached version of <url> if it is available.
"""
data = self.search(url, msg.args[0], {'smallsearch': True})
data = self.search(url, msg.channel, irc.network, {'smallsearch': True})
if data:
m = data[0]
if m['cacheUrl']:
@ -233,10 +234,11 @@ class Google(callbacks.PluginRegexp):
Returns the results of each search, in order, from greatest number
of results to least.
"""
channel = msg.args[0]
channel = msg.channel
network = irc.network
results = []
for arg in args:
text = self.search(arg, channel, {'smallsearch': True})
text = self.search(arg, channel, network, {'smallsearch': True})
i = text.find('id="resultStats"')
stats = utils.web.htmlToText(self._fight_re.search(text).group('stats'))
if stats == '':
@ -246,7 +248,7 @@ class Google(callbacks.PluginRegexp):
results.append((int(count), arg))
results.sort()
results.reverse()
if self.registryValue('bold', msg.args[0]):
if self.registryValue('bold', channel, network):
bold = ircutils.bold
else:
bold = repr
@ -294,26 +296,26 @@ class Google(callbacks.PluginRegexp):
codes (not language names), which are listed here:
https://cloud.google.com/translate/docs/languages
"""
channel = msg.args[0]
(text, language) = self._translate(sourceLang, targetLang, text)
irc.reply(text, language)
translate = wrap(translate, ['something', 'to', 'something', 'text'])
def googleSnarfer(self, irc, msg, match):
r"^google\s+(.*)$"
if not self.registryValue('searchSnarfer', msg.args[0]):
if not self.registryValue('searchSnarfer', msg.channel, irc.network):
return
searchString = match.group(1)
data = self.search(searchString, msg.args[0], {'smallsearch': True})
data = self.search(searchString, msg.channel, irc.network,
{'smallsearch': True})
if data['responseData']['results']:
url = data['responseData']['results'][0]['unescapedUrl']
irc.reply(url, prefixNick=False)
googleSnarfer = urlSnarfer(googleSnarfer)
def _googleUrl(self, s, channel):
def _googleUrl(self, s, channel, network):
s = utils.web.urlquote_plus(s)
url = r'http://%s/search?q=%s' % \
(self.registryValue('baseUrl', channel), s)
(self.registryValue('baseUrl', channel, network), s)
return url
_calcRe1 = re.compile(r'<span class="cwcot".*?>(.*?)</span>', re.I)
@ -325,10 +327,7 @@ class Google(callbacks.PluginRegexp):
Uses Google's calculator to calculate the value of <expression>.
"""
channel = msg.args[0]
if not irc.isChannel(channel):
channel = None
url = self._googleUrl(expr, channel)
url = self._googleUrl(expr, msg.channel, irc.network)
h = {"User-Agent":"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36"}
html = utils.web.getUrl(url, headers=h).decode('utf8')
match = self._calcRe1.search(html)
@ -359,10 +358,7 @@ class Google(callbacks.PluginRegexp):
Looks <phone number> up on Google.
"""
channel = msg.args[0]
if not irc.isChannel(channel):
channel = None
url = self._googleUrl(phonenumber, channel)
url = self._googleUrl(phonenumber, msg.channel, irc.network)
html = utils.web.getUrl(url).decode('utf8')
m = self._phoneRe.search(html)
if m is not None:

View File

@ -92,7 +92,7 @@ class Herald(callbacks.Plugin):
return # Recently split.
channel = msg.args[0]
irc = callbacks.SimpleProxy(irc, msg)
if self.registryValue('heralding', channel):
if self.registryValue('heralding', channel, irc.network):
try:
id = ircdb.users.getUserId(msg.prefix)
if id in self.splitters:
@ -100,22 +100,26 @@ class Herald(callbacks.Plugin):
return
herald = self.db[channel, id]
except KeyError:
default = self.registryValue('default', channel)
default = self.registryValue('default', channel, irc.network)
if default:
default = ircutils.standardSubstitute(irc, msg, default)
msgmaker = ircmsgs.privmsg
if self.registryValue('default.notice', channel):
if self.registryValue('default.notice',
channel, irc.network):
msgmaker = ircmsgs.notice
target = msg.nick
if self.registryValue('default.public', channel):
if self.registryValue('default.public',
channel, irc.network):
target = channel
irc.queueMsg(msgmaker(target, default))
return
now = time.time()
throttle = self.registryValue('throttle', channel)
throttle = self.registryValue('throttle',
channel, irc.network)
if now - self.lastHerald.get((channel, id), 0) > throttle:
if (channel, id) in self.lastParts:
i = self.registryValue('throttle.afterPart', channel)
i = self.registryValue('throttle.afterPart',
channel, irc.network)
if now - self.lastParts[channel, id] < i:
return
self.lastHerald[channel, id] = now
@ -160,7 +164,7 @@ class Herald(callbacks.Plugin):
self.setRegistryValue('default', text, channel)
irc.replySuccess()
else:
resp = self.registryValue('default', channel) or \
resp = self.registryValue('default', channel, irc.network) or \
_('I do not have a default herald set for %s.') % channel
irc.reply(resp)
default = wrap(default, ['channel',

View File

@ -243,16 +243,16 @@ class Karma(callbacks.Plugin):
return thing
def _respond(self, irc, channel, thing, karma):
if self.registryValue('response', channel):
if self.registryValue('response', channel, irc.network):
irc.reply(_('%(thing)s\'s karma is now %(karma)i') %
{'thing': thing, 'karma': karma})
else:
irc.noReply()
def _doKarma(self, irc, msg, channel, thing):
inc = self.registryValue('incrementChars', channel)
dec = self.registryValue('decrementChars', channel)
onlynicks = self.registryValue('onlyNicks', channel)
inc = self.registryValue('incrementChars', channel, irc.network)
dec = self.registryValue('decrementChars', channel, irc.network)
onlynicks = self.registryValue('onlyNicks', channel, irc.network)
karma = ''
for s in inc:
if thing.endswith(s):
@ -262,7 +262,8 @@ class Karma(callbacks.Plugin):
irc.state.channels[channel].users):
return
if ircutils.strEqual(thing, msg.nick) and \
not self.registryValue('allowSelfRating', channel):
not self.registryValue('allowSelfRating',
channel, irc.network):
irc.error(_('You\'re not allowed to adjust your own karma.'))
return
self.db.increment(channel, self._normalizeThing(thing))
@ -274,7 +275,8 @@ class Karma(callbacks.Plugin):
irc.state.channels[channel].users):
return
if ircutils.strEqual(thing, msg.nick) and \
not self.registryValue('allowSelfRating', channel):
not self.registryValue('allowSelfRating',
channel, irc.network):
irc.error(_('You\'re not allowed to adjust your own karma.'))
return
self.db.decrement(channel, self._normalizeThing(thing))
@ -283,23 +285,22 @@ class Karma(callbacks.Plugin):
self._respond(irc, channel, thing, karma[0]-karma[1])
def invalidCommand(self, irc, msg, tokens):
channel = msg.args[0]
if irc.isChannel(channel) and tokens:
if msg.channel and tokens:
thing = ' '.join(tokens)
self._doKarma(irc, msg, channel, thing)
self._doKarma(irc, msg, msg.channel, thing)
def doPrivmsg(self, irc, msg):
# We don't handle this if we've been addressed because invalidCommand
# will handle it for us. This prevents us from accessing the db twice
# and therefore crashing.
if not (msg.addressed or msg.repliedTo):
channel = msg.args[0]
if irc.isChannel(channel) and \
if msg.channel and \
not ircmsgs.isCtcp(msg) and \
self.registryValue('allowUnaddressedKarma', channel):
self.registryValue('allowUnaddressedKarma',
msg.channel, irc.network):
irc = callbacks.SimpleProxy(irc, msg)
thing = msg.args[1].rstrip()
self._doKarma(irc, msg, channel, thing)
self._doKarma(irc, msg, msg.channel, thing)
@internationalizeDocstring
def karma(self, irc, msg, args, channel, things):
@ -320,7 +321,7 @@ class Karma(callbacks.Plugin):
else:
(added, subtracted) = t
total = added - subtracted
if self.registryValue('simpleOutput', channel):
if self.registryValue('simpleOutput', channel, irc.network):
s = format('%s: %i', name, total)
else:
s = format(_('Karma for %q has been increased %n and '
@ -342,7 +343,7 @@ class Karma(callbacks.Plugin):
irc.reply(_('I didn\'t know the karma for any of those '
'things.'))
else: # No name was given. Return the top/bottom N karmas.
limit = self.registryValue('rankingDisplay', channel)
limit = self.registryValue('rankingDisplay', channel, irc.network)
highest = [format('%q (%s)', s, t)
for (s, t) in self.db.top(channel, limit)]
lowest = [format('%q (%s)', s, t)
@ -372,7 +373,8 @@ class Karma(callbacks.Plugin):
necessary if the message isn't sent in the channel itself.
"""
L = self.db.most(channel, kind,
self.registryValue('mostDisplay', channel))
self.registryValue('mostDisplay',
channel, irc.network))
if L:
L = [format('%q: %i', name, i) for (name, i) in L]
irc.reply(format('%L', L))

View File

@ -85,7 +85,7 @@ class Lart(plugins.ChannelIdDatabasePlugin):
text = text.replace('$who', target)
if reason:
text += _(' for ') + reason
if self.registryValue('showIds', channel):
if self.registryValue('showIds', channel, irc.network):
text += format(' (#%i)', lart.id)
irc.reply(text, action=True)
lart = wrap(lart, ['channeldb', optional('id'), 'text'])

View File

@ -48,9 +48,9 @@ class Limiter(callbacks.Plugin):
irc.noReply()
def _enforceLimit(self, irc, channel):
if self.registryValue('enable', channel):
maximum = self.registryValue('maximumExcess', channel)
minimum = self.registryValue('minimumExcess', channel)
if self.registryValue('enable', channel, irc.network):
maximum = self.registryValue('maximumExcess', channel, irc.network)
minimum = self.registryValue('minimumExcess', channel, irc.network)
assert maximum > minimum
currentUsers = len(irc.state.channels[channel].users)
currentLimit = irc.state.channels[channel].modes.get('l', 0)
@ -62,7 +62,7 @@ class Limiter(callbacks.Plugin):
def doJoin(self, irc, msg):
if not ircutils.strEqual(msg.nick, irc.nick):
irc = callbacks.SimpleProxy(irc, msg)
self._enforceLimit(irc, msg.args[0])
self._enforceLimit(irc, msg.channel)
doPart = doJoin
doKick = doJoin

View File

@ -115,9 +115,9 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler):
db.isolation_level = None
return db
def _updateRank(self, channel, regexp):
def _updateRank(self, network, channel, regexp):
subfolder = None if channel == 'global' else channel
if self.registryValue('keepRankInfo', subfolder):
if self.registryValue('keepRankInfo', subfolder, network):
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT usage_count
@ -153,10 +153,10 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler):
return True
def do_privmsg_notice(self, irc, msg):
channel = msg.args[0]
if not irc.isChannel(channel):
channel = msg.channel
if not channel:
return
if self.registryValue('enable', channel):
if self.registryValue('enable', channel, irc.network):
actions = []
results = []
for channel in set(map(plugins.getChannel, (channel, 'global'))):
@ -168,12 +168,12 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler):
results.extend([(channel,)+x for x in cursor.fetchall()])
if len(results) == 0:
return
max_triggers = self.registryValue('maxTriggers', channel)
max_triggers = self.registryValue('maxTriggers', channel, irc.network)
for (channel, regexp, action) in results:
for match in re.finditer(regexp, msg.args[1]):
if match is not None:
thisaction = action
self._updateRank(channel, regexp)
self._updateRank(irc.network, channel, regexp)
for (i, j) in enumerate(match.groups()):
if match.group(i+1) is not None:
thisaction = re.sub(r'\$' + str(i+1), match.group(i+1), thisaction)
@ -192,7 +192,7 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler):
self.do_privmsg_notice(irc, msg)
def doNotice(self, irc, msg):
if self.registryValue('enableForNotices', msg.args[0]):
if self.registryValue('enableForNotices', msg.channel, irc.network):
self.do_privmsg_notice(irc, msg)
@internationalizeDocstring
@ -406,7 +406,7 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler):
return
s = [ "%s: %s" % (ircutils.bold('#'+str(regexp[1])), regexp[0]) for regexp in regexps ]
separator = self.registryValue('listSeparator', channel)
separator = self.registryValue('listSeparator', channel, irc.network)
irc.reply(separator.join(s))
list = wrap(list, ['channelOrGlobal'])
@ -419,7 +419,7 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler):
rankListLength registry value. <channel> is only necessary if the
message isn't sent in the channel itself.
"""
numregexps = self.registryValue('rankListLength', channel)
numregexps = self.registryValue('rankListLength', channel, irc.network)
db = self.getDb(channel)
cursor = db.cursor()
cursor.execute("""SELECT regexp, usage_count

View File

@ -124,7 +124,7 @@ class Misc(callbacks.Plugin):
utils.timeElapsed(punishment, seconds=False)))
return
# Now, for normal handling.
channel = msg.args[0]
channel = msg.channel
# Only bother with the invaildCommand flood handling if it's actually
# enabled
if conf.supybot.abuse.flood.command.invalid():
@ -138,7 +138,8 @@ class Misc(callbacks.Plugin):
msg.prefix != irc.prefix and \
ircutils.isUserHostmask(msg.prefix):
penalty = conf.supybot.abuse.flood.command.invalid.punishment()
banmask = banmasker(msg.prefix, channel=None)
banmask = banmasker(msg.prefix, channel=channel,
network=irc.network)
self.log.info('Ignoring %s for %s seconds due to an apparent '
'invalid command flood.', banmask, penalty)
if tokens and tokens[0] == 'Error:':
@ -153,7 +154,8 @@ class Misc(callbacks.Plugin):
utils.timeElapsed(penalty, seconds=False)))
return
# Now, for normal handling.
if conf.get(conf.supybot.reply.whenNotCommand, channel):
if conf.supybot.reply.whenNotCommand.getSpecific(
channel, irc.network)():
if len(tokens) >= 2:
cb = irc.getCallback(tokens[0])
if cb:
@ -177,7 +179,7 @@ class Misc(callbacks.Plugin):
if channel != irc.nick else _('private'))
if irc.nested:
bracketConfig = conf.supybot.commands.nested.brackets
brackets = conf.get(bracketConfig, channel)
brackets = bracketConfig.getSpecific(channel, irc.network)()
if brackets:
(left, right) = brackets
irc.reply(left + ' '.join(tokens) + right)
@ -386,7 +388,7 @@ class Misc(callbacks.Plugin):
return
try:
L = irc._mores[userHostmask]
number = self.registryValue('mores', msg.args[0])
number = self.registryValue('mores', msg.channel, irc.network)
chunks = [L.pop() for x in range(0, number)]
if L:
if len(L) < 2:
@ -406,7 +408,7 @@ class Misc(callbacks.Plugin):
def _validLastMsg(self, irc, msg):
return msg.prefix and \
msg.command == 'PRIVMSG' and \
irc.isChannel(msg.args[0])
msg.channel
@internationalizeDocstring
def last(self, irc, msg, args, optlist):
@ -423,9 +425,9 @@ class Misc(callbacks.Plugin):
predicates = {}
nolimit = False
skipfirst = True
if irc.isChannel(msg.args[0]):
if msg.channel:
predicates['in'] = lambda m: ircutils.strEqual(m.args[0],
msg.args[0])
msg.channel)
else:
skipfirst = False
for (option, arg) in optlist:
@ -437,7 +439,7 @@ class Misc(callbacks.Plugin):
def f(m, arg=arg):
return ircutils.strEqual(m.args[0], arg)
predicates['in'] = f
if arg != msg.args[0]:
if arg != msg.channel:
skipfirst = False
elif option == 'on':
def f(m, arg=arg):
@ -484,6 +486,7 @@ class Misc(callbacks.Plugin):
predicates.append(userInChannel)
# Make sure the user can't get messages from a +s channel unless
# they're calling the command from that channel or from a query
# TODO: support statusmsg, but be careful about leaking scopes.
def notSecretMsg(m):
return not irc.isChannel(msg.args[0]) \
or msg.args[0] == m.args[0] \

View File

@ -325,7 +325,7 @@ class MoobotFactoids(callbacks.Plugin):
else:
key = ' '.join(tokens)
key = self._sanitizeKey(key)
channel = plugins.getChannel(msg.args[0])
channel = plugins.getChannel(msg.channel)
fact = self.db.getFactoid(channel, key)
if fact:
self.db.updateRequest(channel, key, msg.prefix)
@ -385,7 +385,7 @@ class MoobotFactoids(callbacks.Plugin):
def addFactoid(self, irc, msg, tokens):
# First, check and see if the entire message matches a factoid key
channel = plugins.getChannel(msg.args[0])
channel = plugins.getChannel(msg.channel)
id = self._getUserId(irc, msg.prefix)
try:
(key, fact) = self._getKeyAndFactoid(tokens)
@ -401,7 +401,7 @@ class MoobotFactoids(callbacks.Plugin):
id = self._getUserId(irc, msg.prefix)
(key, regexp) = list(map(' '.join,
utils.iter.split('=~'.__eq__, tokens, maxsplit=1)))
channel = plugins.getChannel(msg.args[0])
channel = plugins.getChannel(msg.channel)
# Check and make sure it's in the DB
fact = self._getFactoid(irc, channel, key)
self._checkNotLocked(irc, channel, key)
@ -422,7 +422,7 @@ class MoobotFactoids(callbacks.Plugin):
isAlso = pairs.index(['is', 'also'])
key = ' '.join(tokens[:isAlso])
new_text = ' '.join(tokens[isAlso+2:])
channel = plugins.getChannel(msg.args[0])
channel = plugins.getChannel(msg.channel)
fact = self._getFactoid(irc, channel, key)
self._checkNotLocked(irc, channel, key)
# It's fair game if we get to here
@ -433,7 +433,7 @@ class MoobotFactoids(callbacks.Plugin):
def replaceFactoid(self, irc, msg, tokens):
# Must be registered!
channel = plugins.getChannel(msg.args[0])
channel = plugins.getChannel(msg.channel)
id = self._getUserId(irc, msg.prefix)
del tokens[0] # remove the "no,"
try:
@ -576,7 +576,7 @@ class MoobotFactoids(callbacks.Plugin):
method = getattr(self, '_most%s' % method, None)
if method is None:
raise callbacks.ArgumentError
limit = self.registryValue('mostCount', channel)
limit = self.registryValue('mostCount', channel, irc.network)
method(irc, channel, limit)
most = wrap(most, ['channeldb',
('literal', ('popular', 'authored', 'recent'))])
@ -652,7 +652,8 @@ class MoobotFactoids(callbacks.Plugin):
if not results:
irc.reply(format(_('No keys matching %q found.'), search))
elif len(results) == 1 and \
self.registryValue('showFactoidIfOnlyOneMatch', channel):
self.registryValue('showFactoidIfOnlyOneMatch',
channel, irc.network):
key = results[0][0]
self.invalidCommand(irc, msg, [key])
else:

View File

@ -186,7 +186,7 @@ class Note(callbacks.Plugin):
specified by separating their names by commas.
"""
# Let's get the from user.
public = irc.isChannel(msg.args[0])
public = bool(msg.channel)
sent = []
for target in targets:
id = self.db.send(user.id, target.id, public, text)
@ -209,7 +209,7 @@ class Note(callbacks.Plugin):
'that have been sent to you.', Raise=True)
self.db.setRead(id)
text += ' (in reply to #%s)' % id
public = irc.isChannel(msg.args[0])
public = bool(msg.channel)
try:
target = ircdb.users.getUser(note.frm)
except KeyError:
@ -271,7 +271,7 @@ class Note(callbacks.Plugin):
note = wrap(note, ['user', ('id', 'note')])
def _formatNoteId(self, irc, msg, note, sent=False):
if note.public or not irc.isChannel(msg.args[0]):
if note.public or not msg.channel:
if sent:
sender = plugins.getUserName(note.to)
return format('#%i to %s', note.id, sender)

View File

@ -258,10 +258,11 @@ class Owner(callbacks.Plugin):
utils.timeElapsed(punishment, seconds=False)))
return
try:
tokens = callbacks.tokenize(s, channel=msg.args[0])
tokens = callbacks.tokenize(s, channel=msg.channel,
network=irc.network)
self.Proxy(irc, msg, tokens)
except SyntaxError as e:
irc.queueMsg(callbacks.error(msg, str(e)))
irc.error(str(e))
def logmark(self, irc, msg, args, text):
"""<text>

View File

@ -84,7 +84,7 @@ class Praise(plugins.ChannelIdDatabasePlugin):
text = text.replace('$who', target)
if reason:
text += _(' for ') + reason
if self.registryValue('showIds', channel):
if self.registryValue('showIds', channel, irc.network):
text += format(' (#%i)', praise.id)
irc.reply(text, action=True)
praise = wrap(praise, ['channeldb', optional('id'), 'text'])

View File

@ -45,7 +45,7 @@ class Protector(callbacks.Plugin):
if ircutils.strEqual(msg.nick, irc.nick):
self.log.debug('%q is immune, it\'s me.', msg)
return True # It's the bot itself.
if msg.nick in self.registryValue('immune', msg.args[0]):
if msg.nick in self.registryValue('immune', msg.channel, irc.network):
self.log.debug('%q is immune, it\'s configured to be immune.', msg)
return True
return False
@ -78,18 +78,18 @@ class Protector(callbacks.Plugin):
self.log.debug('Ignoring %q, %s.', msg, reason)
if not msg.args:
ignore('no msg.args')
elif not irc.isChannel(msg.args[0]):
elif not msg.channel:
ignore('not on a channel')
elif not self.registryValue('enable', msg.args[0]):
elif not self.registryValue('enable', msg.channel, irc.network):
ignore('supybot.plugins.Protector.enable is False.')
elif msg.args[0] not in irc.state.channels:
elif msg.channel not in irc.state.channels:
# One has to wonder how this would happen, but just in case...
ignore('bot isn\'t in channel')
elif irc.nick not in irc.state.channels[msg.args[0]].ops:
elif irc.nick not in irc.state.channels[msg.channel].ops:
ignore('bot is not opped')
elif msg.nick not in irc.state.channels[msg.args[0]].users:
elif msg.nick not in irc.state.channels[msg.channel].users:
ignore('sender is not in channel (ChanServ, maybe?)')
elif msg.nick not in irc.state.channels[msg.args[0]].ops:
elif msg.nick not in irc.state.channels[msg.channel].ops:
ignore('sender is not an op in channel (IRCOP, maybe?)')
elif self.isImmune(irc, msg):
ignore('sender is immune')
@ -97,7 +97,7 @@ class Protector(callbacks.Plugin):
super(Protector, self).__call__(irc, msg)
def doMode(self, irc, msg):
channel = msg.args[0]
channel = msg.channel
chanOp = ircdb.makeChannelCapability(channel, 'op')
chanVoice = ircdb.makeChannelCapability(channel, 'voice')
chanHalfOp = ircdb.makeChannelCapability(channel, 'halfop')
@ -134,7 +134,7 @@ class Protector(callbacks.Plugin):
# Handle bans.
def doKick(self, irc, msg):
channel = msg.args[0]
channel = msg.channel
kicked = msg.args[1].split(',')
protected = []
for nick in kicked:

View File

@ -241,14 +241,17 @@ class QuoteGrabs(callbacks.Plugin):
if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg):
return
irc = callbacks.SimpleProxy(irc, msg)
if irc.isChannel(msg.args[0]):
(chan, payload) = msg.args
words = self.registryValue('randomGrabber.minimumWords', chan)
length = self.registryValue('randomGrabber.minimumCharacters',chan)
if msg.channel:
payload = msg.args[1]
words = self.registryValue('randomGrabber.minimumWords',
msg.channel, irc.network)
length = self.registryValue('randomGrabber.minimumCharacters',
msg.channel, irc.network)
grabTime = \
self.registryValue('randomGrabber.averageTimeBetweenGrabs', chan)
channel = plugins.getChannel(chan)
if self.registryValue('randomGrabber', chan):
self.registryValue('randomGrabber.averageTimeBetweenGrabs',
msg.channel, irc.network)
channel = plugins.getChannel(msg.channel)
if self.registryValue('randomGrabber', msg.channel, irc.network):
if len(payload) > length and len(payload.split()) > words:
try:
last = int(self.db.select(channel, msg.nick))
@ -287,6 +290,8 @@ class QuoteGrabs(callbacks.Plugin):
for m in reversed(irc.state.history):
if m.command == 'PRIVMSG' and ircutils.nickEqual(m.nick, nick) \
and ircutils.strEqual(m.args[0], chan):
# TODO: strip statusmsg prefix for comparison? Must be careful
# abouk leaks, though.
self._grab(channel, irc, m, msg.prefix)
irc.replySuccess()
return

View File

@ -352,7 +352,7 @@ class RSS(callbacks.Plugin):
announced_feeds = set()
for irc in world.ircs:
for channel in irc.state.channels:
announced_feeds |= self.registryValue('announce', channel)
announced_feeds |= self.registryValue('announce', channel, irc.network)
for name in announced_feeds:
feed = self.get_feed(name)
if not feed:
@ -385,14 +385,15 @@ class RSS(callbacks.Plugin):
new_entries = sort_feed_items(new_entries, 'newestFirst')
for irc in world.ircs:
for channel in irc.state.channels:
if feed.name not in self.registryValue('announce', channel):
if feed.name not in self.registryValue('announce',
channel, irc.network):
continue
if initial:
max_entries = \
self.registryValue('initialAnnounceHeadlines', channel)
max_entries = self.registryValue(
'initialAnnounceHeadlines', channel, irc.network)
else:
max_entries = \
self.registryValue('maximumAnnounceHeadlines', channel)
max_entries = self.registryValue(
'maximumAnnounceHeadlines', channel, irc.network)
announced_entries = new_entries[0:max_entries]
announced_entries = sort_feed_items(announced_entries, order)
for entry in announced_entries:
@ -402,9 +403,9 @@ class RSS(callbacks.Plugin):
#################
# Entry rendering
def should_send_entry(self, channel, entry):
whitelist = self.registryValue('keywordWhitelist', channel)
blacklist = self.registryValue('keywordBlacklist', channel)
def should_send_entry(self, network, channel, entry):
whitelist = self.registryValue('keywordWhitelist', channel, network)
blacklist = self.registryValue('keywordBlacklist', channel, network)
# fix shadowing by "from supybot.commands import *"
try:
@ -429,14 +430,15 @@ class RSS(callbacks.Plugin):
_normalize_entry = utils.str.multipleReplacer(
{'\r': ' ', '\n': ' ', '\x00': ''})
def format_entry(self, channel, feed, entry, is_announce):
def format_entry(self, network, channel, feed, entry, is_announce):
key_name = 'announceFormat' if is_announce else 'format'
if feed.name in self.registryValue('feeds'):
specific_key_name = registry.join(['feeds', feed.name, key_name])
template = self.registryValue(specific_key_name, channel) or \
self.registryValue(key_name, channel)
template = self.registryValue(specific_key_name,
channel, network) or \
self.registryValue(key_name, channel, network)
else:
template = self.registryValue(key_name, channel)
template = self.registryValue(key_name, channel, network)
date = entry.get('published_parsed')
date = utils.str.timestamp(date)
s = string.Template(template).substitute(
@ -446,9 +448,9 @@ class RSS(callbacks.Plugin):
return self._normalize_entry(s)
def announce_entry(self, irc, channel, feed, entry):
if self.should_send_entry(channel, entry):
s = self.format_entry(channel, feed, entry, True)
if self.registryValue('notice', channel):
if self.should_send_entry(irc.network, channel, entry):
s = self.format_entry(irc.network, channel, feed, entry, True)
if self.registryValue('notice', channel, irc.network):
m = ircmsgs.notice(channel, s)
else:
m = ircmsgs.privmsg(channel, s)
@ -559,10 +561,7 @@ class RSS(callbacks.Plugin):
feed = self.get_feed(url)
if not feed:
feed = Feed(url, url, True)
if irc.isChannel(msg.args[0]):
channel = msg.args[0]
else:
channel = None
channel = msg.channel
self.update_feed_if_needed(feed)
entries = feed.entries
if not entries:
@ -573,13 +572,13 @@ class RSS(callbacks.Plugin):
s += str(feed.last_exception)
irc.error(s)
return
n = n or self.registryValue('defaultNumberOfHeadlines', channel)
entries = list(filter(lambda e:self.should_send_entry(channel, e),
n = n or self.registryValue('defaultNumberOfHeadlines', channel, irc.network)
entries = list(filter(lambda e:self.should_send_entry(irc.network, channel, e),
feed.entries))
entries = entries[:n]
headlines = map(lambda e:self.format_entry(channel, feed, e, False),
headlines = map(lambda e:self.format_entry(irc.network, channel, feed, e, False),
entries)
sep = self.registryValue('headlineSeparator', channel)
sep = self.registryValue('headlineSeparator', channel, irc.network)
irc.replies(headlines, joiner=sep)
rss = wrap(rss, [first('url', 'feedName'), additional('int')])

View File

@ -197,7 +197,7 @@ class Relay(callbacks.Plugin):
do401 = do402
def _formatPrivmsg(self, nick, network, msg):
channel = msg.args[0]
channel = msg.channel
if self.registryValue('includeNetwork', channel):
network = '@' + network
else:
@ -229,12 +229,12 @@ class Relay(callbacks.Plugin):
assert msg.command in ('PRIVMSG', 'NOTICE', 'TOPIC')
for otherIrc in world.ircs:
if otherIrc != irc and not otherIrc.zombie:
if msg.args[0] in otherIrc.state.channels:
if msg.channel in otherIrc.state.channels:
msg.tag('relayedMsg')
otherIrc.queueMsg(msg)
def _checkRelayMsg(self, msg):
channel = msg.args[0]
channel = msg.channel
if channel in self.lastRelayMsgs:
q = self.lastRelayMsgs[channel]
unformatted = ircutils.stripFormatting(msg.args[1])
@ -247,7 +247,7 @@ class Relay(callbacks.Plugin):
def _punishRelayers(self, msg):
assert self._checkRelayMsg(msg), 'Punishing without checking.'
who = msg.prefix
channel = msg.args[0]
channel = msg.channel
def notPunishing(irc, s, *args):
self.log.info('Not punishing %s in %s on %s: %s.',
msg.prefix, channel, irc.network, s, *args)
@ -268,12 +268,12 @@ class Relay(callbacks.Plugin):
def doPrivmsg(self, irc, msg):
if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg):
return
(channel, text) = msg.args
if irc.isChannel(channel):
text = msg.args[1]
if msg.channel:
irc = self._getRealIrc(irc)
if channel not in self.registryValue('channels'):
if msg.channel not in self.registryValue('channels'):
return
ignores = self.registryValue('ignores', channel)
ignores = self.registryValue('ignores', msg.channel, irc.network)
for ignore in ignores:
if ircutils.hostmaskPatternEqual(ignore, msg.prefix):
self.log.debug('Refusing to relay %s, ignored by %s.',
@ -281,7 +281,8 @@ class Relay(callbacks.Plugin):
return
# Let's try to detect other relay bots.
if self._checkRelayMsg(msg):
if self.registryValue('punishOtherRelayBots', channel):
if self.registryValue('punishOtherRelayBots',
msg.channel, irc.network):
self._punishRelayers(msg)
# Either way, we don't relay the message.
else:
@ -291,13 +292,12 @@ class Relay(callbacks.Plugin):
else:
network = self._getIrcName(irc)
s = self._formatPrivmsg(msg.nick, network, msg)
m = self._msgmaker(channel, s)
m = self._msgmaker(msg.channel, network, s)
self._sendToOthers(irc, m)
def _msgmaker(self, target, s):
def _msgmaker(self, target, network, s):
msg = dynamic.msg
channel = dynamic.channel
if self.registryValue('noticeNonPrivmsgs', dynamic.channel) and \
if self.registryValue('noticeNonPrivmsgs', target) and \
msg.command != 'PRIVMSG':
return ircmsgs.notice(target, s)
else:
@ -423,16 +423,15 @@ class Relay(callbacks.Plugin):
if msg.relayedMsg:
self._addRelayMsg(msg)
else:
channel = msg.args[0]
if channel in self.registryValue('channels'):
if msg.channel in self.registryValue('channels'):
network = self._getIrcName(irc)
s = self._formatPrivmsg(irc.nick, network, msg)
relayMsg = self._msgmaker(channel, s)
relayMsg = self._msgmaker(msg.args[0], s)
self._sendToOthers(irc, relayMsg)
return msg
def _addRelayMsg(self, msg):
channel = msg.args[0]
channel = msg.channel
if channel in self.lastRelayMsgs:
q = self.lastRelayMsgs[channel]
else:

View File

@ -125,8 +125,8 @@ class Seen(callbacks.Plugin):
def doPrivmsg(self, irc, msg):
if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg):
return
if irc.isChannel(msg.args[0]):
channel = msg.args[0]
if msg.channel:
channel = msg.channel
said = ircmsgs.prettyPrint(msg)
self.db.update(channel, msg.nick, said)
self.anydb.update(channel, msg.nick, said)
@ -184,7 +184,8 @@ class Seen(callbacks.Plugin):
results = []
if '*' in name:
if (len(name.replace('*', '')) <
self.registryValue('minimumNonWildcard', channel)):
self.registryValue('minimumNonWildcard',
channel, irc.network)):
irc.error(_('Not enough non-wildcard characters.'),
Raise=True)
results = db.seenWildcard(channel, name)
@ -196,7 +197,7 @@ class Seen(callbacks.Plugin):
reply = format(_('%s was last seen in %s %s ago'),
nick, channel,
utils.timeElapsed(time.time()-when))
if self.registryValue('showLastMessage', channel):
if self.registryValue('showLastMessage', channel, irc.network):
if minisix.PY2:
said = said.decode('utf8')
reply = _('%s: %s') % (reply, said)
@ -277,7 +278,7 @@ class Seen(callbacks.Plugin):
(when, said) = db.seen(channel, '<last>')
reply = format(_('Someone was last seen in %s %s ago'),
channel, utils.timeElapsed(time.time()-when))
if self.registryValue('showLastMessage', channel):
if self.registryValue('showLastMessage', channel, irc.network):
reply = _('%s: %s') % (reply, said)
irc.reply(reply)
except KeyError:
@ -304,7 +305,7 @@ class Seen(callbacks.Plugin):
reply = format(_('%s was last seen in %s %s ago'),
user.name, channel,
utils.timeElapsed(time.time()-when))
if self.registryValue('showLastMessage', channel):
if self.registryValue('showLastMessage', channel, irc.network):
reply = _('%s: %s') % (reply, said)
irc.reply(reply)
except KeyError:

View File

@ -38,8 +38,8 @@ def registerNick(nick, password=''):
p = conf.supybot.plugins.Services.Nickserv.get('password')
h = _('Determines what password the bot will use with NickServ when ' \
'identifying as %s.') % nick
v = conf.registerGlobalValue(p, nick,
registry.String(password, h, private=True))
v = conf.registerNetworkValue(p, nick,
registry.String(password, h, private=True))
if password:
v.setValue(password)
@ -65,7 +65,7 @@ class ValidNickSet(conf.ValidNicks):
List = ircutils.IrcSet
Services = conf.registerPlugin('Services')
conf.registerGlobalValue(Services, 'nicks',
conf.registerNetworkValue(Services, 'nicks',
ValidNickSet([], _("""Determines what nicks the bot will use with
services.""")))
@ -76,19 +76,19 @@ conf.registerGlobalValue(Services, 'disabledNetworks',
Networks(_('QuakeNet').split(), _("""Determines what networks this plugin
will be disabled on.""")))
conf.registerGlobalValue(Services, 'noJoinsUntilIdentified',
conf.registerNetworkValue(Services, 'noJoinsUntilIdentified',
registry.Boolean(False, _("""Determines whether the bot will not join any
channels until it is identified. This may be useful, for instances, if
you have a vhost that isn't set until you're identified, or if you're
joining +r channels that won't allow you to join unless you identify.""")))
conf.registerGlobalValue(Services, 'ghostDelay',
conf.registerNetworkValue(Services, 'ghostDelay',
registry.NonNegativeInteger(60, _("""Determines how many seconds the bot will
wait between successive GHOST attempts. Set this to 0 to disable GHOST.""")))
conf.registerGlobalValue(Services, 'NickServ',
conf.registerNetworkValue(Services, 'NickServ',
ValidNickOrEmptyString('NickServ', _("""Determines what nick the 'NickServ' service
has.""")))
conf.registerGroup(Services.NickServ, 'password')
conf.registerGlobalValue(Services, 'ChanServ',
conf.registerNetworkValue(Services, 'ChanServ',
ValidNickOrEmptyString('ChanServ', _("""Determines what nick the 'ChanServ' service
has.""")))
conf.registerChannelValue(Services.ChanServ, 'password',

View File

@ -55,7 +55,7 @@ class Services(callbacks.Plugin):
def __init__(self, irc):
self.__parent = super(Services, self)
self.__parent.__init__(irc)
for nick in self.registryValue('nicks'):
for nick in self.registryValue('nicks', network=irc.network):
config.registerNick(nick)
self.reset()
@ -75,9 +75,9 @@ class Services(callbacks.Plugin):
def outFilter(self, irc, msg):
if msg.command == 'JOIN' and not self.disabled(irc):
if not self.identified:
if self.registryValue('noJoinsUntilIdentified'):
self.log.info('Holding JOIN to %s until identified.',
msg.args[0])
if self.registryValue('noJoinsUntilIdentified', network=irc.network):
self.log.info('Holding JOIN to %s @ %s until identified.',
msg.channel, irc.network)
self.waitingJoins.setdefault(irc.network, [])
self.waitingJoins[irc.network].append(msg)
return None
@ -90,25 +90,25 @@ class Services(callbacks.Plugin):
else:
return network_nick
def _getNickServPassword(self, nick):
def _getNickServPassword(self, nick, network):
# This should later be nick-specific.
assert nick in self.registryValue('nicks')
return self.registryValue('NickServ.password.%s' % nick)
assert nick in self.registryValue('nicks', network=network)
return self.registryValue('NickServ.password.%s' % nick, network=network)
def _setNickServPassword(self, nick, password):
def _setNickServPassword(self, nick, password, network):
# This also should be nick-specific.
assert nick in self.registryValue('nicks')
self.setRegistryValue('NickServ.password.%s' % nick, password)
assert nick in self.registryValue('nicks', network=network)
self.setRegistryValue('NickServ.password.%s' % nick, password, network=network)
def _doIdentify(self, irc, nick=None):
if self.disabled(irc):
return
if nick is None:
nick = self._getNick(irc.network)
if nick not in self.registryValue('nicks'):
if nick not in self.registryValue('nicks', network=irc.network):
return
nickserv = self.registryValue('NickServ')
password = self._getNickServPassword(nick)
nickserv = self.registryValue('NickServ', network=irc.network)
password = self._getNickServPassword(nick, irc.network)
if not nickserv or not password:
s = 'Tried to identify without a NickServ or password set.'
self.log.warning(s)
@ -127,11 +127,11 @@ class Services(callbacks.Plugin):
return
if nick is None:
nick = self._getNick(irc.network)
if nick not in self.registryValue('nicks'):
if nick not in self.registryValue('nicks', network=irc.network):
return
nickserv = self.registryValue('NickServ')
password = self._getNickServPassword(nick)
ghostDelay = self.registryValue('ghostDelay')
nickserv = self.registryValue('NickServ', network=irc.network)
password = self._getNickServPassword(nick, irc.network)
ghostDelay = self.registryValue('ghostDelay', network=irc.network)
if not ghostDelay:
return
if not nickserv or not password:
@ -157,11 +157,11 @@ class Services(callbacks.Plugin):
if self.disabled(irc):
return
nick = self._getNick(irc.network)
if nick not in self.registryValue('nicks'):
if nick not in self.registryValue('nicks', network=irc.network):
return
nickserv = self.registryValue('NickServ')
password = self._getNickServPassword(nick)
ghostDelay = self.registryValue('ghostDelay')
nickserv = self.registryValue('NickServ', network=irc.network)
password = self._getNickServPassword(nick, irc.network)
ghostDelay = self.registryValue('ghostDelay', network=irc.network)
if not ghostDelay:
return
if nick and nickserv and password and \
@ -181,13 +181,13 @@ class Services(callbacks.Plugin):
if self.disabled(irc):
return
nick = self._getNick(irc.network)
if nick not in self.registryValue('nicks'):
if nick not in self.registryValue('nicks', network=irc.network):
return
nickserv = self.registryValue('NickServ')
nickserv = self.registryValue('NickServ', network=irc.network)
if not nickserv:
self.log.warning('NickServ is unset, cannot identify.')
return
password = self._getNickServPassword(nick)
password = self._getNickServPassword(nick, irc.network)
if not password:
self.log.warning('Password for %s is unset, cannot identify.',nick)
return
@ -205,10 +205,10 @@ class Services(callbacks.Plugin):
if self.disabled(irc):
return
nick = self._getNick(irc.network)
if nick not in self.registryValue('nicks'):
if nick not in self.registryValue('nicks', network=irc.network):
return
if nick and irc.afterConnect:
password = self._getNickServPassword(nick)
password = self._getNickServPassword(nick, irc.network)
if not password:
return
self._doGhost(irc)
@ -232,8 +232,8 @@ class Services(callbacks.Plugin):
def doNotice(self, irc, msg):
if irc.afterConnect:
nickserv = self.registryValue('NickServ')
chanserv = self.registryValue('ChanServ')
nickserv = self.registryValue('NickServ', network=irc.network)
chanserv = self.registryValue('ChanServ', network=irc.network)
if nickserv and ircutils.strEqual(msg.nick, nickserv):
self.doNickservNotice(irc, msg)
elif chanserv and ircutils.strEqual(msg.nick, chanserv):
@ -298,7 +298,7 @@ class Services(callbacks.Plugin):
'Resetting password to empty.' % on
self.log.warning(log)
self.sentGhost = time.time()
self._setNickServPassword(nick, '')
self._setNickServPassword(nick, '', irc.network)
elif self._ghosted(irc, s):
self.log.info('Received "GHOST succeeded" from NickServ %s.', on)
self.sentGhost = None
@ -355,19 +355,19 @@ class Services(callbacks.Plugin):
def checkPrivileges(self, irc, channel):
if self.disabled(irc):
return
chanserv = self.registryValue('ChanServ')
chanserv = self.registryValue('ChanServ', network=irc.network)
on = 'on %s' % irc.network
if chanserv and self.registryValue('ChanServ.op', channel):
if chanserv and self.registryValue('ChanServ.op', channel, irc.network):
if irc.nick not in irc.state.channels[channel].ops:
self.log.info('Requesting op from %s in %s %s.',
chanserv, channel, on)
irc.sendMsg(ircmsgs.privmsg(chanserv, 'op %s' % channel))
if chanserv and self.registryValue('ChanServ.halfop', channel):
if chanserv and self.registryValue('ChanServ.halfop', channel, irc.network):
if irc.nick not in irc.state.channels[channel].halfops:
self.log.info('Requesting halfop from %s in %s %s.',
chanserv, channel, on)
irc.sendMsg(ircmsgs.privmsg(chanserv, 'halfop %s' % channel))
if chanserv and self.registryValue('ChanServ.voice', channel):
if chanserv and self.registryValue('ChanServ.voice', channel, irc.network):
if irc.nick not in irc.state.channels[channel].voices:
self.log.info('Requesting voice from %s in %s %s.',
chanserv, channel, on)
@ -376,7 +376,7 @@ class Services(callbacks.Plugin):
def doMode(self, irc, msg):
if self.disabled(irc):
return
chanserv = self.registryValue('ChanServ')
chanserv = self.registryValue('ChanServ', network=irc.network)
on = 'on %s' % irc.network
if ircutils.strEqual(msg.nick, chanserv):
channel = msg.args[0]
@ -406,7 +406,7 @@ class Services(callbacks.Plugin):
self.__parent.callCommand(command, irc, msg, *args, **kwargs)
def _chanservCommand(self, irc, channel, command, log=False):
chanserv = self.registryValue('ChanServ')
chanserv = self.registryValue('ChanServ', network=irc.network)
if chanserv:
msg = ircmsgs.privmsg(chanserv,
' '.join([command, channel]))
@ -493,7 +493,8 @@ class Services(callbacks.Plugin):
invite = wrap(invite, [('checkChannelCapability', 'op'), 'inChannel'])
def doInvite(self, irc, msg):
if ircutils.strEqual(msg.nick, self.registryValue('ChanServ')):
if ircutils.strEqual(
msg.nick, self.registryValue('ChanServ', etwork=irc.network)):
channel = msg.args[1]
on = 'on %s' % irc.network
networkGroup = conf.supybot.networks.get(irc.network)
@ -506,8 +507,8 @@ class Services(callbacks.Plugin):
Identifies with NickServ using the current nick.
"""
if self.registryValue('NickServ'):
if irc.nick in self.registryValue('nicks'):
if self.registryValue('NickServ', network=irc.network):
if irc.nick in self.registryValue('nicks', network=irc.network):
self._doIdentify(irc, irc.nick)
irc.replySuccess()
else:
@ -525,7 +526,7 @@ class Services(callbacks.Plugin):
Ghosts the bot's given nick and takes it. If no nick is given,
ghosts the bot's configured nick and takes it.
"""
if self.registryValue('NickServ'):
if self.registryValue('NickServ', network=irc.network):
if not nick:
nick = self._getNick(irc.network)
if ircutils.strEqual(nick, irc.nick):
@ -547,13 +548,17 @@ class Services(callbacks.Plugin):
"""
if not password:
try:
self.registryValue('nicks').remove(nick)
v = self.registryValue('nicks', network=irc.network).copy()
v.remove(nick)
self.setRegistryValue('nicks', value=v, network=irc.network)
irc.replySuccess()
except KeyError:
irc.error(_('That nick was not configured with a password.'))
return
else:
self.registryValue('nicks').add(nick)
v = self.registryValue('nicks', network=irc.network).copy()
v.add(nick)
self.setRegistryValue('nicks', value=v, network=irc.network)
config.registerNick(nick, password)
irc.replySuccess()
password = wrap(password, [('checkCapability', 'admin'),
@ -566,7 +571,7 @@ class Services(callbacks.Plugin):
Returns the nicks that this plugin is configured to identify and ghost
with.
"""
L = list(self.registryValue('nicks'))
L = list(self.registryValue('nicks', network=irc.network))
if L:
utils.sortBy(ircutils.toLower, L)
irc.reply(format('%L', L))

View File

@ -30,7 +30,7 @@
from supybot.test import *
class ServicesTestCase(PluginTestCase):
plugins = ('Services',)
plugins = ('Services', 'Config')
config = {
'plugins.Services.NickServ': 'NickServ',
'plugins.Services.ChanServ': 'ChanServ',
@ -48,6 +48,33 @@ class ServicesTestCase(PluginTestCase):
self.failUnless(m.args[0] == 'NickServ')
self.failUnless(m.args[1].lower() == 'identify biff')
def testPasswordConfg(self):
self.assertNotError('config plugins.Services.nicks ""')
self.assertNotError('config network plugins.Services.nicks ""')
self.assertNotError('services password %s bar' % self.nick)
self.assertResponse(
'config plugins.Services.nicks',
'Global: ; test: %s' % self.nick)
self.assertResponse(
'config plugins.Services.nickserv.password.%s' % self.nick,
'Global: bar; test: bar')
self.assertNotError(
'config network plugins.Services.nickserv.password.%s bar2'
% self.nick)
self.assertResponse(
'config plugins.Services.nickserv.password.%s' % self.nick,
'Global: bar; test: bar2')
self.assertResponse(
'config plugins.Services.nickserv.password.%s' % self.nick,
'Global: bar; test: bar2')
m = self.assertNotError('services identify')
self.failUnless(m.args[0] == 'NickServ')
self.failUnless(m.args[1].lower() == 'identify bar2')
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -105,15 +105,17 @@ class ShrinkUrl(callbacks.PluginRegexp):
def _outFilterThread(self, irc, msg):
(channel, text) = msg.args
network = irc.network
for m in utils.web.httpUrlRe.finditer(text):
url = m.group(1)
if len(url) > self.registryValue('minimumLength', channel):
if len(url) > self.registryValue('minimumLength', channel, network):
try:
cmd = self.registryValue('serviceRotation',
channel, value=False)
channel, network, value=False)
cmd = cmd.getService().capitalize()
except ValueError:
cmd = self.registryValue('default', channel).capitalize()
cmd = self.registryValue('default', channel, network) \
.capitalize()
try:
shortUrl = getattr(self, '_get%sUrl' % cmd)(url)
text = text.replace(url, shortUrl)
@ -126,39 +128,41 @@ class ShrinkUrl(callbacks.PluginRegexp):
def outFilter(self, irc, msg):
if msg.command != 'PRIVMSG':
return msg
channel = msg.args[0]
if irc.isChannel(channel):
if msg.channel:
if not msg.shrunken:
if self.registryValue('outFilter', channel):
if self.registryValue('outFilter', msg.channel, irc.network):
if utils.web.httpUrlRe.search(msg.args[1]):
self._outFilterThread(irc, msg)
return None
return msg
def shrinkSnarfer(self, irc, msg, match):
channel = msg.args[0]
if not irc.isChannel(channel):
channel = msg.channel
network = irc.network
if not channel:
return
if self.registryValue('shrinkSnarfer', channel):
if self.registryValue('shrinkSnarfer', channel, network):
url = match.group(0)
r = self.registryValue('nonSnarfingRegexp', channel)
r = self.registryValue('nonSnarfingRegexp', channel, network)
if r and r.search(url) is not None:
self.log.debug('Matched nonSnarfingRegexp: %u', url)
return
minlen = self.registryValue('minimumLength', channel)
minlen = self.registryValue('minimumLength', channel, network)
try:
cmd = self.registryValue('serviceRotation',
channel, value=False)
channel, network, value=False)
cmd = cmd.getService().capitalize()
except ValueError:
cmd = self.registryValue('default', channel).capitalize()
cmd = self.registryValue('default', channel, network) \
.capitalize()
if len(url) >= minlen:
try:
shorturl = getattr(self, '_get%sUrl' % cmd)(url)
except (utils.web.Error, AttributeError, ShrinkError):
self.log.info('Couldn\'t get shorturl for %u', url)
return
if self.registryValue('shrinkSnarfer.showDomain', channel):
if self.registryValue('shrinkSnarfer.showDomain',
channel, network):
domain = ' (at %s)' % utils.web.getDomain(url)
else:
domain = ''

View File

@ -140,9 +140,9 @@ class Status(callbacks.Plugin):
"""
(user, system, childUser, childSystem, elapsed) = os.times()
now = time.time()
target = msg.args[0]
target = (msg.channel, irc.network)
timeRunning = now - world.startedAt
if self.registryValue('cpu.children', target) and \
if self.registryValue('cpu.children', *target) and \
user+system < timeRunning+1: # Fudge for FPU inaccuracies.
children = _('My children have taken %.2f seconds of user time '
'and %.2f seconds of system time '
@ -154,11 +154,11 @@ class Status(callbacks.Plugin):
response = _('I have taken %.2f seconds of user time and %.2f seconds '
'of system time, for a total of %.2f seconds of CPU '
'time. %s') % (user, system, user + system, children)
if self.registryValue('cpu.threads', target):
if self.registryValue('cpu.threads', *target):
response += format('I have spawned %n; I currently have %i still '
'running.',
(world.threadsSpawned, 'thread'), activeThreads)
if self.registryValue('cpu.memory', target):
if self.registryValue('cpu.memory', *target):
mem = None
pid = os.getpid()
plat = sys.platform

View File

@ -50,7 +50,8 @@ class Success(plugins.ChannelIdDatabasePlugin):
class MySuccessClass(self.originalClass):
__slots__ = ()
def __call__(self):
ret = pluginSelf.db.random(dynamic.msg.args[0])
msg = dynamic.msg
ret = pluginSelf.db.random(msg.channel or msg.args[0])
if ret is None:
try:
self.__class__ = pluginSelf.originalClass

View File

@ -175,7 +175,8 @@ class Time(callbacks.Plugin):
<channel> is given without <format>, uses the format for <channel>.
"""
if not format:
format = self.registryValue('format', channel or msg.args[0])
format = self.registryValue('format', channel or msg.channel,
irc.network)
if tzlocal:
irc.reply(datetime.fromtimestamp(seconds, tzlocal()).strftime(format))
else:
@ -209,7 +210,7 @@ class Time(callbacks.Plugin):
timezone = pytz.timezone(timezone)
except pytz.UnknownTimeZoneError:
irc.error(_('Unknown timezone'), Raise=True)
format = self.registryValue("format", msg.args[0])
format = self.registryValue("format", msg.channel, irc.network)
irc.reply(datetime.now(timezone).strftime(format))
tztime = wrap(tztime, ['text'])

View File

@ -64,16 +64,17 @@ def canChangeTopic(irc, msg, args, state):
def getTopic(irc, msg, args, state, format=True):
separator = state.cb.registryValue('separator', state.channel)
separator = state.cb.registryValue('separator', state.channel, irc.network)
if separator in args[0] and not \
state.cb.registryValue('allowSeparatorinTopics', state.channel):
state.cb.registryValue('allowSeparatorinTopics',
state.channel, irc.network):
state.errorInvalid('topic', args[0],
format(_('The topic must not include %q.'),
separator))
topic = args.pop(0)
if format:
env = {'topic': topic}
formatter = state.cb.registryValue('format', state.channel)
formatter = state.cb.registryValue('format', state.channel, irc.network)
topic = ircutils.standardSubstitute(irc, msg, formatter, env)
state.args.append(topic)
@ -90,7 +91,7 @@ def getTopicNumber(irc, msg, args, state):
if n > 0:
n -= 1
topic = irc.state.getTopic(state.channel)
separator = state.cb.registryValue('separator', state.channel)
separator = state.cb.registryValue('separator', state.channel, irc.network)
topics = splitTopic(topic, separator)
if not topics:
state.error(format(_('There are no topics in %s.'), state.channel),
@ -162,24 +163,25 @@ class Topic(callbacks.Plugin):
except (IOError, shutil.Error) as e:
self.log.warning('File error: %s', e)
def _splitTopic(self, topic, channel):
separator = self.registryValue('separator', channel)
def _splitTopic(self, irc, channel):
topic = irc.state.getTopic(channel)
separator = self.registryValue('separator', channel, irc.network)
return splitTopic(topic, separator)
def _joinTopic(self, channel, topics):
separator = self.registryValue('separator', channel)
def _joinTopic(self, irc, channel, topics):
separator = self.registryValue('separator', channel, irc.network)
return separator.join(topics)
def _addUndo(self, channel, topics):
def _addUndo(self, irc, channel, topics):
stack = self.undos.setdefault(channel, [])
stack.append(topics)
maxLen = self.registryValue('undo.max', channel)
maxLen = self.registryValue('undo.max', channel, irc.network)
del stack[:len(stack) - maxLen]
def _addRedo(self, channel, topics):
def _addRedo(self, irc, channel, topics):
stack = self.redos.setdefault(channel, [])
stack.append(topics)
maxLen = self.registryValue('undo.max', channel)
maxLen = self.registryValue('undo.max', channel, irc.network)
del stack[:len(stack) - maxLen]
def _getUndo(self, channel):
@ -197,16 +199,16 @@ class Topic(callbacks.Plugin):
def _formatTopics(self, irc, channel, topics, fit=False):
topics = [s for s in topics if s and not s.isspace()]
self.lastTopics[channel] = topics
newTopic = self._joinTopic(channel, topics)
newTopic = self._joinTopic(irc, channel, topics)
try:
maxLen = irc.state.supported['topiclen']
if fit:
while len(newTopic) > maxLen:
topics.pop(0)
self.lastTopics[channel] = topics
newTopic = self._joinTopic(channel, topics)
newTopic = self._joinTopic(irc, channel, topics)
elif len(newTopic) > maxLen:
if self.registryValue('recognizeTopiclen', channel):
if self.registryValue('recognizeTopiclen', channel, irc.network):
irc.error(format(_('That topic is too long for this '
'server (maximum length: %i; this topic: '
'%i).'), maxLen, len(newTopic)),
@ -219,7 +221,7 @@ class Topic(callbacks.Plugin):
if isinstance(topics, list) or isinstance(topics, tuple):
assert topics is not None
topics = self._formatTopics(irc, channel, topics, fit)
self._addUndo(channel, topics)
self._addUndo(irc, channel, topics)
if not isDo and channel in self.redos:
del self.redos[channel]
irc.queueMsg(ircmsgs.topic(channel, topics))
@ -238,7 +240,8 @@ class Topic(callbacks.Plugin):
c = irc.state.channels[channel]
if msg.nick in c.ops or msg.nick in c.halfops or 't' not in c.modes:
return True
capabilities = self.registryValue('requireManageCapability', channel)
capabilities = self.registryValue('requireManageCapability',
channel, irc.network)
if capabilities:
for capability in re.split(r'\s*;\s*', capabilities):
if capability.startswith('channel,'):
@ -247,7 +250,7 @@ class Topic(callbacks.Plugin):
if capability and ircdb.checkCapability(msg.prefix, capability):
return
capabilities = self.registryValue('requireManageCapability',
channel)
channel, irc.network)
irc.errorNoCapability(capabilities, Raise=True)
else:
return
@ -261,7 +264,7 @@ class Topic(callbacks.Plugin):
# Try to restore the topic when not set yet.
channel = msg.args[1]
c = irc.state.channels.get(channel)
if c is None or not self.registryValue('setOnJoin', channel):
if c is None or not self.registryValue('setOnJoin', channel, irc.network):
return
if irc.nick not in c.ops and 't' in c.modes:
self.log.debug('Not trying to restore topic in %s. I\'m not opped '
@ -274,7 +277,8 @@ class Topic(callbacks.Plugin):
else:
newTopic = self._formatTopics(irc, channel, topics)
if c.topic == '' or (c.topic != newTopic and
self.registryValue('alwaysSetOnJoin', channel)):
self.registryValue('alwaysSetOnJoin',
channel, irc.network)):
self._sendTopics(irc, channel, newTopic)
def do332(self, irc, msg):
@ -282,7 +286,7 @@ class Topic(callbacks.Plugin):
self.watchingFor332.remove(msg.args[1])
# Store an undo for the topic when we join a channel. This allows
# us to undo the first topic change that takes place in a channel.
self._addUndo(msg.args[1], [msg.args[2]])
self._addUndo(irc, msg.args[1], [msg.args[2]])
def topic(self, irc, msg, args, channel):
"""[<channel>]
@ -301,7 +305,7 @@ class Topic(callbacks.Plugin):
if the message isn't sent in the channel itself.
"""
self._checkManageCapabilities(irc, msg, channel)
topics = self._splitTopic(irc.state.getTopic(channel), channel)
topics = self._splitTopic(irc, channel)
topics.append(topic)
self._sendTopics(irc, channel, topics)
add = wrap(add, ['canChangeTopic', rest('topic')])
@ -315,7 +319,7 @@ class Topic(callbacks.Plugin):
itself.
"""
self._checkManageCapabilities(irc, msg, channel)
topics = self._splitTopic(irc.state.getTopic(channel), channel)
topics = self._splitTopic(irc, channel)
topics.append(topic)
self._sendTopics(irc, channel, topics, fit=True)
fit = wrap(fit, ['canChangeTopic', rest('topic')])
@ -326,7 +330,7 @@ class Topic(callbacks.Plugin):
Replaces topic <number> with <topic>.
"""
self._checkManageCapabilities(irc, msg, channel)
topics = self._splitTopic(irc.state.getTopic(channel), channel)
topics = self._splitTopic(irc, channel)
topics[i] = topic
self._sendTopics(irc, channel, topics)
replace = wrap(replace, ['canChangeTopic', 'topicNumber', rest('topic')])
@ -339,7 +343,7 @@ class Topic(callbacks.Plugin):
isn't sent in the channel itself.
"""
self._checkManageCapabilities(irc, msg, channel)
topics = self._splitTopic(irc.state.getTopic(channel), channel)
topics = self._splitTopic(irc, channel)
topics.insert(0, topic)
self._sendTopics(irc, channel, topics)
insert = wrap(insert, ['canChangeTopic', rest('topic')])
@ -351,7 +355,7 @@ class Topic(callbacks.Plugin):
message isn't sent in the channel itself.
"""
self._checkManageCapabilities(irc, msg, channel)
topics = self._splitTopic(irc.state.getTopic(channel), channel)
topics = self._splitTopic(irc, channel)
if len(topics) == 0 or len(topics) == 1:
irc.error(_('I can\'t shuffle 1 or fewer topics.'), Raise=True)
elif len(topics) == 2:
@ -372,7 +376,7 @@ class Topic(callbacks.Plugin):
itself.
"""
self._checkManageCapabilities(irc, msg, channel)
topics = self._splitTopic(irc.state.getTopic(channel), channel)
topics = self._splitTopic(irc, channel)
num = len(topics)
if num == 0 or num == 1:
irc.error(_('I cannot reorder 1 or fewer topics.'), Raise=True)
@ -392,7 +396,7 @@ class Topic(callbacks.Plugin):
Mostly useful for topic reordering. <channel> is only necessary if the
message isn't sent in the channel itself.
"""
topics = self._splitTopic(irc.state.getTopic(channel), channel)
topics = self._splitTopic(irc, channel)
L = []
for (i, t) in enumerate(topics):
L.append(format(_('%i: %s'), i + 1, utils.str.ellipsisify(t, 30)))
@ -407,7 +411,7 @@ class Topic(callbacks.Plugin):
index into the topics. <channel> is only necessary if the message
isn't sent in the channel itself.
"""
topics = self._splitTopic(irc.state.getTopic(channel), channel)
topics = self._splitTopic(irc, channel)
irc.reply(topics[number])
get = wrap(get, ['inChannel', 'topicNumber'])
@ -421,7 +425,7 @@ class Topic(callbacks.Plugin):
isn't sent in the channel itself.
"""
self._checkManageCapabilities(irc, msg, channel)
topics = self._splitTopic(irc.state.getTopic(channel), channel)
topics = self._splitTopic(irc, channel)
topics[number] = replacer(topics[number])
self._sendTopics(irc, channel, topics)
change = wrap(change, ['canChangeTopic', 'topicNumber', 'regexpReplacer'])
@ -435,7 +439,7 @@ class Topic(callbacks.Plugin):
"""
self._checkManageCapabilities(irc, msg, channel)
if number is not None:
topics = self._splitTopic(irc.state.getTopic(channel), channel)
topics = self._splitTopic(irc, channel)
topics[number] = topic
else:
topics = [topic]
@ -453,7 +457,7 @@ class Topic(callbacks.Plugin):
necessary if the message isn't sent in the channel itself.
"""
self._checkManageCapabilities(irc, msg, channel)
topics = self._splitTopic(irc.state.getTopic(channel), channel)
topics = self._splitTopic(irc, channel)
numbers = set(numbers)
for n in numbers:
# Equivalent of marking the topic for deletion; there's no
@ -534,7 +538,7 @@ class Topic(callbacks.Plugin):
channel itself.
"""
self._checkManageCapabilities(irc, msg, channel)
self._addRedo(channel, self._getUndo(channel)) # current topic.
self._addRedo(irc, channel, self._getUndo(channel)) # current topic.
topics = self._getUndo(channel) # This is the topic list we want.
if topics is not None:
self._sendTopics(irc, channel, topics, isDo=True)
@ -564,7 +568,7 @@ class Topic(callbacks.Plugin):
itself.
"""
self._checkManageCapabilities(irc, msg, channel)
topics = self._splitTopic(irc.state.getTopic(channel), channel)
topics = self._splitTopic(irc, channel)
if first == second:
irc.error(_('I refuse to swap the same topic with itself.'))
return
@ -598,7 +602,7 @@ class Topic(callbacks.Plugin):
variable supybot.plugins.Topic.default.
"""
self._checkManageCapabilities(irc, msg, channel)
topic = self.registryValue('default', channel)
topic = self.registryValue('default', channel, irc.network)
if topic:
self._sendTopics(irc, channel, [topic])
else:
@ -613,7 +617,7 @@ class Topic(callbacks.Plugin):
current topic appropriately.
"""
self._checkManageCapabilities(irc, msg, channel)
topics = self._splitTopic(irc.state.getTopic(channel), channel)
topics = self._splitTopic(irc, channel)
self.setRegistryValue('separator', separator, channel)
self._sendTopics(irc, channel, topics)
separator = wrap(separator, ['canChangeTopic', 'something'])

View File

@ -73,19 +73,19 @@ class URL(callbacks.Plugin):
def doPrivmsg(self, irc, msg):
if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg):
return
channel = msg.args[0]
if irc.isChannel(channel):
if msg.channel:
if ircmsgs.isAction(msg):
text = ircmsgs.unAction(msg)
else:
text = msg.args[1]
for url in utils.web.urlRe.findall(text):
r = self.registryValue('nonSnarfingRegexp', channel)
r = self.registryValue('nonSnarfingRegexp',
msg.channel, irc.network)
if r and r.search(url):
self.log.debug('Skipping adding %u to db.', url)
continue
self.log.debug('Adding %u to db.', url)
self.db.add(channel, url, msg)
self.db.add(msg.channel, url, msg)
@internationalizeDocstring
def stats(self, irc, msg, args, channel):

View File

@ -207,17 +207,18 @@ class Unix(callbacks.Plugin):
Returns a fortune from the *nix fortune program.
"""
channel = msg.args[0]
channel = msg.channel
network = irc.network
fortuneCmd = self.registryValue('fortune.command')
if fortuneCmd:
args = [fortuneCmd]
if self.registryValue('fortune.short', channel):
if self.registryValue('fortune.short', channel, network):
args.append('-s')
if self.registryValue('fortune.equal', channel):
if self.registryValue('fortune.equal', channel, network):
args.append('-e')
if self.registryValue('fortune.offensive', channel):
if self.registryValue('fortune.offensive', channel, network):
args.append('-a')
args.extend(self.registryValue('fortune.files', channel))
args.extend(self.registryValue('fortune.files', channel, network))
try:
with open(os.devnull) as null:
inst = subprocess.Popen(args,

View File

@ -46,7 +46,7 @@ class User(callbacks.Plugin):
authentication to the bot. This is a core Supybot plugin that should
not be removed!"""
def _checkNotChannel(self, irc, msg, password=' '):
if password and irc.isChannel(msg.args[0]):
if password and msg.channel:
raise callbacks.Error(conf.supybot.replies.requiresPrivacy())
@internationalizeDocstring
@ -88,7 +88,8 @@ class User(callbacks.Plugin):
users.append(u.name)
if users:
utils.sortBy(str.lower, users)
private = self.registryValue("listInPrivate", msg.args[0])
private = self.registryValue("listInPrivate",
msg.channel, irc.network)
irc.reply(format('%L', users), private=private)
else:
if predicates:

View File

@ -144,7 +144,7 @@ class Web(callbacks.PluginRegexp):
threaded = True
def noIgnore(self, irc, msg):
return not self.registryValue('checkIgnored', msg.args[0])
return not self.registryValue('checkIgnored', msg.channel, irc.network)
def getTitle(self, irc, url, raiseErrors):
size = conf.supybot.protocols.http.peekSize()
@ -184,16 +184,17 @@ class Web(callbacks.PluginRegexp):
@fetch_sandbox
def titleSnarfer(self, irc, msg, match):
channel = msg.args[0]
if not irc.isChannel(channel):
channel = msg.channel
network = irc.network
if not channel:
return
if callbacks.addressed(irc.nick, msg):
return
if self.registryValue('titleSnarfer', channel):
if self.registryValue('titleSnarfer', channel, network):
url = match.group(0)
if not self._checkURLWhitelist(url):
return
r = self.registryValue('nonSnarfingRegexp', channel)
r = self.registryValue('nonSnarfingRegexp', channel, network)
if r and r.search(url):
self.log.debug('Not titleSnarfing %q.', url)
return
@ -203,14 +204,15 @@ class Web(callbacks.PluginRegexp):
(target, title) = r
if title:
domain = utils.web.getDomain(target
if self.registryValue('snarferShowTargetDomain', channel)
if self.registryValue('snarferShowTargetDomain',
channel, network)
else url)
prefix = self.registryValue('snarferPrefix', channel)
prefix = self.registryValue('snarferPrefix', channel, network)
s = "%s %s" % (prefix, title)
if self.registryValue('snarferShowDomain', channel):
if self.registryValue('snarferShowDomain', channel, network):
s += format(_(' (at %s)'), domain)
irc.reply(s, prefixNick=False)
if self.registryValue('snarfMultipleUrls', channel):
if self.registryValue('snarfMultipleUrls', channel, network):
# FIXME: hack
msg.tag('repliedTo', False)
titleSnarfer = urlSnarfer(titleSnarfer)

View File

@ -59,6 +59,9 @@ supybot.log.plugins.individualLogfiles: False
supybot.protocols.irc.throttleTime: 0
supybot.reply.whenAddressedBy.chars: @
supybot.networks.test.server: should.not.need.this
supybot.networks.testnet1.server: should.not.need.this
supybot.networks.testnet2.server: should.not.need.this
supybot.networks.testnet3.server: should.not.need.this
supybot.nick: test
supybot.databases.users.allowUnregistration: True
""" % {'base_dir': os.getcwd()})

View File

@ -156,17 +156,25 @@ def canonicalName(command, preserve_spaces=False):
command = command[:-1]
return ''.join([x for x in command if x not in special]).lower() + reAppend
def reply(msg, s, prefixNick=None, private=None,
notice=None, to=None, action=None, error=False,
stripCtcp=True):
def reply(*args, **kwargs):
warnings.warn('callbacks.reply is deprecated. Use irc.reply instead.',
DeprecationWarning)
return _makeReply(dynamic.irc, *args, **kwargs)
def _makeReply(irc, msg, s,
prefixNick=None, private=None,
notice=None, to=None, action=None, error=False,
stripCtcp=True):
msg.tag('repliedTo')
# Ok, let's make the target:
# XXX This isn't entirely right. Consider to=#foo, private=True.
target = ircutils.replyTo(msg)
if ircutils.isChannel(to):
def isPublic(s):
return irc.isChannel(irc.stripChannelPrefix(s))
if to is not None and isPublic(to):
target = to
if ircutils.isChannel(target):
channel = target
if isPublic(target):
channel = irc.stripChannelPrefix(target)
else:
channel = None
if notice is None:
@ -195,11 +203,11 @@ def reply(msg, s, prefixNick=None, private=None,
s = ircutils.safeArgument(s)
if not s and not action:
s = _('Error: I tried to send you an empty message.')
if prefixNick and ircutils.isChannel(target):
if prefixNick and isPublic(target):
# Let's may sure we don't do, "#channel: foo.".
if not ircutils.isChannel(to):
if not isPublic(to):
s = '%s: %s' % (to, s)
if not ircutils.isChannel(target):
if not isPublic(target):
if conf.supybot.reply.withNoticeWhenPrivate():
notice = True
# And now, let's decide whether it's a PRIVMSG or a NOTICE.
@ -214,11 +222,16 @@ def reply(msg, s, prefixNick=None, private=None,
ret.tag('inReplyTo', msg)
return ret
def error(msg, s, **kwargs):
def error(*args, **kwargs):
warnings.warn('callbacks.error is deprecated. Use irc.error instead.',
DeprecationWarning)
return _makeErrorReply(dynamic.irc, *args, **kwargs)
def _makeErrorReply(irc, msg, s, **kwargs):
"""Makes an error reply to msg with the appropriate error payload."""
kwargs['error'] = True
msg.tag('isError')
return reply(msg, s, **kwargs)
return _makeReply(irc, msg, s, **kwargs)
def getHelp(method, name=None, doc=None):
if name is None:
@ -363,16 +376,16 @@ class Tokenizer(object):
args[-1].append(ends.pop())
return args
def tokenize(s, channel=None):
def tokenize(s, channel=None, network=None):
"""A utility function to create a Tokenizer and tokenize a string."""
pipe = False
brackets = ''
nested = conf.supybot.commands.nested
if nested():
brackets = conf.get(nested.brackets, channel)
brackets = nested.brackets.getSpecific(network, channel)()
if conf.get(nested.pipeSyntax, channel): # No nesting, no pipe.
pipe = True
quotes = conf.get(conf.supybot.commands.quotes, channel)
quotes = conf.supybot.commands.quotes.getSpecific(network, channel)()
try:
ret = Tokenizer(brackets=brackets,pipe=pipe,quotes=quotes).tokenize(s)
return ret
@ -400,8 +413,8 @@ def checkCommandCapability(msg, cb, commandName):
checkCapability(antiCommand)
checkAtEnd = [commandName]
default = conf.supybot.capabilities.default()
if ircutils.isChannel(msg.args[0]):
channel = msg.args[0]
if msg.channel:
channel = msg.channel
checkCapability(ircdb.makeChannelCapability(channel, antiCommand))
chanCommand = ircdb.makeChannelCapability(channel, commandName)
checkAtEnd += [chanCommand]
@ -426,7 +439,7 @@ class RichReplyMethods(object):
return ircutils.standardSubstitute(self, self.msg, s)
def _getConfig(self, wrapper):
return conf.get(wrapper, self.msg.args[0])
return conf.get(wrapper, self.msg.channel)
def replySuccess(self, s='', **kwargs):
v = self._getConfig(conf.supybot.replies.success)
@ -572,6 +585,7 @@ class ReplyIrcProxy(RichReplyMethods):
def __init__(self, irc, msg):
self.irc = irc
self.msg = msg
self.getRealIrc()._setMsgChannel(self.msg)
def getRealIrc(self):
"""Returns the real irclib.Irc object underlying this proxy chain."""
@ -599,7 +613,7 @@ class ReplyIrcProxy(RichReplyMethods):
raise ArgumentError
if msg is None:
msg = self.msg
m = error(msg, s, **kwargs)
m = _makeErrorReply(self, msg, s, **kwargs)
self.irc.queueMsg(m)
return m
@ -609,7 +623,7 @@ class ReplyIrcProxy(RichReplyMethods):
assert not isinstance(s, ircmsgs.IrcMsg), \
'Old code alert: there is no longer a "msg" argument to reply.'
kwargs.pop('noLengthCheck', None)
m = reply(msg, s, **kwargs)
m = _makeReply(self, msg, s, **kwargs)
self.irc.queueMsg(m)
return m
@ -623,8 +637,7 @@ class NestedCommandsIrcProxy(ReplyIrcProxy):
_mores = ircutils.IrcDict()
def __init__(self, irc, msg, args, nested=0):
assert isinstance(args, list), 'Args should be a list, not a string.'
self.irc = irc
self.msg = msg
super(NestedCommandsIrcProxy, self).__init__(irc, msg)
self.nested = nested
self.repliedTo = False
if not self.nested and isinstance(irc, self.__class__):
@ -665,9 +678,9 @@ class NestedCommandsIrcProxy(ReplyIrcProxy):
self.notice = None
self.private = None
self.noLengthCheck = None
if self.irc.isChannel(self.msg.args[0]):
if self.msg.channel:
self.prefixNick = conf.get(conf.supybot.reply.withNickPrefix,
self.msg.args[0])
self.msg.channel)
else:
self.prefixNick = conf.supybot.reply.withNickPrefix()
@ -894,12 +907,12 @@ class NestedCommandsIrcProxy(ReplyIrcProxy):
elif self.noLengthCheck:
# noLengthCheck only matters to NestedCommandsIrcProxy, so
# it's not used here. Just in case you were wondering.
m = reply(msg, s, to=self.to,
notice=self.notice,
action=self.action,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
m = _makeReply(self, msg, s, to=self.to,
notice=self.notice,
action=self.action,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
sendMsg(m)
return m
else:
@ -930,11 +943,11 @@ class NestedCommandsIrcProxy(ReplyIrcProxy):
# action implies noLengthCheck, which has already been
# handled. Let's stick an assert in here just in case.
assert not self.action
m = reply(msg, s, to=self.to,
notice=self.notice,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
m = _makeReply(self, msg, s, to=self.to,
notice=self.notice,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
sendMsg(m)
return m
# The '(XX more messages)' may have not the same
@ -946,11 +959,11 @@ class NestedCommandsIrcProxy(ReplyIrcProxy):
while instant > 1 and msgs:
instant -= 1
response = msgs.pop()
m = reply(msg, response, to=self.to,
notice=self.notice,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
m = _makeReply(self, msg, response, to=self.to,
notice=self.notice,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
sendMsg(m)
# XXX We should somehow allow these to be returned, but
# until someone complains, we'll be fine :) We
@ -976,15 +989,15 @@ class NestedCommandsIrcProxy(ReplyIrcProxy):
pass # We'll leave it as it is.
mask = prefix.split('!', 1)[1]
self._mores[mask] = msgs
public = self.irc.isChannel(msg.args[0])
public = bool(self.msg.channel)
private = self.private or not public
self._mores[msg.nick] = (private, msgs)
m = reply(msg, response, to=self.to,
action=self.action,
notice=self.notice,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
m = _makeReply(self, msg, response, to=self.to,
action=self.action,
notice=self.notice,
private=self.private,
prefixNick=self.prefixNick,
stripCtcp=stripCtcp)
sendMsg(m)
return m
finally:
@ -1035,7 +1048,7 @@ class NestedCommandsIrcProxy(ReplyIrcProxy):
if not isinstance(self.irc, irclib.Irc):
return self.irc.error(s, **kwargs)
else:
m = error(self.msg, s, **kwargs)
m = _makeErrorReply(self, self.msg, s, **kwargs)
self.irc.queueMsg(m)
return m
else:
@ -1346,7 +1359,7 @@ class Commands(BasePlugin, SynchronizedAndFirewalled):
help = getHelp
chan = None
if dynamic.msg is not None:
chan = dynamic.msg.args[0]
chan = dynamic.msg.channel
if simpleSyntax is None:
simpleSyntax = conf.get(conf.supybot.reply.showSimpleSyntax, chan)
if simpleSyntax:
@ -1385,39 +1398,40 @@ class PluginMixin(BasePlugin, irclib.IrcCallback):
else:
noIgnore = self.noIgnore
if noIgnore or \
not ircdb.checkIgnored(msg.prefix, msg.args[0]) or \
not ircdb.checkIgnored(msg.prefix, msg.channel) or \
not ircutils.isUserHostmask(msg.prefix): # Some services impl.
self.__parent.__call__(irc, msg)
else:
self.__parent.__call__(irc, msg)
def registryValue(self, name, channel=None, value=True):
def registryValue(self, name, channel=None, network=None, value=True):
if isinstance(network, bool):
# Network-unaware plugin that uses 'value' as a positional
# argument.
(network, value) = (value, network)
plugin = self.name()
group = conf.supybot.plugins.get(plugin)
names = registry.split(name)
for name in names:
group = group.get(name)
if channel is not None:
if ircutils.isChannel(channel):
group = group.get(channel)
else:
self.log.debug('%s: registryValue got channel=%r', plugin,
channel)
if channel or network:
group = group.getSpecific(network=network, channel=channel)
if value:
return group()
else:
return group
def setRegistryValue(self, name, value, channel=None):
def setRegistryValue(self, name, value, channel=None, network=None):
plugin = self.name()
group = conf.supybot.plugins.get(plugin)
names = registry.split(name)
for name in names:
group = group.get(name)
if channel is None:
group.setValue(value)
else:
group.get(channel).setValue(value)
if network:
group = group.get(':' + network)
if channel:
group = group.get(channel)
group.setValue(value)
def userValue(self, name, prefixOrName, default=None):
try:

View File

@ -205,9 +205,12 @@ def urlSnarfer(f):
"""Protects the snarfer from loops (with other bots) and whatnot."""
def newf(self, irc, msg, match, *L, **kwargs):
url = match.group(0)
channel = msg.args[0]
if not irc.isChannel(channel) or (ircmsgs.isCtcp(msg) and not
ircmsgs.isAction(msg)):
channel = msg.channel
if not channel:
# Don't snarf in private
return
if not (ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg)):
# Don't snarf CTCPs unless they are a /me
return
if ircdb.channels.getChannel(channel).lobotomized:
self.log.debug('Not snarfing in %s: lobotomized.', channel)
@ -491,10 +494,11 @@ def getChannel(irc, msg, args, state):
return
if args and irc.isChannel(args[0]):
channel = args.pop(0)
elif irc.isChannel(msg.args[0]):
channel = msg.args[0]
elif msg.channel:
channel = msg.channel
else:
state.log.debug('Raising ArgumentError because there is no channel.')
print(msg.channel, msg)
raise callbacks.ArgumentError
state.channel = channel
state.args.append(channel)
@ -502,8 +506,8 @@ def getChannel(irc, msg, args, state):
def getChannels(irc, msg, args, state):
if args and all(map(irc.isChannel, args[0].split(','))):
channels = args.pop(0).split(',')
elif irc.isChannel(msg.args[0]):
channels = [msg.args[0]]
elif msg.channel:
channels = [msg.channel]
else:
state.log.debug('Raising ArgumentError because there is no channel.')
raise callbacks.ArgumentError
@ -535,11 +539,11 @@ def inChannel(irc, msg, args, state):
state.error(_('I\'m not in %s.') % state.channel, Raise=True)
def onlyInChannel(irc, msg, args, state):
if not (irc.isChannel(msg.args[0]) and msg.args[0] in irc.state.channels):
if not (msg.channel and msg.channel in irc.state.channels):
state.error(_('This command may only be given in a channel that I am '
'in.'), Raise=True)
else:
state.channel = msg.args[0]
state.channel = msg.channel
state.args.append(state.channel)
def callerInGivenChannel(irc, msg, args, state):
@ -576,8 +580,8 @@ def getChannelOrGlobal(irc, msg, args, state):
elif args and irc.isChannel(args[0]):
channel = args.pop(0)
state.channel = channel
elif irc.isChannel(msg.args[0]):
channel = msg.args[0]
elif msg.channel:
channel = msg.channel
state.channel = channel
else:
state.log.debug('Raising ArgumentError because there is no channel.')
@ -620,11 +624,11 @@ def getSomethingNoSpaces(irc, msg, args, state, *L):
getSomething(irc, msg, args, state, p=p, *L)
def private(irc, msg, args, state):
if irc.isChannel(msg.args[0]):
if msg.channel:
state.errorRequiresPrivacy(Raise=True)
def public(irc, msg, args, state, errmsg=None):
if not irc.isChannel(msg.args[0]):
if not msg.channel:
if errmsg is None:
errmsg = _('This message must be sent in a channel.')
state.error(errmsg, Raise=True)

View File

@ -83,13 +83,14 @@ def registerGroup(Group, name, group=None, **kwargs):
return Group.register(name, group)
def registerGlobalValue(group, name, value):
value.channelValue = False
value._networkValue = False
value._channelValue = False
return group.register(name, value)
def registerChannelValue(group, name, value, opSettable=True):
def registerNetworkValue(group, name, value):
value._supplyDefault = True
value.channelValue = True
value._opSettable = opSettable
value._networkValue = True
value._channelValue = False
g = group.register(name, value)
gname = g._name.lower()
for name in registry._cache.keys():
@ -97,8 +98,30 @@ def registerChannelValue(group, name, value, opSettable=True):
name = name[len(gname)+1:] # +1 for .
parts = registry.split(name)
if len(parts) == 1 and parts[0] and ircutils.isChannel(parts[0]):
# This gets the channel values so they always persist.
# This gets the network values so they always persist.
g.get(parts[0])()
return g
def registerChannelValue(group, name, value, opSettable=True):
value._supplyDefault = True
value._networkValue = True
value._channelValue = True
value._opSettable = opSettable
g = group.register(name, value)
gname = g._name.lower()
for name in registry._cache.keys():
if name.lower().startswith(gname) and len(gname) < len(name):
name = name[len(gname)+1:] # +1 for .
parts = registry.split(name)
if len(parts) == 2 and parts[0] and parts[0].startswith(':') \
and parts[1] and ircutils.isChannel(parts[1]):
# This gets the network+channel values so they always persist.
g.get(parts[0])()
g.get(parts[0]).get(parts[1])()
elif len(parts) == 1 and parts[0] and ircutils.isChannel(parts[0]):
# Old-style variant of the above, without a network
g.get(parts[0])()
return g
def registerPlugin(name, currentValue=None, public=True):
group = registerGlobalValue(supybot.plugins, name,
@ -1128,7 +1151,7 @@ class Banmask(registry.SpaceSeparatedSetOfStrings):
self.error()
self.__parent.setValue(self.List(v))
def makeBanmask(self, hostmask, options=None, channel=None):
def makeBanmask(self, hostmask, options=None, channel=None, network=None):
"""Create a banmask from the given hostmask. If a style of banmask
isn't specified via options, the value of
conf.supybot.protocols.irc.banmask is used.
@ -1138,13 +1161,15 @@ class Banmask(registry.SpaceSeparatedSetOfStrings):
only the exact hostmask will be used."""
if not channel:
channel = dynamic.channel
assert channel is None or ircutils.isChannel(channel)
if not network:
network = dynamic.irc.network
(nick, user, host) = ircutils.splitHostmask(hostmask)
bnick = '*'
buser = '*'
bhost = '*'
if not options:
options = get(supybot.protocols.irc.banmask, channel)
options = supybot.protocols.irc.banmask.getSpecific(
channel, network)()
for option in options:
if option == 'nick':
bnick = nick

View File

@ -843,6 +843,7 @@ class Irc(IrcCommandDispatcher, log.Firewalled):
self.queueMsg(ircmsgs.ping(now))
if msg:
for callback in reversed(self.callbacks):
self._setMsgChannel(msg)
msg = callback.outFilter(self, msg)
if msg is None:
log.debug('%s.outFilter returned None.', callback.name())
@ -875,16 +876,36 @@ class Irc(IrcCommandDispatcher, log.Firewalled):
else:
return None
_numericErrorCommandRe = re.compile(r'^[45][0-9][0-9]$')
def feedMsg(self, msg):
"""Called by the IrcDriver; feeds a message received."""
def _tagMsg(self, msg):
"""Sets attribute on an incoming IRC message. Will usually only be
called by feedMsg, but may be useful in tests as well."""
msg.tag('receivedBy', self)
msg.tag('receivedOn', self.network)
msg.tag('receivedAt', time.time())
if msg.args and self.isChannel(msg.args[0]):
channel = msg.args[0]
self._setMsgChannel(msg)
def _setMsgChannel(self, msg):
if msg.args:
msg.channel = msg.args[0]
if msg.command in ('NOTICE', 'PRIVMSG') and \
not conf.supybot.protocols.irc.strictRfc():
msg.channel = self.stripChannelPrefix(msg.channel)
if not self.isChannel(msg.channel):
msg.channel = None
else:
channel = None
msg.channel = None
def stripChannelPrefix(self, channel):
statusmsg_chars = self.state.supported.get('statusmsg', '')
return channel.lstrip(statusmsg_chars)
_numericErrorCommandRe = re.compile(r'^[45][0-9][0-9]$')
def feedMsg(self, msg):
"""Called by the IrcDriver; feeds a message received."""
self._tagMsg(msg)
channel = msg.channel
preInFilter = str(msg).rstrip('\r\n')
log.debug('Incoming message (%s): %s', self.network, preInFilter)

View File

@ -121,7 +121,7 @@ class IrcMsg(object):
# On second thought, let's use methods for tagging.
__slots__ = ('args', 'command', 'host', 'nick', 'prefix', 'user',
'_hash', '_str', '_repr', '_len', 'tags', 'reply_env',
'server_tags', 'time')
'server_tags', 'time', 'channel')
def __init__(self, s='', command='', args=(), prefix='', msg=None,
reply_env=None):
assert not (msg and s), 'IrcMsg.__init__ cannot accept both s and msg'

View File

@ -659,7 +659,10 @@ def safeArgument(s):
def replyTo(msg):
"""Returns the appropriate target to send responses to msg."""
if isChannel(msg.args[0]):
if msg.channel:
# if message was sent to +#channel, we want to reply to +#channel;
# or unvoiced channel users will see the bot reply without the
# origin query
return msg.args[0]
else:
return msg.nick
@ -867,10 +870,7 @@ def standardSubstitute(irc, msg, text, env=None):
vars.update(msg.reply_env)
if irc and msg:
if isChannel(msg.args[0]):
channel = msg.args[0]
else:
channel = 'somewhere'
channel = msg.channel or 'somewhere'
def randNick():
if channel != 'somewhere':
L = list(irc.state.channels[channel].users)

View File

@ -212,7 +212,7 @@ class Group(object):
s = '%r is not a valid entry in %r' % (attr, self._name)
raise NonExistentRegistryEntry(s)
def __makeChild(self, attr, s):
def _makeChild(self, attr, s):
v = self.__class__(self._default, self._help)
v.set(s)
v._wasSet = False
@ -225,10 +225,13 @@ class Group(object):
return attr in self._children
def __getattr__(self, attr):
if attr in self._children:
if attr.startswith('_'):
raise AttributeError('%s has no attribute %s' %
(self.__class__.__name__, attr))
elif attr in self._children:
return self._children[attr]
elif self._supplyDefault:
return self.__makeChild(attr, str(self))
return self._makeChild(attr, str(self))
else:
self.__nonExistentEntry(attr)
@ -253,7 +256,7 @@ class Group(object):
parts = split(rest)
if len(parts) == 1 and parts[0] == name:
try:
self.__makeChild(name, v)
self._makeChild(name, v)
except InvalidRegistryValue:
# It's probably supposed to be registered later.
pass
@ -328,7 +331,7 @@ class Value(Group):
"""Invalid registry value. If you're getting this message, report it,
because we forgot to put a proper help string here."""
__slots__ = ('__parent', '_default', '_showDefault', '_help', '_callbacks',
'value', 'channelValue', '_opSettable')
'value', '_networkValue', '_channelValue', '_opSettable')
def __init__(self, default, help, setDefault=True,
showDefault=True, **kwargs):
self.__parent = super(Value, self)
@ -337,9 +340,29 @@ class Value(Group):
self._showDefault = showDefault
self._help = utils.str.normalizeWhitespace(help.strip())
self._callbacks = []
self._networkValue = False
self._channelValue = False
if setDefault:
self.setValue(default)
def _makeChild(self, attr, s):
v = self.__class__(self._default, self._help)
v.set(s)
v._wasSet = False
if self._networkValue and self._channelValue:
# If this is both a network-specific and channel-specific value,
# then the child is (only) channel-specific.
v._networkValue = False
v._channelValue = True
v._supplyDefault = True
else:
# Otherwise, the child is neither network-specific or
# channel-specific.
v._supplyDefault = False
v._help = '' # Clear this so it doesn't print a bazillion times.
self.register(attr, v)
return v
def error(self, value=_NoValueGiven):
if hasattr(self, 'errormsg') and value is not _NoValueGiven:
try:
@ -356,6 +379,58 @@ class Value(Group):
e.value = self
raise e
def getSpecific(self, network=None, channel=None, check=True):
"""Gets the network-specific and/or channel-specific value of this
Value.
If `check=True` (the default), this will raise an error if `network`
(resp. `channel`) is provided but this Value is not network-specific
(resp. channel-specific). If `check=False`, then `network` and/or
`channel` may be silently ignored.
"""
if network and not self._networkValue:
if check:
raise NonExistentRegistryEntry('%s is not network-specific' %
self._name)
else:
network = None
if channel and not self._channelValue:
if check:
raise NonExistentRegistryEntry('%s is not channel-specific' %
self._name)
else:
channel = None
if network and channel:
# The complicated case. We want a net+chan specific value,
# which may come in three different ways:
#
# 1. it was set explicitely net+chan
# 2. it's inherited from a net specific value (which may itself be
# inherited from the base value)
# 3. it's inherited from the chan specific value (which is not a
# actually a parent in the registry tree, but we need this to
# load configuration from old bots).
#
# The choice between 2 and 3 is done by checking which of the
# net-specific and chan-specific values was set explicitely by
# a user/admin. In case both were, the net-specific value is used
# (there is no particular reason for this, I just think it makes
# more sense).
network_value = self.get(':' + network)
network_channel_value = network_value.get(channel)
channel_value = self.get(channel)
if network_value._wasSet or network_channel_value._wasSet:
# cases 1 and 2
return network_channel_value
else:
# case 3
return channel_value
elif network:
return self.get(':' + network)
elif channel:
return self.get(channel)
else:
return self
def setName(self, *args):
if self._name == 'unset':
self._lastModified = 0
@ -374,16 +449,23 @@ class Value(Group):
100) convert to an integer in set() and check that the integer is less
than 100 in this method. You *must* call this parent method in your
own setValue."""
self._setValue(v, inherited=False)
def _setValue(self, v, inherited):
"""Like setValue, but accepted an extra 'inherited' argument.
inherited=True means the value is inherited from the parent, so if
the parent gets a new value, this group will get the new value as
well."""
self._lastModified = time.time()
self.value = v
if self._supplyDefault:
for (name, v) in list(self._children.items()):
if not v._wasSet:
self.unregister(name)
for (name, child) in list(self._children.items()):
if not child._wasSet:
child._setValue(v, inherited=True)
# We call the callback once everything is clean
for callback, args, kwargs in self._callbacks:
callback(*args, **kwargs)
self._wasSet = True
self._wasSet = not inherited
def context(self, value):
"""Return a context manager object, which sets this variable to a

View File

@ -96,8 +96,8 @@ def retry(tries=3):
return newf
return decorator
def getTestIrc():
irc = irclib.Irc('test')
def getTestIrc(name='test'):
irc = irclib.Irc(name)
# Gotta clear the connect messages (USER, NICK, etc.)
while irc.takeMsg():
pass

View File

@ -175,6 +175,7 @@ class FunctionsTestCase(SupyTestCase):
self.assertEqual('foobar--', callbacks.canonicalName('foobar--'))
def testAddressed(self):
irc = getTestIrc()
oldprefixchars = str(conf.supybot.reply.whenAddressedBy.chars)
nick = 'supybot'
conf.supybot.reply.whenAddressedBy.chars.set('~!@')
@ -189,6 +190,7 @@ class FunctionsTestCase(SupyTestCase):
for msg in inChannel:
self.assertEqual('foo', callbacks.addressed(nick, msg), msg)
msg = ircmsgs.privmsg(nick, 'foo')
irc._tagMsg(msg)
self.assertEqual('foo', callbacks.addressed(nick, msg))
conf.supybot.reply.whenAddressedBy.chars.set(oldprefixchars)
msg = ircmsgs.privmsg('#foo', '%s::::: bar' % nick)
@ -216,52 +218,84 @@ class FunctionsTestCase(SupyTestCase):
conf.supybot.reply.whenNotAddressed.setValue(original)
def testAddressedWithMultipleNicks(self):
irc = getTestIrc()
msg = ircmsgs.privmsg('#foo', 'bar: baz')
irc._tagMsg(msg)
self.assertEqual(callbacks.addressed('bar', msg), 'baz')
# need to recreate the msg objects since the old ones have already
# been tagged
msg = ircmsgs.privmsg('#foo', 'bar: baz')
irc._tagMsg(msg)
self.assertEqual(callbacks.addressed('biff', msg, nicks=['bar']),
'baz')
def testAddressedWithNickAtEnd(self):
irc = getTestIrc()
msg = ircmsgs.privmsg('#foo', 'baz, bar')
irc._tagMsg(msg)
self.assertEqual(callbacks.addressed('bar', msg,
whenAddressedByNickAtEnd=True),
'baz')
def testAddressedPrefixCharsTakePrecedenceOverNickAtEnd(self):
irc = getTestIrc()
msg = ircmsgs.privmsg('#foo', '@echo foo')
irc._tagMsg(msg)
self.assertEqual(callbacks.addressed('foo', msg,
whenAddressedByNickAtEnd=True,
prefixChars='@'),
'echo foo')
def testReply(self):
irc = getTestIrc()
prefix = 'foo!bar@baz'
channelMsg = ircmsgs.privmsg('#foo', 'bar baz', prefix=prefix)
nonChannelMsg = ircmsgs.privmsg('supybot', 'bar baz', prefix=prefix)
irc._tagMsg(channelMsg)
irc._tagMsg(nonChannelMsg)
self.assertEqual(ircmsgs.notice(nonChannelMsg.nick, 'foo'),
callbacks.reply(channelMsg, 'foo', private=True))
callbacks._makeReply(irc, channelMsg, 'foo',
private=True))
self.assertEqual(ircmsgs.notice(nonChannelMsg.nick, 'foo'),
callbacks.reply(nonChannelMsg, 'foo'))
callbacks._makeReply(irc, nonChannelMsg, 'foo'))
self.assertEqual(ircmsgs.privmsg(channelMsg.args[0],
'%s: foo' % channelMsg.nick),
callbacks.reply(channelMsg, 'foo'))
callbacks._makeReply(irc, channelMsg, 'foo'))
self.assertEqual(ircmsgs.privmsg(channelMsg.args[0],
'foo'),
callbacks.reply(channelMsg, 'foo', prefixNick=False))
callbacks._makeReply(irc, channelMsg, 'foo',
prefixNick=False))
self.assertEqual(ircmsgs.notice(nonChannelMsg.nick, 'foo'),
callbacks.reply(channelMsg, 'foo',
notice=True, private=True))
callbacks._makeReply(irc, channelMsg, 'foo',
notice=True, private=True))
def testReplyStatusmsg(self):
irc = getTestIrc()
prefix = 'foo!bar@baz'
msg = ircmsgs.privmsg('+#foo', 'bar baz', prefix=prefix)
irc._tagMsg(msg)
# No statusmsg set, so understood as being a private message, so
# private reply
self.assertEqual(ircmsgs.notice(msg.nick, 'foo'),
callbacks._makeReply(irc, msg, 'foo'))
irc.state.supported['statusmsg'] = '+'
msg = ircmsgs.privmsg('+#foo', 'bar baz', prefix=prefix)
irc._tagMsg(msg)
print(msg.channel)
self.assertEqual(ircmsgs.privmsg('+#foo', '%s: foo' % msg.nick),
callbacks._makeReply(irc, msg, 'foo'))
def testReplyTo(self):
irc = getTestIrc()
prefix = 'foo!bar@baz'
msg = ircmsgs.privmsg('#foo', 'bar baz', prefix=prefix)
self.assertEqual(callbacks.reply(msg, 'blah', to='blah'),
irc._tagMsg(msg)
self.assertEqual(callbacks._makeReply(irc, msg, 'blah', to='blah'),
ircmsgs.privmsg('#foo', 'blah: blah'))
self.assertEqual(callbacks.reply(msg, 'blah', to='blah', private=True),
self.assertEqual(callbacks._makeReply(irc, msg, 'blah', to='blah',
private=True),
ircmsgs.notice('blah', 'blah'))
def testTokenize(self):
@ -682,7 +716,9 @@ class WithPrivateNoticeTestCase(ChannelPluginTestCase):
class ProxyTestCase(SupyTestCase):
def testHashing(self):
irc = getTestIrc()
msg = ircmsgs.ping('0')
irc._tagMsg(msg)
irc = irclib.Irc('test')
proxy = callbacks.SimpleProxy(irc, msg)
# First one way...

View File

@ -47,6 +47,7 @@ class CommandsTestCase(SupyTestCase):
realIrc = getTestIrc()
realIrc.nick = 'test'
realIrc.state.supported['chantypes'] = '#'
realIrc._tagMsg(msg)
irc = callbacks.SimpleProxy(realIrc, msg)
myspec = Spec(spec, **kwargs)
state = myspec(irc, msg, given)

View File

@ -470,6 +470,42 @@ class IrcTestCase(SupyTestCase):
self.irc.feedMsg(msg2)
self.assertEqual(list(self.irc.state.history), [msg1, msg2])
def testMsgChannel(self):
self.irc.reset()
self.irc.state.supported['statusmsg'] = '@'
self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG #linux :foo bar baz!'))
self.assertEqual(self.irc.state.history[-1].channel, '#linux')
self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG @#linux2 :foo bar baz!'))
self.assertEqual(self.irc.state.history[-1].channel, '#linux2')
self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG +#linux3 :foo bar baz!'))
self.assertEqual(self.irc.state.history[-1].channel, None)
self.irc.state.supported['statusmsg'] = '+@'
self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG #linux :foo bar baz!'))
self.assertEqual(self.irc.state.history[-1].channel, '#linux')
self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG @#linux2 :foo bar baz!'))
self.assertEqual(self.irc.state.history[-1].channel, '#linux2')
self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG +#linux3 :foo bar baz!'))
self.assertEqual(self.irc.state.history[-1].channel, '#linux3')
del self.irc.state.supported['statusmsg']
self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG #linux :foo bar baz!'))
self.assertEqual(self.irc.state.history[-1].channel, '#linux')
self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG @#linux2 :foo bar baz!'))
self.assertEqual(self.irc.state.history[-1].channel, None)
self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG +#linux3 :foo bar baz!'))
self.assertEqual(self.irc.state.history[-1].channel, None)
# Test msg.channel is set only for PRIVMSG and NOTICE
self.irc.state.supported['statusmsg'] = '+@'
self.irc.feedMsg(ircmsgs.IrcMsg('NOTICE @#linux :foo bar baz!'))
self.assertEqual(self.irc.state.history[-1].channel, '#linux')
self.irc.feedMsg(ircmsgs.IrcMsg('NOTICE @#linux2 :foo bar baz!'))
self.assertEqual(self.irc.state.history[-1].channel, '#linux2')
self.irc.feedMsg(ircmsgs.IrcMsg('MODE @#linux3 +v foo'))
self.assertEqual(self.irc.state.history[-1].channel, None)
def testQuit(self):
self.irc.reset()
self.irc.feedMsg(ircmsgs.IrcMsg(':someuser JOIN #foo'))

View File

@ -281,12 +281,10 @@ class FunctionsTestCase(SupyTestCase):
def testStandardSubstitute(self):
# Stub out random msg and irc objects that provide what
# standardSubstitute wants
msg = ircmsgs.IrcMsg(':%s PRIVMSG #channel :stuff' % self.hostmask)
class Irc(object):
nick = 'bob'
network = 'testnet'
irc = getTestIrc()
irc = Irc()
msg = ircmsgs.IrcMsg(':%s PRIVMSG #channel :stuff' % self.hostmask)
irc._tagMsg(msg)
f = ircutils.standardSubstitute
vars = {'foo': 'bar', 'b': 'c', 'i': 100,
@ -344,9 +342,12 @@ class FunctionsTestCase(SupyTestCase):
self.assertEqual('{}|^', ircutils.toLower('[]\\~'))
def testReplyTo(self):
irc = getTestIrc()
prefix = 'foo!bar@baz'
channel = ircmsgs.privmsg('#foo', 'bar baz', prefix=prefix)
private = ircmsgs.privmsg('jemfinch', 'bar baz', prefix=prefix)
irc._tagMsg(channel)
irc._tagMsg(private)
self.assertEqual(ircutils.replyTo(channel), channel.args[0])
self.assertEqual(ircutils.replyTo(private), private.nick)

View File

@ -185,9 +185,17 @@ class ValuesTestCase(SupyTestCase):
self.assertRaises(registry.InvalidRegistryValue,
v.setValue, re.compile(r'foo'))
def testBackslashes(self):
def testBackslashesKeys(self):
conf.supybot.reply.whenAddressedBy.strings.get(':foo').set('=/*')
filename = conf.supybot.directories.conf.dirize('backslashes1.conf')
registry.close(conf.supybot, filename)
registry.open_registry(filename)
value = conf.supybot.reply.whenAddressedBy.strings.get(':foo')()
self.assertEqual(value, set(['=/*']))
def testBackslashesValues(self):
conf.supybot.reply.whenAddressedBy.chars.set('\\')
filename = conf.supybot.directories.conf.dirize('backslashes.conf')
filename = conf.supybot.directories.conf.dirize('backslashes2.conf')
registry.close(conf.supybot, filename)
registry.open_registry(filename)
self.assertEqual(conf.supybot.reply.whenAddressedBy.chars(), '\\')
@ -224,4 +232,34 @@ class SecurityTestCase(SupyTestCase):
self.assertFalse(g._private)
self.assertTrue(g.val._private)
class InheritanceTestCase(SupyTestCase):
def testChild(self):
parent = registry.String('foo', 'help')
parent._supplyDefault = True
self.assertTrue(parent._wasSet)
self.assertEqual(parent(), 'foo')
child = parent.get('child')
self.assertFalse(child._wasSet)
self.assertEqual(child(), 'foo')
parent.setValue('bar')
self.assertTrue(parent._wasSet)
self.assertEqual(parent(), 'bar')
self.assertFalse(child._wasSet)
self.assertEqual(child(), 'bar') # Takes the new parent value
child.setValue('baz')
self.assertTrue(parent._wasSet)
self.assertEqual(parent(), 'bar')
self.assertTrue(child._wasSet)
self.assertEqual(child(), 'baz')
parent.setValue('qux')
self.assertTrue(parent._wasSet)
self.assertEqual(parent(), 'qux')
self.assertTrue(child._wasSet)
self.assertEqual(child(), 'baz') # Keeps its own value
# vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

@ -37,54 +37,53 @@ class holder:
users = set(map(str, range(1000)))
class FunctionsTestCase(SupyTestCase):
class irc:
class state:
channels = {'#foo': holder()}
nick = 'foobar'
network = 'testnet'
@retry()
def testStandardSubstitute(self):
irc = getTestIrc()
irc.state.channels = {'#foo': holder()}
f = ircutils.standardSubstitute
msg = ircmsgs.privmsg('#foo', 'filler', prefix='biff!quux@xyzzy')
s = f(self.irc, msg, '$rand')
irc._tagMsg(msg)
s = f(irc, msg, '$rand')
try:
int(s)
except ValueError:
self.fail('$rand wasn\'t an int.')
s = f(self.irc, msg, '$randomInt')
s = f(irc, msg, '$randomInt')
try:
int(s)
except ValueError:
self.fail('$randomint wasn\'t an int.')
self.assertEqual(f(self.irc, msg, '$botnick'), self.irc.nick)
self.assertEqual(f(self.irc, msg, '$who'), msg.nick)
self.assertEqual(f(self.irc, msg, '$WHO'),
self.assertEqual(f(irc, msg, '$botnick'), irc.nick)
self.assertEqual(f(irc, msg, '$who'), msg.nick)
self.assertEqual(f(irc, msg, '$WHO'),
msg.nick, 'stand. sub. not case-insensitive.')
self.assertEqual(f(self.irc, msg, '$nick'), msg.nick)
self.assertNotEqual(f(self.irc, msg, '$randomdate'), '$randomdate')
q = f(self.irc,msg,'$randomdate\t$randomdate')
self.assertEqual(f(irc, msg, '$nick'), msg.nick)
self.assertNotEqual(f(irc, msg, '$randomdate'), '$randomdate')
q = f(irc,msg,'$randomdate\t$randomdate')
dl = q.split('\t')
if dl[0] == dl[1]:
self.fail ('Two $randomdates in the same string were the same')
q = f(self.irc, msg, '$randomint\t$randomint')
q = f(irc, msg, '$randomint\t$randomint')
dl = q.split('\t')
if dl[0] == dl[1]:
self.fail ('Two $randomints in the same string were the same')
self.assertNotEqual(f(self.irc, msg, '$today'), '$today')
self.assertNotEqual(f(self.irc, msg, '$now'), '$now')
n = f(self.irc, msg, '$randnick')
self.failUnless(n in self.irc.state.channels['#foo'].users)
n = f(self.irc, msg, '$randomnick')
self.failUnless(n in self.irc.state.channels['#foo'].users)
n = f(self.irc, msg, '$randomnick '*100)
self.assertNotEqual(f(irc, msg, '$today'), '$today')
self.assertNotEqual(f(irc, msg, '$now'), '$now')
n = f(irc, msg, '$randnick')
self.failUnless(n in irc.state.channels['#foo'].users)
n = f(irc, msg, '$randomnick')
self.failUnless(n in irc.state.channels['#foo'].users)
n = f(irc, msg, '$randomnick '*100)
L = n.split()
self.failIf(all(L[0].__eq__, L), 'all $randomnicks were the same')
c = f(self.irc, msg, '$channel')
c = f(irc, msg, '$channel')
self.assertEqual(c, msg.args[0])
net = f(self.irc, msg, '$network')
self.assertEqual(net, self.irc.network)
net = f(irc, msg, '$network')
self.assertEqual(net, irc.network)