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

View File

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

View File

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

View File

@ -54,24 +54,26 @@ class Channel(callbacks.Plugin):
self.invites = {} self.invites = {}
def doKick(self, irc, msg): def doKick(self, irc, msg):
channel = msg.args[0] channel = msg.channel
network = irc.network
if msg.args[1] == irc.nick: if msg.args[1] == irc.nick:
if self.registryValue('alwaysRejoin', channel): if self.registryValue('alwaysRejoin', channel, network):
delay = self.registryValue('rejoinDelay', channel) delay = self.registryValue('rejoinDelay', channel, network)
networkGroup = conf.supybot.networks.get(irc.network) networkGroup = conf.supybot.networks.get(irc.network)
if delay: if delay:
def f(): def f():
irc.sendMsg(networkGroup.channels.join(channel)) irc.sendMsg(networkGroup.channels.join(channel))
schedule.addEvent(f, time.time() + delay) schedule.addEvent(f, time.time() + delay)
self.log.info('Kicked from %s by %s. Rejoining after %s ' self.log.info('Kicked from %s @ %s by %s. '
'seconds.', channel, msg.prefix, delay) 'Rejoining after %s seconds.',
channel, network, msg.prefix, delay)
else: else:
self.log.info('Kicked from %s by %s. Rejoining.', self.log.info('Kicked from %s @ %s by %s. Rejoining.',
channel, msg.prefix) channel, network, msg.prefix)
irc.sendMsg(networkGroup.channels.join(channel)) irc.sendMsg(networkGroup.channels.join(channel))
else: else:
self.log.info('Kicked from %s by %s. Not auto-rejoining.', self.log.info('Kicked from %s @ %s by %s. Not auto-rejoining.',
channel, msg.prefix) channel, network, msg.prefix)
def _sendMsg(self, irc, msg): def _sendMsg(self, irc, msg):
irc.queueMsg(msg) irc.queueMsg(msg)
@ -274,7 +276,7 @@ class Channel(callbacks.Plugin):
supybot.plugins.Channel.partMsg will be used. No part message will be 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. 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) reason = ircutils.standardSubstitute(irc, msg, reason)
self._sendMsg(irc, ircmsgs.part(channel, reason)) self._sendMsg(irc, ircmsgs.part(channel, reason))
networkGroup = conf.supybot.networks.get(irc.network) networkGroup = conf.supybot.networks.get(irc.network)
@ -907,12 +909,20 @@ class Channel(callbacks.Plugin):
nicks if --count option is provided. nicks if --count option is provided.
""" """
# Make sure we don't elicit information about private channels to # 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') capability = ircdb.makeChannelCapability(channel, 'op')
if 's' in irc.state.channels[channel].modes and \ 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 \ 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): msg.nick not in irc.state.channels[channel].users):
irc.error(_('You don\'t have access to that information.'), irc.error(_('You don\'t have access to that information.'),
Raise=True) Raise=True)
@ -920,7 +930,7 @@ class Channel(callbacks.Plugin):
keys = [option for (option, arg) in optlist] keys = [option for (option, arg) in optlist]
if 'count' not in keys: if 'count' not in keys:
utils.sortBy(str.lower, L) 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) irc.reply(utils.str.commaAndify(L), private=private)
else: else:
irc.reply(str(len(L))) irc.reply(str(len(L)))
@ -961,10 +971,8 @@ class Channel(callbacks.Plugin):
supybot.plugins.Channel.partMsg will be used. No part message will be supybot.plugins.Channel.partMsg will be used. No part message will be
used if no default is configured. used if no default is configured.
""" """
if channel is None: channel = channel or msg.channel
if irc.isChannel(msg.args[0]): if not channel:
channel = msg.args[0]
else:
irc.error(Raise=True) irc.error(Raise=True)
capability = ircdb.makeChannelCapability(channel, 'op') capability = ircdb.makeChannelCapability(channel, 'op')
if not ircdb.checkCapabilities(msg.prefix, [capability, 'admin']): if not ircdb.checkCapabilities(msg.prefix, [capability, 'admin']):
@ -976,7 +984,7 @@ class Channel(callbacks.Plugin):
pass pass
if channel not in irc.state.channels: if channel not in irc.state.channels:
irc.error(_('I\'m not in %s.') % channel, Raise=True) 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) reason = ircutils.standardSubstitute(irc, msg, reason)
irc.queueMsg(ircmsgs.part(channel, reason)) irc.queueMsg(ircmsgs.part(channel, reason))
if msg.nick in irc.state.channels[channel].users: 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': if e.args[0] != 'I/O operation on a closed file':
self.log.exception('Odd exception:') self.log.exception('Odd exception:')
def logNameTimestamp(self, channel): def logNameTimestamp(self, network, channel):
format = self.registryValue('filenameTimestamp', channel) format = self.registryValue('filenameTimestamp', channel, network)
return time.strftime(format) return time.strftime(format)
def getLogName(self, channel): def getLogName(self, network, channel):
if self.registryValue('rotateLogs', channel): if self.registryValue('rotateLogs', channel, network):
name = '%s.%s.log' % (channel, self.logNameTimestamp(channel)) return '%s.%s.log' % (channel, self.logNameTimestamp(network, channel))
else: else:
name = '%s.log' % channel name = '%s.log' % channel
return utils.file.sanitizeName(name) return utils.file.sanitizeName(name)
@ -120,8 +120,8 @@ class ChannelLogger(callbacks.Plugin):
def checkLogNames(self): def checkLogNames(self):
for (irc, logs) in self.logs.items(): for (irc, logs) in self.logs.items():
for (channel, log) in list(logs.items()): for (channel, log) in list(logs.items()):
if self.registryValue('rotateLogs', channel): if self.registryValue('rotateLogs', channel, irc.network):
name = self.getLogName(channel) name = self.getLogName(irc.network, channel)
if name != os.path.basename(log.name): if name != os.path.basename(log.name):
log.close() log.close()
del logs[channel] del logs[channel]
@ -137,7 +137,7 @@ class ChannelLogger(callbacks.Plugin):
return logs[channel] return logs[channel]
else: else:
try: try:
name = self.getLogName(channel) name = self.getLogName(irc.network, channel)
logDir = self.getLogDir(irc, channel) logDir = self.getLogDir(irc, channel)
log = open(os.path.join(logDir, name), encoding='utf-8', mode='a') log = open(os.path.join(logDir, name), encoding='utf-8', mode='a')
logs[channel] = log logs[channel] = log
@ -158,14 +158,14 @@ class ChannelLogger(callbacks.Plugin):
return ircutils.toLower(channel) return ircutils.toLower(channel)
def doLog(self, irc, channel, s, *args): def doLog(self, irc, channel, s, *args):
if not self.registryValue('enable', channel): if not self.registryValue('enable', channel, irc.network):
return return
s = format(s, *args) s = format(s, *args)
channel = self.normalizeChannel(irc, channel) channel = self.normalizeChannel(irc, channel)
log = self.getLog(irc, channel) log = self.getLog(irc, channel)
if self.registryValue('timestamp', channel): if self.registryValue('timestamp', channel, irc.network):
self.timestamp(log) self.timestamp(log)
if self.registryValue('stripFormatting', channel): if self.registryValue('stripFormatting', channel, irc.network):
s = ircutils.stripFormatting(s) s = ircutils.stripFormatting(s)
if minisix.PY2: if minisix.PY2:
s = s.decode('utf8', 'ignore') s = s.decode('utf8', 'ignore')
@ -177,7 +177,8 @@ class ChannelLogger(callbacks.Plugin):
(recipients, text) = msg.args (recipients, text) = msg.args
for channel in recipients.split(','): for channel in recipients.split(','):
if irc.isChannel(channel): if irc.isChannel(channel):
noLogPrefix = self.registryValue('noLogPrefix', channel) noLogPrefix = self.registryValue('noLogPrefix',
channel, irc.network)
cap = ircdb.makeChannelCapability(channel, 'logChannelMessages') cap = ircdb.makeChannelCapability(channel, 'logChannelMessages')
try: try:
logChannelMessages = ircdb.checkCapability(msg.prefix, cap, logChannelMessages = ircdb.checkCapability(msg.prefix, cap,
@ -219,7 +220,7 @@ class ChannelLogger(callbacks.Plugin):
def doJoin(self, irc, msg): def doJoin(self, irc, msg):
for channel in msg.args[0].split(','): for channel in msg.args[0].split(','):
if(self.registryValue('showJoinParts', channel)): if(self.registryValue('showJoinParts', channel, irc.network)):
self.doLog(irc, channel, self.doLog(irc, channel,
'*** %s <%s> has joined %s\n', '*** %s <%s> has joined %s\n',
msg.nick, msg.prefix, channel) msg.nick, msg.prefix, channel)
@ -244,7 +245,7 @@ class ChannelLogger(callbacks.Plugin):
else: else:
reason = "" reason = ""
for channel in msg.args[0].split(','): for channel in msg.args[0].split(','):
if(self.registryValue('showJoinParts', channel)): if(self.registryValue('showJoinParts', channel, irc.network)):
self.doLog(irc, channel, self.doLog(irc, channel,
'*** %s <%s> has left %s%s\n', '*** %s <%s> has left %s%s\n',
msg.nick, msg.prefix, channel, reason) msg.nick, msg.prefix, channel, reason)
@ -270,7 +271,7 @@ class ChannelLogger(callbacks.Plugin):
else: else:
reason = "" reason = ""
for channel in msg.tagged('channels'): for channel in msg.tagged('channels'):
if(self.registryValue('showJoinParts', channel)): if(self.registryValue('showJoinParts', channel, irc.network)):
self.doLog(irc, channel, self.doLog(irc, channel,
'*** %s <%s> has quit IRC%s\n', '*** %s <%s> has quit IRC%s\n',
msg.nick, msg.prefix, reason) msg.nick, msg.prefix, reason)

View File

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

View File

@ -151,10 +151,10 @@ class Config(callbacks.Plugin):
def _list(self, irc, group): def _list(self, irc, group):
L = [] L = []
for (vname, v) in group._children.items(): 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: irc.isChannel(vname) and not v._children:
continue continue
if hasattr(v, 'channelValue') and v.channelValue: if hasattr(v, '_channelValue') and v._channelValue:
vname = '#' + vname vname = '#' + vname
if v._added and not all(irc.isChannel, v._added): if v._added and not all(irc.isChannel, v._added):
vname = '@' + vname vname = '@' + vname
@ -198,13 +198,33 @@ class Config(callbacks.Plugin):
irc.reply(_('There were no matching configuration variables.')) irc.reply(_('There were no matching configuration variables.'))
search = wrap(search, ['lowered']) # XXX compose with withoutSpaces? 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 ' ' value = str(group) or ' '
if addChannel and irc.isChannel(msg.args[0]) and not irc.nested: if addGlobal and not irc.nested:
s = str(group.get(msg.args[0])) if global_group._channelValue and channel:
value = _('Global: %s; %s: %s') % (value, msg.args[0], s) # TODO: also show the network value when relevant
if hasattr(group, 'value'): value = _(
if not group._private: '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) return (value, None)
else: else:
capability = getCapability(irc, group._name) capability = getCapability(irc, group._name)
@ -215,7 +235,7 @@ class Config(callbacks.Plugin):
else: else:
irc.error(_('That registry variable has no value. Use the list ' irc.error(_('That registry variable has no value. Use the list '
'command in this plugin to see what variables are ' '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): def _setValue(self, irc, msg, group, value):
if isReadOnly(group._name): if isReadOnly(group._name):
@ -230,28 +250,44 @@ class Config(callbacks.Plugin):
irc.errorNoCapability(capability, Raise=True) irc.errorNoCapability(capability, Raise=True)
@internationalizeDocstring @internationalizeDocstring
def channel(self, irc, msg, args, channels, group, value): def channel(self, irc, msg, args, network, channels, group, value):
"""[<channel>] <name> [<value>] """[<network>] [<channel>] <name> [<value>]
If <value> is given, sets the channel configuration variable for <name> 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 configuration value of <name>. <channel> is only necessary if the
message isn't sent in the channel itself. More than one channel may message isn't sent in the channel itself. More than one channel may
be given at once by separating them with commas.""" be given at once by separating them with commas.
if not group.channelValue: <network> defaults to the current network."""
if not group._channelValue:
irc.error(_('That configuration variable is not a channel-specific ' irc.error(_('That configuration variable is not a channel-specific '
'configuration variable.')) 'configuration variable.'))
return return
if value is not None: if value is not None:
for channel in channels: for channel in channels:
assert irc.isChannel(channel) 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) 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() irc.replySuccess()
else: else:
if network == '*':
network = None
values = [] values = []
private = None private = None
for channel in channels: 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)) values.append((channel, value))
if private_value: if private_value:
private = True private = True
@ -261,7 +297,32 @@ class Config(callbacks.Plugin):
private=private) private=private)
else: else:
irc.reply(values[0][1], private=private) 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')]) additional('text')])
@internationalizeDocstring @internationalizeDocstring
@ -276,7 +337,10 @@ class Config(callbacks.Plugin):
self._setValue(irc, msg, group, value) self._setValue(irc, msg, group, value)
irc.replySuccess() irc.replySuccess()
else: 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) irc.reply(value, private=private)
config = wrap(config, ['settableConfigVar', additional('text')]) config = wrap(config, ['settableConfigVar', additional('text')])
@ -290,11 +354,10 @@ class Config(callbacks.Plugin):
s = group.help() s = group.help()
if s: if s:
if hasattr(group, 'value') and not group._private: if hasattr(group, 'value') and not group._private:
channel = msg.args[0] if msg.channel and \
if irc.isChannel(channel) and \ msg.channel in group._children:
channel in group._children:
globvalue = str(group) globvalue = str(group)
chanvalue = str(group.get(channel)) chanvalue = str(group.get(msg.channel))
if chanvalue != globvalue: if chanvalue != globvalue:
s += _(' (Current global value: %s; ' s += _(' (Current global value: %s; '
'current channel value: %s)') % \ 'current channel value: %s)') % \

View File

@ -133,13 +133,13 @@ class ConfigTestCase(ChannelPluginTestCase):
'^Completely: Error: ', '^Completely: Error: ',
frm=self.prefix3) frm=self.prefix3)
self.assertResponse('config plugins.Config.%s' % var_name, 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, self.assertNotRegexp('config channel plugins.Config.%s 1' % var_name,
'^Completely: Error: ', '^Completely: Error: ',
frm=self.prefix3) frm=self.prefix3)
self.assertResponse('config plugins.Config.%s' % var_name, self.assertResponse('config plugins.Config.%s' % var_name,
'Global: 0; #test: 1') 'Global: 0; #test @ test: 1')
def testOpNonEditable(self): def testOpNonEditable(self):
var_name = 'testOpNonEditable' + random_string() var_name = 'testOpNonEditable' + random_string()
@ -154,18 +154,18 @@ class ConfigTestCase(ChannelPluginTestCase):
'^Completely: Error: ', '^Completely: Error: ',
frm=self.prefix3) frm=self.prefix3)
self.assertResponse('config plugins.Config.%s' % var_name, 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, self.assertRegexp('config channel plugins.Config.%s 1' % var_name,
'^Completely: Error: ', '^Completely: Error: ',
frm=self.prefix3) frm=self.prefix3)
self.assertResponse('config plugins.Config.%s' % var_name, 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, self.assertNotRegexp('config channel plugins.Config.%s 1' % var_name,
'^Completely: Error: ') '^Completely: Error: ')
self.assertResponse('config plugins.Config.%s' % var_name, self.assertResponse('config plugins.Config.%s' % var_name,
'Global: 0; #test: 1') 'Global: 0; #test @ test: 1')
def testChannel(self): def testChannel(self):
self.assertResponse('config reply.whenAddressedBy.strings ^', 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 #testchan1 reply.whenAddressedBy.strings', '.')
self.assertResponse('config channel #testchan2 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: # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -30,7 +30,7 @@
from supybot.test import * from supybot.test import *
class ServicesTestCase(PluginTestCase): class ServicesTestCase(PluginTestCase):
plugins = ('Services',) plugins = ('Services', 'Config')
config = { config = {
'plugins.Services.NickServ': 'NickServ', 'plugins.Services.NickServ': 'NickServ',
'plugins.Services.ChanServ': 'ChanServ', 'plugins.Services.ChanServ': 'ChanServ',
@ -48,6 +48,33 @@ class ServicesTestCase(PluginTestCase):
self.failUnless(m.args[0] == 'NickServ') self.failUnless(m.args[0] == 'NickServ')
self.failUnless(m.args[1].lower() == 'identify biff') 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: # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

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

View File

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

View File

@ -50,7 +50,8 @@ class Success(plugins.ChannelIdDatabasePlugin):
class MySuccessClass(self.originalClass): class MySuccessClass(self.originalClass):
__slots__ = () __slots__ = ()
def __call__(self): 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: if ret is None:
try: try:
self.__class__ = pluginSelf.originalClass self.__class__ = pluginSelf.originalClass

View File

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

View File

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

View File

@ -73,19 +73,19 @@ class URL(callbacks.Plugin):
def doPrivmsg(self, irc, msg): def doPrivmsg(self, irc, msg):
if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg):
return return
channel = msg.args[0] if msg.channel:
if irc.isChannel(channel):
if ircmsgs.isAction(msg): if ircmsgs.isAction(msg):
text = ircmsgs.unAction(msg) text = ircmsgs.unAction(msg)
else: else:
text = msg.args[1] text = msg.args[1]
for url in utils.web.urlRe.findall(text): 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): if r and r.search(url):
self.log.debug('Skipping adding %u to db.', url) self.log.debug('Skipping adding %u to db.', url)
continue continue
self.log.debug('Adding %u to db.', url) self.log.debug('Adding %u to db.', url)
self.db.add(channel, url, msg) self.db.add(msg.channel, url, msg)
@internationalizeDocstring @internationalizeDocstring
def stats(self, irc, msg, args, channel): 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. Returns a fortune from the *nix fortune program.
""" """
channel = msg.args[0] channel = msg.channel
network = irc.network
fortuneCmd = self.registryValue('fortune.command') fortuneCmd = self.registryValue('fortune.command')
if fortuneCmd: if fortuneCmd:
args = [fortuneCmd] args = [fortuneCmd]
if self.registryValue('fortune.short', channel): if self.registryValue('fortune.short', channel, network):
args.append('-s') args.append('-s')
if self.registryValue('fortune.equal', channel): if self.registryValue('fortune.equal', channel, network):
args.append('-e') args.append('-e')
if self.registryValue('fortune.offensive', channel): if self.registryValue('fortune.offensive', channel, network):
args.append('-a') args.append('-a')
args.extend(self.registryValue('fortune.files', channel)) args.extend(self.registryValue('fortune.files', channel, network))
try: try:
with open(os.devnull) as null: with open(os.devnull) as null:
inst = subprocess.Popen(args, 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 authentication to the bot. This is a core Supybot plugin that should
not be removed!""" not be removed!"""
def _checkNotChannel(self, irc, msg, password=' '): 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()) raise callbacks.Error(conf.supybot.replies.requiresPrivacy())
@internationalizeDocstring @internationalizeDocstring
@ -88,7 +88,8 @@ class User(callbacks.Plugin):
users.append(u.name) users.append(u.name)
if users: if users:
utils.sortBy(str.lower, 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) irc.reply(format('%L', users), private=private)
else: else:
if predicates: if predicates:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -843,6 +843,7 @@ class Irc(IrcCommandDispatcher, log.Firewalled):
self.queueMsg(ircmsgs.ping(now)) self.queueMsg(ircmsgs.ping(now))
if msg: if msg:
for callback in reversed(self.callbacks): for callback in reversed(self.callbacks):
self._setMsgChannel(msg)
msg = callback.outFilter(self, msg) msg = callback.outFilter(self, msg)
if msg is None: if msg is None:
log.debug('%s.outFilter returned None.', callback.name()) log.debug('%s.outFilter returned None.', callback.name())
@ -875,16 +876,36 @@ class Irc(IrcCommandDispatcher, log.Firewalled):
else: else:
return None return None
_numericErrorCommandRe = re.compile(r'^[45][0-9][0-9]$') def _tagMsg(self, msg):
def feedMsg(self, msg): """Sets attribute on an incoming IRC message. Will usually only be
"""Called by the IrcDriver; feeds a message received.""" called by feedMsg, but may be useful in tests as well."""
msg.tag('receivedBy', self) msg.tag('receivedBy', self)
msg.tag('receivedOn', self.network) msg.tag('receivedOn', self.network)
msg.tag('receivedAt', time.time()) 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: 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') preInFilter = str(msg).rstrip('\r\n')
log.debug('Incoming message (%s): %s', self.network, preInFilter) 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. # On second thought, let's use methods for tagging.
__slots__ = ('args', 'command', 'host', 'nick', 'prefix', 'user', __slots__ = ('args', 'command', 'host', 'nick', 'prefix', 'user',
'_hash', '_str', '_repr', '_len', 'tags', 'reply_env', '_hash', '_str', '_repr', '_len', 'tags', 'reply_env',
'server_tags', 'time') 'server_tags', 'time', 'channel')
def __init__(self, s='', command='', args=(), prefix='', msg=None, def __init__(self, s='', command='', args=(), prefix='', msg=None,
reply_env=None): reply_env=None):
assert not (msg and s), 'IrcMsg.__init__ cannot accept both s and msg' 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): def replyTo(msg):
"""Returns the appropriate target to send responses to 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] return msg.args[0]
else: else:
return msg.nick return msg.nick
@ -867,10 +870,7 @@ def standardSubstitute(irc, msg, text, env=None):
vars.update(msg.reply_env) vars.update(msg.reply_env)
if irc and msg: if irc and msg:
if isChannel(msg.args[0]): channel = msg.channel or 'somewhere'
channel = msg.args[0]
else:
channel = 'somewhere'
def randNick(): def randNick():
if channel != 'somewhere': if channel != 'somewhere':
L = list(irc.state.channels[channel].users) 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) s = '%r is not a valid entry in %r' % (attr, self._name)
raise NonExistentRegistryEntry(s) raise NonExistentRegistryEntry(s)
def __makeChild(self, attr, s): def _makeChild(self, attr, s):
v = self.__class__(self._default, self._help) v = self.__class__(self._default, self._help)
v.set(s) v.set(s)
v._wasSet = False v._wasSet = False
@ -225,10 +225,13 @@ class Group(object):
return attr in self._children return attr in self._children
def __getattr__(self, attr): 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] return self._children[attr]
elif self._supplyDefault: elif self._supplyDefault:
return self.__makeChild(attr, str(self)) return self._makeChild(attr, str(self))
else: else:
self.__nonExistentEntry(attr) self.__nonExistentEntry(attr)
@ -253,7 +256,7 @@ class Group(object):
parts = split(rest) parts = split(rest)
if len(parts) == 1 and parts[0] == name: if len(parts) == 1 and parts[0] == name:
try: try:
self.__makeChild(name, v) self._makeChild(name, v)
except InvalidRegistryValue: except InvalidRegistryValue:
# It's probably supposed to be registered later. # It's probably supposed to be registered later.
pass pass
@ -328,7 +331,7 @@ class Value(Group):
"""Invalid registry value. If you're getting this message, report it, """Invalid registry value. If you're getting this message, report it,
because we forgot to put a proper help string here.""" because we forgot to put a proper help string here."""
__slots__ = ('__parent', '_default', '_showDefault', '_help', '_callbacks', __slots__ = ('__parent', '_default', '_showDefault', '_help', '_callbacks',
'value', 'channelValue', '_opSettable') 'value', '_networkValue', '_channelValue', '_opSettable')
def __init__(self, default, help, setDefault=True, def __init__(self, default, help, setDefault=True,
showDefault=True, **kwargs): showDefault=True, **kwargs):
self.__parent = super(Value, self) self.__parent = super(Value, self)
@ -337,9 +340,29 @@ class Value(Group):
self._showDefault = showDefault self._showDefault = showDefault
self._help = utils.str.normalizeWhitespace(help.strip()) self._help = utils.str.normalizeWhitespace(help.strip())
self._callbacks = [] self._callbacks = []
self._networkValue = False
self._channelValue = False
if setDefault: if setDefault:
self.setValue(default) 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): def error(self, value=_NoValueGiven):
if hasattr(self, 'errormsg') and value is not _NoValueGiven: if hasattr(self, 'errormsg') and value is not _NoValueGiven:
try: try:
@ -356,6 +379,58 @@ class Value(Group):
e.value = self e.value = self
raise e 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): def setName(self, *args):
if self._name == 'unset': if self._name == 'unset':
self._lastModified = 0 self._lastModified = 0
@ -374,16 +449,23 @@ class Value(Group):
100) convert to an integer in set() and check that the integer is less 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 than 100 in this method. You *must* call this parent method in your
own setValue.""" 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._lastModified = time.time()
self.value = v self.value = v
if self._supplyDefault: if self._supplyDefault:
for (name, v) in list(self._children.items()): for (name, child) in list(self._children.items()):
if not v._wasSet: if not child._wasSet:
self.unregister(name) child._setValue(v, inherited=True)
# We call the callback once everything is clean # We call the callback once everything is clean
for callback, args, kwargs in self._callbacks: for callback, args, kwargs in self._callbacks:
callback(*args, **kwargs) callback(*args, **kwargs)
self._wasSet = True self._wasSet = not inherited
def context(self, value): def context(self, value):
"""Return a context manager object, which sets this variable to a """Return a context manager object, which sets this variable to a

View File

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

View File

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

View File

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

View File

@ -470,6 +470,42 @@ class IrcTestCase(SupyTestCase):
self.irc.feedMsg(msg2) self.irc.feedMsg(msg2)
self.assertEqual(list(self.irc.state.history), [msg1, 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): def testQuit(self):
self.irc.reset() self.irc.reset()
self.irc.feedMsg(ircmsgs.IrcMsg(':someuser JOIN #foo')) self.irc.feedMsg(ircmsgs.IrcMsg(':someuser JOIN #foo'))

View File

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

View File

@ -185,9 +185,17 @@ class ValuesTestCase(SupyTestCase):
self.assertRaises(registry.InvalidRegistryValue, self.assertRaises(registry.InvalidRegistryValue,
v.setValue, re.compile(r'foo')) 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('\\') 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.close(conf.supybot, filename)
registry.open_registry(filename) registry.open_registry(filename)
self.assertEqual(conf.supybot.reply.whenAddressedBy.chars(), '\\') self.assertEqual(conf.supybot.reply.whenAddressedBy.chars(), '\\')
@ -224,4 +232,34 @@ class SecurityTestCase(SupyTestCase):
self.assertFalse(g._private) self.assertFalse(g._private)
self.assertTrue(g.val._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: # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79:

View File

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