From d844b1d19ceea0ec516d06e6508c7030edb45637 Mon Sep 17 00:00:00 2001 From: GLolol Date: Wed, 4 Feb 2015 21:10:18 -0800 Subject: [PATCH 01/17] Admin: allow specifying a default part message in plugins.admin.partmsg (Closes ProgVal#391) Cherry-picked from commit GLolol@f69c789. --- plugins/Admin/config.py | 9 ++++++--- plugins/Admin/plugin.py | 8 ++++++-- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/plugins/Admin/config.py b/plugins/Admin/config.py index 49a018483..bab52ad1e 100644 --- a/plugins/Admin/config.py +++ b/plugins/Admin/config.py @@ -43,8 +43,11 @@ def configure(advanced): Admin = conf.registerPlugin('Admin') -# This is where your configuration variables (if any) should go. For example: -# conf.registerGlobalValue(Admin, 'someConfigVariableName', -# registry.Boolean(False, """Help for someConfigVariableName.""")) + +conf.registerChannelValue(Admin, 'partMsg', + registry.String('%version%', _("""Determines what part message should be + used by default. If the part command is called without a part message, + this will be used. If this value is empty, then no part message will + be used (they are optional in the IRC protocol)."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/Admin/plugin.py b/plugins/Admin/plugin.py index 44e5a437d..fe9281f5a 100644 --- a/plugins/Admin/plugin.py +++ b/plugins/Admin/plugin.py @@ -238,7 +238,9 @@ class Admin(callbacks.Plugin): Tells the bot to part the list of channels you give it. is only necessary if you want the bot to part a channel other than the current channel. If is specified, use it as the part - message. + message. Otherwise, the default part message specified in + supybot.plugins.Admin.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]): @@ -252,7 +254,9 @@ class Admin(callbacks.Plugin): pass if channel not in irc.state.channels: irc.error(_('I\'m not in %s.') % channel, Raise=True) - irc.queueMsg(ircmsgs.part(channel, reason or msg.nick)) + reason = (reason or self.registryValue("partMsg", channel)) + reason = reason.replace("%version%", "Supybot %s" % conf.version) + irc.queueMsg(ircmsgs.part(channel, reason)) if msg.nick in irc.state.channels[channel].users: irc.noReply() else: From 0e0feb1a8489638c8fc7dfa9a189cba6111db471 Mon Sep 17 00:00:00 2001 From: James Lu Date: Wed, 4 Feb 2015 21:20:45 -0800 Subject: [PATCH 02/17] Owner: substitute %version% for the Supybot version in plugins.Owner.quitMsg (Closes ProgVal#847) Cherry-picked from commit GLolol@205199e. --- plugins/Owner/config.py | 4 ++-- plugins/Owner/plugin.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/plugins/Owner/config.py b/plugins/Owner/config.py index 25e5d9915..02852e5b4 100644 --- a/plugins/Owner/config.py +++ b/plugins/Owner/config.py @@ -46,10 +46,10 @@ conf.registerGlobalValue(Owner, 'public', registry.Boolean(True, """Determines whether this plugin is publicly visible.""")) conf.registerGlobalValue(Owner, 'quitMsg', - registry.String('', """Determines what quit message will be used by default. + registry.String('%version%', """Determines what quit message will be used by default. If the quit command is called without a quit message, this will be used. If this value is empty, the nick of the person giving the quit command will be - used.""")) + used. %version% is automatically expanded to the bot's current version.""")) conf.registerGroup(conf.supybot.commands, 'renames', orderAlphabetically=True) diff --git a/plugins/Owner/plugin.py b/plugins/Owner/plugin.py index 8f5afc245..e3ed912ea 100644 --- a/plugins/Owner/plugin.py +++ b/plugins/Owner/plugin.py @@ -344,9 +344,11 @@ class Owner(callbacks.Plugin): Exits the bot with the QUIT message . If is not given, the default quit message (supybot.plugins.Owner.quitMsg) will be used. - If there is no default quitMsg set, your nick will be used. + If there is no default quitMsg set, your nick will be used. %version% + is automatically expanded to the bot's current version. """ text = text or self.registryValue('quitMsg') or msg.nick + text = text.replace("%version%", "Supybot %s" % conf.version) irc.noReply() m = ircmsgs.quit(text) world.upkeep() From b243c83e4127efdd2e714fb76aae3316fc6b96da Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 5 Feb 2015 20:26:14 -0800 Subject: [PATCH 03/17] Admin/Owner: Use standard substitution for part/quit messages --- plugins/Admin/config.py | 5 +++-- plugins/Admin/plugin.py | 2 +- plugins/Owner/config.py | 5 +++-- plugins/Owner/plugin.py | 6 +++--- src/ircutils.py | 3 ++- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/plugins/Admin/config.py b/plugins/Admin/config.py index bab52ad1e..86023660e 100644 --- a/plugins/Admin/config.py +++ b/plugins/Admin/config.py @@ -45,9 +45,10 @@ def configure(advanced): Admin = conf.registerPlugin('Admin') conf.registerChannelValue(Admin, 'partMsg', - registry.String('%version%', _("""Determines what part message should be + registry.String('$version', _("""Determines what part message should be used by default. If the part command is called without a part message, this will be used. If this value is empty, then no part message will - be used (they are optional in the IRC protocol)."""))) + be used (they are optional in the IRC protocol). The standard + substitutions ($version, $nick, etc.) are all handled appropriately."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/Admin/plugin.py b/plugins/Admin/plugin.py index fe9281f5a..634a0ede2 100644 --- a/plugins/Admin/plugin.py +++ b/plugins/Admin/plugin.py @@ -255,7 +255,7 @@ class Admin(callbacks.Plugin): 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.replace("%version%", "Supybot %s" % conf.version) + reason = ircutils.standardSubstitute(irc, msg, reason) irc.queueMsg(ircmsgs.part(channel, reason)) if msg.nick in irc.state.channels[channel].users: irc.noReply() diff --git a/plugins/Owner/config.py b/plugins/Owner/config.py index 02852e5b4..243e4ee07 100644 --- a/plugins/Owner/config.py +++ b/plugins/Owner/config.py @@ -46,10 +46,11 @@ conf.registerGlobalValue(Owner, 'public', registry.Boolean(True, """Determines whether this plugin is publicly visible.""")) conf.registerGlobalValue(Owner, 'quitMsg', - registry.String('%version%', """Determines what quit message will be used by default. + registry.String('$version', """Determines what quit message will be used by default. If the quit command is called without a quit message, this will be used. If this value is empty, the nick of the person giving the quit command will be - used. %version% is automatically expanded to the bot's current version.""")) + used. The standard substitutions ($version, $nick, etc.) are all handled + appropriately.""")) conf.registerGroup(conf.supybot.commands, 'renames', orderAlphabetically=True) diff --git a/plugins/Owner/plugin.py b/plugins/Owner/plugin.py index e3ed912ea..dc8de8981 100644 --- a/plugins/Owner/plugin.py +++ b/plugins/Owner/plugin.py @@ -344,11 +344,11 @@ class Owner(callbacks.Plugin): Exits the bot with the QUIT message . If is not given, the default quit message (supybot.plugins.Owner.quitMsg) will be used. - If there is no default quitMsg set, your nick will be used. %version% - is automatically expanded to the bot's current version. + If there is no default quitMsg set, your nick will be used. The standard + substitutions ($version, $nick, etc.) are all handled appropriately. """ text = text or self.registryValue('quitMsg') or msg.nick - text = text.replace("%version%", "Supybot %s" % conf.version) + text = ircutils.standardSubstitute(irc, msg, text) irc.noReply() m = ircmsgs.quit(text) world.upkeep() diff --git a/src/ircutils.py b/src/ircutils.py index 9768e676d..49caf9d06 100644 --- a/src/ircutils.py +++ b/src/ircutils.py @@ -49,7 +49,7 @@ from cStringIO import StringIO as sio from . import utils from . import minisix - +from .version import version def debug(s, *args): """Prints a debug string. Most likely replaced by our logging debug.""" @@ -713,6 +713,7 @@ def standardSubstitute(irc, msg, text, env=None): 'm': localtime[4], 'min': localtime[4], 'minute': localtime[4], 's': localtime[5], 'sec': localtime[5], 'second': localtime[5], 'tz': time.strftime('%Z', localtime), + 'version': 'Supybot %s' % version, }) if msg.reply_env: vars.update(msg.reply_env) From 0c85c6d796d7dbc16a9908108c8e60f55ae5f8eb Mon Sep 17 00:00:00 2001 From: James Lu Date: Thu, 5 Feb 2015 22:30:18 -0800 Subject: [PATCH 04/17] supybot: also expand substitutes when exiting on Ctrl-C --- scripts/supybot | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/supybot b/scripts/supybot index 00ebd9bf2..41ca10d78 100644 --- a/scripts/supybot +++ b/scripts/supybot @@ -69,6 +69,7 @@ import supybot.i18n as i18n import supybot.utils as utils import supybot.registry as registry import supybot.questions as questions +import supybot.ircutils as ircutils from supybot.version import version @@ -104,6 +105,8 @@ def main(): for irc in world.ircs: quitmsg = conf.supybot.plugins.Owner.quitMsg() or \ 'Ctrl-C at console.' + msg = ircmsgs.ping('Ctrl-C at console.') + quitmsg = ircutils.standardSubstitute(irc, msg, quitmsg) irc.queueMsg(ircmsgs.quit(quitmsg)) irc.die() except SystemExit as e: From 59b39ea45ebf4fa5f52e64d173d0af9f9077b821 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 7 Feb 2015 10:01:47 -0800 Subject: [PATCH 05/17] scripts/supybot: use None as msg for standardSubstitute on Ctrl-C --- scripts/supybot | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/supybot b/scripts/supybot index 41ca10d78..a658ea78a 100644 --- a/scripts/supybot +++ b/scripts/supybot @@ -105,8 +105,7 @@ def main(): for irc in world.ircs: quitmsg = conf.supybot.plugins.Owner.quitMsg() or \ 'Ctrl-C at console.' - msg = ircmsgs.ping('Ctrl-C at console.') - quitmsg = ircutils.standardSubstitute(irc, msg, quitmsg) + quitmsg = ircutils.standardSubstitute(irc, None, quitmsg) irc.queueMsg(ircmsgs.quit(quitmsg)) irc.die() except SystemExit as e: From a85fbead23c3b9e074f9ed2428870b312a612733 Mon Sep 17 00:00:00 2001 From: James Lu Date: Tue, 10 Feb 2015 01:38:29 -0500 Subject: [PATCH 06/17] Seen: prevent KeyError when calling commands on channels that the bot is not in This fixes a bug introdiced by 38ff1a1137. --- plugins/Seen/plugin.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/plugins/Seen/plugin.py b/plugins/Seen/plugin.py index 55e9c15c5..77fb08e2e 100644 --- a/plugins/Seen/plugin.py +++ b/plugins/Seen/plugin.py @@ -236,6 +236,8 @@ class Seen(callbacks.Plugin): if name and ircutils.strEqual(name, irc.nick): irc.reply(_("You've found me!")) return + if channel not in irc.state.channels: + irc.error(_("I'm not in %s." % channel), Raise=True) if msg.nick not in irc.state.channels[channel].users: irc.error(format('You must be in %s to use this command.', channel)) return @@ -256,6 +258,8 @@ class Seen(callbacks.Plugin): if name and ircutils.strEqual(name, irc.nick): irc.reply(_("You've found me!")) return + if channel not in irc.state.channels: + irc.error(_("I'm not in %s." % channel), Raise=True) if msg.nick not in irc.state.channels[channel].users: irc.error(format('You must be in %s to use this command.', channel)) return @@ -295,6 +299,8 @@ class Seen(callbacks.Plugin): Returns the last thing said in . is only necessary if the message isn't sent in the channel itself. """ + if channel not in irc.state.channels: + irc.error(_("I'm not in %s." % channel), Raise=True) if msg.nick not in irc.state.channels[channel].users: irc.error(format('You must be in %s to use this command.', channel)) return @@ -327,6 +333,8 @@ class Seen(callbacks.Plugin): is only necessary if the message isn't sent in the channel itself. """ + if channel not in irc.state.channels: + irc.error(_("I'm not in %s." % channel), Raise=True) if msg.nick not in irc.state.channels[channel].users: irc.error(format('You must be in %s to use this command.', channel)) return From 468d2762c1d2d9963a3e72cd78a32ff9022c530e Mon Sep 17 00:00:00 2001 From: Valentin Lorentz Date: Tue, 10 Feb 2015 09:12:26 +0100 Subject: [PATCH 07/17] Seen: Factorize code used for checks. --- plugins/Seen/plugin.py | 46 +++++++++++++++++------------------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/plugins/Seen/plugin.py b/plugins/Seen/plugin.py index 77fb08e2e..da762b728 100644 --- a/plugins/Seen/plugin.py +++ b/plugins/Seen/plugin.py @@ -225,6 +225,17 @@ class Seen(callbacks.Plugin): except KeyError: irc.reply(format(_('I have not seen %s.'), name)) + def _checkChannelPresence(self, irc, channel, target, you): + if channel not in irc.state.channels: + irc.error(_("I'm not in %s." % channel), Raise=True) + if target not in irc.state.channels[channel].users: + if you: + msg = format(_('You must be in %s to use this command.'), channel) + else: + msg = format(_('%s must be in %s to use this command.'), + target, channel) + irc.error(msg, Raise=True) + @internationalizeDocstring def seen(self, irc, msg, args, channel, name): """[] @@ -236,11 +247,7 @@ class Seen(callbacks.Plugin): if name and ircutils.strEqual(name, irc.nick): irc.reply(_("You've found me!")) return - if channel not in irc.state.channels: - irc.error(_("I'm not in %s." % channel), Raise=True) - if msg.nick not in irc.state.channels[channel].users: - irc.error(format('You must be in %s to use this command.', channel)) - return + self._checkChannelPresence(irc, channel, msg.nick, True) self._seen(irc, channel, name) seen = wrap(seen, ['channel', 'something']) @@ -258,11 +265,7 @@ class Seen(callbacks.Plugin): if name and ircutils.strEqual(name, irc.nick): irc.reply(_("You've found me!")) return - if channel not in irc.state.channels: - irc.error(_("I'm not in %s." % channel), Raise=True) - if msg.nick not in irc.state.channels[channel].users: - irc.error(format('You must be in %s to use this command.', channel)) - return + self._checkChannelPresence(irc, channel, msg.nick, True) if name and optlist: raise callbacks.ArgumentError elif name: @@ -299,11 +302,7 @@ class Seen(callbacks.Plugin): Returns the last thing said in . is only necessary if the message isn't sent in the channel itself. """ - if channel not in irc.state.channels: - irc.error(_("I'm not in %s." % channel), Raise=True) - if msg.nick not in irc.state.channels[channel].users: - irc.error(format('You must be in %s to use this command.', channel)) - return + self._checkChannelPresence(irc, channel, msg.nick, True) self._last(irc, channel) last = wrap(last, ['channel']) @@ -333,11 +332,7 @@ class Seen(callbacks.Plugin): is only necessary if the message isn't sent in the channel itself. """ - if channel not in irc.state.channels: - irc.error(_("I'm not in %s." % channel), Raise=True) - if msg.nick not in irc.state.channels[channel].users: - irc.error(format('You must be in %s to use this command.', channel)) - return + self._checkChannelPresence(irc, channel, msg.nick, True) self._user(irc, channel, user) user = wrap(user, ['channel', 'otherUser']) @@ -351,13 +346,10 @@ class Seen(callbacks.Plugin): """ if nick is None: nick = msg.nick - if channel not in irc.state.channels: - irc.error(_('I am not in %s.') % channel) - return - if nick not in irc.state.channels[channel].users: - irc.error(format(_('%s must be in %s to use this command.'), - ('You' if nick == msg.nick else nick), channel)) - return + you = True + else: + you = False + self._checkChannelPresence(irc, channel, nick, you) if nick is None: nick = msg.nick end = None # By default, up until the most recent message. From 75288193ad893f2e7e9c9644bafc96fa35312068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20L=C3=B6thberg?= Date: Sat, 24 Jan 2015 19:38:54 +0100 Subject: [PATCH 08/17] QuoteGrabs: Add say command The say command just returns the text of the grab, so you can get just the quote from a specific qid. --- plugins/QuoteGrabs/plugin.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/plugins/QuoteGrabs/plugin.py b/plugins/QuoteGrabs/plugin.py index 8eb43d968..00f444e6b 100644 --- a/plugins/QuoteGrabs/plugin.py +++ b/plugins/QuoteGrabs/plugin.py @@ -61,8 +61,11 @@ class QuoteGrabsRecord(dbi.Record): def __str__(self): grabber = plugins.getUserName(self.grabber) - return format(_('%s (Said by: %s; grabbed by %s at %t)'), - self.text, self.hostmask, grabber, self.at) + if self.at: + return format(_('%s (Said by: %s; grabbed by %s at %t)'), + self.text, self.hostmask, grabber, self.at) + else: + return format('%s', self.text) class SqliteQuoteGrabsDB(object): def __init__(self, filename): @@ -105,7 +108,7 @@ class SqliteQuoteGrabsDB(object): db.commit() return db - def get(self, channel, id): + def get(self, channel, id, quoteonly = 0): db = self._getDb(channel) cursor = db.cursor() cursor.execute("""SELECT id, nick, quote, hostmask, added_at, added_by @@ -114,8 +117,11 @@ class SqliteQuoteGrabsDB(object): if len(results) == 0: raise dbi.NoRecordError (id, by, quote, hostmask, at, grabber) = results[0] - return QuoteGrabsRecord(id, by=by, text=quote, hostmask=hostmask, - at=int(at), grabber=grabber) + if quoteonly is 0: + return QuoteGrabsRecord(id, by=by, text=quote, hostmask=hostmask, + at=int(at), grabber=grabber) + else: + return QuoteGrabsRecord(id, text=quote) def random(self, channel, nick): db = self._getDb(channel) @@ -359,6 +365,20 @@ class QuoteGrabs(callbacks.Plugin): 'grabbed quotes in the database?')) random = wrap(random, ['channeldb', additional('nick')]) + @internationalizeDocstring + def say(self, irc, msg, args, channel, id): + """[] + + Return the quotegrab with the given . is only necessary + if the message isn't sent in the channel itself. + """ + try: + irc.reply(self.db.get(channel, id, 1)) + except dbi.NoRecordError: + irc.error(_('No quotegrab for id %s') % utils.str.quoted(id), + Raise=True) + say = wrap(say, ['channeldb', 'id']) + @internationalizeDocstring def get(self, irc, msg, args, channel, id): """[] From 41effcea13b4a5831dd80835f47829027b39f19f Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 20 Feb 2015 16:50:40 -0800 Subject: [PATCH 09/17] Network.whois: simplify message when user isn't on any public channels The current message ("xyz isn't on any non-secret channels or is using a channel-list hiding umode.") is long and awkwardly worded. This also removes an extra period from showing up at the end of the output. --- plugins/Network/plugin.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/Network/plugin.py b/plugins/Network/plugin.py index aeaaa8636..e060fcf23 100644 --- a/plugins/Network/plugin.py +++ b/plugins/Network/plugin.py @@ -243,8 +243,7 @@ class Network(callbacks.Plugin): L.append(format(_('is on %L'), normal)) else: if command == 'whois': - L = [_('isn\'t on any non-secret channels or is using a ' - 'channel-list hiding umode.')] + L = [_('isn\'t on any publically visible channels')] else: L = [] channels = format('%L', L) From 5f23855d278ad4d5527c90cc4000b2c2eddef971 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 20 Feb 2015 22:20:49 -0800 Subject: [PATCH 10/17] oops how do i english --- plugins/Network/plugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Network/plugin.py b/plugins/Network/plugin.py index e060fcf23..548039e95 100644 --- a/plugins/Network/plugin.py +++ b/plugins/Network/plugin.py @@ -243,7 +243,7 @@ class Network(callbacks.Plugin): L.append(format(_('is on %L'), normal)) else: if command == 'whois': - L = [_('isn\'t on any publically visible channels')] + L = [_('isn\'t on any publicly visible channels')] else: L = [] channels = format('%L', L) From 1654bfca0c59212987fb89529f0b27a9f192bd3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20L=C3=B6thberg?= Date: Sat, 21 Feb 2015 19:59:47 +0100 Subject: [PATCH 11/17] dictclient: Encode as utf-8 instead of ASCII dict.org has dictionaries with words containing non-ASCII characters. --- plugins/Dict/local/dictclient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/Dict/local/dictclient.py b/plugins/Dict/local/dictclient.py index c6f48f4f6..c5540cf6e 100644 --- a/plugins/Dict/local/dictclient.py +++ b/plugins/Dict/local/dictclient.py @@ -165,7 +165,7 @@ class Connection: def sendcommand(self, command): """Takes a command, without a newline character, and sends it to the server.""" - self.wfile.write(command.encode('ascii') + b"\n") + self.wfile.write(command.encode('utf-8') + b"\n") def define(self, database, word): """Returns a list of Definition objects for each matching From d4aa4362e5b0a041463b91340ab30f2350d235ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20L=C3=B6thberg?= Date: Sat, 21 Feb 2015 20:03:29 +0100 Subject: [PATCH 12/17] dictclient: Remove whitespace at end of lines --- plugins/Dict/local/dictclient.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/Dict/local/dictclient.py b/plugins/Dict/local/dictclient.py index c5540cf6e..a7e1f5798 100644 --- a/plugins/Dict/local/dictclient.py +++ b/plugins/Dict/local/dictclient.py @@ -111,7 +111,7 @@ class Connection: capstr, msgid = re.search('<(.*)> (<.*>)$', string).groups() self.capabilities = capstr.split('.') self.messageid = msgid - + def getcapabilities(self): """Returns a list of the capabilities advertised by the server.""" return self.capabilities @@ -126,7 +126,7 @@ class Connection: network traffic!""" if hasattr(self, 'dbdescs'): return self.dbdescs - + self.sendcommand("SHOW DB") self.dbdescs = self.get100dict() return self.dbdescs @@ -182,7 +182,7 @@ class Connection: if database != '*' and database != '!' and \ not database in self.getdbdescs(): raise Exception("Invalid database '%s' specified" % database) - + self.sendcommand("DEFINE " + enquote(database) + " " + enquote(word)) code = self.getresultcode()[0] @@ -249,11 +249,11 @@ class Database: a database name.""" self.conn = dictconn self.name = dbname - + def getname(self): """Returns the short name for this database.""" return self.name - + def getdescription(self): if hasattr(self, 'description'): return self.description @@ -264,7 +264,7 @@ class Database: else: self.description = self.conn.getdbdescs()[self.getname()] return self.description - + def getinfo(self): """Returns a string of info describing this database.""" if hasattr(self, 'info'): From a67fb94875cb48d3c5a9c90861e60d4aedbbbb02 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 27 Feb 2015 18:11:10 -0800 Subject: [PATCH 13/17] Move 'part' command from Admin to Channel and require #channel,op instead of admin Closes #79. --- plugins/Admin/config.py | 4 ---- plugins/Admin/plugin.py | 27 --------------------------- plugins/Admin/test.py | 21 --------------------- plugins/Channel/config.py | 7 ++++++- plugins/Channel/plugin.py | 35 +++++++++++++++++++++++++++++++++++ plugins/Channel/test.py | 23 ++++++++++++++++++++++- 6 files changed, 63 insertions(+), 54 deletions(-) diff --git a/plugins/Admin/config.py b/plugins/Admin/config.py index 49a018483..0a6f64211 100644 --- a/plugins/Admin/config.py +++ b/plugins/Admin/config.py @@ -41,10 +41,6 @@ def configure(advanced): from supybot.questions import expect, anything, something, yn conf.registerPlugin('Admin', True) - Admin = conf.registerPlugin('Admin') -# This is where your configuration variables (if any) should go. For example: -# conf.registerGlobalValue(Admin, 'someConfigVariableName', -# registry.Boolean(False, """Help for someConfigVariableName.""")) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/Admin/plugin.py b/plugins/Admin/plugin.py index 44e5a437d..cd18a6a84 100644 --- a/plugins/Admin/plugin.py +++ b/plugins/Admin/plugin.py @@ -232,33 +232,6 @@ class Admin(callbacks.Plugin): nick = wrap(nick, [additional('nick'), additional('something')]) @internationalizeDocstring - def part(self, irc, msg, args, channel, reason): - """[] [] - - Tells the bot to part the list of channels you give it. is - only necessary if you want the bot to part a channel other than the - current channel. If is specified, use it as the part - message. - """ - if channel is None: - if irc.isChannel(msg.args[0]): - channel = msg.args[0] - else: - irc.error(Raise=True) - try: - network = conf.supybot.networks.get(irc.network) - network.channels().remove(channel) - except KeyError: - pass - if channel not in irc.state.channels: - irc.error(_('I\'m not in %s.') % channel, Raise=True) - irc.queueMsg(ircmsgs.part(channel, reason or msg.nick)) - if msg.nick in irc.state.channels[channel].users: - irc.noReply() - else: - irc.replySuccess() - part = wrap(part, [optional('validChannel'), additional('text')]) - class capability(callbacks.Commands): @internationalizeDocstring diff --git a/plugins/Admin/test.py b/plugins/Admin/test.py index 5f94faf6f..6c090daef 100644 --- a/plugins/Admin/test.py +++ b/plugins/Admin/test.py @@ -94,27 +94,6 @@ class AdminTestCase(PluginTestCase): self.assertEqual(m.args[0], '#foo') self.assertEqual(m.args[1], 'key') - def testPart(self): - def getAfterJoinMessages(): - m = self.irc.takeMsg() - self.assertEqual(m.command, 'MODE') - m = self.irc.takeMsg() - self.assertEqual(m.command, 'MODE') - m = self.irc.takeMsg() - self.assertEqual(m.command, 'WHO') - self.assertError('part #foo') - self.assertRegexp('part #foo', 'not in') - self.irc.feedMsg(ircmsgs.join('#foo', prefix=self.prefix)) - getAfterJoinMessages() - m = self.getMsg('part #foo') - self.assertEqual(m.command, 'PART') - self.irc.feedMsg(ircmsgs.join('#foo', prefix=self.prefix)) - getAfterJoinMessages() - m = self.getMsg('part #foo reason') - self.assertEqual(m.command, 'PART') - self.assertEqual(m.args[0], '#foo') - self.assertEqual(m.args[1], 'reason') - def testNick(self): original = conf.supybot.nick() try: diff --git a/plugins/Channel/config.py b/plugins/Channel/config.py index 19f7462be..bccdb28f0 100644 --- a/plugins/Channel/config.py +++ b/plugins/Channel/config.py @@ -51,6 +51,11 @@ conf.registerChannelValue(Channel, 'nicksInPrivate', registry.Boolean(True, _("""Determines whether the output of 'nicks' will be sent in private. This prevents mass-highlights of a channel's users, accidental or on purpose."""))) - +conf.registerChannelValue(Channel, 'partMsg', + registry.String('$version', _("""Determines what part message should be + used by default. If the part command is called without a part message, + this will be used. If this value is empty, then no part message will + be used (they are optional in the IRC protocol). The standard + substitutions ($version, $nick, etc.) are all handled appropriately."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/Channel/plugin.py b/plugins/Channel/plugin.py index 75834daa2..1450fdfb6 100644 --- a/plugins/Channel/plugin.py +++ b/plugins/Channel/plugin.py @@ -941,6 +941,41 @@ class Channel(callbacks.Plugin): self.alertOps(irc, channel, text, frm=msg.nick) alert = wrap(alert, ['inChannel', 'text']) + @internationalizeDocstring + def part(self, irc, msg, args, channel, reason): + """[] [] + + Tells the bot to part the list of channels you give it. is + only necessary if you want the bot to part a channel other than the + current channel. If is specified, use it as the part + message. Otherwise, the default part message specified in + 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) + capability = ircdb.makeChannelCapability(channel, 'op') + hostmask = irc.state.nickToHostmask(msg.nick) + if not ircdb.checkCapability(hostmask, capability): + irc.errorNoCapability(capability, Raise=True) + try: + network = conf.supybot.networks.get(irc.network) + network.channels().remove(channel) + except KeyError: + 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 = ircutils.standardSubstitute(irc, msg, reason) + irc.queueMsg(ircmsgs.part(channel, reason)) + if msg.nick in irc.state.channels[channel].users: + irc.noReply() + else: + irc.replySuccess() + part = wrap(part, [optional('validChannel'), additional('text')]) Class = Channel diff --git a/plugins/Channel/test.py b/plugins/Channel/test.py index 6a8e4568e..2ac9bc148 100644 --- a/plugins/Channel/test.py +++ b/plugins/Channel/test.py @@ -251,6 +251,27 @@ class ChannelTestCase(ChannelPluginTestCase): def testNicks(self): self.assertResponse('channel nicks', 'bar, foo, and test') self.assertResponse('channel nicks --count', '3') - + + def testPart(self): + def getAfterJoinMessages(): + m = self.irc.takeMsg() + self.assertEqual(m.command, 'MODE') + m = self.irc.takeMsg() + self.assertEqual(m.command, 'MODE') + m = self.irc.takeMsg() + self.assertEqual(m.command, 'WHO') + self.assertError('part #foo') + self.assertRegexp('part #foo', 'not in') + self.irc.feedMsg(ircmsgs.join('#foo', prefix=self.prefix)) + getAfterJoinMessages() + m = self.getMsg('part #foo') + self.assertEqual(m.command, 'PART') + self.irc.feedMsg(ircmsgs.join('#foo', prefix=self.prefix)) + getAfterJoinMessages() + m = self.getMsg('part #foo reason') + self.assertEqual(m.command, 'PART') + self.assertEqual(m.args[0], '#foo') + self.assertEqual(m.args[1], 'reason') + # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: From 32988ee3a3d59f723d850485196cc9ff9e28baa1 Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 27 Feb 2015 18:27:58 -0800 Subject: [PATCH 14/17] Channel.cycle: allow specifying part message + defaults to plugins.Channel.partMsg Closes #1062. --- plugins/Channel/plugin.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/plugins/Channel/plugin.py b/plugins/Channel/plugin.py index 1450fdfb6..d4e425013 100644 --- a/plugins/Channel/plugin.py +++ b/plugins/Channel/plugin.py @@ -258,17 +258,22 @@ class Channel(callbacks.Plugin): any('nickInChannel')]) @internationalizeDocstring - def cycle(self, irc, msg, args, channel): + def cycle(self, irc, msg, args, channel, reason): """[] If you have the #channel,op capability, this will cause the bot to "cycle", or PART and then JOIN the channel. is only necessary - if the message isn't sent in the channel itself. + if the message isn't sent in the channel itself. If is not + specified, the default part message specified in + 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. """ - self._sendMsg(irc, ircmsgs.part(channel, msg.nick)) + reason = (reason or self.registryValue("partMsg", channel)) + reason = ircutils.standardSubstitute(irc, msg, reason) + self._sendMsg(irc, ircmsgs.part(channel, reason)) networkGroup = conf.supybot.networks.get(irc.network) self._sendMsg(irc, networkGroup.channels.join(channel)) - cycle = wrap(cycle, ['op']) + cycle = wrap(cycle, ['op', additional('text')]) @internationalizeDocstring def kick(self, irc, msg, args, channel, nicks, reason): From f3107a928f0459aa477a06ca72da2c659cccd68c Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 27 Feb 2015 18:45:37 -0800 Subject: [PATCH 15/17] Channel: allow adding an auto-rejoin delay via supybot.plugins.Channel.rejoinDelay Closes #1011. --- plugins/Channel/config.py | 5 ++++- plugins/Channel/plugin.py | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/plugins/Channel/config.py b/plugins/Channel/config.py index 19f7462be..8d57a5f13 100644 --- a/plugins/Channel/config.py +++ b/plugins/Channel/config.py @@ -51,6 +51,9 @@ conf.registerChannelValue(Channel, 'nicksInPrivate', registry.Boolean(True, _("""Determines whether the output of 'nicks' will be sent in private. This prevents mass-highlights of a channel's users, accidental or on purpose."""))) - +conf.registerChannelValue(Channel, 'rejoinDelay', + registry.NonNegativeInteger(0, _("""Determines how many seconds the bot will wait + before rejoining a channel if kicked and + supybot.plugins.Channel.alwaysRejoin is on."""))) # vim:set shiftwidth=4 softtabstop=4 expandtab textwidth=79: diff --git a/plugins/Channel/plugin.py b/plugins/Channel/plugin.py index 75834daa2..1f170362e 100644 --- a/plugins/Channel/plugin.py +++ b/plugins/Channel/plugin.py @@ -30,6 +30,7 @@ import sys import fnmatch +import time import supybot.conf as conf import supybot.ircdb as ircdb @@ -46,6 +47,7 @@ class Channel(callbacks.Plugin): """This plugin provides various commands for channel management, such as setting modes and channel-wide bans/ignores/capabilities. This is a core Supybot plugin that should not be removed!""" + def __init__(self, irc): self.__parent = super(Channel, self) self.__parent.__init__(irc) @@ -55,10 +57,18 @@ class Channel(callbacks.Plugin): channel = msg.args[0] if msg.args[1] == irc.nick: if self.registryValue('alwaysRejoin', channel): - self.log.info('Kicked from %s by %s. Rejoining.' % - (channel, msg.prefix)) + delay = self.registryValue('rejoinDelay', channel) networkGroup = conf.supybot.networks.get(irc.network) - irc.sendMsg(networkGroup.channels.join(channel)) + 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) + else: + self.log.info('Kicked from %s by %s. Rejoining.', + channel, msg.prefix) + irc.sendMsg(networkGroup.channels.join(channel)) else: self.log.info('Kicked from %s by %s. Not auto-rejoining.' % (channel, msg.prefix)) From aba37ff9517e70d162290f4f5f31086aad153213 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 28 Feb 2015 00:13:06 -0800 Subject: [PATCH 16/17] conf.py: Remove reference to non-existent? "user configuration variable" Closes #654. --- src/conf.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/conf.py b/src/conf.py index 84d65bc52..92385713f 100644 --- a/src/conf.py +++ b/src/conf.py @@ -470,8 +470,7 @@ registerChannelValue(supybot.reply, 'withNotice', registerGlobalValue(supybot.reply, 'withNoticeWhenPrivate', registry.Boolean(True, _("""Determines whether the bot will reply with a notice when it is sending a private message, in order not to open a /query - window in clients. This can be overridden by individual users via the user - configuration variable reply.withNoticeWhenPrivate."""))) + window in clients."""))) registerChannelValue(supybot.reply, 'withNickPrefix', registry.Boolean(True, _("""Determines whether the bot will always prefix From dae81168d3eb3518b5cfafadd734bc4ce00a64f6 Mon Sep 17 00:00:00 2001 From: James Lu Date: Sat, 28 Feb 2015 09:52:42 -0800 Subject: [PATCH 17/17] ShrinkUrl: Remove ln (Closes #618). ln-s.net has been dead for over half a year now; both their website homepage and any previous shortened links return an HTTP 503. Thus, it isn't very useful to leave this broken command lingering around... --- plugins/ShrinkUrl/config.py | 6 +++--- plugins/ShrinkUrl/plugin.py | 31 ------------------------------- plugins/ShrinkUrl/test.py | 13 +++---------- 3 files changed, 6 insertions(+), 44 deletions(-) diff --git a/plugins/ShrinkUrl/config.py b/plugins/ShrinkUrl/config.py index 08e651f96..11b7672d0 100644 --- a/plugins/ShrinkUrl/config.py +++ b/plugins/ShrinkUrl/config.py @@ -42,8 +42,8 @@ def configure(advanced): conf.supybot.plugins.ShrinkUrl.shrinkSnarfer.setValue(True) class ShrinkService(registry.OnlySomeStrings): - """Valid values include 'ln', 'tiny', 'goo', 'ur1', and 'x0'.""" - validStrings = ('ln', 'tiny', 'goo', 'ur1', 'x0') + """Valid values include 'tiny', 'goo', 'ur1', and 'x0'.""" + validStrings = ('tiny', 'goo', 'ur1', 'x0') class ShrinkCycle(registry.SpaceSeparatedListOfStrings): """Valid values include 'ln', 'tiny', 'goo', 'ur1', and 'x0'.""" @@ -71,7 +71,7 @@ conf.registerChannelValue(ShrinkUrl, 'shrinkSnarfer', shrink snarfer is enabled. This snarfer will watch for URLs in the channel, and if they're sufficiently long (as determined by supybot.plugins.ShrinkUrl.minimumLength) it will post a - smaller URL from either ln-s.net or tinyurl.com, as denoted in + smaller URL from tinyurl.com, as denoted in supybot.plugins.ShrinkUrl.default."""))) conf.registerChannelValue(ShrinkUrl.shrinkSnarfer, 'showDomain', registry.Boolean(True, _("""Determines whether the snarfer will show the diff --git a/plugins/ShrinkUrl/plugin.py b/plugins/ShrinkUrl/plugin.py index 0e6f76de4..16e38a08b 100644 --- a/plugins/ShrinkUrl/plugin.py +++ b/plugins/ShrinkUrl/plugin.py @@ -171,37 +171,6 @@ class ShrinkUrl(callbacks.PluginRegexp): shrinkSnarfer = urlSnarfer(shrinkSnarfer) shrinkSnarfer.__doc__ = utils.web._httpUrlRe - @retry - def _getLnUrl(self, url): - url = utils.web.urlquote(url) - try: - return self.db.get('ln', url) - except KeyError: - text = utils.web.getUrl('http://ln-s.net/home/api.jsp?url=' + url) - text = text.decode() - (code, text) = text.split(None, 1) - text = text.strip() - if code == '200': - self.db.set('ln', url, text) - return text - else: - raise ShrinkError(text) - - @internationalizeDocstring - def ln(self, irc, msg, args, url): - """ - - Returns an ln-s.net version of . - """ - try: - lnurl = self._getLnUrl(url) - m = irc.reply(lnurl) - if m is not None: - m.tag('shrunken') - except ShrinkError as e: - irc.error(str(e)) - ln = thread(wrap(ln, ['httpUrl'])) - @retry def _getTinyUrl(self, url): try: diff --git a/plugins/ShrinkUrl/test.py b/plugins/ShrinkUrl/test.py index b8df89d35..9003415ea 100644 --- a/plugins/ShrinkUrl/test.py +++ b/plugins/ShrinkUrl/test.py @@ -39,8 +39,6 @@ class ShrinkUrlTestCase(ChannelPluginTestCase): 'term=all+your+base+are+belong+to+us' tests = {'tiny': [(sfUrl, r'http://tinyurl.com/b7wyvfz'), (udUrl, r'http://tinyurl.com/u479')], - 'ln': [(sfUrl, r'http://ln-s.net/\+PE-'), - (udUrl, r'http://ln-s.net/2\$K')], 'goo': [(sfUrl, r'http://goo.gl/3c59N'), (udUrl, r'http://goo.gl/ocTga')], 'ur1': [(sfUrl, r'http://ur1.ca/9xl25'), @@ -62,16 +60,14 @@ class ShrinkUrlTestCase(ChannelPluginTestCase): origsnarfer = snarfer() try: self.assertNotError( - 'config plugins.ShrinkUrl.serviceRotation ln x0') + 'config plugins.ShrinkUrl.serviceRotation goo x0') self.assertError( - 'config plugins.ShrinkUrl.serviceRotation ln x1') + 'config plugins.ShrinkUrl.serviceRotation goo x1') snarfer.setValue(True) self.assertSnarfRegexp(self.udUrl, r'.*%s.* \(at' % - self.tests['ln'][1][1]) + self.tests['goo'][1][1]) self.assertSnarfRegexp(self.udUrl, r'.*%s.* \(at' % self.tests['x0'][1][1]) - self.assertSnarfRegexp(self.udUrl, r'.*%s.* \(at' % - self.tests['ln'][1][1]) finally: cycle.setValue(origcycle) snarfer.setValue(origsnarfer) @@ -93,9 +89,6 @@ class ShrinkUrlTestCase(ChannelPluginTestCase): def testTinysnarf(self): self._snarf('tiny') - def testLnsnarf(self): - self._snarf('ln') - def testGoosnarf(self): self._snarf('goo')