Merge branch 'testing' of github.com:ProgVal/Limnoria into testing

This commit is contained in:
Valentin Lorentz 2015-03-02 20:35:56 +00:00
commit 18bafc725f
18 changed files with 158 additions and 145 deletions

View File

@ -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:

View File

@ -231,34 +231,6 @@ class Admin(callbacks.Plugin):
irc.reply(irc.nick)
nick = wrap(nick, [additional('nick'), additional('something')])
@internationalizeDocstring
def part(self, irc, msg, args, channel, reason):
"""[<channel>] [<reason>]
Tells the bot to part the list of channels you give it. <channel> is
only necessary if you want the bot to part a channel other than the
current channel. If <reason> 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

View File

@ -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:

View File

@ -51,6 +51,15 @@ 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.""")))
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:

View File

@ -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))
@ -258,17 +268,22 @@ class Channel(callbacks.Plugin):
any('nickInChannel')])
@internationalizeDocstring
def cycle(self, irc, msg, args, channel):
def cycle(self, irc, msg, args, channel, reason):
"""[<channel>]
If you have the #channel,op capability, this will cause the bot to
"cycle", or PART and then JOIN the channel. <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 <reason> 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):
@ -941,6 +956,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):
"""[<channel>] [<reason>]
Tells the bot to part the list of channels you give it. <channel> is
only necessary if you want the bot to part a channel other than the
current channel. If <reason> 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

View File

@ -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:

View File

@ -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
@ -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
@ -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'):

View File

@ -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 publicly visible channels')]
else:
L = []
channels = format('%L', L)

View File

@ -46,10 +46,11 @@ 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. The standard substitutions ($version, $nick, etc.) are all handled
appropriately."""))
conf.registerGroup(conf.supybot.commands, 'renames', orderAlphabetically=True)

View File

@ -344,9 +344,11 @@ class Owner(callbacks.Plugin):
Exits the bot with the QUIT message <text>. If <text> 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. The standard
substitutions ($version, $nick, etc.) are all handled appropriately.
"""
text = text or self.registryValue('quitMsg') or msg.nick
text = ircutils.standardSubstitute(irc, msg, text)
irc.noReply()
m = ircmsgs.quit(text)
world.upkeep()

View File

@ -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):
"""[<channel>] <id>
Return the quotegrab with the given <id>. <channel> 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):
"""[<channel>] <id>

View File

@ -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):
"""[<channel>] <nick>
@ -236,9 +247,7 @@ class Seen(callbacks.Plugin):
if name and ircutils.strEqual(name, irc.nick):
irc.reply(_("You've found me!"))
return
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'])
@ -256,9 +265,7 @@ class Seen(callbacks.Plugin):
if name and ircutils.strEqual(name, irc.nick):
irc.reply(_("You've found me!"))
return
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:
@ -295,9 +302,7 @@ class Seen(callbacks.Plugin):
Returns the last thing said in <channel>. <channel> is only necessary
if the message isn't sent in the channel itself.
"""
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'])
@ -327,9 +332,7 @@ class Seen(callbacks.Plugin):
<channel> is only necessary if the message isn't sent in the channel
itself.
"""
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'])
@ -343,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.

View File

@ -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

View File

@ -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):
"""<url>
Returns an ln-s.net version of <url>.
"""
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:

View File

@ -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')

View File

@ -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,7 @@ def main():
for irc in world.ircs:
quitmsg = conf.supybot.plugins.Owner.quitMsg() or \
'Ctrl-C at console.'
quitmsg = ircutils.standardSubstitute(irc, None, quitmsg)
irc.queueMsg(ircmsgs.quit(quitmsg))
irc.die()
except SystemExit as e:

View File

@ -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

View File

@ -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."""
@ -690,6 +690,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 irc:
vars.update({