From 0a2802f56e967eacb7e27c7ed910fa39a251126e Mon Sep 17 00:00:00 2001 From: Jeremy Fincher Date: Fri, 17 Sep 2004 04:56:38 +0000 Subject: [PATCH] others/timeparse.py --- TODO | 3 + others/timeparse.py | 231 ++++++++++++++++++++++++++++++++++++++++++++ plugins/Ctcp.py | 64 ++++++++++-- plugins/Debian.py | 3 +- plugins/Dunno.py | 13 +-- plugins/Factoids.py | 15 +-- plugins/Format.py | 12 +-- plugins/Herald.py | 3 +- plugins/Lookup.py | 7 +- plugins/Observer.py | 7 +- plugins/Quotes.py | 5 +- plugins/Todo.py | 24 ++--- plugins/Topic.py | 8 +- plugins/Words.py | 4 +- 14 files changed, 328 insertions(+), 71 deletions(-) create mode 100644 others/timeparse.py diff --git a/TODO b/TODO index 9e86f9688..1718dd943 100644 --- a/TODO +++ b/TODO @@ -1,6 +1,9 @@ Roughly in order of precedence (the closer to the front of the file, the more likely it'll be done before next release): +* We should have a channel-global value in ircdb.IrcChannel for + ignoring unregistered users. + * It'd be nice to be able to use a backslash to continue lines in the registry, so we could linewrap long strings or lists. diff --git a/others/timeparse.py b/others/timeparse.py new file mode 100644 index 000000000..c9c1d1b7e --- /dev/null +++ b/others/timeparse.py @@ -0,0 +1,231 @@ +#!/usr/bin/python + + # + # Copyright (c) 2004, Mike Taylor + # All rights reserved. + # + # Redistribution and use in source and binary forms, with or without + # modification, are permitted provided that the following conditions are met: + # + # * Redistributions of source code must retain the above copyright notice, + # this list of conditions, and the following disclaimer. + # * Redistributions in binary form must reproduce the above copyright notice, + # this list of conditions, and the following disclaimer in the + # documentation and/or other materials provided with the distribution. + # * Neither the name of the author of this software nor the name of + # contributors to this software may be used to endorse or promote products + # derived from this software without specific prior written consent. + # + # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + # POSSIBILITY OF SUCH DAMAGE. + # + +__author__ = "Mike Taylor " +__copyright__ = "Copyright (c) 2004 Mike Taylor" +__license__ = "BSD" +__revision__ = "$Id$" + +import os, string, re, time + +RE_SPECIAL = r'(?P^[in|last|next]+)\s+' +RE_UNITS = r'\s+(?P[hour|minute|second|day|week|month|year]+)' +RE_QUNITS = r'(?P[0-9]+[hmsdwmy])' +RE_MODIFIER = r'(?P[from|before|after|ago|prior]+)\s+' + +CRE_SPECIAL = re.compile(RE_SPECIAL, re.IGNORECASE) +CRE_UNITS = re.compile(RE_UNITS, re.IGNORECASE) +CRE_QUNITS = re.compile(RE_QUNITS, re.IGNORECASE) +CRE_MODIFIER = re.compile(RE_MODIFIER, re.IGNORECASE) + + # Used to adjust the returned date before/after the source + +_Modifiers = {'from': 1, + 'before': -1, + 'after': 1, + 'ago': 1, + 'prior': -1} + +_Minute = 60 +_Hour = 60 * _Minute +_Day = 24 * _Hour +_Week = 7 * _Day +_Month = 30 * _Day +_Year = 365 * _Day + + # This looks hokey - but it is a nice simple way to get + # the proper unit value and it has the advantage that + # later I can morph it into something localized. + # Any trailing s will be removed before lookup. + +_Units = {'second': 1, + 'sec': 1, + 's': 1, + 'minute': _Minute, + 'min': _Minute, + 'm': _Minute, + 'hour': _Hour, + 'hr': _Hour, + 'h': _Hour, + 'day': _Day, + 'dy': _Day, + 'd': _Day, + 'week': _Week, + 'wk': _Week, + 'w': _Week, + 'month': _Month, + 'mth': _Month, + 'm': _Month, + 'year': _Year, + 'yr': _Year, + 'y': _Year} + +def _buildTime(sourceTime, quantity, modifier, units): + """Take quantity, modifier and units strings and convert them + into values, calcuate the time and return the adjusted + sourceTime + """ + # print '[%s][%s][%s]' % (quantity, modifier, units) + + q = int(quantity) + + if _Modifiers.has_key(modifier): + q = q * _Modifiers[modifier] + + if units[-1] == 's': + units = units[:-1] + + if _Units.has_key(units): + u = _Units[units] + else: + u = 1 + + # print 'sourceTime [%d]' % sourceTime + # print 'quantity [%d]' % q + # print 'units [%d]' % u + + return sourceTime + (q * u) + + +def parse(timeString, sourceTime=None): + """Parse timeString and return the number of seconds from sourceTime + that the timeString expression represents. + + This version of parse understands only the more basic of expressions + in this form: + + + + Example: + + 5 minutes from now + last week + 2 hours before noon + + Valid units - hour, minute, second, month, week, day and year + (including their plural forms) + Valid modifiers - from, before, after, ago, prior + """ + + if sourceTime == None: + sourceTime = int(time.time()) + else: + sourceTime = int(sourceTime) + + quantity = '' + units = '' + modifier = '' + target = '' + + s = string.strip(string.lower(timeString)) + + m = CRE_SPECIAL.search(s) + + if m <> None: + target = 'now' + + if m.group('special') == 'last': + modifier = 'before' + else: + modifier = 'from' + + s = s[m.end('special'):] + + m = CRE_UNITS.search(s) + + if m <> None: + units = m.group('units') + quantity = s[:m.start('units')] + s = s[m.end('units'):] + + else: + m = CRE_MODIFIER.search(s) + + if m <> None: + modifier = m.group('modifier') + target = s[m.end('modifier'):] + s = s[:m.start('modifier'):] + + m = CRE_UNITS.search(s) + + if m <> None: + units = m.group('units') + quantity = s[:m.start('units')] + target = s[m.end('units'):] + + return _buildTime(sourceTime, quantity, modifier, units) + +def _test(text, value): + print text + + v = parse(text) + + print '\t%s\t%d\t%d' % ((v == value), value, v) + + # + # TODO + # + # - make month unit adjustment aware of the actual number of days in each + # month between source and target + # - handle edge case where quantity and unit are merged: 5s for 5 sec + # - handle compound/nested quantites and modifiers + # - bring unit test over from prototype into this file + # - convert 'five' to 5 and also 'twenty five' to 25 + + + +if __name__ == '__main__': + start = int(time.time()) + tests = { '5 minutes from now': start + 5 * _Minute, + '5 min from now': start + 5 * _Minute, + 'in 5 minutes': start + 5 * _Minute, + '5 days from today': start + 5 * _Day, + '5 days before today': start - 5 * _Day, + '5 minutes': start + 5 * _Minute, + '5 min': start + 5 * _Minute, + '30 seconds from now': start + 30, + '30 sec from now': start + 30, + '30 seconds': start + 30, + '30 sec': start + 30, + '1 week': start + _Week, + '1 wk': start + _Week, + '1 week': start + _Week, + '5 days': start + 5 * _Day, + '1 year': start + _Year, + '2 years': start + 2 * _Year} + + for test in tests.keys(): + _test(test, tests[test]) + + + + + diff --git a/plugins/Ctcp.py b/plugins/Ctcp.py index 3dacd35f0..9862fb772 100644 --- a/plugins/Ctcp.py +++ b/plugins/Ctcp.py @@ -45,9 +45,12 @@ import time sys.path.append(os.pardir) import supybot.conf as conf +import supybot.utils as utils import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils +import supybot.privmsgs as privmsgs import supybot.registry as registry +import supybot.schedule as schedule import supybot.callbacks as callbacks conf.registerPlugin('Ctcp') @@ -63,8 +66,13 @@ conf.registerGlobalValue(conf.supybot.abuse.flood.ctcp, 'maximum', conf.registerGlobalValue(conf.supybot.abuse.flood.ctcp, 'punishment', registry.PositiveInteger(300, """Determines how many seconds the bot will ignore CTCP messages from users who flood it with CTCP messages.""")) +conf.registerGlobalValue(conf.supybot.plugins.Ctcp, 'versionWait', + registry.PositiveInteger(10, """Determines how many seconds the bot will + wait after getting a version command (not a CTCP VERSION, but an actual + call of the command in this plugin named "version") before replying with + the results it has collected.""")) -class Ctcp(callbacks.PrivmsgRegexp): +class Ctcp(callbacks.PrivmsgCommandAndRegexp): public = False def __init__(self): self.__parent = super(Ctcp, self) @@ -96,35 +104,77 @@ class Ctcp(callbacks.PrivmsgRegexp): s = '\x01%s\x01' % s irc.reply(s, notice=True, private=True, to=msg.nick) - def ping(self, irc, msg, match): + regexps = ('ctcpPing', 'ctcpVersion', 'ctcpUserinfo', + 'ctcpTime', 'ctcpFinger', 'ctcpSource') + def ctcpPing(self, irc, msg, match): "\x01PING (.*)\x01" self.log.info('Received CTCP PING from %s', msg.prefix) self._reply(irc, msg, 'PING %s' % match.group(1)) - def version(self, irc, msg, match): + def ctcpVersion(self, irc, msg, match): "\x01VERSION\x01" self.log.info('Received CTCP VERSION from %s', msg.prefix) self._reply(irc, msg, 'VERSION Supybot %s' % conf.version) - def userinfo(self, irc, msg, match): + def ctcpUserinfo(self, irc, msg, match): "\x01USERINFO\x01" self.log.info('Received CTCP USERINFO from %s', msg.prefix) self._reply(irc, msg, 'USERINFO') - def time(self, irc, msg, match): + def ctcpTime(self, irc, msg, match): "\x01TIME\x01" self.log.info('Received CTCP TIME from %s' % msg.prefix) self._reply(irc, msg, time.ctime()) - def finger(self, irc, msg, match): + def ctcpFinger(self, irc, msg, match): "\x01FINGER\x01" self.log.info('Received CTCP FINGER from %s' % msg.prefix) self._reply(irc, msg, 'Supybot, the best Python IRC bot in existence!') - def source(self, irc, msg, match): + def ctcpSource(self, irc, msg, match): "\x01SOURCE\x01" self.log.info('Received CTCP SOURCE from %s' % msg.prefix) self._reply(irc, msg, 'http://www.sourceforge.net/projects/supybot/') + def doNotice(self, irc, msg): + if ircmsgs.isCtcp(msg): + try: + (version, payload) = msg.args[1][1:-1].split(None, 1) + except ValueError: + return + if version == 'VERSION': + self.versions.setdefault(payload, []).append(msg.nick) + + def version(self, irc, msg, args): + """[] [--nicks] + + Sends a CTCP VERSION to , returning the various + version strings returned. It waits for 10 seconds before returning + the versions received at that point. If --nicks is given, nicks are + associated with the version strings; otherwise, only the version + strings are given. + """ + self.versions = ircutils.IrcDict() + nicks = False + while '--nicks' in args: + nicks = True + args.remove('--nicks') + channel = privmsgs.getChannel(msg, args) + irc.queueMsg(ircmsgs.privmsg(channel, '\x01VERSION\x01')) + def doReply(): + if self.versions: + L = [] + for (reply, nicks) in self.versions.iteritems(): + if nicks: + L.append('%s responded with %r' % + (utils.commaAndify(nicks), reply)) + else: + L.append(reply) + irc.reply(utils.commaAndify(L)) + else: + irc.reply('I received no version responses.') + wait = self.registryValue('versionWait') + schedule.addEvent(doReply, time.time()+wait) + Class = Ctcp # vim:set shiftwidth=4 tabstop=8 expandtab textwidth=78: diff --git a/plugins/Debian.py b/plugins/Debian.py index cad078043..60e6a5a00 100644 --- a/plugins/Debian.py +++ b/plugins/Debian.py @@ -275,8 +275,7 @@ class Debian(callbacks.Privmsg, r = utils.perlReToPythonRe(arg) predicates.append(r.search) except ValueError: - irc.error('%r is not a valid regexp.' % arg) - return + irc.errorInvalid('regular expression', arg, Raise=True) elif option == '--arch': arg = '_%s.' % arg archPredicate = lambda s, arg=arg: (arg in s) diff --git a/plugins/Dunno.py b/plugins/Dunno.py index 0eed0a633..a158d5020 100644 --- a/plugins/Dunno.py +++ b/plugins/Dunno.py @@ -194,8 +194,7 @@ class Dunno(callbacks.Privmsg): try: id = int(id) except ValueError: - irc.error('Invalid id: %r' % id) - return + irc.error('id', id, Raise=True) dunno = self.db.get(channel, id) if by != dunno.by: cap = ircdb.makeChannelCapability(channel, 'op') @@ -238,8 +237,7 @@ class Dunno(callbacks.Privmsg): try: id = int(id) except ValueError: - irc.error('%r is not a valid dunno id.' % id) - return + irc.errorInvalid('dunno id', id, Raise=True) try: dunno = self.db.get(channel, id) name = ircdb.users.getUser(dunno.by).name @@ -268,18 +266,15 @@ class Dunno(callbacks.Privmsg): try: id = int(id) except ValueError: - irc.error('%r is not a valid dunno id.' % id) - return + irc.errorInvalid('dunno id', id, Raise=True) try: _ = self.db.get(channel, id) except KeyError: irc.error('There is no dunno #%s.' % id) - return try: replacer = utils.perlReToReplacer(regexp) except: - irc.error('%r is not a valid regular expression.' % regexp) - return + irc.errorInvalid('regular expression', regexp, Raise=True) self.db.change(channel, id, replacer) irc.replySuccess() diff --git a/plugins/Factoids.py b/plugins/Factoids.py index 6100a1941..74e6a7843 100644 --- a/plugins/Factoids.py +++ b/plugins/Factoids.py @@ -171,8 +171,7 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg): try: irc.reply(factoids[number-1]) except IndexError: - irc.error('That\'s not a valid number for that key.') - return + irc.errorInvalid('number for that key', number, Raise=True) else: intro = self.registryValue('factoidPrefix', channel) prefix = '%r %s' % (key, intro) @@ -214,8 +213,7 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg): try: number = int(number) except ValueError: - irc.error('%s is not a valid number.' % number) - return + irc.errorInvalid('number', number, Raise=True) else: number = 0 factoids = self._lookupFactoid(channel, key) @@ -293,8 +291,7 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg): try: (_, id) = results[number] except IndexError: - irc.error('Invalid factoid number.') - return + irc.errorInvalid('factoid number', number, Raise=True) cursor.execute("DELETE FROM factoids WHERE id=%s", id) db.commit() irc.replySuccess() @@ -370,15 +367,13 @@ class Factoids(plugins.ChannelDBHandler, callbacks.Privmsg): try: replacer = utils.perlReToReplacer(regexp) except ValueError, e: - irc.error('Invalid regexp: %s' % e) - return + irc.errorInvalid('regular expression', regexp, str(e), Raise=True) try: number = int(number) if number <= 0: raise ValueError except ValueError: - irc.error('Invalid key id.') - return + irc.errorInvalid('id', number, Raise=True) db = self.getDb(channel) cursor = db.cursor() cursor.execute("""SELECT factoids.id, factoids.fact diff --git a/plugins/Format.py b/plugins/Format.py index 47e23d5b3..0cffe55b5 100644 --- a/plugins/Format.py +++ b/plugins/Format.py @@ -101,14 +101,12 @@ class Format(callbacks.Privmsg): try: fg = ircutils.mircColors[fg] except KeyError: - irc.error('%r is not a valid foreground color.' % fg) - return + irc.errorInvalid('foreground color', fg, Raise=True) if bg is not None: try: bg = ircutils.mircColors[bg] except KeyError: - irc.error('%r is not a valid background color.' % bg) - return + irc.errorInvalid('background color', bg, Raise=True) irc.reply(ircutils.mircColor(text, fg=fg, bg=bg)) def join(self, irc, msg, args): @@ -172,7 +170,7 @@ class Format(callbacks.Privmsg): try: size = int(size) except ValueError: - irc.error('%r is not a valid integer.' % size) + irc.errorInvalid('integer', size, Raise=True) irc.reply(text[:size]) def field(self, irc, msg, args): @@ -187,11 +185,11 @@ class Format(callbacks.Privmsg): if index > 0: index -= 1 except ValueError: - irc.error('%r is not a valid integer.' % number) + irc.errorInvalid('integer', number, Raise=True) try: irc.reply(text.split()[index]) except IndexError: - irc.error('That\'s not a valid field.') + irc.errorInvalid('field') def format(self, irc, msg, args): """ [ ...] diff --git a/plugins/Herald.py b/plugins/Herald.py index 51130bd06..68f36bf7a 100644 --- a/plugins/Herald.py +++ b/plugins/Herald.py @@ -252,8 +252,7 @@ class Herald(callbacks.Privmsg): try: changer = utils.perlReToReplacer(regexp) except ValueError, e: - irc.error('That\'s not a valid regexp: %s.' % e) - return + irc.errorInvalid('regular expression', regexp, str(e), Raise=True) s = self.db[channel, id] newS = changer(s) self.db[channel, id] = newS diff --git a/plugins/Lookup.py b/plugins/Lookup.py index f75805685..2463a405e 100644 --- a/plugins/Lookup.py +++ b/plugins/Lookup.py @@ -133,8 +133,7 @@ class Lookup(callbacks.Privmsg): name = privmsgs.getArgs(args) name = callbacks.canonicalName(name) if name not in self.lookupDomains: - irc.error('That\'s not a valid lookup to remove.') - return + irc.errorInvalid('lookup', name, Raise=True) db = self.dbHandler.getDb() cursor = db.cursor() try: @@ -249,9 +248,7 @@ class Lookup(callbacks.Privmsg): try: r = utils.perlReToPythonRe(arg) except ValueError, e: - irc.error('%r is not a valid regular expression' % - arg) - return + irc.errorInvalid('regular expression', arg, Raise=True) def p(s, r=r): return int(bool(r.search(s))) db.create_function(predicateName, 1, p) diff --git a/plugins/Observer.py b/plugins/Observer.py index be83cbe87..027845b03 100644 --- a/plugins/Observer.py +++ b/plugins/Observer.py @@ -194,7 +194,7 @@ class Observer(callbacks.Privmsg): """ name = privmsgs.getArgs(args) if name not in self.registryValue('observers'): - irc.error('That\'s not a valid observer.', Raise=True) + irc.errorInvalid('observer', name, Raise=True) g = self.registryValue('observers.%s' % name, value=False) regexp = g() command = g.command() @@ -222,8 +222,9 @@ class Observer(callbacks.Privmsg): probability = 1.0 (name, regexp, command) = privmsgs.getArgs(args, required=3) if not registry.isValidRegistryName(name): - irc.error('That\'s not a valid observer name. Please be sure ' - 'there are no spaces in the name.', Raise=True) + irc.errorInvalid('observer name', name, + 'Please be sure there are no spaces in the name.', + Raise=True) registerObserver(name, regexp, command, probability) irc.replySuccess() diff --git a/plugins/Quotes.py b/plugins/Quotes.py index e9c3c54fd..43c6ab2c4 100644 --- a/plugins/Quotes.py +++ b/plugins/Quotes.py @@ -323,8 +323,7 @@ class Quotes(callbacks.Privmsg): try: id = int(id) except ValueError: - irc.error('Invalid id: %r' % id) - return + irc.errorInvalid('id', id, Raise=True) try: quote = self.db.get(channel, id) irc.reply(str(quote)) @@ -342,7 +341,7 @@ class Quotes(callbacks.Privmsg): try: id = int(id) except ValueError: - irc.error('That\'s not a valid id: %r' % id) + irc.errorInvalid('id', id, Raise=True) try: self.db.remove(channel, id) irc.replySuccess() diff --git a/plugins/Todo.py b/plugins/Todo.py index 5c4b4867c..03784c6c5 100644 --- a/plugins/Todo.py +++ b/plugins/Todo.py @@ -112,9 +112,7 @@ class Todo(callbacks.Privmsg): try: userid = ircdb.users.getUserId(arg) except KeyError: - irc.error( - '%r is not a valid task id or username' % arg) - return + irc.errorInvalid('task or username', arg, Raise=True) db = self.dbHandler.getDb() cursor = db.cursor() if not userid and not taskid: @@ -151,8 +149,7 @@ class Todo(callbacks.Privmsg): cursor.execute("""SELECT userid,priority,added_at,task,active FROM todo WHERE id = %s""", taskid) if cursor.rowcount == 0: - irc.error('%r is not a valid task id' % taskid) - return + irc.errorInvalid('task id', taskid, Raise=True) (userid, pri, added_at, task, active) = cursor.fetchone() # Construct and return the reply user = ircdb.users.getUser(userid) @@ -191,8 +188,7 @@ class Todo(callbacks.Privmsg): try: priority = int(arg) except ValueError, e: - irc.error('%r is an invalid priority' % arg) - return + irc.errorInvalid('priority', arg, Raise=True) text = privmsgs.getArgs(rest) db = self.dbHandler.getDb() cursor = db.cursor() @@ -272,9 +268,7 @@ class Todo(callbacks.Privmsg): try: r = utils.perlReToPythonRe(arg) except ValueError, e: - irc.error('%r is not a valid regular expression' % - arg) - return + irc.errorInvalid('regular expression', arg, Raise=True) def p(s, r=r): return int(bool(r.search(s))) db.create_function(predicateName, 1, p) @@ -337,19 +331,17 @@ class Todo(callbacks.Privmsg): try: replacer = utils.perlReToReplacer(regexp) except ValueError: - irc.error('%r is not a valid regexp' % regexp) - return + irc.errorInvalid('regular expression', regexp, Raise=True) db = self.dbHandler.getDb() cursor = db.cursor() cursor.execute("""SELECT task FROM todo WHERE userid = %s AND id = %s AND active = 1""", userid, taskid) if cursor.rowcount == 0: - irc.error('%r is not a valid task id' % taskid) - return + irc.errorInvalid('task id', taskid, Raise=True) newtext = replacer(cursor.fetchone()[0]) - cursor.execute("""UPDATE todo SET task = %s - WHERE id = %s""", newtext, taskid) + cursor.execute("""UPDATE todo SET task = %s WHERE id = %s""", + newtext, taskid) db.commit() irc.replySuccess() diff --git a/plugins/Topic.py b/plugins/Topic.py index 772c2d51d..3146526af 100644 --- a/plugins/Topic.py +++ b/plugins/Topic.py @@ -166,7 +166,7 @@ class Topic(callbacks.Privmsg): n += len(topics) return n except (ValueError, IndexError): - irc.error('That\'s not a valid topic number.', Raise=True) + irc.errorInvalid('topic number', n, Raise=True) def topic(self, irc, msg, args, channel): """[] @@ -308,14 +308,12 @@ class Topic(callbacks.Privmsg): (number, regexp) = privmsgs.getArgs(args, required=2) topics = self._splitTopic(irc.state.getTopic(channel), channel) if not topics: - irc.error('There are no topics to change.') - return + irc.error('There are no topics to change.', Raise=True) number = self._topicNumber(irc, number, topics=topics) try: replacer = utils.perlReToReplacer(regexp) except ValueError, e: - irc.error('The regexp wasn\'t valid: %s' % e) - return + irc.errorInvalid('regexp', regexp, Raise=True) topics[number] = replacer(topics[number]) self._sendTopics(irc, channel, topics) change = privmsgs.channel(change) diff --git a/plugins/Words.py b/plugins/Words.py index f74d881a1..88c26d332 100644 --- a/plugins/Words.py +++ b/plugins/Words.py @@ -303,10 +303,10 @@ class Words(callbacks.Privmsg): else: # User input an invalid character if len(letter) == 1: - irc.error('That is not a valid letter.') + irc.errorInvalid('letter', letter, Raise=True) # User input an invalid word (len(try) != len(hidden)) else: - irc.error('That is not a valid word guess.') + irc.errorInvalid('word guess', letter, Raise=True) # Verify if the user won or lost if game.guessed and game.tries > 0: self._hangmanReply(irc, channel,