From 4f024cb0b217fc5a54d3cf7ddc5584329fd9d825 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Thu, 15 Aug 2019 12:22:43 +0200 Subject: [PATCH 01/16] Add network-specific config values. --- plugins/Config/plugin.py | 57 +++++++++++++++++++++------- plugins/Config/test.py | 58 +++++++++++++++++++++++++--- scripts/supybot-test | 3 ++ src/callbacks.py | 24 ++++++------ src/conf.py | 15 ++++++-- src/registry.py | 82 +++++++++++++++++++++++++++++++++++++--- src/test.py | 4 +- 7 files changed, 201 insertions(+), 42 deletions(-) diff --git a/plugins/Config/plugin.py b/plugins/Config/plugin.py index a5505bf7a..e0e6fb91c 100644 --- a/plugins/Config/plugin.py +++ b/plugins/Config/plugin.py @@ -151,10 +151,10 @@ class Config(callbacks.Plugin): def _list(self, irc, group): L = [] for (vname, v) in group._children.items(): - if hasattr(group, 'channelValue') and group.channelValue and \ + if hasattr(group, '_channelValue') and group._channelValue and \ irc.isChannel(vname) and not v._children: continue - if hasattr(v, 'channelValue') and v.channelValue: + if hasattr(v, '_channelValue') and v._channelValue: vname = '#' + vname if v._added and not all(irc.isChannel, v._added): vname = '@' + vname @@ -198,11 +198,20 @@ class Config(callbacks.Plugin): irc.reply(_('There were no matching configuration variables.')) search = wrap(search, ['lowered']) # XXX compose with withoutSpaces? - def _getValue(self, irc, msg, group, addChannel=False): + def _getValue(self, irc, msg, group, network=None, channel=None, addGlobal=False): + global_value = str(group) or ' ' + group = group.getSpecific( + network=network.network, channel=channel, check=None) value = str(group) or ' ' - if addChannel and irc.isChannel(msg.args[0]) and not irc.nested: - s = str(group.get(msg.args[0])) - value = _('Global: %s; %s: %s') % (value, msg.args[0], s) + if addGlobal and not irc.nested: + value = _( + 'Global: %(global_value)s; ' + '%(channel_name)s @ %(network_name)s: %(channel_value)s') % { + 'global_value': global_value, + 'channel_name': msg.args[0], + 'network_name': irc.network, + 'channel_value': value, + } if hasattr(group, 'value'): if not group._private: return (value, None) @@ -230,28 +239,44 @@ class Config(callbacks.Plugin): irc.errorNoCapability(capability, Raise=True) @internationalizeDocstring - def channel(self, irc, msg, args, channels, group, value): - """[] [] + def channel(self, irc, msg, args, network, channels, group, value): + """[] [] [] If is given, sets the channel configuration variable for - to for . Otherwise, returns the current channel + to for on the . + Otherwise, returns the current channel configuration value of . is only necessary if the message isn't sent in the channel itself. More than one channel may - be given at once by separating them with commas.""" - if not group.channelValue: + be given at once by separating them with commas. + defaults to the current network.""" + if not group._channelValue: irc.error(_('That configuration variable is not a channel-specific ' 'configuration variable.')) return if value is not None: for channel in channels: assert irc.isChannel(channel) + + # Sets the non-network-specific value, for forward + # compatibility, ie. this will work even if the owner rolls + # back Limnoria to an older version. + # It's also an easy way to support plugins which are not + # network-aware. self._setValue(irc, msg, group.get(channel), value) + + if network != '*': + # Set the network-specific value + self._setValue(irc, msg, group.get(':' + network.network).get(channel), value) + irc.replySuccess() else: + if network == '*': + network = None values = [] private = None for channel in channels: - (value, private_value) = self._getValue(irc, msg, group.get(channel)) + (value, private_value) = \ + self._getValue(irc, msg, group, network, channel) values.append((channel, value)) if private_value: private = True @@ -261,7 +286,8 @@ class Config(callbacks.Plugin): for (channel, value) in values])) else: irc.reply(values[0][1]) - channel = wrap(channel, ['channels', 'settableConfigVar', + channel = wrap(channel, [optional(first(('literal', '*'), 'networkIrc')), + 'channels', 'settableConfigVar', additional('text')]) @internationalizeDocstring @@ -276,7 +302,10 @@ class Config(callbacks.Plugin): self._setValue(irc, msg, group, value) irc.replySuccess() else: - (value, private) = self._getValue(irc, msg, group, addChannel=group.channelValue) + (value, private) = self._getValue( + irc, msg, group, network=irc, + channel=msg.args[0] if irc.isChannel(msg.args[0]) else None, + addGlobal=group._channelValue) irc.reply(value, private=private) config = wrap(config, ['settableConfigVar', additional('text')]) diff --git a/plugins/Config/test.py b/plugins/Config/test.py index 7ccd11765..b07f28da7 100644 --- a/plugins/Config/test.py +++ b/plugins/Config/test.py @@ -133,13 +133,13 @@ class ConfigTestCase(ChannelPluginTestCase): '^Completely: Error: ', frm=self.prefix3) self.assertResponse('config plugins.Config.%s' % var_name, - 'Global: 0; #test: 0') + 'Global: 0; #test @ test: 0') self.assertNotRegexp('config channel plugins.Config.%s 1' % var_name, '^Completely: Error: ', frm=self.prefix3) self.assertResponse('config plugins.Config.%s' % var_name, - 'Global: 0; #test: 1') + 'Global: 0; #test @ test: 1') def testOpNonEditable(self): var_name = 'testOpNonEditable' + random_string() @@ -154,18 +154,18 @@ class ConfigTestCase(ChannelPluginTestCase): '^Completely: Error: ', frm=self.prefix3) self.assertResponse('config plugins.Config.%s' % var_name, - 'Global: 0; #test: 0') + 'Global: 0; #test @ test: 0') self.assertRegexp('config channel plugins.Config.%s 1' % var_name, '^Completely: Error: ', frm=self.prefix3) self.assertResponse('config plugins.Config.%s' % var_name, - 'Global: 0; #test: 0') + 'Global: 0; #test @ test: 0') self.assertNotRegexp('config channel plugins.Config.%s 1' % var_name, '^Completely: Error: ') self.assertResponse('config plugins.Config.%s' % var_name, - 'Global: 0; #test: 1') + 'Global: 0; #test @ test: 1') def testChannel(self): self.assertResponse('config reply.whenAddressedBy.strings ^', @@ -181,6 +181,54 @@ class ConfigTestCase(ChannelPluginTestCase): self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings', '.') self.assertResponse('config channel #testchan2 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): + self.assertResponse('config channel reply.whenAddressedBy.strings', '^') + + # 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: diff --git a/scripts/supybot-test b/scripts/supybot-test index 518d006db..d57b0bc0a 100644 --- a/scripts/supybot-test +++ b/scripts/supybot-test @@ -59,6 +59,9 @@ supybot.log.plugins.individualLogfiles: False supybot.protocols.irc.throttleTime: 0 supybot.reply.whenAddressedBy.chars: @ supybot.networks.test.server: should.not.need.this +supybot.networks.testnet1.server: should.not.need.this +supybot.networks.testnet2.server: should.not.need.this +supybot.networks.testnet3.server: should.not.need.this supybot.nick: test supybot.databases.users.allowUnregistration: True """ % {'base_dir': os.getcwd()}) diff --git a/src/callbacks.py b/src/callbacks.py index 6d1676e97..17d0af319 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -1391,33 +1391,33 @@ class PluginMixin(BasePlugin, irclib.IrcCallback): else: self.__parent.__call__(irc, msg) - def registryValue(self, name, channel=None, value=True): + def registryValue(self, name, channel=None, network=None, value=True): + if isinstance(network, bool): + # Network-unaware plugin that uses 'value' as a positional + # argument. + (network, value) = (value, network) plugin = self.name() group = conf.supybot.plugins.get(plugin) names = registry.split(name) for name in names: group = group.get(name) - if channel is not None: - if ircutils.isChannel(channel): - group = group.get(channel) - else: - self.log.debug('%s: registryValue got channel=%r', plugin, - channel) if value: + group = group.getSpecific(network=network, channel=channel) return group() else: return group - def setRegistryValue(self, name, value, channel=None): + def setRegistryValue(self, name, value, channel=None, network=None): plugin = self.name() group = conf.supybot.plugins.get(plugin) names = registry.split(name) for name in names: group = group.get(name) - if channel is None: - group.setValue(value) - else: - group.get(channel).setValue(value) + if network: + group = group.get(':' + network) + if channel: + group = group.get(channel) + group.setValue(value) def userValue(self, name, prefixOrName, default=None): try: diff --git a/src/conf.py b/src/conf.py index 5b83f2d68..b9cbd9fdb 100644 --- a/src/conf.py +++ b/src/conf.py @@ -83,12 +83,14 @@ def registerGroup(Group, name, group=None, **kwargs): return Group.register(name, group) def registerGlobalValue(group, name, value): - value.channelValue = False + value._networkValue = False + value._channelValue = False return group.register(name, value) def registerChannelValue(group, name, value, opSettable=True): value._supplyDefault = True - value.channelValue = True + value._networkValue = True + value._channelValue = True value._opSettable = opSettable g = group.register(name, value) gname = g._name.lower() @@ -96,8 +98,13 @@ def registerChannelValue(group, name, value, opSettable=True): if name.lower().startswith(gname) and len(gname) < len(name): name = name[len(gname)+1:] # +1 for . parts = registry.split(name) - if len(parts) == 1 and parts[0] and ircutils.isChannel(parts[0]): - # This gets the channel values so they always persist. + 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])() def registerPlugin(name, currentValue=None, public=True): diff --git a/src/registry.py b/src/registry.py index 16d19c5b0..793aa0724 100644 --- a/src/registry.py +++ b/src/registry.py @@ -212,7 +212,7 @@ class Group(object): s = '%r is not a valid entry in %r' % (attr, self._name) raise NonExistentRegistryEntry(s) - def __makeChild(self, attr, s): + def _makeChild(self, attr, s): v = self.__class__(self._default, self._help) v.set(s) v._wasSet = False @@ -225,10 +225,12 @@ class Group(object): return attr in self._children def __getattr__(self, attr): - if attr in self._children: + if attr.startswith('_'): + object.__getattr__(self, attr) + elif attr in self._children: return self._children[attr] elif self._supplyDefault: - return self.__makeChild(attr, str(self)) + return self._makeChild(attr, str(self)) else: self.__nonExistentEntry(attr) @@ -253,7 +255,7 @@ class Group(object): parts = split(rest) if len(parts) == 1 and parts[0] == name: try: - self.__makeChild(name, v) + self._makeChild(name, v) except InvalidRegistryValue: # It's probably supposed to be registered later. pass @@ -328,7 +330,7 @@ class Value(Group): """Invalid registry value. If you're getting this message, report it, because we forgot to put a proper help string here.""" __slots__ = ('__parent', '_default', '_showDefault', '_help', '_callbacks', - 'value', 'channelValue', '_opSettable') + 'value', '_networkValue', '_channelValue', '_opSettable') def __init__(self, default, help, setDefault=True, showDefault=True, **kwargs): self.__parent = super(Value, self) @@ -340,6 +342,24 @@ class Value(Group): if setDefault: self.setValue(default) + def _makeChild(self, attr, s): + v = self.__class__(self._default, self._help) + v.set(s) + v._wasSet = False + if self._networkValue and self._channelValue: + # If this is both a network-specific and channel-specific value, + # then the child is (only) channel-specific. + v._networkValue = False + v._channelValue = True + v._supplyDefault = True + else: + # Otherwise, the child is neither network-specific or + # channel-specific. + v._supplyDefault = False + v._help = '' # Clear this so it doesn't print a bazillion times. + self.register(attr, v) + return v + def error(self, value=_NoValueGiven): if hasattr(self, 'errormsg') and value is not _NoValueGiven: try: @@ -356,6 +376,58 @@ class Value(Group): e.value = self raise e + def getSpecific(self, network=None, channel=None, check=True): + """Gets the network-specific and/or channel-specific value of this + Value. + If `check=True` (the default), this will raise an error if `network` + (resp. `channel`) is provided but this Value is not network-specific + (resp. channel-specific). If `check=False`, then `network` and/or + `channel` may be silently ignored. + """ + if network and not self._networkValue: + if check: + raise NonExistentRegistryEntry('%s is not network-specific' % + self._name) + else: + network = None + if channel and not self._channelValue: + if check: + raise NonExistentRegistryEntry('%s is not channel-specific' % + self._name) + else: + channel = None + if network and channel: + # The complicated case. We want a net+chan specific value, + # which may come in three different ways: + # + # 1. it was set explicitely net+chan + # 2. it's inherited from a net specific value (which may itself be + # inherited from the base value) + # 3. it's inherited from the chan specific value (which is not a + # actually a parent in the registry tree, but we need this to + # load configuration from old bots). + # + # The choice between 2 and 3 is done by checking which of the + # net-specific and chan-specific values was set explicitely by + # a user/admin. In case both were, the net-specific value is used + # (there is no particular reason for this, I just think it makes + # more sense). + network_value = self.get(':' + network) + network_channel_value = network_value.get(channel) + channel_value = self.get(channel) + if network_value._wasSet or network_channel_value._wasSet: + # cases 1 and 2 + return network_channel_value + else: + # case 3 + return channel_value + elif network: + return self.get(':' + network) + elif channel: + return self.get(channel) + else: + return self + def setName(self, *args): if self._name == 'unset': self._lastModified = 0 diff --git a/src/test.py b/src/test.py index a90b4786b..816f85056 100644 --- a/src/test.py +++ b/src/test.py @@ -96,8 +96,8 @@ def retry(tries=3): return newf return decorator -def getTestIrc(): - irc = irclib.Irc('test') +def getTestIrc(name='test'): + irc = irclib.Irc(name) # Gotta clear the connect messages (USER, NICK, etc.) while irc.takeMsg(): pass From 7a7cdb9f05f05f0082504ffa2d626b123e4d3ca9 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 17 Aug 2019 22:23:51 +0200 Subject: [PATCH 02/16] Add a 'channel' attribute to IrcMsg objects. It's nicer to use 'irc.channel' instead of 'irc.args[0]', and .channel provides the actual channel name (stripped of the statusmsg prefix), so it can be used by plugins when they want to use the actual channel name. --- src/irclib.py | 11 ++++++++++- src/ircmsgs.py | 2 +- test/test_irclib.py | 27 +++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/src/irclib.py b/src/irclib.py index 744e0a5b5..bef88fb7a 100644 --- a/src/irclib.py +++ b/src/irclib.py @@ -881,10 +881,19 @@ class Irc(IrcCommandDispatcher, log.Firewalled): msg.tag('receivedBy', self) msg.tag('receivedOn', self.network) msg.tag('receivedAt', time.time()) - if msg.args and self.isChannel(msg.args[0]): + + # Check if the message is addressed to a channel + if msg.args: channel = msg.args[0] + if not conf.supybot.protocols.irc.strictRfc(): + statusmsg_chars = self.state.supported.get('statusmsg', '') + channel = channel.lstrip(statusmsg_chars) + if not self.isChannel(channel): + channel = None else: channel = None + msg.channel = channel + preInFilter = str(msg).rstrip('\r\n') log.debug('Incoming message (%s): %s', self.network, preInFilter) diff --git a/src/ircmsgs.py b/src/ircmsgs.py index 760e7d7e5..d8af5025f 100644 --- a/src/ircmsgs.py +++ b/src/ircmsgs.py @@ -116,7 +116,7 @@ class IrcMsg(object): # On second thought, let's use methods for tagging. __slots__ = ('args', 'command', 'host', 'nick', 'prefix', 'user', '_hash', '_str', '_repr', '_len', 'tags', 'reply_env', - 'server_tags', 'time') + 'server_tags', 'time', 'channel') def __init__(self, s='', command='', args=(), prefix='', msg=None, reply_env=None): assert not (msg and s), 'IrcMsg.__init__ cannot accept both s and msg' diff --git a/test/test_irclib.py b/test/test_irclib.py index f320b6cd8..d00f754be 100644 --- a/test/test_irclib.py +++ b/test/test_irclib.py @@ -470,6 +470,33 @@ class IrcTestCase(SupyTestCase): self.irc.feedMsg(msg2) self.assertEqual(list(self.irc.state.history), [msg1, msg2]) + def testMsgChannel(self): + self.irc.reset() + + self.irc.state.supported['statusmsg'] = '@' + self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG #linux :foo bar baz!')) + self.assertEqual(self.irc.state.history[-1].channel, '#linux') + self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG @#linux2 :foo bar baz!')) + self.assertEqual(self.irc.state.history[-1].channel, '#linux2') + self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG +#linux3 :foo bar baz!')) + self.assertEqual(self.irc.state.history[-1].channel, None) + + self.irc.state.supported['statusmsg'] = '+@' + self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG #linux :foo bar baz!')) + self.assertEqual(self.irc.state.history[-1].channel, '#linux') + self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG @#linux2 :foo bar baz!')) + self.assertEqual(self.irc.state.history[-1].channel, '#linux2') + self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG +#linux3 :foo bar baz!')) + self.assertEqual(self.irc.state.history[-1].channel, '#linux3') + + del self.irc.state.supported['statusmsg'] + self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG #linux :foo bar baz!')) + self.assertEqual(self.irc.state.history[-1].channel, '#linux') + self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG @#linux2 :foo bar baz!')) + self.assertEqual(self.irc.state.history[-1].channel, None) + self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG +#linux3 :foo bar baz!')) + self.assertEqual(self.irc.state.history[-1].channel, None) + def testQuit(self): self.irc.reset() self.irc.feedMsg(ircmsgs.IrcMsg(':someuser JOIN #foo')) From 5b40b5136d4961a821cf30807e776243e41db680 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sun, 18 Aug 2019 10:09:11 +0200 Subject: [PATCH 03/16] Limit statusmsg prefix stripping to PRIVMSG and NOTICE. --- src/irclib.py | 5 +++-- test/test_irclib.py | 9 +++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/irclib.py b/src/irclib.py index bef88fb7a..280bfefac 100644 --- a/src/irclib.py +++ b/src/irclib.py @@ -882,10 +882,11 @@ class Irc(IrcCommandDispatcher, log.Firewalled): msg.tag('receivedOn', self.network) msg.tag('receivedAt', time.time()) - # Check if the message is addressed to a channel + # Check if the message is sent to a channel if msg.args: channel = msg.args[0] - if not conf.supybot.protocols.irc.strictRfc(): + if msg.command in ('NOTICE', 'PRIVMSG') and \ + not conf.supybot.protocols.irc.strictRfc(): statusmsg_chars = self.state.supported.get('statusmsg', '') channel = channel.lstrip(statusmsg_chars) if not self.isChannel(channel): diff --git a/test/test_irclib.py b/test/test_irclib.py index d00f754be..10622233d 100644 --- a/test/test_irclib.py +++ b/test/test_irclib.py @@ -497,6 +497,15 @@ class IrcTestCase(SupyTestCase): self.irc.feedMsg(ircmsgs.IrcMsg('PRIVMSG +#linux3 :foo bar baz!')) self.assertEqual(self.irc.state.history[-1].channel, None) + # Test msg.channel is set only for PRIVMSG and NOTICE + self.irc.state.supported['statusmsg'] = '+@' + self.irc.feedMsg(ircmsgs.IrcMsg('NOTICE @#linux :foo bar baz!')) + self.assertEqual(self.irc.state.history[-1].channel, '#linux') + self.irc.feedMsg(ircmsgs.IrcMsg('NOTICE @#linux2 :foo bar baz!')) + self.assertEqual(self.irc.state.history[-1].channel, '#linux2') + self.irc.feedMsg(ircmsgs.IrcMsg('MODE @#linux3 +v foo')) + self.assertEqual(self.irc.state.history[-1].channel, None) + def testQuit(self): self.irc.reset() self.irc.feedMsg(ircmsgs.IrcMsg(':someuser JOIN #foo')) From 8bb31a54e719af4deaa52b28c22792880492f711 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 24 Aug 2019 14:14:33 +0200 Subject: [PATCH 04/16] Strip statusmsg chars in core to determine the actual channel. Use msg.channel if relevant, otherwise strip them locally. --- plugins/Owner/plugin.py | 2 +- src/callbacks.py | 101 ++++++++++++++++++-------------- src/commands.py | 30 ++++++---- src/irclib.py | 28 +++++---- src/ircutils.py | 10 ++-- test/test_callbacks.py | 54 ++++++++++++++--- test/test_commands.py | 1 + test/test_ircutils.py | 11 ++-- test/test_standardSubstitute.py | 47 ++++++++------- 9 files changed, 173 insertions(+), 111 deletions(-) diff --git a/plugins/Owner/plugin.py b/plugins/Owner/plugin.py index 07dc88a5d..3326f9ea5 100644 --- a/plugins/Owner/plugin.py +++ b/plugins/Owner/plugin.py @@ -261,7 +261,7 @@ class Owner(callbacks.Plugin): tokens = callbacks.tokenize(s, channel=msg.args[0]) self.Proxy(irc, msg, tokens) except SyntaxError as e: - irc.queueMsg(callbacks.error(msg, str(e))) + irc.error(str(e)) def logmark(self, irc, msg, args, text): """ diff --git a/src/callbacks.py b/src/callbacks.py index 6d1676e97..219da6468 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -156,17 +156,25 @@ def canonicalName(command, preserve_spaces=False): command = command[:-1] return ''.join([x for x in command if x not in special]).lower() + reAppend -def reply(msg, s, prefixNick=None, private=None, - notice=None, to=None, action=None, error=False, - stripCtcp=True): +def reply(*args, **kwargs): + warnings.warn('callbacks.reply is deprecated. Use irc.reply instead.', + DeprecationWarning) + return _makeReply(dynamic.irc, *args, **kwargs) + +def _makeReply(irc, msg, s, + prefixNick=None, private=None, + notice=None, to=None, action=None, error=False, + stripCtcp=True): msg.tag('repliedTo') # Ok, let's make the target: # XXX This isn't entirely right. Consider to=#foo, private=True. target = ircutils.replyTo(msg) - if ircutils.isChannel(to): + def isPublic(s): + return irc.isChannel(irc._stripChannelPrefix(s)) + if to is not None and isPublic(to): target = to - if ircutils.isChannel(target): - channel = target + if isPublic(target): + channel = irc._stripChannelPrefix(target) else: channel = None if notice is None: @@ -195,11 +203,11 @@ def reply(msg, s, prefixNick=None, private=None, s = ircutils.safeArgument(s) if not s and not action: s = _('Error: I tried to send you an empty message.') - if prefixNick and ircutils.isChannel(target): + if prefixNick and isPublic(target): # Let's may sure we don't do, "#channel: foo.". - if not ircutils.isChannel(to): + if not isPublic(to): s = '%s: %s' % (to, s) - if not ircutils.isChannel(target): + if not isPublic(target): if conf.supybot.reply.withNoticeWhenPrivate(): notice = True # And now, let's decide whether it's a PRIVMSG or a NOTICE. @@ -214,11 +222,16 @@ def reply(msg, s, prefixNick=None, private=None, ret.tag('inReplyTo', msg) return ret -def error(msg, s, **kwargs): +def error(*args, **kwargs): + warnings.warn('callbacks.error is deprecated. Use irc.error instead.', + DeprecationWarning) + return _makeErrorReply(dynamic.irc, *args, **kwargs) + +def _makeErrorReply(irc, msg, s, **kwargs): """Makes an error reply to msg with the appropriate error payload.""" kwargs['error'] = True msg.tag('isError') - return reply(msg, s, **kwargs) + return _makeReply(irc, msg, s, **kwargs) def getHelp(method, name=None, doc=None): if name is None: @@ -400,8 +413,8 @@ def checkCommandCapability(msg, cb, commandName): checkCapability(antiCommand) checkAtEnd = [commandName] default = conf.supybot.capabilities.default() - if ircutils.isChannel(msg.args[0]): - channel = msg.args[0] + if msg.channel: + channel = msg.channel checkCapability(ircdb.makeChannelCapability(channel, antiCommand)) chanCommand = ircdb.makeChannelCapability(channel, commandName) checkAtEnd += [chanCommand] @@ -426,7 +439,7 @@ class RichReplyMethods(object): return ircutils.standardSubstitute(self, self.msg, s) def _getConfig(self, wrapper): - return conf.get(wrapper, self.msg.args[0]) + return conf.get(wrapper, self.msg.channel) def replySuccess(self, s='', **kwargs): v = self._getConfig(conf.supybot.replies.success) @@ -599,7 +612,7 @@ class ReplyIrcProxy(RichReplyMethods): raise ArgumentError if msg is None: msg = self.msg - m = error(msg, s, **kwargs) + m = _makeErrorReply(self, msg, s, **kwargs) self.irc.queueMsg(m) return m @@ -609,7 +622,7 @@ class ReplyIrcProxy(RichReplyMethods): assert not isinstance(s, ircmsgs.IrcMsg), \ 'Old code alert: there is no longer a "msg" argument to reply.' kwargs.pop('noLengthCheck', None) - m = reply(msg, s, **kwargs) + m = _makeReply(self, msg, s, **kwargs) self.irc.queueMsg(m) return m @@ -665,9 +678,9 @@ class NestedCommandsIrcProxy(ReplyIrcProxy): self.notice = None self.private = None self.noLengthCheck = None - if self.irc.isChannel(self.msg.args[0]): + if self.msg.channel: self.prefixNick = conf.get(conf.supybot.reply.withNickPrefix, - self.msg.args[0]) + self.msg.channel) else: self.prefixNick = conf.supybot.reply.withNickPrefix() @@ -894,12 +907,12 @@ class NestedCommandsIrcProxy(ReplyIrcProxy): elif self.noLengthCheck: # noLengthCheck only matters to NestedCommandsIrcProxy, so # it's not used here. Just in case you were wondering. - m = reply(msg, s, to=self.to, - notice=self.notice, - action=self.action, - private=self.private, - prefixNick=self.prefixNick, - stripCtcp=stripCtcp) + m = _makeReply(self, msg, s, to=self.to, + notice=self.notice, + action=self.action, + private=self.private, + prefixNick=self.prefixNick, + stripCtcp=stripCtcp) sendMsg(m) return m else: @@ -930,11 +943,11 @@ class NestedCommandsIrcProxy(ReplyIrcProxy): # action implies noLengthCheck, which has already been # handled. Let's stick an assert in here just in case. assert not self.action - m = reply(msg, s, to=self.to, - notice=self.notice, - private=self.private, - prefixNick=self.prefixNick, - stripCtcp=stripCtcp) + m = _makeReply(self, msg, s, to=self.to, + notice=self.notice, + private=self.private, + prefixNick=self.prefixNick, + stripCtcp=stripCtcp) sendMsg(m) return m # The '(XX more messages)' may have not the same @@ -946,11 +959,11 @@ class NestedCommandsIrcProxy(ReplyIrcProxy): while instant > 1 and msgs: instant -= 1 response = msgs.pop() - m = reply(msg, response, to=self.to, - notice=self.notice, - private=self.private, - prefixNick=self.prefixNick, - stripCtcp=stripCtcp) + m = _makeReply(self, msg, response, to=self.to, + notice=self.notice, + private=self.private, + prefixNick=self.prefixNick, + stripCtcp=stripCtcp) sendMsg(m) # XXX We should somehow allow these to be returned, but # until someone complains, we'll be fine :) We @@ -976,15 +989,15 @@ class NestedCommandsIrcProxy(ReplyIrcProxy): pass # We'll leave it as it is. mask = prefix.split('!', 1)[1] self._mores[mask] = msgs - public = self.irc.isChannel(msg.args[0]) + public = bool(self.msg.channel) private = self.private or not public self._mores[msg.nick] = (private, msgs) - m = reply(msg, response, to=self.to, - action=self.action, - notice=self.notice, - private=self.private, - prefixNick=self.prefixNick, - stripCtcp=stripCtcp) + m = _makeReply(self, msg, response, to=self.to, + action=self.action, + notice=self.notice, + private=self.private, + prefixNick=self.prefixNick, + stripCtcp=stripCtcp) sendMsg(m) return m finally: @@ -1035,7 +1048,7 @@ class NestedCommandsIrcProxy(ReplyIrcProxy): if not isinstance(self.irc, irclib.Irc): return self.irc.error(s, **kwargs) else: - m = error(self.msg, s, **kwargs) + m = _makeErrorReply(self, self.msg, s, **kwargs) self.irc.queueMsg(m) return m else: @@ -1346,7 +1359,7 @@ class Commands(BasePlugin, SynchronizedAndFirewalled): help = getHelp chan = None if dynamic.msg is not None: - chan = dynamic.msg.args[0] + chan = dynamic.msg.channel if simpleSyntax is None: simpleSyntax = conf.get(conf.supybot.reply.showSimpleSyntax, chan) if simpleSyntax: @@ -1385,7 +1398,7 @@ class PluginMixin(BasePlugin, irclib.IrcCallback): else: noIgnore = self.noIgnore if noIgnore or \ - not ircdb.checkIgnored(msg.prefix, msg.args[0]) or \ + not ircdb.checkIgnored(msg.prefix, msg.channel) or \ not ircutils.isUserHostmask(msg.prefix): # Some services impl. self.__parent.__call__(irc, msg) else: diff --git a/src/commands.py b/src/commands.py index dada7b9d5..f06ab92de 100644 --- a/src/commands.py +++ b/src/commands.py @@ -205,9 +205,12 @@ def urlSnarfer(f): """Protects the snarfer from loops (with other bots) and whatnot.""" def newf(self, irc, msg, match, *L, **kwargs): url = match.group(0) - channel = msg.args[0] - if not irc.isChannel(channel) or (ircmsgs.isCtcp(msg) and not - ircmsgs.isAction(msg)): + channel = msg.channel + if not channel: + # Don't snarf in private + return + if not (ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg)): + # Don't snarf CTCPs unless they are a /me return if ircdb.channels.getChannel(channel).lobotomized: self.log.debug('Not snarfing in %s: lobotomized.', channel) @@ -491,10 +494,11 @@ def getChannel(irc, msg, args, state): return if args and irc.isChannel(args[0]): channel = args.pop(0) - elif irc.isChannel(msg.args[0]): - channel = msg.args[0] + elif msg.channel: + channel = msg.channel else: state.log.debug('Raising ArgumentError because there is no channel.') + print(msg.channel, msg) raise callbacks.ArgumentError state.channel = channel state.args.append(channel) @@ -502,8 +506,8 @@ def getChannel(irc, msg, args, state): def getChannels(irc, msg, args, state): if args and all(map(irc.isChannel, args[0].split(','))): channels = args.pop(0).split(',') - elif irc.isChannel(msg.args[0]): - channels = [msg.args[0]] + elif msg.channel: + channels = [msg.channel] else: state.log.debug('Raising ArgumentError because there is no channel.') raise callbacks.ArgumentError @@ -535,11 +539,11 @@ def inChannel(irc, msg, args, state): state.error(_('I\'m not in %s.') % state.channel, Raise=True) def onlyInChannel(irc, msg, args, state): - if not (irc.isChannel(msg.args[0]) and msg.args[0] in irc.state.channels): + if not (msg.channel and msg.channel in irc.state.channels): state.error(_('This command may only be given in a channel that I am ' 'in.'), Raise=True) else: - state.channel = msg.args[0] + state.channel = msg.channel state.args.append(state.channel) def callerInGivenChannel(irc, msg, args, state): @@ -576,8 +580,8 @@ def getChannelOrGlobal(irc, msg, args, state): elif args and irc.isChannel(args[0]): channel = args.pop(0) state.channel = channel - elif irc.isChannel(msg.args[0]): - channel = msg.args[0] + elif msg.channel: + channel = msg.channel state.channel = channel else: state.log.debug('Raising ArgumentError because there is no channel.') @@ -620,11 +624,11 @@ def getSomethingNoSpaces(irc, msg, args, state, *L): getSomething(irc, msg, args, state, p=p, *L) def private(irc, msg, args, state): - if irc.isChannel(msg.args[0]): + if msg.channel: state.errorRequiresPrivacy(Raise=True) def public(irc, msg, args, state, errmsg=None): - if not irc.isChannel(msg.args[0]): + if not msg.channel: if errmsg is None: errmsg = _('This message must be sent in a channel.') state.error(errmsg, Raise=True) diff --git a/src/irclib.py b/src/irclib.py index 280bfefac..c82552836 100644 --- a/src/irclib.py +++ b/src/irclib.py @@ -875,25 +875,33 @@ class Irc(IrcCommandDispatcher, log.Firewalled): else: return None - _numericErrorCommandRe = re.compile(r'^[45][0-9][0-9]$') - def feedMsg(self, msg): - """Called by the IrcDriver; feeds a message received.""" + def _tagMsg(self, msg): + """Sets attribute on an incoming IRC message. Will usually only be + called by feedMsg, but may be useful in tests as well.""" msg.tag('receivedBy', self) msg.tag('receivedOn', self.network) msg.tag('receivedAt', time.time()) # Check if the message is sent to a channel if msg.args: - channel = msg.args[0] + msg.channel = msg.args[0] if msg.command in ('NOTICE', 'PRIVMSG') and \ not conf.supybot.protocols.irc.strictRfc(): - statusmsg_chars = self.state.supported.get('statusmsg', '') - channel = channel.lstrip(statusmsg_chars) - if not self.isChannel(channel): - channel = None + msg.channel = self._stripChannelPrefix(msg.channel) + if not self.isChannel(msg.channel): + msg.channel = None else: - channel = None - msg.channel = channel + msg.channel = None + + def _stripChannelPrefix(self, channel): + statusmsg_chars = self.state.supported.get('statusmsg', '') + return channel.lstrip(statusmsg_chars) + + _numericErrorCommandRe = re.compile(r'^[45][0-9][0-9]$') + def feedMsg(self, msg): + """Called by the IrcDriver; feeds a message received.""" + self._tagMsg(msg) + channel = msg.channel preInFilter = str(msg).rstrip('\r\n') log.debug('Incoming message (%s): %s', self.network, preInFilter) diff --git a/src/ircutils.py b/src/ircutils.py index 8a87e66fc..435e0b34a 100644 --- a/src/ircutils.py +++ b/src/ircutils.py @@ -659,7 +659,10 @@ def safeArgument(s): def replyTo(msg): """Returns the appropriate target to send responses to msg.""" - if isChannel(msg.args[0]): + if msg.channel: + # if message was sent to +#channel, we want to reply to +#channel; + # or unvoiced channel users will see the bot reply without the + # origin query return msg.args[0] else: return msg.nick @@ -867,10 +870,7 @@ def standardSubstitute(irc, msg, text, env=None): vars.update(msg.reply_env) if irc and msg: - if isChannel(msg.args[0]): - channel = msg.args[0] - else: - channel = 'somewhere' + channel = msg.channel or 'somewhere' def randNick(): if channel != 'somewhere': L = list(irc.state.channels[channel].users) diff --git a/test/test_callbacks.py b/test/test_callbacks.py index 07334b609..f22d8dff9 100644 --- a/test/test_callbacks.py +++ b/test/test_callbacks.py @@ -175,6 +175,7 @@ class FunctionsTestCase(SupyTestCase): self.assertEqual('foobar--', callbacks.canonicalName('foobar--')) def testAddressed(self): + irc = getTestIrc() oldprefixchars = str(conf.supybot.reply.whenAddressedBy.chars) nick = 'supybot' conf.supybot.reply.whenAddressedBy.chars.set('~!@') @@ -189,6 +190,7 @@ class FunctionsTestCase(SupyTestCase): for msg in inChannel: self.assertEqual('foo', callbacks.addressed(nick, msg), msg) msg = ircmsgs.privmsg(nick, 'foo') + irc._tagMsg(msg) self.assertEqual('foo', callbacks.addressed(nick, msg)) conf.supybot.reply.whenAddressedBy.chars.set(oldprefixchars) msg = ircmsgs.privmsg('#foo', '%s::::: bar' % nick) @@ -216,52 +218,84 @@ class FunctionsTestCase(SupyTestCase): conf.supybot.reply.whenNotAddressed.setValue(original) def testAddressedWithMultipleNicks(self): + irc = getTestIrc() msg = ircmsgs.privmsg('#foo', 'bar: baz') + irc._tagMsg(msg) self.assertEqual(callbacks.addressed('bar', msg), 'baz') # need to recreate the msg objects since the old ones have already # been tagged msg = ircmsgs.privmsg('#foo', 'bar: baz') + irc._tagMsg(msg) self.assertEqual(callbacks.addressed('biff', msg, nicks=['bar']), 'baz') def testAddressedWithNickAtEnd(self): + irc = getTestIrc() msg = ircmsgs.privmsg('#foo', 'baz, bar') + irc._tagMsg(msg) self.assertEqual(callbacks.addressed('bar', msg, whenAddressedByNickAtEnd=True), 'baz') def testAddressedPrefixCharsTakePrecedenceOverNickAtEnd(self): + irc = getTestIrc() msg = ircmsgs.privmsg('#foo', '@echo foo') + irc._tagMsg(msg) self.assertEqual(callbacks.addressed('foo', msg, whenAddressedByNickAtEnd=True, prefixChars='@'), 'echo foo') - def testReply(self): + irc = getTestIrc() prefix = 'foo!bar@baz' channelMsg = ircmsgs.privmsg('#foo', 'bar baz', prefix=prefix) nonChannelMsg = ircmsgs.privmsg('supybot', 'bar baz', prefix=prefix) + irc._tagMsg(channelMsg) + irc._tagMsg(nonChannelMsg) self.assertEqual(ircmsgs.notice(nonChannelMsg.nick, 'foo'), - callbacks.reply(channelMsg, 'foo', private=True)) + callbacks._makeReply(irc, channelMsg, 'foo', + private=True)) self.assertEqual(ircmsgs.notice(nonChannelMsg.nick, 'foo'), - callbacks.reply(nonChannelMsg, 'foo')) + callbacks._makeReply(irc, nonChannelMsg, 'foo')) self.assertEqual(ircmsgs.privmsg(channelMsg.args[0], '%s: foo' % channelMsg.nick), - callbacks.reply(channelMsg, 'foo')) + callbacks._makeReply(irc, channelMsg, 'foo')) self.assertEqual(ircmsgs.privmsg(channelMsg.args[0], 'foo'), - callbacks.reply(channelMsg, 'foo', prefixNick=False)) + callbacks._makeReply(irc, channelMsg, 'foo', + prefixNick=False)) self.assertEqual(ircmsgs.notice(nonChannelMsg.nick, 'foo'), - callbacks.reply(channelMsg, 'foo', - notice=True, private=True)) + callbacks._makeReply(irc, channelMsg, 'foo', + notice=True, private=True)) + + def testReplyStatusmsg(self): + irc = getTestIrc() + prefix = 'foo!bar@baz' + msg = ircmsgs.privmsg('+#foo', 'bar baz', prefix=prefix) + irc._tagMsg(msg) + + # No statusmsg set, so understood as being a private message, so + # private reply + self.assertEqual(ircmsgs.notice(msg.nick, 'foo'), + callbacks._makeReply(irc, msg, 'foo')) + + irc.state.supported['statusmsg'] = '+' + msg = ircmsgs.privmsg('+#foo', 'bar baz', prefix=prefix) + irc._tagMsg(msg) + print(msg.channel) + self.assertEqual(ircmsgs.privmsg('+#foo', '%s: foo' % msg.nick), + callbacks._makeReply(irc, msg, 'foo')) def testReplyTo(self): + irc = getTestIrc() prefix = 'foo!bar@baz' msg = ircmsgs.privmsg('#foo', 'bar baz', prefix=prefix) - self.assertEqual(callbacks.reply(msg, 'blah', to='blah'), + irc._tagMsg(msg) + self.assertEqual(callbacks._makeReply(irc, msg, 'blah', to='blah'), ircmsgs.privmsg('#foo', 'blah: blah')) - self.assertEqual(callbacks.reply(msg, 'blah', to='blah', private=True), + self.assertEqual(callbacks._makeReply(irc, msg, 'blah', to='blah', + private=True), ircmsgs.notice('blah', 'blah')) def testTokenize(self): @@ -682,7 +716,9 @@ class WithPrivateNoticeTestCase(ChannelPluginTestCase): class ProxyTestCase(SupyTestCase): def testHashing(self): + irc = getTestIrc() msg = ircmsgs.ping('0') + irc._tagMsg(msg) irc = irclib.Irc('test') proxy = callbacks.SimpleProxy(irc, msg) # First one way... diff --git a/test/test_commands.py b/test/test_commands.py index 54eead35e..4b35fd598 100644 --- a/test/test_commands.py +++ b/test/test_commands.py @@ -47,6 +47,7 @@ class CommandsTestCase(SupyTestCase): realIrc = getTestIrc() realIrc.nick = 'test' realIrc.state.supported['chantypes'] = '#' + realIrc._tagMsg(msg) irc = callbacks.SimpleProxy(realIrc, msg) myspec = Spec(spec, **kwargs) state = myspec(irc, msg, given) diff --git a/test/test_ircutils.py b/test/test_ircutils.py index 8d77629e1..71d647780 100644 --- a/test/test_ircutils.py +++ b/test/test_ircutils.py @@ -281,12 +281,10 @@ class FunctionsTestCase(SupyTestCase): def testStandardSubstitute(self): # Stub out random msg and irc objects that provide what # standardSubstitute wants - msg = ircmsgs.IrcMsg(':%s PRIVMSG #channel :stuff' % self.hostmask) - class Irc(object): - nick = 'bob' - network = 'testnet' + irc = getTestIrc() - irc = Irc() + msg = ircmsgs.IrcMsg(':%s PRIVMSG #channel :stuff' % self.hostmask) + irc._tagMsg(msg) f = ircutils.standardSubstitute vars = {'foo': 'bar', 'b': 'c', 'i': 100, @@ -344,9 +342,12 @@ class FunctionsTestCase(SupyTestCase): self.assertEqual('{}|^', ircutils.toLower('[]\\~')) def testReplyTo(self): + irc = getTestIrc() prefix = 'foo!bar@baz' channel = ircmsgs.privmsg('#foo', 'bar baz', prefix=prefix) private = ircmsgs.privmsg('jemfinch', 'bar baz', prefix=prefix) + irc._tagMsg(channel) + irc._tagMsg(private) self.assertEqual(ircutils.replyTo(channel), channel.args[0]) self.assertEqual(ircutils.replyTo(private), private.nick) diff --git a/test/test_standardSubstitute.py b/test/test_standardSubstitute.py index d108d7b53..f39d4ae3e 100644 --- a/test/test_standardSubstitute.py +++ b/test/test_standardSubstitute.py @@ -37,54 +37,53 @@ class holder: users = set(map(str, range(1000))) class FunctionsTestCase(SupyTestCase): - class irc: - class state: - channels = {'#foo': holder()} - nick = 'foobar' - network = 'testnet' @retry() def testStandardSubstitute(self): + irc = getTestIrc() + irc.state.channels = {'#foo': holder()} + f = ircutils.standardSubstitute msg = ircmsgs.privmsg('#foo', 'filler', prefix='biff!quux@xyzzy') - s = f(self.irc, msg, '$rand') + irc._tagMsg(msg) + s = f(irc, msg, '$rand') try: int(s) except ValueError: self.fail('$rand wasn\'t an int.') - s = f(self.irc, msg, '$randomInt') + s = f(irc, msg, '$randomInt') try: int(s) except ValueError: self.fail('$randomint wasn\'t an int.') - self.assertEqual(f(self.irc, msg, '$botnick'), self.irc.nick) - self.assertEqual(f(self.irc, msg, '$who'), msg.nick) - self.assertEqual(f(self.irc, msg, '$WHO'), + self.assertEqual(f(irc, msg, '$botnick'), irc.nick) + self.assertEqual(f(irc, msg, '$who'), msg.nick) + self.assertEqual(f(irc, msg, '$WHO'), msg.nick, 'stand. sub. not case-insensitive.') - self.assertEqual(f(self.irc, msg, '$nick'), msg.nick) - self.assertNotEqual(f(self.irc, msg, '$randomdate'), '$randomdate') - q = f(self.irc,msg,'$randomdate\t$randomdate') + self.assertEqual(f(irc, msg, '$nick'), msg.nick) + self.assertNotEqual(f(irc, msg, '$randomdate'), '$randomdate') + q = f(irc,msg,'$randomdate\t$randomdate') dl = q.split('\t') if dl[0] == dl[1]: self.fail ('Two $randomdates in the same string were the same') - q = f(self.irc, msg, '$randomint\t$randomint') + q = f(irc, msg, '$randomint\t$randomint') dl = q.split('\t') if dl[0] == dl[1]: self.fail ('Two $randomints in the same string were the same') - self.assertNotEqual(f(self.irc, msg, '$today'), '$today') - self.assertNotEqual(f(self.irc, msg, '$now'), '$now') - n = f(self.irc, msg, '$randnick') - self.failUnless(n in self.irc.state.channels['#foo'].users) - n = f(self.irc, msg, '$randomnick') - self.failUnless(n in self.irc.state.channels['#foo'].users) - n = f(self.irc, msg, '$randomnick '*100) + self.assertNotEqual(f(irc, msg, '$today'), '$today') + self.assertNotEqual(f(irc, msg, '$now'), '$now') + n = f(irc, msg, '$randnick') + self.failUnless(n in irc.state.channels['#foo'].users) + n = f(irc, msg, '$randomnick') + self.failUnless(n in irc.state.channels['#foo'].users) + n = f(irc, msg, '$randomnick '*100) L = n.split() self.failIf(all(L[0].__eq__, L), 'all $randomnicks were the same') - c = f(self.irc, msg, '$channel') + c = f(irc, msg, '$channel') self.assertEqual(c, msg.args[0]) - net = f(self.irc, msg, '$network') - self.assertEqual(net, self.irc.network) + net = f(irc, msg, '$network') + self.assertEqual(net, irc.network) From c1ae3f5c81cc8cf4effc7670e99c9270e31a1c9c Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 24 Aug 2019 17:50:05 +0200 Subject: [PATCH 05/16] all plugins: Use msg.channel instead of msg.args[0] + give network name to self.registryValue. --- plugins/Alias/plugin.py | 8 ++-- plugins/Anonymous/plugin.py | 9 ++-- plugins/AutoMode/plugin.py | 33 +++++++------- plugins/BadWords/plugin.py | 22 ++++----- plugins/Channel/plugin.py | 48 +++++++++++--------- plugins/ChannelLogger/plugin.py | 23 +++++----- plugins/ChannelStats/plugin.py | 8 ++-- plugins/Config/plugin.py | 20 ++++----- plugins/Dict/plugin.py | 8 ++-- plugins/Dunno/plugin.py | 8 ++-- plugins/Factoids/plugin.py | 46 +++++++++++-------- plugins/Filter/plugin.py | 7 +-- plugins/Games/plugin.py | 2 +- plugins/Google/plugin.py | 48 +++++++++----------- plugins/Herald/plugin.py | 18 +++++--- plugins/Karma/plugin.py | 34 +++++++------- plugins/Lart/plugin.py | 2 +- plugins/Limiter/plugin.py | 8 ++-- plugins/MessageParser/plugin.py | 20 ++++----- plugins/Misc/plugin.py | 21 +++++---- plugins/MoobotFactoids/plugin.py | 15 ++++--- plugins/Note/plugin.py | 6 +-- plugins/Owner/plugin.py | 3 +- plugins/Praise/plugin.py | 2 +- plugins/Protector/plugin.py | 18 ++++---- plugins/QuoteGrabs/plugin.py | 19 +++++--- plugins/RSS/plugin.py | 47 ++++++++++---------- plugins/Relay/plugin.py | 33 +++++++------- plugins/Seen/plugin.py | 13 +++--- plugins/Services/plugin.py | 10 ++--- plugins/ShrinkUrl/plugin.py | 32 ++++++++------ plugins/Status/plugin.py | 8 ++-- plugins/Success/plugin.py | 3 +- plugins/Time/plugin.py | 5 ++- plugins/Topic/plugin.py | 76 +++++++++++++++++--------------- plugins/URL/plugin.py | 8 ++-- plugins/Unix/plugin.py | 11 ++--- plugins/User/plugin.py | 5 ++- plugins/Web/plugin.py | 20 +++++---- src/callbacks.py | 13 +++--- src/conf.py | 8 ++-- src/irclib.py | 9 ++-- 42 files changed, 407 insertions(+), 350 deletions(-) diff --git a/plugins/Alias/plugin.py b/plugins/Alias/plugin.py index 8ea30d205..582b430b6 100644 --- a/plugins/Alias/plugin.py +++ b/plugins/Alias/plugin.py @@ -49,9 +49,9 @@ def getChannel(irc, msg, args): If the channel was given in args, args is modified (the channel is removed). """ - if args and irc.isChannel(msg.args[0]): + if args and msg.channel: if conf.supybot.reply.requireChannelCommandsToBeSentInChannel(): - if args[0] != msg.args[0]: + if args[0] != msg.channel: s = 'Channel commands must be sent in the channel to which ' \ 'they apply; if this is not the behavior you desire, ' \ 'ask the bot\'s administrator to change the registry ' \ @@ -60,8 +60,8 @@ def getChannel(irc, msg, args): 'to False.' raise callbacks.Error(s) return args.pop(0) - elif irc.isChannel(msg.args[0]): - return msg.args[0] + elif msg.channel: + return msg.channel else: raise callbacks.Error('Command must be sent in a channel or ' \ 'include a channel in its arguments.') diff --git a/plugins/Anonymous/plugin.py b/plugins/Anonymous/plugin.py index cc5dcd5ec..6e7da8dff 100644 --- a/plugins/Anonymous/plugin.py +++ b/plugins/Anonymous/plugin.py @@ -50,17 +50,20 @@ class Anonymous(callbacks.Plugin): supybot.plugins.Anonymous.requireRegistration. """ def _preCheck(self, irc, msg, target, action): - if self.registryValue('requireRegistration', target): + if self.registryValue('requireRegistration', target, irc.network): try: foo = ircdb.users.getUser(msg.prefix) except KeyError: irc.errorNotRegistered(Raise=True) - capability = self.registryValue('requireCapability', target) + capability = self.registryValue('requireCapability', + target, irc.network) if capability: if not ircdb.checkCapability(msg.prefix, capability): irc.errorNoCapability(capability, Raise=True) if action != 'tell': - if self.registryValue('requirePresenceInChannel', target) and \ + require_presence = self.registryValue('requirePresenceInChannel', + target, irc.network) + if require_presence and \ msg.nick not in irc.state.channels[target].users: irc.error(format(_('You must be in %s to %q in there.'), target, action), Raise=True) diff --git a/plugins/AutoMode/plugin.py b/plugins/AutoMode/plugin.py index 01cf7fa06..3a139bd4a 100644 --- a/plugins/AutoMode/plugin.py +++ b/plugins/AutoMode/plugin.py @@ -48,12 +48,13 @@ class AutoMode(callbacks.Plugin): """This plugin, when configured, allows the bot to automatically set modes on users when they join.""" def doJoin(self, irc, msg): - channel = msg.args[0] + channel = msg.channel + network = irc.network if ircutils.strEqual(irc.nick, msg.nick): return - if not self.registryValue('enable', channel): + if not self.registryValue('enable', channel, network): return - fallthrough = self.registryValue('fallthrough', channel) + fallthrough = self.registryValue('fallthrough', channel, network) def do(type): cap = ircdb.makeChannelCapability(channel, type) cap_auto = ircdb.makeChannelCapability(channel, 'auto'+type) @@ -63,7 +64,7 @@ class AutoMode(callbacks.Plugin): ignoreChannelOp=True, ignoreDefaultAllow=True) except KeyError: apply_mode = False - if self.registryValue('alternativeCapabilities', channel): + if self.registryValue('alternativeCapabilities', channel, network): try: override = ircdb.checkCapability(msg.prefix, cap_auto, ignoreOwner=not self.registryValue('owner'), @@ -73,9 +74,9 @@ class AutoMode(callbacks.Plugin): else: override = False if apply_mode or override: - if override or self.registryValue(type, channel): - self.log.info('Scheduling auto-%s of %s in %s.', - type, msg.prefix, channel) + if override or self.registryValue(type, channel, network): + self.log.info('Scheduling auto-%s of %s in %s @ %s.', + type, msg.prefix, channel, network) def dismiss(): """Determines whether or not a mode has already been applied.""" @@ -87,8 +88,9 @@ class AutoMode(callbacks.Plugin): raise Continue # Even if fallthrough, let's only do one. elif not fallthrough: self.log.debug('%s has %s, but supybot.plugins.AutoMode.%s' - ' is not enabled in %s, refusing to fall ' - 'through.', msg.prefix, cap, type, channel) + ' is not enabled in %s @ %s, refusing to ' + 'fall through.', + msg.prefix, cap, type, channel, network) raise Continue def schedule_msg(msg, dismiss): def f(): @@ -96,7 +98,7 @@ class AutoMode(callbacks.Plugin): irc.queueMsg(msg) else: self.log.info('Dismissing auto-mode for %s.', msg.args[2]) - delay = self.registryValue('delay', channel) + delay = self.registryValue('delay', channel, network) if delay: schedule.addEvent(f, time.time() + delay) else: @@ -107,7 +109,7 @@ class AutoMode(callbacks.Plugin): except KeyError: return pattern = re.compile('-|\+') - for item in self.registryValue('extra', channel): + for item in self.registryValue('extra', channel, network): try: username, modes = pattern.split(item, maxsplit=1) modes = item[len(username)] + modes @@ -118,8 +120,8 @@ class AutoMode(callbacks.Plugin): if username != user.name: continue else: - self.log.info('Scheduling auto-modes %s of %s in %s.', - modes, msg.prefix, channel) + self.log.info('Scheduling auto-modes %s of %s in %s @ %s.', + modes, msg.prefix, channel, network) modes = [modes] + \ ([msg.nick]*len(pattern.sub('', modes))) schedule_msg(ircmsgs.mode(channel, modes), lambda :False) @@ -133,8 +135,9 @@ class AutoMode(callbacks.Plugin): finally: extra_modes() c = ircdb.channels.getChannel(channel) - if c.checkBan(msg.prefix) and self.registryValue('ban', channel): - period = self.registryValue('ban.period', channel) + if c.checkBan(msg.prefix) and self.registryValue('ban', + channel, network): + period = self.registryValue('ban.period', channel, network) if period: def unban(): try: diff --git a/plugins/BadWords/plugin.py b/plugins/BadWords/plugin.py index e2f5c4c8f..e1526dbfd 100644 --- a/plugins/BadWords/plugin.py +++ b/plugins/BadWords/plugin.py @@ -72,10 +72,11 @@ class BadWords(callbacks.Privmsg): # We need to check for bad words here rather than in doPrivmsg because # messages don't get to doPrivmsg if the user is ignored. if msg.command == 'PRIVMSG' and self.words(): - channel = msg.args[0] - self.updateRegexp(channel) + channel = msg.channel + self.updateRegexp(channel, irc.network) s = ircutils.stripFormatting(msg.args[1]) - if irc.isChannel(channel) and self.registryValue('kick', channel): + if irc.isChannel(channel) \ + and self.registryValue('kick', channel, irc.network): if self.regexp.search(s): c = irc.state.channels[channel] cap = ircdb.makeChannelCapability(channel, 'op') @@ -86,22 +87,23 @@ class BadWords(callbacks.Privmsg): "they are halfop+ or can't be " "kicked.", msg.nick, channel) else: - message = self.registryValue('kick.message', channel) + message = self.registryValue('kick.message', + channel, irc.network) irc.queueMsg(ircmsgs.kick(channel, msg.nick, message)) else: self.log.warning('Should kick %s from %s, but not opped.', msg.nick, channel) return msg - def updateRegexp(self, channel): + def updateRegexp(self, channel, network): if self.lastModified < self.words.lastModified: - self.makeRegexp(self.words(), channel) + self.makeRegexp(self.words(), channel, network) self.lastModified = time.time() def outFilter(self, irc, msg): if self.filtering and msg.command == 'PRIVMSG' and self.words(): - channel = msg.args[0] - self.updateRegexp(channel) + channel = msg.channel + self.updateRegexp(channel, irc.network) s = msg.args[1] if self.registryValue('stripFormatting'): s = ircutils.stripFormatting(s) @@ -110,9 +112,9 @@ class BadWords(callbacks.Privmsg): msg = ircmsgs.privmsg(msg.args[0], t, msg=msg) return msg - def makeRegexp(self, iterable, channel): + def makeRegexp(self, iterable, channel, network): s = '(%s)' % '|'.join(map(re.escape, iterable)) - if self.registryValue('requireWordBoundaries', channel): + if self.registryValue('requireWordBoundaries', channel, network): s = r'\b%s\b' % s self.regexp = re.compile(s, re.I) diff --git a/plugins/Channel/plugin.py b/plugins/Channel/plugin.py index 0b83eba7c..6c9b60bc3 100644 --- a/plugins/Channel/plugin.py +++ b/plugins/Channel/plugin.py @@ -54,24 +54,26 @@ class Channel(callbacks.Plugin): self.invites = {} def doKick(self, irc, msg): - channel = msg.args[0] + channel = msg.channel + network = irc.network if msg.args[1] == irc.nick: - if self.registryValue('alwaysRejoin', channel): - delay = self.registryValue('rejoinDelay', channel) + if self.registryValue('alwaysRejoin', channel, network): + delay = self.registryValue('rejoinDelay', channel, network) networkGroup = conf.supybot.networks.get(irc.network) if delay: def f(): irc.sendMsg(networkGroup.channels.join(channel)) schedule.addEvent(f, time.time() + delay) - self.log.info('Kicked from %s by %s. Rejoining after %s ' - 'seconds.', channel, msg.prefix, delay) + self.log.info('Kicked from %s @ %s by %s. ' + 'Rejoining after %s seconds.', + channel, network, msg.prefix, delay) else: - self.log.info('Kicked from %s by %s. Rejoining.', - channel, msg.prefix) + self.log.info('Kicked from %s @ %s by %s. Rejoining.', + channel, network, msg.prefix) irc.sendMsg(networkGroup.channels.join(channel)) else: - self.log.info('Kicked from %s by %s. Not auto-rejoining.', - channel, msg.prefix) + self.log.info('Kicked from %s @ %s by %s. Not auto-rejoining.', + channel, network, msg.prefix) def _sendMsg(self, irc, msg): irc.queueMsg(msg) @@ -274,7 +276,7 @@ class Channel(callbacks.Plugin): supybot.plugins.Channel.partMsg will be used. No part message will be used if neither a cycle reason nor a default part message is given. """ - reason = (reason or self.registryValue("partMsg", channel)) + reason = (reason or self.registryValue("partMsg", channel, irc.network)) reason = ircutils.standardSubstitute(irc, msg, reason) self._sendMsg(irc, ircmsgs.part(channel, reason)) networkGroup = conf.supybot.networks.get(irc.network) @@ -907,12 +909,20 @@ class Channel(callbacks.Plugin): nicks if --count option is provided. """ # Make sure we don't elicit information about private channels to - # people or channels that shouldn't know + # people or channels that shouldn't know. Someone is allowed if + # any of these are true: + # * the channel is not secret (mode +s), + # * the request is sent to the channel itself (FIXME: what about + # channels without +n?), + # * the requester is op, + # * the request is not sent to a channel (or else everyone in the + # channel would see the response) and the requester is in the + # channel themselves capability = ircdb.makeChannelCapability(channel, 'op') if 's' in irc.state.channels[channel].modes and \ - msg.args[0] != channel and \ + msg.channel != channel and \ not ircdb.checkCapability(msg.prefix, capability) and \ - (irc.isChannel(msg.args[0]) or \ + (msg.channel or \ msg.nick not in irc.state.channels[channel].users): irc.error(_('You don\'t have access to that information.'), Raise=True) @@ -920,7 +930,7 @@ class Channel(callbacks.Plugin): keys = [option for (option, arg) in optlist] if 'count' not in keys: utils.sortBy(str.lower, L) - private = self.registryValue("nicksInPrivate", channel) + private = self.registryValue("nicksInPrivate", channel, irc.network) irc.reply(utils.str.commaAndify(L), private=private) else: irc.reply(str(len(L))) @@ -961,11 +971,9 @@ class Channel(callbacks.Plugin): supybot.plugins.Channel.partMsg will be used. No part message will be used if no default is configured. """ - if channel is None: - if irc.isChannel(msg.args[0]): - channel = msg.args[0] - else: - irc.error(Raise=True) + channel = channel or msg.channel + if not channel: + irc.error(Raise=True) capability = ircdb.makeChannelCapability(channel, 'op') if not ircdb.checkCapabilities(msg.prefix, [capability, 'admin']): irc.errorNoCapability(capability, Raise=True) @@ -976,7 +984,7 @@ class Channel(callbacks.Plugin): pass if channel not in irc.state.channels: irc.error(_('I\'m not in %s.') % channel, Raise=True) - reason = (reason or self.registryValue("partMsg", channel)) + reason = (reason or self.registryValue("partMsg", channel, irc.network)) reason = ircutils.standardSubstitute(irc, msg, reason) irc.queueMsg(ircmsgs.part(channel, reason)) if msg.nick in irc.state.channels[channel].users: diff --git a/plugins/ChannelLogger/plugin.py b/plugins/ChannelLogger/plugin.py index a05dc6f87..314b592dc 100644 --- a/plugins/ChannelLogger/plugin.py +++ b/plugins/ChannelLogger/plugin.py @@ -90,11 +90,11 @@ class ChannelLogger(callbacks.Plugin): self.log.exception('Odd exception:') def logNameTimestamp(self, channel): - format = self.registryValue('filenameTimestamp', channel) + format = self.registryValue('filenameTimestamp', channel, irc.network) return time.strftime(format) def getLogName(self, channel): - if self.registryValue('rotateLogs', channel): + if self.registryValue('rotateLogs', channel, irc.network): return '%s.%s.log' % (channel, self.logNameTimestamp(channel)) else: return '%s.log' % channel @@ -118,7 +118,7 @@ class ChannelLogger(callbacks.Plugin): def checkLogNames(self): for (irc, logs) in self.logs.items(): for (channel, log) in list(logs.items()): - if self.registryValue('rotateLogs', channel): + if self.registryValue('rotateLogs', channel, irc.network): name = self.getLogName(channel) if name != os.path.basename(log.name): log.close() @@ -156,26 +156,27 @@ class ChannelLogger(callbacks.Plugin): return ircutils.toLower(channel) def doLog(self, irc, channel, s, *args): - if not self.registryValue('enable', channel): + if not self.registryValue('enable', channel, irc.network): return s = format(s, *args) channel = self.normalizeChannel(irc, channel) log = self.getLog(irc, channel) - if self.registryValue('timestamp', channel): + if self.registryValue('timestamp', channel, irc.network): self.timestamp(log) - if self.registryValue('stripFormatting', channel): + if self.registryValue('stripFormatting', channel, irc.network): s = ircutils.stripFormatting(s) if minisix.PY2: s = s.decode('utf8', 'ignore') log.write(s) - if self.registryValue('flushImmediately'): + if self.registryValue('flushImmediately', irc.network): log.flush() def doPrivmsg(self, irc, msg): (recipients, text) = msg.args for channel in recipients.split(','): if irc.isChannel(channel): - noLogPrefix = self.registryValue('noLogPrefix', channel) + noLogPrefix = self.registryValue('noLogPrefix', + channel, irc.network) cap = ircdb.makeChannelCapability(channel, 'logChannelMessages') try: logChannelMessages = ircdb.checkCapability(msg.prefix, cap, @@ -217,7 +218,7 @@ class ChannelLogger(callbacks.Plugin): def doJoin(self, irc, msg): for channel in msg.args[0].split(','): - if(self.registryValue('showJoinParts', channel)): + if(self.registryValue('showJoinParts', channel, irc.network)): self.doLog(irc, channel, '*** %s <%s> has joined %s\n', msg.nick, msg.prefix, channel) @@ -242,7 +243,7 @@ class ChannelLogger(callbacks.Plugin): else: reason = "" for channel in msg.args[0].split(','): - if(self.registryValue('showJoinParts', channel)): + if(self.registryValue('showJoinParts', channel, irc.network)): self.doLog(irc, channel, '*** %s <%s> has left %s%s\n', msg.nick, msg.prefix, channel, reason) @@ -268,7 +269,7 @@ class ChannelLogger(callbacks.Plugin): else: reason = "" for channel in msg.tagged('channels'): - if(self.registryValue('showJoinParts', channel)): + if(self.registryValue('showJoinParts', channel, irc.network)): self.doLog(irc, channel, '*** %s <%s> has quit IRC%s\n', msg.nick, msg.prefix, reason) diff --git a/plugins/ChannelStats/plugin.py b/plugins/ChannelStats/plugin.py index 13e3e8b5a..0fcefe075 100644 --- a/plugins/ChannelStats/plugin.py +++ b/plugins/ChannelStats/plugin.py @@ -142,8 +142,8 @@ class StatsDB(plugins.ChannelUserDB): return UserStat(*L) def addMsg(self, irc, msg, id=None): - if msg.args and irc.isChannel(msg.args[0]): - channel = plugins.getChannel(msg.args[0]) + if msg.channel: + channel = plugins.getChannel(msg.channel) if (channel, 'channelStats') not in self: self[channel, 'channelStats'] = ChannelStat() self[channel, 'channelStats'].addMsg(msg) @@ -186,8 +186,8 @@ class ChannelStats(callbacks.Plugin): def outFilter(self, irc, msg): if msg.command == 'PRIVMSG': - if irc.isChannel(msg.args[0]): - if self.registryValue('selfStats', msg.args[0]): + if msg.channel: + if self.registryValue('selfStats', msg.channel, irc.network): try: self.outFiltering = True self.db.addMsg(irc, msg, 0) diff --git a/plugins/Config/plugin.py b/plugins/Config/plugin.py index e0e6fb91c..89817acd3 100644 --- a/plugins/Config/plugin.py +++ b/plugins/Config/plugin.py @@ -199,21 +199,22 @@ class Config(callbacks.Plugin): search = wrap(search, ['lowered']) # XXX compose with withoutSpaces? 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=None) + network=network.network, channel=channel, check=False) value = str(group) or ' ' if addGlobal and not irc.nested: value = _( 'Global: %(global_value)s; ' '%(channel_name)s @ %(network_name)s: %(channel_value)s') % { 'global_value': global_value, - 'channel_name': msg.args[0], + 'channel_name': msg.channel, 'network_name': irc.network, 'channel_value': value, } - if hasattr(group, 'value'): - if not group._private: + if hasattr(global_group, 'value'): + if not global_group._private: return (value, None) else: capability = getCapability(irc, group._name) @@ -224,7 +225,7 @@ class Config(callbacks.Plugin): else: irc.error(_('That registry variable has no value. Use the list ' 'command in this plugin to see what variables are ' - 'available in this group.')) + 'available in this group.'), Raise=True) def _setValue(self, irc, msg, group, value): if isReadOnly(group._name): @@ -304,7 +305,7 @@ class Config(callbacks.Plugin): else: (value, private) = self._getValue( irc, msg, group, network=irc, - channel=msg.args[0] if irc.isChannel(msg.args[0]) else None, + channel=msg.channel, addGlobal=group._channelValue) irc.reply(value, private=private) config = wrap(config, ['settableConfigVar', additional('text')]) @@ -319,11 +320,10 @@ class Config(callbacks.Plugin): s = group.help() if s: if hasattr(group, 'value') and not group._private: - channel = msg.args[0] - if irc.isChannel(channel) and \ - channel in group._children: + if msg.channel and \ + msg.channel in group._children: globvalue = str(group) - chanvalue = str(group.get(channel)) + chanvalue = str(group.get(msg.channel)) if chanvalue != globvalue: s += _(' (Current global value: %s; ' 'current channel value: %s)') % \ diff --git a/plugins/Dict/plugin.py b/plugins/Dict/plugin.py index e0049fe4f..7bb2f6a23 100644 --- a/plugins/Dict/plugin.py +++ b/plugins/Dict/plugin.py @@ -93,13 +93,13 @@ class Dict(callbacks.Plugin): if words[0] in dbs: dictionary = words.pop(0) else: - default = self.registryValue('default', msg.args[0]) + default = self.registryValue('default', msg.channel, irc.network) if default in dbs: dictionary = default else: if default: - self.log.info('Default dict for %s is not a supported ' - 'dictionary: %s.', msg.args[0], default) + self.log.info('Default dict for %s @ %s is not a supported ' + 'dictionary: %s.', msg.channel, irc.network, default) dictionary = '*' if not words: irc.error(_('You must give a word to define.'), Raise=True) @@ -123,7 +123,7 @@ class Dict(callbacks.Plugin): L.append('%s: %s' % (db, s)) utils.sortBy(len, L) if dictionary == '*' and len(dbs) > 1 and \ - self.registryValue("showDictName", msg.args[0]): + self.registryValue("showDictName", msg.channel, irc.network): s = format(_('%L responded: %s'), list(dbs), '; '.join(L)) else: s = '; '.join(L) diff --git a/plugins/Dunno/plugin.py b/plugins/Dunno/plugin.py index ef96a3d54..7d396be53 100644 --- a/plugins/Dunno/plugin.py +++ b/plugins/Dunno/plugin.py @@ -42,12 +42,12 @@ class Dunno(plugins.ChannelIdDatabasePlugin): callAfter = ['MoobotFactoids', 'Factoids', 'Infobot'] def invalidCommand(self, irc, msg, tokens): - channel = msg.args[0] - if irc.isChannel(channel): - dunno = self.db.random(channel) + if msg.channel: + dunno = self.db.random(msg.channel) if dunno is not None: dunno = dunno.text - prefixNick = self.registryValue('prefixNick', channel) + prefixNick = self.registryValue('prefixNick', + msg.channel, irc.network) env = {'command': tokens[0]} self.log.info('Issuing "dunno" answer, %s is not a command.', tokens[0]) diff --git a/plugins/Factoids/plugin.py b/plugins/Factoids/plugin.py index 1028d8fb6..3c6287dc9 100644 --- a/plugins/Factoids/plugin.py +++ b/plugins/Factoids/plugin.py @@ -53,7 +53,8 @@ from supybot.utils.seq import dameraulevenshtein def getFactoid(irc, msg, args, state): assert not state.channel callConverter('channel', irc, msg, args, state) - separator = state.cb.registryValue('learnSeparator', state.channel) + separator = state.cb.registryValue('learnSeparator', + state.channel, irc.network) try: i = args.index(separator) except ValueError: @@ -137,7 +138,8 @@ class FactoidsCallback(httpserver.SupyHTTPServerCallback): {'title': 'Factoids - not a channel', 'error': 'This is not a channel'}) return - if not self._plugin.registryValue('web.channel', channel): + if not self._plugin.registryValue('web.channel', + channel, irc.network): self.send_response(403) self.send_header('Content-type', 'text/html; charset=utf-8') self.end_headers() @@ -252,13 +254,16 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): method = self.getCommandMethod(command) if method.__func__.__name__ == 'learn': chan = None + network = None if dynamic.msg is not None: - chan = dynamic.msg.args[0] - s = self.registryValue('learnSeparator', chan) + chan = dynamic.msg.channel + if dynamic.irc is not None: + network = dynamic.irc.network + s = self.registryValue('learnSeparator', chan, network) help = callbacks.getHelp if simpleSyntax is None: - simpleSyntax = conf.get(conf.supybot.reply.showSimpleSyntax, - chan) + simpleSyntax = conf.supybot.reply.showSimpleSyntax.getSpecific( + dynamic.irc.network, chan)() if simpleSyntax: help = callbacks.getSyntax return help(method, @@ -276,7 +281,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): return (keyresults, factresults,) def learn(self, irc, msg, args, channel, key, factoid): - if self.registryValue('requireVoice', channel) and \ + if self.registryValue('requireVoice', channel, irc.network) and \ not irc.state.channels[channel].isVoicePlus(msg.nick): irc.error(_('You have to be at least voiced to teach factoids.'), Raise=True) @@ -366,8 +371,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): return [] - def _updateRank(self, channel, factoids): - if self.registryValue('keepRankInfo', channel): + def _updateRank(self, network, channel, factoids): + if self.registryValue('keepRankInfo', channel, network): db = self.getDb(channel) cursor = db.cursor() for (fact,factid,relationid) in factoids: @@ -391,7 +396,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): if number: try: irc.reply(format_fact(factoids[number-1][0])) - self._updateRank(channel, [factoids[number-1]]) + self._updateRank(irc.network, channel, [factoids[number-1]]) except IndexError: irc.error(_('That\'s not a valid number for that key.')) return @@ -399,7 +404,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): env = {'key': key} def prefixer(v): env['value'] = v - formatter = self.registryValue('format', msg.args[0]) + formatter = self.registryValue('format', + msg.channel, irc.network) return ircutils.standardSubstitute(irc, msg, formatter, env) if len(factoids) == 1: @@ -413,7 +419,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): counter += 1 irc.replies(factoidsS, prefixer=prefixer, joiner=', or ', onlyPrefixFirst=True) - self._updateRank(channel, factoids) + self._updateRank(irc.network, channel, factoids) elif error: irc.error(_('No factoid matches that key.')) @@ -429,9 +435,9 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): irc.error('No factoid matches that key.') def invalidCommand(self, irc, msg, tokens): - if irc.isChannel(msg.args[0]): - channel = msg.args[0] - if self.registryValue('replyWhenInvalidCommand', channel): + channel = msg.channel + if channel: + if self.registryValue('replyWhenInvalidCommand', channel, irc.network): key = ' '.join(tokens) factoids = self._lookupFactoid(channel, key) if factoids: @@ -556,7 +562,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): itself. """ if not number: - number = self.registryValue('rankListLength', channel) + number = self.registryValue('rankListLength', channel, irc.network) db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT keys.key, relations.usage_count @@ -655,7 +661,7 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): is only necessary if the message isn't sent in the channel itself. """ - if self.registryValue('requireVoice', channel) and \ + if self.registryValue('requireVoice', channel, irc.network) and \ not irc.state.channels[channel].isVoicePlus(msg.nick): irc.error(_('You have to be at least voiced to remove factoids.'), Raise=True) number = None @@ -834,7 +840,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): if cursor.rowcount == 0: irc.reply(_('No keys matched that query.')) elif cursor.rowcount == 1 and \ - self.registryValue('showFactoidIfOnlyOneMatch', channel): + self.registryValue('showFactoidIfOnlyOneMatch', + channel, irc.network): self.whatis(irc, msg, [channel, cursor.fetchone()[0]]) elif cursor.rowcount > 100: irc.reply(_('More than 100 keys matched that query; ' @@ -843,7 +850,8 @@ class Factoids(callbacks.Plugin, plugins.ChannelDBHandler): if len(results) == 0: irc.reply(_('No keys matched that query.')) elif len(results) == 1 and \ - self.registryValue('showFactoidIfOnlyOneMatch', channel): + self.registryValue('showFactoidIfOnlyOneMatch', + channel, irc.network): self.whatis(irc, msg, [channel, results[0][0]]) elif len(results) > 100: irc.reply(_('More than 100 keys matched that query; ' diff --git a/plugins/Filter/plugin.py b/plugins/Filter/plugin.py index d9da9f6c6..4160bae7a 100644 --- a/plugins/Filter/plugin.py +++ b/plugins/Filter/plugin.py @@ -63,12 +63,12 @@ class Filter(callbacks.Plugin): def outFilter(self, irc, msg): if msg.command in ('PRIVMSG', 'NOTICE'): - if msg.args[0] in self.outFilters: + if msg.channel in self.outFilters: if ircmsgs.isAction(msg): s = ircmsgs.unAction(msg) else: s = msg.args[1] - methods = self.outFilters[msg.args[0]] + methods = self.outFilters[msg.channel] for filtercommand in methods: myIrc = MyFilterProxy() filtercommand(myIrc, msg, [s]) @@ -651,7 +651,8 @@ class Filter(callbacks.Plugin): "internationalization" becomes "i18n"). """ L = [] - minimum = self.registryValue('shrink.minimum', msg.args[0]) + minimum = self.registryValue('shrink.minimum', + msg.channel, irc.network) r = re.compile(r'[A-Za-z]{%s,}' % minimum) def shrink(m): s = m.group(0) diff --git a/plugins/Games/plugin.py b/plugins/Games/plugin.py index fa96aa9d0..fad442f98 100644 --- a/plugins/Games/plugin.py +++ b/plugins/Games/plugin.py @@ -130,7 +130,7 @@ class Games(callbacks.Plugin): self._rouletteBullet = random.randrange(0, 6) irc.reply(_('*SPIN* Are you feeling lucky?'), prefixNick=False) return - channel = msg.args[0] + channel = msg.channel if self._rouletteChamber == self._rouletteBullet: self._rouletteBullet = random.randrange(0, 6) self._rouletteChamber = random.randrange(0, 6) diff --git a/plugins/Google/plugin.py b/plugins/Google/plugin.py index 42a6f1a0f..800863d33 100644 --- a/plugins/Google/plugin.py +++ b/plugins/Google/plugin.py @@ -69,7 +69,7 @@ class Google(callbacks.PluginRegexp): _googleRe = re.compile(r'\b(google)\b', re.I) def outFilter(self, irc, msg): if msg.command == 'PRIVMSG' and \ - self.registryValue('colorfulFilter', msg.args[0]): + self.registryValue('colorfulFilter', msg.channel, irc.network): s = msg.args[1] s = re.sub(self._googleRe, self._getColorGoogle, s) msg = ircmsgs.privmsg(msg.args[0], s, msg=msg) @@ -88,7 +88,7 @@ class Google(callbacks.PluginRegexp): _gsearchUrl = 'https://www.google.com/search' - def search(self, query, channel, options={}): + def search(self, query, channel, network, options={}): """search("search phrase", options={}) Valid options are: @@ -121,11 +121,11 @@ class Google(callbacks.PluginRegexp): opts['safe'] = v elif k == 'language': opts['hl'] = v - defLang = self.registryValue('defaultLanguage', channel) + defLang = self.registryValue('defaultLanguage', channel, network) if 'hl' not in opts and defLang: opts['hl'] = defLang.strip('lang_') if 'safe' not in opts: - opts['safe'] = self.registryValue('searchFilter', dynamic.channel) + opts['safe'] = self.registryValue('searchFilter', channel, network) if 'rsz' not in opts: opts['rsz'] = 'large' @@ -169,7 +169,8 @@ class Google(callbacks.PluginRegexp): If option --snippet is given, returns also the page text snippet. """ opts = dict(opts) - data = self.search(text, msg.args[0], {'smallsearch': True}) + data = self.search(text, msg.channel, irc.network, + {'smallsearch': True}) data = self.decode(data) if data: url = data[0]['url'] @@ -195,13 +196,13 @@ class Google(callbacks.PluginRegexp): if 'language' in optlist and optlist['language'].lower() not in \ conf.supybot.plugins.Google.safesearch.validStrings: irc.errorInvalid('language') - data = self.search(text, msg.args[0], dict(optlist)) - bold = self.registryValue('bold', msg.args[0]) - max = self.registryValue('maximumResults', msg.args[0]) + data = self.search(text, msg.channel, irc.network, dict(optlist)) + bold = self.registryValue('bold', msg.channel, irc.network) + max = self.registryValue('maximumResults', msg.channel, irc.network) # We don't use supybot.reply.oneToOne here, because you generally # do not want @google to echo ~20 lines of results, even if you # have reply.oneToOne enabled. - onetoone = self.registryValue('oneToOne', msg.args[0]) + onetoone = self.registryValue('oneToOne', msg.channel, irc.network) for result in self.formatData(data, bold=bold, max=max, onetoone=onetoone): irc.reply(result) @@ -215,7 +216,7 @@ class Google(callbacks.PluginRegexp): Returns a link to the cached version of if it is available. """ - data = self.search(url, msg.args[0], {'smallsearch': True}) + data = self.search(url, msg.channel, irc.network, {'smallsearch': True}) if data: m = data[0] if m['cacheUrl']: @@ -233,10 +234,11 @@ class Google(callbacks.PluginRegexp): Returns the results of each search, in order, from greatest number of results to least. """ - channel = msg.args[0] + channel = msg.channel + network = irc.network results = [] for arg in args: - text = self.search(arg, channel, {'smallsearch': True}) + text = self.search(arg, channel, network, {'smallsearch': True}) i = text.find('id="resultStats"') stats = utils.web.htmlToText(self._fight_re.search(text).group('stats')) if stats == '': @@ -246,7 +248,7 @@ class Google(callbacks.PluginRegexp): results.append((int(count), arg)) results.sort() results.reverse() - if self.registryValue('bold', msg.args[0]): + if self.registryValue('bold', channel, network): bold = ircutils.bold else: bold = repr @@ -294,26 +296,26 @@ class Google(callbacks.PluginRegexp): codes (not language names), which are listed here: https://cloud.google.com/translate/docs/languages """ - channel = msg.args[0] (text, language) = self._translate(sourceLang, targetLang, text) irc.reply(text, language) translate = wrap(translate, ['something', 'to', 'something', 'text']) def googleSnarfer(self, irc, msg, match): r"^google\s+(.*)$" - if not self.registryValue('searchSnarfer', msg.args[0]): + if not self.registryValue('searchSnarfer', msg.channel, irc.network): return searchString = match.group(1) - data = self.search(searchString, msg.args[0], {'smallsearch': True}) + data = self.search(searchString, msg.channel, irc.network, + {'smallsearch': True}) if data['responseData']['results']: url = data['responseData']['results'][0]['unescapedUrl'] irc.reply(url, prefixNick=False) googleSnarfer = urlSnarfer(googleSnarfer) - def _googleUrl(self, s, channel): + def _googleUrl(self, s, channel, network): s = utils.web.urlquote_plus(s) url = r'http://%s/search?q=%s' % \ - (self.registryValue('baseUrl', channel), s) + (self.registryValue('baseUrl', channel, network), s) return url _calcRe1 = re.compile(r'(.*?)', re.I) @@ -325,10 +327,7 @@ class Google(callbacks.PluginRegexp): Uses Google's calculator to calculate the value of . """ - channel = msg.args[0] - if not irc.isChannel(channel): - channel = None - url = self._googleUrl(expr, channel) + url = self._googleUrl(expr, msg.channel, irc.network) h = {"User-Agent":"Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36"} html = utils.web.getUrl(url, headers=h).decode('utf8') match = self._calcRe1.search(html) @@ -359,10 +358,7 @@ class Google(callbacks.PluginRegexp): Looks up on Google. """ - channel = msg.args[0] - if not irc.isChannel(channel): - channel = None - url = self._googleUrl(phonenumber, channel) + url = self._googleUrl(phonenumber, msg.channel, irc.network) html = utils.web.getUrl(url).decode('utf8') m = self._phoneRe.search(html) if m is not None: diff --git a/plugins/Herald/plugin.py b/plugins/Herald/plugin.py index 058fa8158..e9c22832b 100644 --- a/plugins/Herald/plugin.py +++ b/plugins/Herald/plugin.py @@ -92,7 +92,7 @@ class Herald(callbacks.Plugin): return # Recently split. channel = msg.args[0] irc = callbacks.SimpleProxy(irc, msg) - if self.registryValue('heralding', channel): + if self.registryValue('heralding', channel, irc.network): try: id = ircdb.users.getUserId(msg.prefix) if id in self.splitters: @@ -100,22 +100,26 @@ class Herald(callbacks.Plugin): return herald = self.db[channel, id] except KeyError: - default = self.registryValue('default', channel) + default = self.registryValue('default', channel, irc.network) if default: default = ircutils.standardSubstitute(irc, msg, default) msgmaker = ircmsgs.privmsg - if self.registryValue('default.notice', channel): + if self.registryValue('default.notice', + channel, irc.network): msgmaker = ircmsgs.notice target = msg.nick - if self.registryValue('default.public', channel): + if self.registryValue('default.public', + channel, irc.network): target = channel irc.queueMsg(msgmaker(target, default)) return now = time.time() - throttle = self.registryValue('throttle', channel) + throttle = self.registryValue('throttle', + channel, irc.network) if now - self.lastHerald.get((channel, id), 0) > throttle: if (channel, id) in self.lastParts: - i = self.registryValue('throttle.afterPart', channel) + i = self.registryValue('throttle.afterPart', + channel, irc.network) if now - self.lastParts[channel, id] < i: return self.lastHerald[channel, id] = now @@ -160,7 +164,7 @@ class Herald(callbacks.Plugin): self.setRegistryValue('default', text, channel) irc.replySuccess() else: - resp = self.registryValue('default', channel) or \ + resp = self.registryValue('default', channel, irc.network) or \ _('I do not have a default herald set for %s.') % channel irc.reply(resp) default = wrap(default, ['channel', diff --git a/plugins/Karma/plugin.py b/plugins/Karma/plugin.py index b8a1363e7..aa08287cf 100644 --- a/plugins/Karma/plugin.py +++ b/plugins/Karma/plugin.py @@ -243,16 +243,16 @@ class Karma(callbacks.Plugin): return thing def _respond(self, irc, channel, thing, karma): - if self.registryValue('response', channel): + if self.registryValue('response', channel, irc.network): irc.reply(_('%(thing)s\'s karma is now %(karma)i') % {'thing': thing, 'karma': karma}) else: irc.noReply() def _doKarma(self, irc, msg, channel, thing): - inc = self.registryValue('incrementChars', channel) - dec = self.registryValue('decrementChars', channel) - onlynicks = self.registryValue('onlyNicks', channel) + inc = self.registryValue('incrementChars', channel, irc.network) + dec = self.registryValue('decrementChars', channel, irc.network) + onlynicks = self.registryValue('onlyNicks', channel, irc.network) karma = '' for s in inc: if thing.endswith(s): @@ -262,7 +262,8 @@ class Karma(callbacks.Plugin): irc.state.channels[channel].users): return if ircutils.strEqual(thing, msg.nick) and \ - not self.registryValue('allowSelfRating', channel): + not self.registryValue('allowSelfRating', + channel, irc.network): irc.error(_('You\'re not allowed to adjust your own karma.')) return self.db.increment(channel, self._normalizeThing(thing)) @@ -274,7 +275,8 @@ class Karma(callbacks.Plugin): irc.state.channels[channel].users): return if ircutils.strEqual(thing, msg.nick) and \ - not self.registryValue('allowSelfRating', channel): + not self.registryValue('allowSelfRating', + channel, irc.network): irc.error(_('You\'re not allowed to adjust your own karma.')) return self.db.decrement(channel, self._normalizeThing(thing)) @@ -283,23 +285,22 @@ class Karma(callbacks.Plugin): self._respond(irc, channel, thing, karma[0]-karma[1]) def invalidCommand(self, irc, msg, tokens): - channel = msg.args[0] - if irc.isChannel(channel) and tokens: + if msg.channel and tokens: thing = ' '.join(tokens) - self._doKarma(irc, msg, channel, thing) + self._doKarma(irc, msg, msg.channel, thing) def doPrivmsg(self, irc, msg): # We don't handle this if we've been addressed because invalidCommand # will handle it for us. This prevents us from accessing the db twice # and therefore crashing. if not (msg.addressed or msg.repliedTo): - channel = msg.args[0] - if irc.isChannel(channel) and \ + if msg.channel and \ not ircmsgs.isCtcp(msg) and \ - self.registryValue('allowUnaddressedKarma', channel): + self.registryValue('allowUnaddressedKarma', + msg.channel, irc.network): irc = callbacks.SimpleProxy(irc, msg) thing = msg.args[1].rstrip() - self._doKarma(irc, msg, channel, thing) + self._doKarma(irc, msg, msg.channel, thing) @internationalizeDocstring def karma(self, irc, msg, args, channel, things): @@ -320,7 +321,7 @@ class Karma(callbacks.Plugin): else: (added, subtracted) = t total = added - subtracted - if self.registryValue('simpleOutput', channel): + if self.registryValue('simpleOutput', channel, irc.network): s = format('%s: %i', name, total) else: s = format(_('Karma for %q has been increased %n and ' @@ -342,7 +343,7 @@ class Karma(callbacks.Plugin): irc.reply(_('I didn\'t know the karma for any of those ' 'things.')) else: # No name was given. Return the top/bottom N karmas. - limit = self.registryValue('rankingDisplay', channel) + limit = self.registryValue('rankingDisplay', channel, irc.network) highest = [format('%q (%s)', s, t) for (s, t) in self.db.top(channel, limit)] lowest = [format('%q (%s)', s, t) @@ -372,7 +373,8 @@ class Karma(callbacks.Plugin): necessary if the message isn't sent in the channel itself. """ L = self.db.most(channel, kind, - self.registryValue('mostDisplay', channel)) + self.registryValue('mostDisplay', + channel, irc.network)) if L: L = [format('%q: %i', name, i) for (name, i) in L] irc.reply(format('%L', L)) diff --git a/plugins/Lart/plugin.py b/plugins/Lart/plugin.py index 56382c219..b7660aa06 100644 --- a/plugins/Lart/plugin.py +++ b/plugins/Lart/plugin.py @@ -85,7 +85,7 @@ class Lart(plugins.ChannelIdDatabasePlugin): text = text.replace('$who', target) if reason: text += _(' for ') + reason - if self.registryValue('showIds', channel): + if self.registryValue('showIds', channel, irc.network): text += format(' (#%i)', lart.id) irc.reply(text, action=True) lart = wrap(lart, ['channeldb', optional('id'), 'text']) diff --git a/plugins/Limiter/plugin.py b/plugins/Limiter/plugin.py index d4e321a90..67da133ca 100644 --- a/plugins/Limiter/plugin.py +++ b/plugins/Limiter/plugin.py @@ -48,9 +48,9 @@ class Limiter(callbacks.Plugin): irc.noReply() def _enforceLimit(self, irc, channel): - if self.registryValue('enable', channel): - maximum = self.registryValue('maximumExcess', channel) - minimum = self.registryValue('minimumExcess', channel) + if self.registryValue('enable', channel, irc.network): + maximum = self.registryValue('maximumExcess', channel, irc.network) + minimum = self.registryValue('minimumExcess', channel, irc.network) assert maximum > minimum currentUsers = len(irc.state.channels[channel].users) currentLimit = irc.state.channels[channel].modes.get('l', 0) @@ -62,7 +62,7 @@ class Limiter(callbacks.Plugin): def doJoin(self, irc, msg): if not ircutils.strEqual(msg.nick, irc.nick): irc = callbacks.SimpleProxy(irc, msg) - self._enforceLimit(irc, msg.args[0]) + self._enforceLimit(irc, msg.channel) doPart = doJoin doKick = doJoin diff --git a/plugins/MessageParser/plugin.py b/plugins/MessageParser/plugin.py index b410f1517..a14933576 100644 --- a/plugins/MessageParser/plugin.py +++ b/plugins/MessageParser/plugin.py @@ -115,9 +115,9 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): db.isolation_level = None return db - def _updateRank(self, channel, regexp): + def _updateRank(self, network, channel, regexp): subfolder = None if channel == 'global' else channel - if self.registryValue('keepRankInfo', subfolder): + if self.registryValue('keepRankInfo', subfolder, network): db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT usage_count @@ -153,10 +153,10 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): return True def do_privmsg_notice(self, irc, msg): - channel = msg.args[0] - if not irc.isChannel(channel): + channel = msg.channel + if not channel: return - if self.registryValue('enable', channel): + if self.registryValue('enable', channel, irc.network): actions = [] results = [] for channel in set(map(plugins.getChannel, (channel, 'global'))): @@ -168,12 +168,12 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): results.extend([(channel,)+x for x in cursor.fetchall()]) if len(results) == 0: return - max_triggers = self.registryValue('maxTriggers', channel) + max_triggers = self.registryValue('maxTriggers', channel, irc.network) for (channel, regexp, action) in results: for match in re.finditer(regexp, msg.args[1]): if match is not None: thisaction = action - self._updateRank(channel, regexp) + self._updateRank(irc.network, channel, regexp) for (i, j) in enumerate(match.groups()): if match.group(i+1) is not None: thisaction = re.sub(r'\$' + str(i+1), match.group(i+1), thisaction) @@ -192,7 +192,7 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): self.do_privmsg_notice(irc, msg) def doNotice(self, irc, msg): - if self.registryValue('enableForNotices', msg.args[0]): + if self.registryValue('enableForNotices', msg.channel, irc.network): self.do_privmsg_notice(irc, msg) @internationalizeDocstring @@ -406,7 +406,7 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): return s = [ "%s: %s" % (ircutils.bold('#'+str(regexp[1])), regexp[0]) for regexp in regexps ] - separator = self.registryValue('listSeparator', channel) + separator = self.registryValue('listSeparator', channel, irc.network) irc.reply(separator.join(s)) list = wrap(list, ['channelOrGlobal']) @@ -419,7 +419,7 @@ class MessageParser(callbacks.Plugin, plugins.ChannelDBHandler): rankListLength registry value. is only necessary if the message isn't sent in the channel itself. """ - numregexps = self.registryValue('rankListLength', channel) + numregexps = self.registryValue('rankListLength', channel, irc.network) db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT regexp, usage_count diff --git a/plugins/Misc/plugin.py b/plugins/Misc/plugin.py index bfc587315..63ce7ac18 100644 --- a/plugins/Misc/plugin.py +++ b/plugins/Misc/plugin.py @@ -124,7 +124,7 @@ class Misc(callbacks.Plugin): utils.timeElapsed(punishment, seconds=False))) return # Now, for normal handling. - channel = msg.args[0] + channel = msg.channel # Only bother with the invaildCommand flood handling if it's actually # enabled if conf.supybot.abuse.flood.command.invalid(): @@ -138,7 +138,8 @@ class Misc(callbacks.Plugin): msg.prefix != irc.prefix and \ ircutils.isUserHostmask(msg.prefix): penalty = conf.supybot.abuse.flood.command.invalid.punishment() - banmask = banmasker(msg.prefix, channel=None) + banmask = banmasker(msg.prefix, channel=channel, + network=irc.network) self.log.info('Ignoring %s for %s seconds due to an apparent ' 'invalid command flood.', banmask, penalty) if tokens and tokens[0] == 'Error:': @@ -153,7 +154,8 @@ class Misc(callbacks.Plugin): utils.timeElapsed(penalty, seconds=False))) return # Now, for normal handling. - if conf.get(conf.supybot.reply.whenNotCommand, channel): + if conf.supybot.reply.whenNotCommand.getSpecific( + channel, irc.network)(): if len(tokens) >= 2: cb = irc.getCallback(tokens[0]) if cb: @@ -177,7 +179,7 @@ class Misc(callbacks.Plugin): if channel != irc.nick else _('private')) if irc.nested: bracketConfig = conf.supybot.commands.nested.brackets - brackets = conf.get(bracketConfig, channel) + brackets = bracketConfig.getSpecific(channel, irc.network)() if brackets: (left, right) = brackets irc.reply(left + ' '.join(tokens) + right) @@ -386,7 +388,7 @@ class Misc(callbacks.Plugin): return try: L = irc._mores[userHostmask] - number = self.registryValue('mores', msg.args[0]) + number = self.registryValue('mores', msg.channel, irc.network) chunks = [L.pop() for x in range(0, number)] if L: if len(L) < 2: @@ -406,7 +408,7 @@ class Misc(callbacks.Plugin): def _validLastMsg(self, irc, msg): return msg.prefix and \ msg.command == 'PRIVMSG' and \ - irc.isChannel(msg.args[0]) + msg.channel @internationalizeDocstring def last(self, irc, msg, args, optlist): @@ -423,9 +425,9 @@ class Misc(callbacks.Plugin): predicates = {} nolimit = False skipfirst = True - if irc.isChannel(msg.args[0]): + if msg.channel: predicates['in'] = lambda m: ircutils.strEqual(m.args[0], - msg.args[0]) + msg.channel) else: skipfirst = False for (option, arg) in optlist: @@ -437,7 +439,7 @@ class Misc(callbacks.Plugin): def f(m, arg=arg): return ircutils.strEqual(m.args[0], arg) predicates['in'] = f - if arg != msg.args[0]: + if arg != msg.channel: skipfirst = False elif option == 'on': def f(m, arg=arg): @@ -484,6 +486,7 @@ class Misc(callbacks.Plugin): predicates.append(userInChannel) # Make sure the user can't get messages from a +s channel unless # they're calling the command from that channel or from a query + # TODO: support statusmsg, but be careful about leaking scopes. def notSecretMsg(m): return not irc.isChannel(msg.args[0]) \ or msg.args[0] == m.args[0] \ diff --git a/plugins/MoobotFactoids/plugin.py b/plugins/MoobotFactoids/plugin.py index 8f0ce4635..b4a18e631 100755 --- a/plugins/MoobotFactoids/plugin.py +++ b/plugins/MoobotFactoids/plugin.py @@ -325,7 +325,7 @@ class MoobotFactoids(callbacks.Plugin): else: key = ' '.join(tokens) key = self._sanitizeKey(key) - channel = plugins.getChannel(msg.args[0]) + channel = plugins.getChannel(msg.channel) fact = self.db.getFactoid(channel, key) if fact: self.db.updateRequest(channel, key, msg.prefix) @@ -385,7 +385,7 @@ class MoobotFactoids(callbacks.Plugin): def addFactoid(self, irc, msg, tokens): # First, check and see if the entire message matches a factoid key - channel = plugins.getChannel(msg.args[0]) + channel = plugins.getChannel(msg.channel) id = self._getUserId(irc, msg.prefix) try: (key, fact) = self._getKeyAndFactoid(tokens) @@ -401,7 +401,7 @@ class MoobotFactoids(callbacks.Plugin): id = self._getUserId(irc, msg.prefix) (key, regexp) = list(map(' '.join, utils.iter.split('=~'.__eq__, tokens, maxsplit=1))) - channel = plugins.getChannel(msg.args[0]) + channel = plugins.getChannel(msg.channel) # Check and make sure it's in the DB fact = self._getFactoid(irc, channel, key) self._checkNotLocked(irc, channel, key) @@ -422,7 +422,7 @@ class MoobotFactoids(callbacks.Plugin): isAlso = pairs.index(['is', 'also']) key = ' '.join(tokens[:isAlso]) new_text = ' '.join(tokens[isAlso+2:]) - channel = plugins.getChannel(msg.args[0]) + channel = plugins.getChannel(msg.channel) fact = self._getFactoid(irc, channel, key) self._checkNotLocked(irc, channel, key) # It's fair game if we get to here @@ -433,7 +433,7 @@ class MoobotFactoids(callbacks.Plugin): def replaceFactoid(self, irc, msg, tokens): # Must be registered! - channel = plugins.getChannel(msg.args[0]) + channel = plugins.getChannel(msg.channel) id = self._getUserId(irc, msg.prefix) del tokens[0] # remove the "no," try: @@ -576,7 +576,7 @@ class MoobotFactoids(callbacks.Plugin): method = getattr(self, '_most%s' % method, None) if method is None: raise callbacks.ArgumentError - limit = self.registryValue('mostCount', channel) + limit = self.registryValue('mostCount', channel, irc.network) method(irc, channel, limit) most = wrap(most, ['channeldb', ('literal', ('popular', 'authored', 'recent'))]) @@ -652,7 +652,8 @@ class MoobotFactoids(callbacks.Plugin): if not results: irc.reply(format(_('No keys matching %q found.'), search)) elif len(results) == 1 and \ - self.registryValue('showFactoidIfOnlyOneMatch', channel): + self.registryValue('showFactoidIfOnlyOneMatch', + channel, irc.network): key = results[0][0] self.invalidCommand(irc, msg, [key]) else: diff --git a/plugins/Note/plugin.py b/plugins/Note/plugin.py index b89ae2a7a..6061b4ce5 100644 --- a/plugins/Note/plugin.py +++ b/plugins/Note/plugin.py @@ -186,7 +186,7 @@ class Note(callbacks.Plugin): specified by separating their names by commas. """ # Let's get the from user. - public = irc.isChannel(msg.args[0]) + public = bool(msg.channel) sent = [] for target in targets: id = self.db.send(user.id, target.id, public, text) @@ -209,7 +209,7 @@ class Note(callbacks.Plugin): 'that have been sent to you.', Raise=True) self.db.setRead(id) text += ' (in reply to #%s)' % id - public = irc.isChannel(msg.args[0]) + public = bool(msg.channel) try: target = ircdb.users.getUser(note.frm) except KeyError: @@ -271,7 +271,7 @@ class Note(callbacks.Plugin): note = wrap(note, ['user', ('id', 'note')]) def _formatNoteId(self, irc, msg, note, sent=False): - if note.public or not irc.isChannel(msg.args[0]): + if note.public or not msg.channel: if sent: sender = plugins.getUserName(note.to) return format('#%i to %s', note.id, sender) diff --git a/plugins/Owner/plugin.py b/plugins/Owner/plugin.py index 3326f9ea5..622432c67 100644 --- a/plugins/Owner/plugin.py +++ b/plugins/Owner/plugin.py @@ -258,7 +258,8 @@ class Owner(callbacks.Plugin): utils.timeElapsed(punishment, seconds=False))) return try: - tokens = callbacks.tokenize(s, channel=msg.args[0]) + tokens = callbacks.tokenize(s, channel=msg.channel, + network=irc.network) self.Proxy(irc, msg, tokens) except SyntaxError as e: irc.error(str(e)) diff --git a/plugins/Praise/plugin.py b/plugins/Praise/plugin.py index ad054bb94..18e015d13 100644 --- a/plugins/Praise/plugin.py +++ b/plugins/Praise/plugin.py @@ -84,7 +84,7 @@ class Praise(plugins.ChannelIdDatabasePlugin): text = text.replace('$who', target) if reason: text += _(' for ') + reason - if self.registryValue('showIds', channel): + if self.registryValue('showIds', channel, irc.network): text += format(' (#%i)', praise.id) irc.reply(text, action=True) praise = wrap(praise, ['channeldb', optional('id'), 'text']) diff --git a/plugins/Protector/plugin.py b/plugins/Protector/plugin.py index 026e3e26f..3f3c7c587 100644 --- a/plugins/Protector/plugin.py +++ b/plugins/Protector/plugin.py @@ -45,7 +45,7 @@ class Protector(callbacks.Plugin): if ircutils.strEqual(msg.nick, irc.nick): self.log.debug('%q is immune, it\'s me.', msg) return True # It's the bot itself. - if msg.nick in self.registryValue('immune', msg.args[0]): + if msg.nick in self.registryValue('immune', msg.channel, irc.network): self.log.debug('%q is immune, it\'s configured to be immune.', msg) return True return False @@ -78,18 +78,18 @@ class Protector(callbacks.Plugin): self.log.debug('Ignoring %q, %s.', msg, reason) if not msg.args: ignore('no msg.args') - elif not irc.isChannel(msg.args[0]): + elif not msg.channel: ignore('not on a channel') - elif not self.registryValue('enable', msg.args[0]): + elif not self.registryValue('enable', msg.channel, irc.network): ignore('supybot.plugins.Protector.enable is False.') - elif msg.args[0] not in irc.state.channels: + elif msg.channel not in irc.state.channels: # One has to wonder how this would happen, but just in case... ignore('bot isn\'t in channel') - elif irc.nick not in irc.state.channels[msg.args[0]].ops: + elif irc.nick not in irc.state.channels[msg.channel].ops: ignore('bot is not opped') - elif msg.nick not in irc.state.channels[msg.args[0]].users: + elif msg.nick not in irc.state.channels[msg.channel].users: ignore('sender is not in channel (ChanServ, maybe?)') - elif msg.nick not in irc.state.channels[msg.args[0]].ops: + elif msg.nick not in irc.state.channels[msg.channel].ops: ignore('sender is not an op in channel (IRCOP, maybe?)') elif self.isImmune(irc, msg): ignore('sender is immune') @@ -97,7 +97,7 @@ class Protector(callbacks.Plugin): super(Protector, self).__call__(irc, msg) def doMode(self, irc, msg): - channel = msg.args[0] + channel = msg.channel chanOp = ircdb.makeChannelCapability(channel, 'op') chanVoice = ircdb.makeChannelCapability(channel, 'voice') chanHalfOp = ircdb.makeChannelCapability(channel, 'halfop') @@ -134,7 +134,7 @@ class Protector(callbacks.Plugin): # Handle bans. def doKick(self, irc, msg): - channel = msg.args[0] + channel = msg.channel kicked = msg.args[1].split(',') protected = [] for nick in kicked: diff --git a/plugins/QuoteGrabs/plugin.py b/plugins/QuoteGrabs/plugin.py index 54517681f..4155c6a8c 100644 --- a/plugins/QuoteGrabs/plugin.py +++ b/plugins/QuoteGrabs/plugin.py @@ -241,14 +241,17 @@ class QuoteGrabs(callbacks.Plugin): if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): return irc = callbacks.SimpleProxy(irc, msg) - if irc.isChannel(msg.args[0]): - (chan, payload) = msg.args - words = self.registryValue('randomGrabber.minimumWords', chan) - length = self.registryValue('randomGrabber.minimumCharacters',chan) + if msg.channel: + payload = msg.args[1] + words = self.registryValue('randomGrabber.minimumWords', + msg.channel, irc.network) + length = self.registryValue('randomGrabber.minimumCharacters', + msg.channel, irc.network) grabTime = \ - self.registryValue('randomGrabber.averageTimeBetweenGrabs', chan) - channel = plugins.getChannel(chan) - if self.registryValue('randomGrabber', chan): + self.registryValue('randomGrabber.averageTimeBetweenGrabs', + msg.channel, irc.network) + channel = plugins.getChannel(msg.channel) + if self.registryValue('randomGrabber', msg.channel, irc.network): if len(payload) > length and len(payload.split()) > words: try: last = int(self.db.select(channel, msg.nick)) @@ -287,6 +290,8 @@ class QuoteGrabs(callbacks.Plugin): for m in reversed(irc.state.history): if m.command == 'PRIVMSG' and ircutils.nickEqual(m.nick, nick) \ and ircutils.strEqual(m.args[0], chan): + # TODO: strip statusmsg prefix for comparison? Must be careful + # abouk leaks, though. self._grab(channel, irc, m, msg.prefix) irc.replySuccess() return diff --git a/plugins/RSS/plugin.py b/plugins/RSS/plugin.py index 020e853bc..8a685adc0 100644 --- a/plugins/RSS/plugin.py +++ b/plugins/RSS/plugin.py @@ -352,7 +352,7 @@ class RSS(callbacks.Plugin): announced_feeds = set() for irc in world.ircs: for channel in irc.state.channels: - announced_feeds |= self.registryValue('announce', channel) + announced_feeds |= self.registryValue('announce', channel, irc.network) for name in announced_feeds: feed = self.get_feed(name) if not feed: @@ -385,14 +385,15 @@ class RSS(callbacks.Plugin): new_entries = sort_feed_items(new_entries, 'newestFirst') for irc in world.ircs: for channel in irc.state.channels: - if feed.name not in self.registryValue('announce', channel): + if feed.name not in self.registryValue('announce', + channel, irc.network): continue if initial: - max_entries = \ - self.registryValue('initialAnnounceHeadlines', channel) + max_entries = self.registryValue( + 'initialAnnounceHeadlines', channel, irc.network) else: - max_entries = \ - self.registryValue('maximumAnnounceHeadlines', channel) + max_entries = self.registryValue( + 'maximumAnnounceHeadlines', channel, irc.network) announced_entries = new_entries[0:max_entries] announced_entries = sort_feed_items(announced_entries, order) for entry in announced_entries: @@ -402,9 +403,9 @@ class RSS(callbacks.Plugin): ################# # Entry rendering - def should_send_entry(self, channel, entry): - whitelist = self.registryValue('keywordWhitelist', channel) - blacklist = self.registryValue('keywordBlacklist', channel) + def should_send_entry(self, network, channel, entry): + whitelist = self.registryValue('keywordWhitelist', channel, network) + blacklist = self.registryValue('keywordBlacklist', channel, network) # fix shadowing by "from supybot.commands import *" try: @@ -429,14 +430,15 @@ class RSS(callbacks.Plugin): _normalize_entry = utils.str.multipleReplacer( {'\r': ' ', '\n': ' ', '\x00': ''}) - def format_entry(self, channel, feed, entry, is_announce): + def format_entry(self, network, channel, feed, entry, is_announce): key_name = 'announceFormat' if is_announce else 'format' if feed.name in self.registryValue('feeds'): specific_key_name = registry.join(['feeds', feed.name, key_name]) - template = self.registryValue(specific_key_name, channel) or \ - self.registryValue(key_name, channel) + template = self.registryValue(specific_key_name, + channel, network) or \ + self.registryValue(key_name, channel, network) else: - template = self.registryValue(key_name, channel) + template = self.registryValue(key_name, channel, network) date = entry.get('published_parsed') date = utils.str.timestamp(date) s = string.Template(template).substitute( @@ -446,9 +448,9 @@ class RSS(callbacks.Plugin): return self._normalize_entry(s) def announce_entry(self, irc, channel, feed, entry): - if self.should_send_entry(channel, entry): - s = self.format_entry(channel, feed, entry, True) - if self.registryValue('notice', channel): + if self.should_send_entry(irc.network, channel, entry): + s = self.format_entry(irc.network, channel, feed, entry, True) + if self.registryValue('notice', channel, irc.network): m = ircmsgs.notice(channel, s) else: m = ircmsgs.privmsg(channel, s) @@ -559,10 +561,7 @@ class RSS(callbacks.Plugin): feed = self.get_feed(url) if not feed: feed = Feed(url, url, True) - if irc.isChannel(msg.args[0]): - channel = msg.args[0] - else: - channel = None + channel = msg.channel self.update_feed_if_needed(feed) entries = feed.entries if not entries: @@ -573,13 +572,13 @@ class RSS(callbacks.Plugin): s += str(feed.last_exception) irc.error(s) return - n = n or self.registryValue('defaultNumberOfHeadlines', channel) - entries = list(filter(lambda e:self.should_send_entry(channel, e), + n = n or self.registryValue('defaultNumberOfHeadlines', channel, irc.network) + entries = list(filter(lambda e:self.should_send_entry(irc.network, channel, e), feed.entries)) entries = entries[:n] - headlines = map(lambda e:self.format_entry(channel, feed, e, False), + headlines = map(lambda e:self.format_entry(irc.network, channel, feed, e, False), entries) - sep = self.registryValue('headlineSeparator', channel) + sep = self.registryValue('headlineSeparator', channel, irc.network) irc.replies(headlines, joiner=sep) rss = wrap(rss, [first('url', 'feedName'), additional('int')]) diff --git a/plugins/Relay/plugin.py b/plugins/Relay/plugin.py index e429e74b0..6b2675a0f 100644 --- a/plugins/Relay/plugin.py +++ b/plugins/Relay/plugin.py @@ -197,7 +197,7 @@ class Relay(callbacks.Plugin): do401 = do402 def _formatPrivmsg(self, nick, network, msg): - channel = msg.args[0] + channel = msg.channel if self.registryValue('includeNetwork', channel): network = '@' + network else: @@ -229,12 +229,12 @@ class Relay(callbacks.Plugin): assert msg.command in ('PRIVMSG', 'NOTICE', 'TOPIC') for otherIrc in world.ircs: if otherIrc != irc and not otherIrc.zombie: - if msg.args[0] in otherIrc.state.channels: + if msg.channel in otherIrc.state.channels: msg.tag('relayedMsg') otherIrc.queueMsg(msg) def _checkRelayMsg(self, msg): - channel = msg.args[0] + channel = msg.channel if channel in self.lastRelayMsgs: q = self.lastRelayMsgs[channel] unformatted = ircutils.stripFormatting(msg.args[1]) @@ -247,7 +247,7 @@ class Relay(callbacks.Plugin): def _punishRelayers(self, msg): assert self._checkRelayMsg(msg), 'Punishing without checking.' who = msg.prefix - channel = msg.args[0] + channel = msg.channel def notPunishing(irc, s, *args): self.log.info('Not punishing %s in %s on %s: %s.', msg.prefix, channel, irc.network, s, *args) @@ -268,12 +268,12 @@ class Relay(callbacks.Plugin): def doPrivmsg(self, irc, msg): if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): return - (channel, text) = msg.args - if irc.isChannel(channel): + text = msg.args[1] + if msg.channel: irc = self._getRealIrc(irc) - if channel not in self.registryValue('channels'): + if msg.channel not in self.registryValue('channels'): return - ignores = self.registryValue('ignores', channel) + ignores = self.registryValue('ignores', msg.channel, irc.network) for ignore in ignores: if ircutils.hostmaskPatternEqual(ignore, msg.prefix): self.log.debug('Refusing to relay %s, ignored by %s.', @@ -281,7 +281,8 @@ class Relay(callbacks.Plugin): return # Let's try to detect other relay bots. if self._checkRelayMsg(msg): - if self.registryValue('punishOtherRelayBots', channel): + if self.registryValue('punishOtherRelayBots', + msg.channel, irc.network): self._punishRelayers(msg) # Either way, we don't relay the message. else: @@ -291,13 +292,12 @@ class Relay(callbacks.Plugin): else: network = self._getIrcName(irc) s = self._formatPrivmsg(msg.nick, network, msg) - m = self._msgmaker(channel, s) + m = self._msgmaker(msg.channel, network, s) self._sendToOthers(irc, m) - def _msgmaker(self, target, s): + def _msgmaker(self, target, network, s): msg = dynamic.msg - channel = dynamic.channel - if self.registryValue('noticeNonPrivmsgs', dynamic.channel) and \ + if self.registryValue('noticeNonPrivmsgs', target) and \ msg.command != 'PRIVMSG': return ircmsgs.notice(target, s) else: @@ -423,16 +423,15 @@ class Relay(callbacks.Plugin): if msg.relayedMsg: self._addRelayMsg(msg) else: - channel = msg.args[0] - if channel in self.registryValue('channels'): + if msg.channel in self.registryValue('channels'): network = self._getIrcName(irc) s = self._formatPrivmsg(irc.nick, network, msg) - relayMsg = self._msgmaker(channel, s) + relayMsg = self._msgmaker(msg.args[0], s) self._sendToOthers(irc, relayMsg) return msg def _addRelayMsg(self, msg): - channel = msg.args[0] + channel = msg.channel if channel in self.lastRelayMsgs: q = self.lastRelayMsgs[channel] else: diff --git a/plugins/Seen/plugin.py b/plugins/Seen/plugin.py index 11209f3f8..ffb3ca32d 100644 --- a/plugins/Seen/plugin.py +++ b/plugins/Seen/plugin.py @@ -125,8 +125,8 @@ class Seen(callbacks.Plugin): def doPrivmsg(self, irc, msg): if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): return - if irc.isChannel(msg.args[0]): - channel = msg.args[0] + if msg.channel: + channel = msg.channel said = ircmsgs.prettyPrint(msg) self.db.update(channel, msg.nick, said) self.anydb.update(channel, msg.nick, said) @@ -184,7 +184,8 @@ class Seen(callbacks.Plugin): results = [] if '*' in name: if (len(name.replace('*', '')) < - self.registryValue('minimumNonWildcard', channel)): + self.registryValue('minimumNonWildcard', + channel, irc.network)): irc.error(_('Not enough non-wildcard characters.'), Raise=True) results = db.seenWildcard(channel, name) @@ -196,7 +197,7 @@ class Seen(callbacks.Plugin): reply = format(_('%s was last seen in %s %s ago'), nick, channel, utils.timeElapsed(time.time()-when)) - if self.registryValue('showLastMessage', channel): + if self.registryValue('showLastMessage', channel, irc.network): if minisix.PY2: said = said.decode('utf8') reply = _('%s: %s') % (reply, said) @@ -277,7 +278,7 @@ class Seen(callbacks.Plugin): (when, said) = db.seen(channel, '') reply = format(_('Someone was last seen in %s %s ago'), channel, utils.timeElapsed(time.time()-when)) - if self.registryValue('showLastMessage', channel): + if self.registryValue('showLastMessage', channel, irc.network): reply = _('%s: %s') % (reply, said) irc.reply(reply) except KeyError: @@ -304,7 +305,7 @@ class Seen(callbacks.Plugin): reply = format(_('%s was last seen in %s %s ago'), user.name, channel, utils.timeElapsed(time.time()-when)) - if self.registryValue('showLastMessage', channel): + if self.registryValue('showLastMessage', channel, irc.network): reply = _('%s: %s') % (reply, said) irc.reply(reply) except KeyError: diff --git a/plugins/Services/plugin.py b/plugins/Services/plugin.py index d268af47d..542e79a76 100644 --- a/plugins/Services/plugin.py +++ b/plugins/Services/plugin.py @@ -76,8 +76,8 @@ class Services(callbacks.Plugin): if msg.command == 'JOIN' and not self.disabled(irc): if not self.identified: if self.registryValue('noJoinsUntilIdentified'): - self.log.info('Holding JOIN to %s until identified.', - msg.args[0]) + self.log.info('Holding JOIN to %s @ %s until identified.', + msg.channel, irc.network) self.waitingJoins.setdefault(irc.network, []) self.waitingJoins[irc.network].append(msg) return None @@ -357,17 +357,17 @@ class Services(callbacks.Plugin): return chanserv = self.registryValue('ChanServ') on = 'on %s' % irc.network - if chanserv and self.registryValue('ChanServ.op', channel): + if chanserv and self.registryValue('ChanServ.op', channel, irc.network): if irc.nick not in irc.state.channels[channel].ops: self.log.info('Requesting op from %s in %s %s.', chanserv, channel, on) irc.sendMsg(ircmsgs.privmsg(chanserv, 'op %s' % channel)) - if chanserv and self.registryValue('ChanServ.halfop', channel): + if chanserv and self.registryValue('ChanServ.halfop', channel, irc.network): if irc.nick not in irc.state.channels[channel].halfops: self.log.info('Requesting halfop from %s in %s %s.', chanserv, channel, on) irc.sendMsg(ircmsgs.privmsg(chanserv, 'halfop %s' % channel)) - if chanserv and self.registryValue('ChanServ.voice', channel): + if chanserv and self.registryValue('ChanServ.voice', channel, irc.network): if irc.nick not in irc.state.channels[channel].voices: self.log.info('Requesting voice from %s in %s %s.', chanserv, channel, on) diff --git a/plugins/ShrinkUrl/plugin.py b/plugins/ShrinkUrl/plugin.py index de994fafe..7b5285173 100644 --- a/plugins/ShrinkUrl/plugin.py +++ b/plugins/ShrinkUrl/plugin.py @@ -105,15 +105,17 @@ class ShrinkUrl(callbacks.PluginRegexp): def _outFilterThread(self, irc, msg): (channel, text) = msg.args + network = irc.network for m in utils.web.httpUrlRe.finditer(text): url = m.group(1) - if len(url) > self.registryValue('minimumLength', channel): + if len(url) > self.registryValue('minimumLength', channel, network): try: cmd = self.registryValue('serviceRotation', - channel, value=False) + channel, network, value=False) cmd = cmd.getService().capitalize() except ValueError: - cmd = self.registryValue('default', channel).capitalize() + cmd = self.registryValue('default', channel, network) \ + .capitalize() try: shortUrl = getattr(self, '_get%sUrl' % cmd)(url) text = text.replace(url, shortUrl) @@ -126,39 +128,41 @@ class ShrinkUrl(callbacks.PluginRegexp): def outFilter(self, irc, msg): if msg.command != 'PRIVMSG': return msg - channel = msg.args[0] - if irc.isChannel(channel): + if msg.channel: if not msg.shrunken: - if self.registryValue('outFilter', channel): + if self.registryValue('outFilter', msg.channel, irc.network): if utils.web.httpUrlRe.search(msg.args[1]): self._outFilterThread(irc, msg) return None return msg def shrinkSnarfer(self, irc, msg, match): - channel = msg.args[0] - if not irc.isChannel(channel): + channel = msg.channel + network = irc.network + if not channel: return - if self.registryValue('shrinkSnarfer', channel): + if self.registryValue('shrinkSnarfer', channel, network): url = match.group(0) - r = self.registryValue('nonSnarfingRegexp', channel) + r = self.registryValue('nonSnarfingRegexp', channel, network) if r and r.search(url) is not None: self.log.debug('Matched nonSnarfingRegexp: %u', url) return - minlen = self.registryValue('minimumLength', channel) + minlen = self.registryValue('minimumLength', channel, network) try: cmd = self.registryValue('serviceRotation', - channel, value=False) + channel, network, value=False) cmd = cmd.getService().capitalize() except ValueError: - cmd = self.registryValue('default', channel).capitalize() + cmd = self.registryValue('default', channel, network) \ + .capitalize() if len(url) >= minlen: try: shorturl = getattr(self, '_get%sUrl' % cmd)(url) except (utils.web.Error, AttributeError, ShrinkError): self.log.info('Couldn\'t get shorturl for %u', url) return - if self.registryValue('shrinkSnarfer.showDomain', channel): + if self.registryValue('shrinkSnarfer.showDomain', + channel, network): domain = ' (at %s)' % utils.web.getDomain(url) else: domain = '' diff --git a/plugins/Status/plugin.py b/plugins/Status/plugin.py index 5ab3f333d..3ee47de71 100644 --- a/plugins/Status/plugin.py +++ b/plugins/Status/plugin.py @@ -140,9 +140,9 @@ class Status(callbacks.Plugin): """ (user, system, childUser, childSystem, elapsed) = os.times() now = time.time() - target = msg.args[0] + target = (msg.channel, irc.network) timeRunning = now - world.startedAt - if self.registryValue('cpu.children', target) and \ + if self.registryValue('cpu.children', *target) and \ user+system < timeRunning+1: # Fudge for FPU inaccuracies. children = _('My children have taken %.2f seconds of user time ' 'and %.2f seconds of system time ' @@ -154,11 +154,11 @@ class Status(callbacks.Plugin): response = _('I have taken %.2f seconds of user time and %.2f seconds ' 'of system time, for a total of %.2f seconds of CPU ' 'time. %s') % (user, system, user + system, children) - if self.registryValue('cpu.threads', target): + if self.registryValue('cpu.threads', *target): response += format('I have spawned %n; I currently have %i still ' 'running.', (world.threadsSpawned, 'thread'), activeThreads) - if self.registryValue('cpu.memory', target): + if self.registryValue('cpu.memory', *target): mem = None pid = os.getpid() plat = sys.platform diff --git a/plugins/Success/plugin.py b/plugins/Success/plugin.py index 602a90df6..efce3ae4c 100644 --- a/plugins/Success/plugin.py +++ b/plugins/Success/plugin.py @@ -50,7 +50,8 @@ class Success(plugins.ChannelIdDatabasePlugin): class MySuccessClass(self.originalClass): __slots__ = () def __call__(self): - ret = pluginSelf.db.random(dynamic.msg.args[0]) + msg = dynamic.msg + ret = pluginSelf.db.random(msg.channel or msg.args[0]) if ret is None: try: self.__class__ = pluginSelf.originalClass diff --git a/plugins/Time/plugin.py b/plugins/Time/plugin.py index a9e649c29..408710653 100644 --- a/plugins/Time/plugin.py +++ b/plugins/Time/plugin.py @@ -175,7 +175,8 @@ class Time(callbacks.Plugin): is given without , uses the format for . """ if not format: - format = self.registryValue('format', channel or msg.args[0]) + format = self.registryValue('format', channel or msg.channel, + irc.network) if tzlocal: irc.reply(datetime.fromtimestamp(seconds, tzlocal()).strftime(format)) else: @@ -209,7 +210,7 @@ class Time(callbacks.Plugin): timezone = pytz.timezone(timezone) except pytz.UnknownTimeZoneError: irc.error(_('Unknown timezone'), Raise=True) - format = self.registryValue("format", msg.args[0]) + format = self.registryValue("format", msg.channel, irc.network) irc.reply(datetime.now(timezone).strftime(format)) tztime = wrap(tztime, ['text']) diff --git a/plugins/Topic/plugin.py b/plugins/Topic/plugin.py index 76eb44dbc..184f45a11 100644 --- a/plugins/Topic/plugin.py +++ b/plugins/Topic/plugin.py @@ -64,16 +64,17 @@ def canChangeTopic(irc, msg, args, state): def getTopic(irc, msg, args, state, format=True): - separator = state.cb.registryValue('separator', state.channel) + separator = state.cb.registryValue('separator', state.channel, irc.network) if separator in args[0] and not \ - state.cb.registryValue('allowSeparatorinTopics', state.channel): + state.cb.registryValue('allowSeparatorinTopics', + state.channel, irc.network): state.errorInvalid('topic', args[0], format(_('The topic must not include %q.'), separator)) topic = args.pop(0) if format: env = {'topic': topic} - formatter = state.cb.registryValue('format', state.channel) + formatter = state.cb.registryValue('format', state.channel, irc.network) topic = ircutils.standardSubstitute(irc, msg, formatter, env) state.args.append(topic) @@ -90,7 +91,7 @@ def getTopicNumber(irc, msg, args, state): if n > 0: n -= 1 topic = irc.state.getTopic(state.channel) - separator = state.cb.registryValue('separator', state.channel) + separator = state.cb.registryValue('separator', state.channel, irc.network) topics = splitTopic(topic, separator) if not topics: state.error(format(_('There are no topics in %s.'), state.channel), @@ -162,24 +163,25 @@ class Topic(callbacks.Plugin): except (IOError, shutil.Error) as e: self.log.warning('File error: %s', e) - def _splitTopic(self, topic, channel): - separator = self.registryValue('separator', channel) + def _splitTopic(self, irc, channel): + topic = irc.state.getTopic(channel) + separator = self.registryValue('separator', channel, irc.network) return splitTopic(topic, separator) - def _joinTopic(self, channel, topics): - separator = self.registryValue('separator', channel) + def _joinTopic(self, irc, channel, topics): + separator = self.registryValue('separator', channel, irc.network) return separator.join(topics) - def _addUndo(self, channel, topics): + def _addUndo(self, irc, channel, topics): stack = self.undos.setdefault(channel, []) stack.append(topics) - maxLen = self.registryValue('undo.max', channel) + maxLen = self.registryValue('undo.max', channel, irc.network) del stack[:len(stack) - maxLen] - def _addRedo(self, channel, topics): + def _addRedo(self, irc, channel, topics): stack = self.redos.setdefault(channel, []) stack.append(topics) - maxLen = self.registryValue('undo.max', channel) + maxLen = self.registryValue('undo.max', channel, irc.network) del stack[:len(stack) - maxLen] def _getUndo(self, channel): @@ -197,16 +199,16 @@ class Topic(callbacks.Plugin): def _formatTopics(self, irc, channel, topics, fit=False): topics = [s for s in topics if s and not s.isspace()] self.lastTopics[channel] = topics - newTopic = self._joinTopic(channel, topics) + newTopic = self._joinTopic(irc, channel, topics) try: maxLen = irc.state.supported['topiclen'] if fit: while len(newTopic) > maxLen: topics.pop(0) self.lastTopics[channel] = topics - newTopic = self._joinTopic(channel, topics) + newTopic = self._joinTopic(irc, channel, topics) elif len(newTopic) > maxLen: - if self.registryValue('recognizeTopiclen', channel): + if self.registryValue('recognizeTopiclen', channel, irc.network): irc.error(format(_('That topic is too long for this ' 'server (maximum length: %i; this topic: ' '%i).'), maxLen, len(newTopic)), @@ -219,7 +221,7 @@ class Topic(callbacks.Plugin): if isinstance(topics, list) or isinstance(topics, tuple): assert topics is not None topics = self._formatTopics(irc, channel, topics, fit) - self._addUndo(channel, topics) + self._addUndo(irc, channel, topics) if not isDo and channel in self.redos: del self.redos[channel] irc.queueMsg(ircmsgs.topic(channel, topics)) @@ -238,7 +240,8 @@ class Topic(callbacks.Plugin): c = irc.state.channels[channel] if msg.nick in c.ops or msg.nick in c.halfops or 't' not in c.modes: return True - capabilities = self.registryValue('requireManageCapability', channel) + capabilities = self.registryValue('requireManageCapability', + channel, irc.network) if capabilities: for capability in re.split(r'\s*;\s*', capabilities): if capability.startswith('channel,'): @@ -247,7 +250,7 @@ class Topic(callbacks.Plugin): if capability and ircdb.checkCapability(msg.prefix, capability): return capabilities = self.registryValue('requireManageCapability', - channel) + channel, irc.network) irc.errorNoCapability(capabilities, Raise=True) else: return @@ -261,7 +264,7 @@ class Topic(callbacks.Plugin): # Try to restore the topic when not set yet. channel = msg.args[1] c = irc.state.channels.get(channel) - if c is None or not self.registryValue('setOnJoin', channel): + if c is None or not self.registryValue('setOnJoin', channel, irc.network): return if irc.nick not in c.ops and 't' in c.modes: self.log.debug('Not trying to restore topic in %s. I\'m not opped ' @@ -274,7 +277,8 @@ class Topic(callbacks.Plugin): else: newTopic = self._formatTopics(irc, channel, topics) if c.topic == '' or (c.topic != newTopic and - self.registryValue('alwaysSetOnJoin', channel)): + self.registryValue('alwaysSetOnJoin', + channel, irc.network)): self._sendTopics(irc, channel, newTopic) def do332(self, irc, msg): @@ -282,7 +286,7 @@ class Topic(callbacks.Plugin): self.watchingFor332.remove(msg.args[1]) # Store an undo for the topic when we join a channel. This allows # us to undo the first topic change that takes place in a channel. - self._addUndo(msg.args[1], [msg.args[2]]) + self._addUndo(irc, msg.args[1], [msg.args[2]]) def topic(self, irc, msg, args, channel): """[] @@ -301,7 +305,7 @@ class Topic(callbacks.Plugin): if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) - topics = self._splitTopic(irc.state.getTopic(channel), channel) + topics = self._splitTopic(irc, channel) topics.append(topic) self._sendTopics(irc, channel, topics) add = wrap(add, ['canChangeTopic', rest('topic')]) @@ -315,7 +319,7 @@ class Topic(callbacks.Plugin): itself. """ self._checkManageCapabilities(irc, msg, channel) - topics = self._splitTopic(irc.state.getTopic(channel), channel) + topics = self._splitTopic(irc, channel) topics.append(topic) self._sendTopics(irc, channel, topics, fit=True) fit = wrap(fit, ['canChangeTopic', rest('topic')]) @@ -326,7 +330,7 @@ class Topic(callbacks.Plugin): Replaces topic with . """ self._checkManageCapabilities(irc, msg, channel) - topics = self._splitTopic(irc.state.getTopic(channel), channel) + topics = self._splitTopic(irc, channel) topics[i] = topic self._sendTopics(irc, channel, topics) replace = wrap(replace, ['canChangeTopic', 'topicNumber', rest('topic')]) @@ -339,7 +343,7 @@ class Topic(callbacks.Plugin): isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) - topics = self._splitTopic(irc.state.getTopic(channel), channel) + topics = self._splitTopic(irc, channel) topics.insert(0, topic) self._sendTopics(irc, channel, topics) insert = wrap(insert, ['canChangeTopic', rest('topic')]) @@ -351,7 +355,7 @@ class Topic(callbacks.Plugin): message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) - topics = self._splitTopic(irc.state.getTopic(channel), channel) + topics = self._splitTopic(irc, channel) if len(topics) == 0 or len(topics) == 1: irc.error(_('I can\'t shuffle 1 or fewer topics.'), Raise=True) elif len(topics) == 2: @@ -372,7 +376,7 @@ class Topic(callbacks.Plugin): itself. """ self._checkManageCapabilities(irc, msg, channel) - topics = self._splitTopic(irc.state.getTopic(channel), channel) + topics = self._splitTopic(irc, channel) num = len(topics) if num == 0 or num == 1: irc.error(_('I cannot reorder 1 or fewer topics.'), Raise=True) @@ -392,7 +396,7 @@ class Topic(callbacks.Plugin): Mostly useful for topic reordering. is only necessary if the message isn't sent in the channel itself. """ - topics = self._splitTopic(irc.state.getTopic(channel), channel) + topics = self._splitTopic(irc, channel) L = [] for (i, t) in enumerate(topics): L.append(format(_('%i: %s'), i + 1, utils.str.ellipsisify(t, 30))) @@ -407,7 +411,7 @@ class Topic(callbacks.Plugin): index into the topics. is only necessary if the message isn't sent in the channel itself. """ - topics = self._splitTopic(irc.state.getTopic(channel), channel) + topics = self._splitTopic(irc, channel) irc.reply(topics[number]) get = wrap(get, ['inChannel', 'topicNumber']) @@ -421,7 +425,7 @@ class Topic(callbacks.Plugin): isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) - topics = self._splitTopic(irc.state.getTopic(channel), channel) + topics = self._splitTopic(irc, channel) topics[number] = replacer(topics[number]) self._sendTopics(irc, channel, topics) change = wrap(change, ['canChangeTopic', 'topicNumber', 'regexpReplacer']) @@ -435,7 +439,7 @@ class Topic(callbacks.Plugin): """ self._checkManageCapabilities(irc, msg, channel) if number is not None: - topics = self._splitTopic(irc.state.getTopic(channel), channel) + topics = self._splitTopic(irc, channel) topics[number] = topic else: topics = [topic] @@ -453,7 +457,7 @@ class Topic(callbacks.Plugin): necessary if the message isn't sent in the channel itself. """ self._checkManageCapabilities(irc, msg, channel) - topics = self._splitTopic(irc.state.getTopic(channel), channel) + topics = self._splitTopic(irc, channel) numbers = set(numbers) for n in numbers: # Equivalent of marking the topic for deletion; there's no @@ -534,7 +538,7 @@ class Topic(callbacks.Plugin): channel itself. """ self._checkManageCapabilities(irc, msg, channel) - self._addRedo(channel, self._getUndo(channel)) # current topic. + self._addRedo(irc, channel, self._getUndo(channel)) # current topic. topics = self._getUndo(channel) # This is the topic list we want. if topics is not None: self._sendTopics(irc, channel, topics, isDo=True) @@ -564,7 +568,7 @@ class Topic(callbacks.Plugin): itself. """ self._checkManageCapabilities(irc, msg, channel) - topics = self._splitTopic(irc.state.getTopic(channel), channel) + topics = self._splitTopic(irc, channel) if first == second: irc.error(_('I refuse to swap the same topic with itself.')) return @@ -598,7 +602,7 @@ class Topic(callbacks.Plugin): variable supybot.plugins.Topic.default. """ self._checkManageCapabilities(irc, msg, channel) - topic = self.registryValue('default', channel) + topic = self.registryValue('default', channel, irc.network) if topic: self._sendTopics(irc, channel, [topic]) else: @@ -613,7 +617,7 @@ class Topic(callbacks.Plugin): current topic appropriately. """ self._checkManageCapabilities(irc, msg, channel) - topics = self._splitTopic(irc.state.getTopic(channel), channel) + topics = self._splitTopic(irc, channel) self.setRegistryValue('separator', separator, channel) self._sendTopics(irc, channel, topics) separator = wrap(separator, ['canChangeTopic', 'something']) diff --git a/plugins/URL/plugin.py b/plugins/URL/plugin.py index e87997f4f..9ab4d8ea4 100644 --- a/plugins/URL/plugin.py +++ b/plugins/URL/plugin.py @@ -73,19 +73,19 @@ class URL(callbacks.Plugin): def doPrivmsg(self, irc, msg): if ircmsgs.isCtcp(msg) and not ircmsgs.isAction(msg): return - channel = msg.args[0] - if irc.isChannel(channel): + if msg.channel: if ircmsgs.isAction(msg): text = ircmsgs.unAction(msg) else: text = msg.args[1] for url in utils.web.urlRe.findall(text): - r = self.registryValue('nonSnarfingRegexp', channel) + r = self.registryValue('nonSnarfingRegexp', + msg.channel, irc.network) if r and r.search(url): self.log.debug('Skipping adding %u to db.', url) continue self.log.debug('Adding %u to db.', url) - self.db.add(channel, url, msg) + self.db.add(msg.channel, url, msg) @internationalizeDocstring def stats(self, irc, msg, args, channel): diff --git a/plugins/Unix/plugin.py b/plugins/Unix/plugin.py index b86b1617f..1ae297669 100644 --- a/plugins/Unix/plugin.py +++ b/plugins/Unix/plugin.py @@ -207,17 +207,18 @@ class Unix(callbacks.Plugin): Returns a fortune from the *nix fortune program. """ - channel = msg.args[0] + channel = msg.channel + network = irc.network fortuneCmd = self.registryValue('fortune.command') if fortuneCmd: args = [fortuneCmd] - if self.registryValue('fortune.short', channel): + if self.registryValue('fortune.short', channel, network): args.append('-s') - if self.registryValue('fortune.equal', channel): + if self.registryValue('fortune.equal', channel, network): args.append('-e') - if self.registryValue('fortune.offensive', channel): + if self.registryValue('fortune.offensive', channel, network): args.append('-a') - args.extend(self.registryValue('fortune.files', channel)) + args.extend(self.registryValue('fortune.files', channel, network)) try: with open(os.devnull) as null: inst = subprocess.Popen(args, diff --git a/plugins/User/plugin.py b/plugins/User/plugin.py index d8f4b0a38..d4d34303f 100644 --- a/plugins/User/plugin.py +++ b/plugins/User/plugin.py @@ -46,7 +46,7 @@ class User(callbacks.Plugin): authentication to the bot. This is a core Supybot plugin that should not be removed!""" def _checkNotChannel(self, irc, msg, password=' '): - if password and irc.isChannel(msg.args[0]): + if password and msg.channel: raise callbacks.Error(conf.supybot.replies.requiresPrivacy()) @internationalizeDocstring @@ -88,7 +88,8 @@ class User(callbacks.Plugin): users.append(u.name) if users: utils.sortBy(str.lower, users) - private = self.registryValue("listInPrivate", msg.args[0]) + private = self.registryValue("listInPrivate", + msg.channel, irc.network) irc.reply(format('%L', users), private=private) else: if predicates: diff --git a/plugins/Web/plugin.py b/plugins/Web/plugin.py index 1ac362714..6af6ea635 100644 --- a/plugins/Web/plugin.py +++ b/plugins/Web/plugin.py @@ -144,7 +144,7 @@ class Web(callbacks.PluginRegexp): threaded = True def noIgnore(self, irc, msg): - return not self.registryValue('checkIgnored', msg.args[0]) + return not self.registryValue('checkIgnored', msg.channel, irc.network) def getTitle(self, irc, url, raiseErrors): size = conf.supybot.protocols.http.peekSize() @@ -184,16 +184,17 @@ class Web(callbacks.PluginRegexp): @fetch_sandbox def titleSnarfer(self, irc, msg, match): - channel = msg.args[0] - if not irc.isChannel(channel): + channel = msg.channel + network = irc.network + if not channel: return if callbacks.addressed(irc.nick, msg): return - if self.registryValue('titleSnarfer', channel): + if self.registryValue('titleSnarfer', channel, network): url = match.group(0) if not self._checkURLWhitelist(url): return - r = self.registryValue('nonSnarfingRegexp', channel) + r = self.registryValue('nonSnarfingRegexp', channel, network) if r and r.search(url): self.log.debug('Not titleSnarfing %q.', url) return @@ -203,14 +204,15 @@ class Web(callbacks.PluginRegexp): (target, title) = r if title: domain = utils.web.getDomain(target - if self.registryValue('snarferShowTargetDomain', channel) + if self.registryValue('snarferShowTargetDomain', + channel, network) else url) - prefix = self.registryValue('snarferPrefix', channel) + prefix = self.registryValue('snarferPrefix', channel, network) s = "%s %s" % (prefix, title) - if self.registryValue('snarferShowDomain', channel): + if self.registryValue('snarferShowDomain', channel, network): s += format(_(' (at %s)'), domain) irc.reply(s, prefixNick=False) - if self.registryValue('snarfMultipleUrls', channel): + if self.registryValue('snarfMultipleUrls', channel, network): # FIXME: hack msg.tag('repliedTo', False) titleSnarfer = urlSnarfer(titleSnarfer) diff --git a/src/callbacks.py b/src/callbacks.py index e3128def6..e88147de7 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -170,11 +170,11 @@ def _makeReply(irc, msg, s, # XXX This isn't entirely right. Consider to=#foo, private=True. target = ircutils.replyTo(msg) def isPublic(s): - return irc.isChannel(irc._stripChannelPrefix(s)) + return irc.isChannel(irc.stripChannelPrefix(s)) if to is not None and isPublic(to): target = to if isPublic(target): - channel = irc._stripChannelPrefix(target) + channel = irc.stripChannelPrefix(target) else: channel = None if notice is None: @@ -376,16 +376,16 @@ class Tokenizer(object): args[-1].append(ends.pop()) return args -def tokenize(s, channel=None): +def tokenize(s, channel=None, network=None): """A utility function to create a Tokenizer and tokenize a string.""" pipe = False brackets = '' nested = conf.supybot.commands.nested if nested(): - brackets = conf.get(nested.brackets, channel) + brackets = nested.brackets.getSpecific(network, channel)() if conf.get(nested.pipeSyntax, channel): # No nesting, no pipe. pipe = True - quotes = conf.get(conf.supybot.commands.quotes, channel) + quotes = conf.supybot.commands.quotes.getSpecific(network, channel)() try: ret = Tokenizer(brackets=brackets,pipe=pipe,quotes=quotes).tokenize(s) return ret @@ -1414,8 +1414,9 @@ class PluginMixin(BasePlugin, irclib.IrcCallback): names = registry.split(name) for name in names: group = group.get(name) - if value: + if channel or network: group = group.getSpecific(network=network, channel=channel) + if value: return group() else: return group diff --git a/src/conf.py b/src/conf.py index b9cbd9fdb..a2a0e12ca 100644 --- a/src/conf.py +++ b/src/conf.py @@ -1135,7 +1135,7 @@ class Banmask(registry.SpaceSeparatedSetOfStrings): self.error() self.__parent.setValue(self.List(v)) - def makeBanmask(self, hostmask, options=None, channel=None): + def makeBanmask(self, hostmask, options=None, channel=None, network=None): """Create a banmask from the given hostmask. If a style of banmask isn't specified via options, the value of conf.supybot.protocols.irc.banmask is used. @@ -1145,13 +1145,15 @@ class Banmask(registry.SpaceSeparatedSetOfStrings): only the exact hostmask will be used.""" if not channel: channel = dynamic.channel - assert channel is None or ircutils.isChannel(channel) + if not network: + network = dynamic.irc.network (nick, user, host) = ircutils.splitHostmask(hostmask) bnick = '*' buser = '*' bhost = '*' if not options: - options = get(supybot.protocols.irc.banmask, channel) + options = supybot.protocols.irc.banmask.getSpecific( + channel, network)() for option in options: if option == 'nick': bnick = nick diff --git a/src/irclib.py b/src/irclib.py index c82552836..f38e20ff3 100644 --- a/src/irclib.py +++ b/src/irclib.py @@ -843,6 +843,7 @@ class Irc(IrcCommandDispatcher, log.Firewalled): self.queueMsg(ircmsgs.ping(now)) if msg: for callback in reversed(self.callbacks): + self._setMsgChannel(msg) msg = callback.outFilter(self, msg) if msg is None: log.debug('%s.outFilter returned None.', callback.name()) @@ -882,18 +883,20 @@ class Irc(IrcCommandDispatcher, log.Firewalled): msg.tag('receivedOn', self.network) msg.tag('receivedAt', time.time()) - # Check if the message is sent to a channel + 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) + msg.channel = self.stripChannelPrefix(msg.channel) if not self.isChannel(msg.channel): msg.channel = None else: msg.channel = None - def _stripChannelPrefix(self, channel): + def stripChannelPrefix(self, channel): statusmsg_chars = self.state.supported.get('statusmsg', '') return channel.lstrip(statusmsg_chars) From 633eea07300024a13edb5afeaab5d8dbaf41eb05 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 24 Aug 2019 23:35:46 +0200 Subject: [PATCH 06/16] Config: Fix test failure when another test set this value before. --- plugins/Config/test.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plugins/Config/test.py b/plugins/Config/test.py index b07f28da7..8c42d0f52 100644 --- a/plugins/Config/test.py +++ b/plugins/Config/test.py @@ -216,7 +216,11 @@ class ConfigTestCase(ChannelPluginTestCase): # Inherit from global value (nothing was set of current net or current # chan): - self.assertResponse('config channel reply.whenAddressedBy.strings', '^') + (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', '@') From e9bf05bbab6e2256b5127de9e7b1392096a94691 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 24 Aug 2019 23:37:23 +0200 Subject: [PATCH 07/16] Don't unregister subtree if only the child is unset. Because now, grandchildren may have set values. --- src/registry.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/registry.py b/src/registry.py index 793aa0724..ce1b59983 100644 --- a/src/registry.py +++ b/src/registry.py @@ -446,16 +446,23 @@ class Value(Group): 100) convert to an integer in set() and check that the integer is less than 100 in this method. You *must* call this parent method in your own setValue.""" + self._setValue(v, inherited=False) + + def _setValue(self, v, inherited): + """Like setValue, but accepted an extra 'inherited' argument. + inherited=True means the value is inherited from the parent, so if + the parent gets a new value, this group will get the new value as + well.""" self._lastModified = time.time() self.value = v if self._supplyDefault: - for (name, v) in list(self._children.items()): - if not v._wasSet: - self.unregister(name) + for (name, child) in list(self._children.items()): + if not child._wasSet: + child._setValue(v, inherited=True) # We call the callback once everything is clean for callback, args, kwargs in self._callbacks: callback(*args, **kwargs) - self._wasSet = True + self._wasSet = not inherited def context(self, value): """Return a context manager object, which sets this variable to a From cac3de71b1fe8b851ef75b30b52ab712cfa2f922 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sat, 24 Aug 2019 23:37:47 +0200 Subject: [PATCH 08/16] Add test for escaping registry key names. --- test/test_registry.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/test_registry.py b/test/test_registry.py index b59f02daf..6a5b5eadf 100644 --- a/test/test_registry.py +++ b/test/test_registry.py @@ -185,9 +185,17 @@ class ValuesTestCase(SupyTestCase): self.assertRaises(registry.InvalidRegistryValue, v.setValue, re.compile(r'foo')) - def testBackslashes(self): + def testBackslashesKeys(self): + conf.supybot.reply.whenAddressedBy.strings.get(':foo').set('=/*') + filename = conf.supybot.directories.conf.dirize('backslashes1.conf') + registry.close(conf.supybot, filename) + registry.open_registry(filename) + value = conf.supybot.reply.whenAddressedBy.strings.get(':foo')() + self.assertEqual(value, {'=/*'}) + + def testBackslashesValues(self): conf.supybot.reply.whenAddressedBy.chars.set('\\') - filename = conf.supybot.directories.conf.dirize('backslashes.conf') + filename = conf.supybot.directories.conf.dirize('backslashes2.conf') registry.close(conf.supybot, filename) registry.open_registry(filename) self.assertEqual(conf.supybot.reply.whenAddressedBy.chars(), '\\') From 7e1a1a23a9da75f2e1bdb6c1dc300ec2d34452e9 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sun, 25 Aug 2019 00:06:35 +0200 Subject: [PATCH 09/16] Fix fallback of Group.__getattr__ on protected attributes. --- src/registry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/registry.py b/src/registry.py index ce1b59983..b7ceb7d25 100644 --- a/src/registry.py +++ b/src/registry.py @@ -226,7 +226,8 @@ class Group(object): def __getattr__(self, attr): if attr.startswith('_'): - object.__getattr__(self, attr) + raise AttributeError('%s has no attribute %s' % + (self.__class__.__name__, attr)) elif attr in self._children: return self._children[attr] elif self._supplyDefault: From e878279e6a6c6b94a45e38f5d023547134890c2d Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sun, 25 Aug 2019 00:07:11 +0200 Subject: [PATCH 10/16] py2.6 compat for cac3de71b1fe8b851ef75b30b52ab712cfa2f922. --- test/test_registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_registry.py b/test/test_registry.py index 6a5b5eadf..5ef40458c 100644 --- a/test/test_registry.py +++ b/test/test_registry.py @@ -191,7 +191,7 @@ class ValuesTestCase(SupyTestCase): registry.close(conf.supybot, filename) registry.open_registry(filename) value = conf.supybot.reply.whenAddressedBy.strings.get(':foo')() - self.assertEqual(value, {'=/*'}) + self.assertEqual(value, set(['=/*'])) def testBackslashesValues(self): conf.supybot.reply.whenAddressedBy.chars.set('\\') From 0b5bd625ebca8f88ce6bf62ea65a2a2c0fb8a2e3 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sun, 25 Aug 2019 00:07:24 +0200 Subject: [PATCH 11/16] Add test for simple registry value inheritance. --- src/registry.py | 2 ++ test/test_registry.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/registry.py b/src/registry.py index b7ceb7d25..c37bf0312 100644 --- a/src/registry.py +++ b/src/registry.py @@ -340,6 +340,8 @@ class Value(Group): self._showDefault = showDefault self._help = utils.str.normalizeWhitespace(help.strip()) self._callbacks = [] + self._networkValue = False + self._channelValue = False if setDefault: self.setValue(default) diff --git a/test/test_registry.py b/test/test_registry.py index 5ef40458c..6274a89fb 100644 --- a/test/test_registry.py +++ b/test/test_registry.py @@ -232,4 +232,34 @@ class SecurityTestCase(SupyTestCase): self.assertFalse(g._private) self.assertTrue(g.val._private) + +class InheritanceTestCase(SupyTestCase): + def testChild(self): + parent = registry.String('foo', 'help') + parent._supplyDefault = True + self.assertTrue(parent._wasSet) + self.assertEqual(parent(), 'foo') + + child = parent.get('child') + self.assertFalse(child._wasSet) + self.assertEqual(child(), 'foo') + + parent.setValue('bar') + self.assertTrue(parent._wasSet) + self.assertEqual(parent(), 'bar') + self.assertFalse(child._wasSet) + self.assertEqual(child(), 'bar') # Takes the new parent value + + child.setValue('baz') + self.assertTrue(parent._wasSet) + self.assertEqual(parent(), 'bar') + self.assertTrue(child._wasSet) + self.assertEqual(child(), 'baz') + + parent.setValue('qux') + self.assertTrue(parent._wasSet) + self.assertEqual(parent(), 'qux') + self.assertTrue(child._wasSet) + self.assertEqual(child(), 'baz') # Keeps its own value + # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From 2f4644f7b346b1aeb565677f423475f6da8e8b3c Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sun, 25 Aug 2019 10:24:57 +0200 Subject: [PATCH 12/16] Config: Prevent accidental leak of private values on public channels. --- plugins/Config/plugin.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/Config/plugin.py b/plugins/Config/plugin.py index 89817acd3..2e015eaff 100644 --- a/plugins/Config/plugin.py +++ b/plugins/Config/plugin.py @@ -282,11 +282,11 @@ class Config(callbacks.Plugin): if private_value: private = True if len(channels) > 1: - irc.reply('; '.join([ - '%s: %s' % (channel, value) - for (channel, value) in values])) + irc.reply('; '.join(['%s: %s' % (channel, value) + for (channel, value) in values]), + private=private) else: - irc.reply(values[0][1]) + irc.reply(values[0][1], private=private) channel = wrap(channel, [optional(first(('literal', '*'), 'networkIrc')), 'channels', 'settableConfigVar', additional('text')]) From c07086d7dc51d46fc2c1501b9e96b27da0b03dc3 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sun, 25 Aug 2019 10:25:14 +0200 Subject: [PATCH 13/16] Config: Add command '@config network'. --- plugins/Config/plugin.py | 24 ++++++++++++++++++++++++ plugins/Config/test.py | 19 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/plugins/Config/plugin.py b/plugins/Config/plugin.py index 2e015eaff..29ad3c9fe 100644 --- a/plugins/Config/plugin.py +++ b/plugins/Config/plugin.py @@ -291,6 +291,30 @@ class Config(callbacks.Plugin): 'channels', 'settableConfigVar', additional('text')]) + def network(self, irc, msg, args, network, group, value): + """[] [] + + If is given, sets the network configuration variable for + to for . + Otherwise, returns the current network configuration value of . + defaults to the current network.""" + if not group._networkValue: + irc.error(_('That configuration variable is not a network-specific ' + 'configuration variable.')) + return + if value is not None: + self._setValue(irc, msg, group.get(':' + network.network), value) + + irc.replySuccess() + else: + values = [] + private = None + (value, private) = \ + self._getValue(irc, msg, group, network) + irc.reply(value, private=private) + network = wrap(network, ['networkIrc', 'settableConfigVar', + additional('text')]) + @internationalizeDocstring def config(self, irc, msg, args, group, value): """ [] diff --git a/plugins/Config/test.py b/plugins/Config/test.py index 8c42d0f52..e79cd506c 100644 --- a/plugins/Config/test.py +++ b/plugins/Config/test.py @@ -181,6 +181,25 @@ class ConfigTestCase(ChannelPluginTestCase): self.assertResponse('config channel #testchan1 reply.whenAddressedBy.strings', '.') self.assertResponse('config channel #testchan2 reply.whenAddressedBy.strings', '.') + def testNetwork(self): + getTestIrc('testnet1') + getTestIrc('testnet2') + self.assertResponse('config reply.whenAddressedBy.strings ^', + 'The operation succeeded.') + self.assertResponse('config network reply.whenAddressedBy.strings @', + 'The operation succeeded.') + self.assertResponse('config network reply.whenAddressedBy.strings', '@') + self.assertNotError('config network reply.whenAddressedBy.strings $') + self.assertResponse('config network testnet1 reply.whenAddressedBy.strings', '^') + self.assertResponse('config network testnet2 reply.whenAddressedBy.strings', '^') + self.assertResponse('config network reply.whenAddressedBy.strings', '$') + self.assertResponse('config network testnet1 reply.whenAddressedBy.strings', '^') + self.assertResponse('config network testnet2 reply.whenAddressedBy.strings', '^') + + self.assertNotError('config network testnet1 reply.whenAddressedBy.strings =') + self.assertResponse('config network testnet1 reply.whenAddressedBy.strings', '=') + self.assertResponse('config network testnet2 reply.whenAddressedBy.strings', '^') + def testChannelNetwork(self): irc = self.irc irc1 = getTestIrc('testnet1') From b65d78518cb98a0f90cfc976336b78b72ef52c02 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sun, 25 Aug 2019 14:00:11 +0200 Subject: [PATCH 14/16] Services: Add support for network-specific password. --- plugins/Config/plugin.py | 28 +++++++++----- plugins/Services/config.py | 14 +++---- plugins/Services/plugin.py | 79 ++++++++++++++++++++------------------ plugins/Services/test.py | 29 +++++++++++++- src/conf.py | 16 ++++++++ 5 files changed, 112 insertions(+), 54 deletions(-) diff --git a/plugins/Config/plugin.py b/plugins/Config/plugin.py index 29ad3c9fe..7a416d38d 100644 --- a/plugins/Config/plugin.py +++ b/plugins/Config/plugin.py @@ -205,14 +205,24 @@ class Config(callbacks.Plugin): network=network.network, channel=channel, check=False) value = str(group) or ' ' if addGlobal and not irc.nested: - value = _( - 'Global: %(global_value)s; ' - '%(channel_name)s @ %(network_name)s: %(channel_value)s') % { - 'global_value': global_value, - 'channel_name': msg.channel, - 'network_name': irc.network, - 'channel_value': value, - } + if global_group._channelValue and channel: + # TODO: also show the network value when relevant + value = _( + 'Global: %(global_value)s; ' + '%(channel_name)s @ %(network_name)s: %(channel_value)s') % { + 'global_value': global_value, + 'channel_name': msg.channel, + 'network_name': irc.network, + 'channel_value': value, + } + elif global_group._networkValue and network: + value = _( + 'Global: %(global_value)s; ' + '%(network_name)s: %(network_value)s') % { + 'global_value': global_value, + 'network_name': irc.network, + 'network_value': value, + } if hasattr(global_group, 'value'): if not global_group._private: return (value, None) @@ -330,7 +340,7 @@ class Config(callbacks.Plugin): (value, private) = self._getValue( irc, msg, group, network=irc, channel=msg.channel, - addGlobal=group._channelValue) + addGlobal=group._channelValue or group._networkValue) irc.reply(value, private=private) config = wrap(config, ['settableConfigVar', additional('text')]) diff --git a/plugins/Services/config.py b/plugins/Services/config.py index 9e53445b4..f41d91045 100644 --- a/plugins/Services/config.py +++ b/plugins/Services/config.py @@ -38,8 +38,8 @@ def registerNick(nick, password=''): p = conf.supybot.plugins.Services.Nickserv.get('password') h = _('Determines what password the bot will use with NickServ when ' \ 'identifying as %s.') % nick - v = conf.registerGlobalValue(p, nick, - registry.String(password, h, private=True)) + v = conf.registerNetworkValue(p, nick, + registry.String(password, h, private=True)) if password: v.setValue(password) @@ -65,7 +65,7 @@ class ValidNickSet(conf.ValidNicks): List = ircutils.IrcSet Services = conf.registerPlugin('Services') -conf.registerGlobalValue(Services, 'nicks', +conf.registerNetworkValue(Services, 'nicks', ValidNickSet([], _("""Determines what nicks the bot will use with services."""))) @@ -76,19 +76,19 @@ conf.registerGlobalValue(Services, 'disabledNetworks', Networks(_('QuakeNet').split(), _("""Determines what networks this plugin will be disabled on."""))) -conf.registerGlobalValue(Services, 'noJoinsUntilIdentified', +conf.registerNetworkValue(Services, 'noJoinsUntilIdentified', registry.Boolean(False, _("""Determines whether the bot will not join any channels until it is identified. This may be useful, for instances, if you have a vhost that isn't set until you're identified, or if you're joining +r channels that won't allow you to join unless you identify."""))) -conf.registerGlobalValue(Services, 'ghostDelay', +conf.registerNetworkValue(Services, 'ghostDelay', registry.NonNegativeInteger(60, _("""Determines how many seconds the bot will wait between successive GHOST attempts. Set this to 0 to disable GHOST."""))) -conf.registerGlobalValue(Services, 'NickServ', +conf.registerNetworkValue(Services, 'NickServ', ValidNickOrEmptyString('NickServ', _("""Determines what nick the 'NickServ' service has."""))) conf.registerGroup(Services.NickServ, 'password') -conf.registerGlobalValue(Services, 'ChanServ', +conf.registerNetworkValue(Services, 'ChanServ', ValidNickOrEmptyString('ChanServ', _("""Determines what nick the 'ChanServ' service has."""))) conf.registerChannelValue(Services.ChanServ, 'password', diff --git a/plugins/Services/plugin.py b/plugins/Services/plugin.py index 542e79a76..b9057d7ea 100644 --- a/plugins/Services/plugin.py +++ b/plugins/Services/plugin.py @@ -55,7 +55,7 @@ class Services(callbacks.Plugin): def __init__(self, irc): self.__parent = super(Services, self) self.__parent.__init__(irc) - for nick in self.registryValue('nicks'): + for nick in self.registryValue('nicks', network=irc.network): config.registerNick(nick) self.reset() @@ -75,7 +75,7 @@ class Services(callbacks.Plugin): def outFilter(self, irc, msg): if msg.command == 'JOIN' and not self.disabled(irc): if not self.identified: - if self.registryValue('noJoinsUntilIdentified'): + if self.registryValue('noJoinsUntilIdentified', network=irc.network): self.log.info('Holding JOIN to %s @ %s until identified.', msg.channel, irc.network) self.waitingJoins.setdefault(irc.network, []) @@ -90,25 +90,25 @@ class Services(callbacks.Plugin): else: return network_nick - def _getNickServPassword(self, nick): + def _getNickServPassword(self, nick, network): # This should later be nick-specific. - assert nick in self.registryValue('nicks') - return self.registryValue('NickServ.password.%s' % nick) + assert nick in self.registryValue('nicks', network=network) + return self.registryValue('NickServ.password.%s' % nick, network=network) - def _setNickServPassword(self, nick, password): + def _setNickServPassword(self, nick, password, network): # This also should be nick-specific. - assert nick in self.registryValue('nicks') - self.setRegistryValue('NickServ.password.%s' % nick, password) + assert nick in self.registryValue('nicks', network=network) + self.setRegistryValue('NickServ.password.%s' % nick, password, network=network) def _doIdentify(self, irc, nick=None): if self.disabled(irc): return if nick is None: nick = self._getNick(irc.network) - if nick not in self.registryValue('nicks'): + if nick not in self.registryValue('nicks', network=irc.network): return - nickserv = self.registryValue('NickServ') - password = self._getNickServPassword(nick) + nickserv = self.registryValue('NickServ', network=irc.network) + password = self._getNickServPassword(nick, irc.network) if not nickserv or not password: s = 'Tried to identify without a NickServ or password set.' self.log.warning(s) @@ -127,11 +127,11 @@ class Services(callbacks.Plugin): return if nick is None: nick = self._getNick(irc.network) - if nick not in self.registryValue('nicks'): + if nick not in self.registryValue('nicks', network=irc.network): return - nickserv = self.registryValue('NickServ') - password = self._getNickServPassword(nick) - ghostDelay = self.registryValue('ghostDelay') + nickserv = self.registryValue('NickServ', network=irc.network) + password = self._getNickServPassword(nick, irc.network) + ghostDelay = self.registryValue('ghostDelay', network=irc.network) if not ghostDelay: return if not nickserv or not password: @@ -157,11 +157,11 @@ class Services(callbacks.Plugin): if self.disabled(irc): return nick = self._getNick(irc.network) - if nick not in self.registryValue('nicks'): + if nick not in self.registryValue('nicks', network=irc.network): return - nickserv = self.registryValue('NickServ') - password = self._getNickServPassword(nick) - ghostDelay = self.registryValue('ghostDelay') + nickserv = self.registryValue('NickServ', network=irc.network) + password = self._getNickServPassword(nick, irc.network) + ghostDelay = self.registryValue('ghostDelay', network=irc.network) if not ghostDelay: return if nick and nickserv and password and \ @@ -181,13 +181,13 @@ class Services(callbacks.Plugin): if self.disabled(irc): return nick = self._getNick(irc.network) - if nick not in self.registryValue('nicks'): + if nick not in self.registryValue('nicks', network=irc.network): return - nickserv = self.registryValue('NickServ') + nickserv = self.registryValue('NickServ', network=irc.network) if not nickserv: self.log.warning('NickServ is unset, cannot identify.') return - password = self._getNickServPassword(nick) + password = self._getNickServPassword(nick, irc.network) if not password: self.log.warning('Password for %s is unset, cannot identify.',nick) return @@ -205,10 +205,10 @@ class Services(callbacks.Plugin): if self.disabled(irc): return nick = self._getNick(irc.network) - if nick not in self.registryValue('nicks'): + if nick not in self.registryValue('nicks', network=irc.network): return if nick and irc.afterConnect: - password = self._getNickServPassword(nick) + password = self._getNickServPassword(nick, irc.network) if not password: return self._doGhost(irc) @@ -232,8 +232,8 @@ class Services(callbacks.Plugin): def doNotice(self, irc, msg): if irc.afterConnect: - nickserv = self.registryValue('NickServ') - chanserv = self.registryValue('ChanServ') + nickserv = self.registryValue('NickServ', network=irc.network) + chanserv = self.registryValue('ChanServ', network=irc.network) if nickserv and ircutils.strEqual(msg.nick, nickserv): self.doNickservNotice(irc, msg) elif chanserv and ircutils.strEqual(msg.nick, chanserv): @@ -298,7 +298,7 @@ class Services(callbacks.Plugin): 'Resetting password to empty.' % on self.log.warning(log) self.sentGhost = time.time() - self._setNickServPassword(nick, '') + self._setNickServPassword(nick, '', irc.network) elif self._ghosted(irc, s): self.log.info('Received "GHOST succeeded" from NickServ %s.', on) self.sentGhost = None @@ -355,7 +355,7 @@ class Services(callbacks.Plugin): def checkPrivileges(self, irc, channel): if self.disabled(irc): return - chanserv = self.registryValue('ChanServ') + chanserv = self.registryValue('ChanServ', network=irc.network) on = 'on %s' % irc.network if chanserv and self.registryValue('ChanServ.op', channel, irc.network): if irc.nick not in irc.state.channels[channel].ops: @@ -376,7 +376,7 @@ class Services(callbacks.Plugin): def doMode(self, irc, msg): if self.disabled(irc): return - chanserv = self.registryValue('ChanServ') + chanserv = self.registryValue('ChanServ', network=irc.network) on = 'on %s' % irc.network if ircutils.strEqual(msg.nick, chanserv): channel = msg.args[0] @@ -406,7 +406,7 @@ class Services(callbacks.Plugin): self.__parent.callCommand(command, irc, msg, *args, **kwargs) def _chanservCommand(self, irc, channel, command, log=False): - chanserv = self.registryValue('ChanServ') + chanserv = self.registryValue('ChanServ', network=irc.network) if chanserv: msg = ircmsgs.privmsg(chanserv, ' '.join([command, channel])) @@ -493,7 +493,8 @@ class Services(callbacks.Plugin): invite = wrap(invite, [('checkChannelCapability', 'op'), 'inChannel']) def doInvite(self, irc, msg): - if ircutils.strEqual(msg.nick, self.registryValue('ChanServ')): + if ircutils.strEqual( + msg.nick, self.registryValue('ChanServ', etwork=irc.network)): channel = msg.args[1] on = 'on %s' % irc.network networkGroup = conf.supybot.networks.get(irc.network) @@ -506,8 +507,8 @@ class Services(callbacks.Plugin): Identifies with NickServ using the current nick. """ - if self.registryValue('NickServ'): - if irc.nick in self.registryValue('nicks'): + if self.registryValue('NickServ', network=irc.network): + if irc.nick in self.registryValue('nicks', network=irc.network): self._doIdentify(irc, irc.nick) irc.replySuccess() else: @@ -525,7 +526,7 @@ class Services(callbacks.Plugin): Ghosts the bot's given nick and takes it. If no nick is given, ghosts the bot's configured nick and takes it. """ - if self.registryValue('NickServ'): + if self.registryValue('NickServ', network=irc.network): if not nick: nick = self._getNick(irc.network) if ircutils.strEqual(nick, irc.nick): @@ -547,13 +548,17 @@ class Services(callbacks.Plugin): """ if not password: try: - self.registryValue('nicks').remove(nick) + v = self.registryValue('nicks', network=irc.network).copy() + v.remove(nick) + self.setRegistryValue('nicks', value=v, network=irc.network) irc.replySuccess() except KeyError: irc.error(_('That nick was not configured with a password.')) return else: - self.registryValue('nicks').add(nick) + v = self.registryValue('nicks', network=irc.network).copy() + v.add(nick) + self.setRegistryValue('nicks', value=v, network=irc.network) config.registerNick(nick, password) irc.replySuccess() password = wrap(password, [('checkCapability', 'admin'), @@ -566,7 +571,7 @@ class Services(callbacks.Plugin): Returns the nicks that this plugin is configured to identify and ghost with. """ - L = list(self.registryValue('nicks')) + L = list(self.registryValue('nicks', network=irc.network)) if L: utils.sortBy(ircutils.toLower, L) irc.reply(format('%L', L)) diff --git a/plugins/Services/test.py b/plugins/Services/test.py index 9033de648..dd58d6af7 100644 --- a/plugins/Services/test.py +++ b/plugins/Services/test.py @@ -30,7 +30,7 @@ from supybot.test import * class ServicesTestCase(PluginTestCase): - plugins = ('Services',) + plugins = ('Services', 'Config') config = { 'plugins.Services.NickServ': 'NickServ', 'plugins.Services.ChanServ': 'ChanServ', @@ -48,6 +48,33 @@ class ServicesTestCase(PluginTestCase): self.failUnless(m.args[0] == 'NickServ') self.failUnless(m.args[1].lower() == 'identify biff') + def testPasswordConfg(self): + self.assertNotError('config plugins.Services.nicks ""') + self.assertNotError('config network plugins.Services.nicks ""') + + self.assertNotError('services password %s bar' % self.nick) + + self.assertResponse( + 'config plugins.Services.nicks', + 'Global: ; test: %s' % self.nick) + self.assertResponse( + 'config plugins.Services.nickserv.password.%s' % self.nick, + 'Global: bar; test: bar') + + self.assertNotError( + 'config network plugins.Services.nickserv.password.%s bar2' + % self.nick) + self.assertResponse( + 'config plugins.Services.nickserv.password.%s' % self.nick, + 'Global: bar; test: bar2') + self.assertResponse( + 'config plugins.Services.nickserv.password.%s' % self.nick, + 'Global: bar; test: bar2') + + m = self.assertNotError('services identify') + self.failUnless(m.args[0] == 'NickServ') + self.failUnless(m.args[1].lower() == 'identify bar2') + # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/src/conf.py b/src/conf.py index a2a0e12ca..a1dae0736 100644 --- a/src/conf.py +++ b/src/conf.py @@ -87,6 +87,21 @@ def registerGlobalValue(group, name, value): value._channelValue = False return group.register(name, value) +def registerNetworkValue(group, name, value): + value._supplyDefault = True + value._networkValue = True + value._channelValue = False + 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) == 1 and parts[0] and ircutils.isChannel(parts[0]): + # This gets the network values so they always persist. + g.get(parts[0])() + return g + def registerChannelValue(group, name, value, opSettable=True): value._supplyDefault = True value._networkValue = True @@ -106,6 +121,7 @@ def registerChannelValue(group, name, value, opSettable=True): elif len(parts) == 1 and parts[0] and ircutils.isChannel(parts[0]): # Old-style variant of the above, without a network g.get(parts[0])() + return g def registerPlugin(name, currentValue=None, public=True): group = registerGlobalValue(supybot.plugins, name, From de9cea89cf69f80c675b81a342930979db963c65 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sun, 8 Sep 2019 19:05:13 +0200 Subject: [PATCH 15/16] ChannelLogger: Fix NameError on 'irc'. --- plugins/ChannelLogger/plugin.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/plugins/ChannelLogger/plugin.py b/plugins/ChannelLogger/plugin.py index 314b592dc..5bf853352 100644 --- a/plugins/ChannelLogger/plugin.py +++ b/plugins/ChannelLogger/plugin.py @@ -89,13 +89,13 @@ class ChannelLogger(callbacks.Plugin): if e.args[0] != 'I/O operation on a closed file': self.log.exception('Odd exception:') - def logNameTimestamp(self, channel): - format = self.registryValue('filenameTimestamp', channel, irc.network) + def logNameTimestamp(self, network, channel): + format = self.registryValue('filenameTimestamp', channel, network) return time.strftime(format) - def getLogName(self, channel): - if self.registryValue('rotateLogs', channel, irc.network): - return '%s.%s.log' % (channel, self.logNameTimestamp(channel)) + def getLogName(self, network, channel): + if self.registryValue('rotateLogs', channel, network): + return '%s.%s.log' % (channel, self.logNameTimestamp(network, channel)) else: return '%s.log' % channel @@ -119,7 +119,7 @@ class ChannelLogger(callbacks.Plugin): for (irc, logs) in self.logs.items(): for (channel, log) in list(logs.items()): if self.registryValue('rotateLogs', channel, irc.network): - name = self.getLogName(channel) + name = self.getLogName(irc.network, channel) if name != os.path.basename(log.name): log.close() del logs[channel] @@ -135,7 +135,7 @@ class ChannelLogger(callbacks.Plugin): return logs[channel] else: try: - name = self.getLogName(channel) + name = self.getLogName(irc.network, channel) logDir = self.getLogDir(irc, channel) log = open(os.path.join(logDir, name), encoding='utf-8', mode='a') logs[channel] = log @@ -168,7 +168,7 @@ class ChannelLogger(callbacks.Plugin): if minisix.PY2: s = s.decode('utf8', 'ignore') log.write(s) - if self.registryValue('flushImmediately', irc.network): + if self.registryValue('flushImmediately'): log.flush() def doPrivmsg(self, irc, msg): From 67c2bacd693759be9580c5e95271974197918e51 Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Sun, 8 Sep 2019 21:35:35 +0200 Subject: [PATCH 16/16] Set msg.channel in ReplyIrcProxy and NestedCommandsIrcProxy. Needed when plugins use a self.Proxy with a crafted message; else the called commands will assume the message was sent in private. --- src/callbacks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/callbacks.py b/src/callbacks.py index e88147de7..8a41dee67 100644 --- a/src/callbacks.py +++ b/src/callbacks.py @@ -585,6 +585,7 @@ class ReplyIrcProxy(RichReplyMethods): def __init__(self, irc, msg): self.irc = irc self.msg = msg + self.getRealIrc()._setMsgChannel(self.msg) def getRealIrc(self): """Returns the real irclib.Irc object underlying this proxy chain.""" @@ -636,8 +637,7 @@ class NestedCommandsIrcProxy(ReplyIrcProxy): _mores = ircutils.IrcDict() def __init__(self, irc, msg, args, nested=0): assert isinstance(args, list), 'Args should be a list, not a string.' - self.irc = irc - self.msg = msg + super(NestedCommandsIrcProxy, self).__init__(irc, msg) self.nested = nested self.repliedTo = False if not self.nested and isinstance(irc, self.__class__):