def add(self, irc, msg, args, name, trackertype, url, description): """<name> <type> <url> [<description>] Add a bugtracker <url> to the list of defined bugtrackers. <type> is the type of the tracker (currently only Launchpad, Debbugs, Bugzilla, Issuezilla, Mantis and Trac are known). <name> is the name that will be used to reference the bugzilla in all commands. Unambiguous abbreviations of <name> will be accepted also. <description> is the common name for the bugzilla and will be listed with the bugzilla query; if not given, it defaults to <name>. """ name = name.lower() if not description: description = name if url[-1] == '/': url = url[:-1] trackertype = trackertype.lower() if trackertype in defined_bugtrackers: self.db[name] = defined_bugtrackers[trackertype](name,url,description) else: irc.error("Bugtrackers of type '%s' are not understood" % trackertype) return registerBugtracker(name, url, description, trackertype) self.shorthand = utils.abbrev(self.db.keys()) irc.replySuccess()
def get_tracker(self, snarfurl, sfdata): snarfurl = snarfurl.replace('sf.net','sourceforge.net') snarfhost = snarfurl.replace('http://','').replace('https://','') # Begin HACK # launchpad.net has many URLs that can confuse us # make sure 'bug' in in the URL, somewhere if 'launchpad' in snarfhost: if not 'bug' in snarfhost: # Not a bug URL return None if snarfhost.lower().startswith("code."): return None if snarfhost.startswith('pad.lv'): # Launchpad URL shortening snarfhost = snarfhost[:snarfhost.rfind('/')] snarfhost = '/'.join( (_ for _ in snarfhost.split('/') if _) ) if '/' in snarfhost: # it's not a bug URL return None return self.db.get('launchpad', None) # End HACK # At this point, we are only interested in the host part of the URL if '/' in snarfurl: snarfhost = snarfhost[:snarfhost.index('/')] if 'sourceforge.net' in snarfurl: # See below return None for t in self.db.keys(): tracker = self.db.get(t, None) if not tracker: self.log.error("No tracker for key %r" % t) continue url = tracker.url.replace('http://','').replace('https://','') if 'sourceforge.net' in url: # sourceforge.net has no API or structured bug exporting, HTML # scraping is not good enough. Especially as SF keep changing it continue if '/' in url: url = url[:url.index('/')] if url in snarfhost: return tracker if snarfhost == 'pad.lv': # Launchpad URL shortening return self.db.get('lp', None) # No tracker found, bummer. Let's try and add one if 'show_bug.cgi' in snarfurl: tracker = Bugzilla().get_tracker(snarfurl) if tracker: self.db[tracker.name] = tracker self.shorthand = utils.abbrev(self.db.keys()) return tracker return None
def getLiteral(irc, msg, args, state, literals, errmsg=None): # ??? Should we allow abbreviations? if isinstance(literals, basestring): literals = (literals,) abbrevs = utils.abbrev(literals) if args[0] in abbrevs: state.args.append(abbrevs[args.pop(0)]) elif errmsg is not None: state.error(errmsg, Raise=True) else: raise callbacks.ArgumentError
def getLiteral(irc, msg, args, state, literals, errmsg=None): # ??? Should we allow abbreviations? if isinstance(literals, basestring): literals = (literals, ) abbrevs = utils.abbrev(literals) if args[0] in abbrevs: state.args.append(abbrevs[args.pop(0)]) elif errmsg is not None: state.error(errmsg, Raise=True) else: raise callbacks.ArgumentError
def __init__(self, irc): self.__parent = super(Bugtracker, self) self.__parent.__init__(irc) self.db = ircutils.IrcDict() for name in self.registryValue('bugtrackers'): registerBugtracker(name) group = self.registryValue('bugtrackers.%s' % name.replace('.','\\.'), value=False) if group.trackertype() in defined_bugtrackers: self.db[name] = defined_bugtrackers[group.trackertype()](name, group.url(), group.description(), group.trackertype()) else: supylog.warning("Bugtracker: Unknown trackertype: %s (%s)" % (group.trackertype(), name)) self.shorthand = utils.abbrev(list(self.db.keys())) self.shown = {}
def __init__(self, irc): callbacks.PluginRegexp.__init__(self, irc) self.db = ircutils.IrcDict() # self.events = [] for name in self.registryValue('bugtrackers'): registerBugtracker(name) group = self.registryValue('bugtrackers.%s' % name.replace('.','\\.'), value=False) if group.trackertype() in defined_bugtrackers.keys(): self.db[name] = defined_bugtrackers[group.trackertype()](name, group.url(), group.description()) else: self.log.warning("Bugtracker: Unknown trackertype: %s (%s)" % (group.trackertype(), name)) self.shorthand = utils.abbrev(self.db.keys()) self.shown = {}
def testAbbrev(self): L = ['abc', 'bcd', 'bbe', 'foo', 'fool'] d = utils.abbrev(L) def getItem(s): return d[s] self.assertRaises(KeyError, getItem, 'f') self.assertRaises(KeyError, getItem, 'fo') self.assertRaises(KeyError, getItem, 'b') self.assertEqual(d['bb'], 'bbe') self.assertEqual(d['bc'], 'bcd') self.assertEqual(d['a'], 'abc') self.assertEqual(d['ab'], 'abc') self.assertEqual(d['fool'], 'fool') self.assertEqual(d['foo'], 'foo')
def remove(self, irc, msg, args, name): """<abbreviation> Remove the bugtracker associated with <abbreviation> from the list of defined bugtrackers. """ try: name = self.shorthand[name.lower()] del self.db[name] self.registryValue('bugtrackers').remove(name) self.shorthand = utils.abbrev(self.db.keys()) irc.replySuccess() except KeyError: s = self.registryValue('replyNoBugtracker', ircutils.isChannel(msg.args[0]) and msg.args[0] or None) irc.error(s % name)
def rename(self, irc, msg, args, oldname, newname, newdesc): """<oldname> <newname> Rename the bugtracker associated with <oldname> to <newname>. """ try: name = self.shorthand[oldname.lower()] group = self.registryValue('bugtrackers.%s' % name.replace('.','\\.'), value=False) d = group.description() if newdesc: d = newdesc self.db[newname] = defined_bugtrackers[group.trackertype()](name,group.url(),d) registerBugtracker(newname, group.url(), d, group.trackertype()) del self.db[name] self.registryValue('bugtrackers').remove(name) self.shorthand = utils.abbrev(self.db.keys()) irc.replySuccess() except KeyError: s = self.registryValue('replyNoBugtracker', ircutils.isChannel(msg.args[0]) and msg.args[0] or None) irc.error(s % name)
def get_tracker(self, snarfurl, bugid): # SourceForge short domain snarfurl = snarfurl.replace('sf.net', 'sourceforge.net', 1) # Launchpad URL shortening snarfurl = re.sub(r'pad\.lv/(bug=)?(?P<bug>[0-9]+)', r'launchpad.net/bugs/\g<bug>', snarfurl) for t in list(self.db.keys()): tracker = self.db.get(t, None) if not tracker: supylog.error("No tracker for key '%s'" % t) continue url = tracker.url[tracker.url.rfind('://')+3:] if url in snarfurl: return tracker # No tracker found, bummer. Let's try and get one if 'show_bug.cgi' in snarfurl: tracker = Bugzilla().get_tracker(snarfurl) elif 'sourceforge.net' in snarfurl: tracker = SourceForge().get_tracker(snarfurl) elif 'github.com' in snarfurl: tracker = GitHub().get_tracker(snarfurl) elif re.match(r'[^\s/]+/[^\s/]+/[^\s/]+/(issues|pulls|merge_requests)', snarfurl): tracker = GitLab().get_tracker(snarfurl, bugid) if not tracker: tracker = Gitea().get_tracker(snarfurl, bugid) elif 'view.php' in snarfurl: tracker = Mantis().get_tracker(snarfurl) elif '/ticket/' in snarfurl: tracker = Trac().get_tracker(snarfurl) else: return if tracker: self.db[tracker.name] = tracker self.shorthand = utils.abbrev(list(self.db.keys())) return tracker
def add(self, irc, msg, args, name, trackertype, url, description): """<name> <type> <url> [<description>] Add a bugtracker to the list of defined bugtrackers. Currently supported types are Launchpad, Debbugs, Bugzilla, SourceForge, Github, GitLab, Gitea, Mantis, and Trac. <name> will be used to reference the bugtracker in all commands. Unambiguous abbreviations of it will also be accepted. <description> will be used to reference the bugtracker in the query result. If not given, it defaults to <name>. """ name = name.lower() if not description: description = name if url[-1] == '/': url = url[:-1] trackertype = trackertype.lower() if trackertype in defined_bugtrackers: self.db[name] = defined_bugtrackers[trackertype](name, url, description, trackertype) else: irc.error("Bugtrackers of type '%s' are not understood" % trackertype) return registerBugtracker(name, url, description, trackertype) self.shorthand = utils.abbrev(list(self.db.keys())) irc.replySuccess()
class Karma(callbacks.Plugin): """ Provides a simple tracker for setting Karma (thing++, thing--). If ``config plugins.karma.allowUnaddressedKarma`` is set to ``True`` (default since 2014.05.07), saying `boats++` will give 1 karma to ``boats``, and ``ships--`` will subtract 1 karma from ``ships``. However, if you use this in a sentence, like ``That deserves a ++. Kevin++``, 1 karma will be added to ``That deserves a ++. Kevin``, so you should only add or subtract karma in a line that doesn't have anything else in it. Alternatively, you can restrict karma tracking to nicks in the current channel by setting `config plugins.Karma.onlyNicks` to ``True``. If ``config plugins.karma.allowUnaddressedKarma` is set to `False``, you must address the bot with nick or prefix to add or subtract karma. """ callBefore = ('Factoids', 'MoobotFactoids', 'Infobot') def __init__(self, irc): self.__parent = super(Karma, self) self.__parent.__init__(irc) self.db = KarmaDB() def die(self): self.__parent.die() self.db.close() def _normalizeThing(self, thing): assert thing if thing[0] == '(' and thing[-1] == ')': thing = thing[1:-1] return thing def _respond(self, irc, channel, thing, karma): 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, 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): thing = thing[:-len(s)] # Don't reply if the target isn't a nick if onlynicks and thing.lower() not in map( ircutils.toLower, irc.state.channels[channel].users): return if ircutils.strEqual(thing, msg.nick) and \ 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)) karma = self.db.get(channel, self._normalizeThing(thing)) for s in dec: if thing.endswith(s): thing = thing[:-len(s)] if onlynicks and thing.lower() not in map( ircutils.toLower, irc.state.channels[channel].users): return if ircutils.strEqual(thing, msg.nick) and \ 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)) karma = self.db.get(channel, self._normalizeThing(thing)) if karma: self._respond(irc, channel, thing, karma[0] - karma[1]) def invalidCommand(self, irc, msg, tokens): if msg.channel and tokens: thing = ' '.join(tokens) 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): if msg.channel and \ not ircmsgs.isCtcp(msg) and \ self.registryValue('allowUnaddressedKarma', msg.channel, irc.network): irc = callbacks.SimpleProxy(irc, msg) thing = msg.args[1].rstrip() self._doKarma(irc, msg, msg.channel, thing) @internationalizeDocstring def karma(self, irc, msg, args, channel, things): """[<channel>] [<thing> ...] Returns the karma of <thing>. If <thing> is not given, returns the top N karmas, where N is determined by the config variable supybot.plugins.Karma.rankingDisplay. If one <thing> is given, returns the details of its karma; if more than one <thing> is given, returns the total karma of each of the things. <channel> is only necessary if the message isn't sent on the channel itself. """ if len(things) == 1: name = things[0] t = self.db.get(channel, name) if t is None: irc.reply(format(_('%s has neutral karma.'), name)) else: (added, subtracted) = t total = added - subtracted if self.registryValue('simpleOutput', channel, irc.network): s = format('%s: %i', name, total) else: s = format( _('Karma for %q has been increased %n and ' 'decreased %n for a total karma of %s.'), name, (added, _('time')), (subtracted, _('time')), total) irc.reply(s) elif len(things) > 1: (L, neutrals) = self.db.gets(channel, things) if L: s = format('%L', [format('%s: %i', *t) for t in L]) if neutrals: neutral = format('. %L %h neutral karma', neutrals, len(neutrals)) s += neutral irc.reply(s + '.') else: 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, irc.network) highest = [ format('%q (%s)', s, t) for (s, t) in self.db.top(channel, limit) ] lowest = [ format('%q (%s)', s, t) for (s, t) in self.db.bottom(channel, limit) ] if not (highest and lowest): irc.error(_('I have no karma for this channel.')) return rank = self.db.rank(channel, msg.nick) if rank is not None: total = self.db.size(channel) rankS = format(_(' You (%s) are ranked %i out of %i.'), msg.nick, rank, total) else: rankS = '' s = format(_('Highest karma: %L. Lowest karma: %L.%s'), highest, lowest, rankS) irc.reply(s) karma = wrap(karma, ['channel', any('something')]) _mostAbbrev = utils.abbrev(['increased', 'decreased', 'active']) @internationalizeDocstring def most(self, irc, msg, args, channel, kind): """[<channel>] {increased,decreased,active} Returns the most increased, the most decreased, or the most active (the sum of increased and decreased) karma things. <channel> is only necessary if the message isn't sent in the channel itself. """ L = self.db.most( channel, kind, self.registryValue('mostDisplay', channel, irc.network)) if L: L = [format('%q: %i', name, i) for (name, i) in L] irc.reply(format('%L', L)) else: irc.error(_('I have no karma for this channel.')) most = wrap(most, ['channel', ('literal', ['increased', 'decreased', 'active'])]) @internationalizeDocstring def clear(self, irc, msg, args, channel, name): """[<channel>] [<name>] Resets the karma of <name> to 0. If <name> is not given, resets everything. """ self.db.clear(channel, name or None) irc.replySuccess() clear = wrap(clear, [('checkChannelCapability', 'op'), optional('text')]) @internationalizeDocstring def dump(self, irc, msg, args, channel, filename): """[<channel>] <filename> Dumps the Karma database for <channel> to <filename> in the bot's data directory. <channel> is only necessary if the message isn't sent in the channel itself. """ checkAllowShell(irc) self.db.dump(channel, filename) irc.replySuccess() dump = wrap(dump, [('checkCapability', 'owner'), 'channeldb', 'filename']) @internationalizeDocstring def load(self, irc, msg, args, channel, filename): """[<channel>] <filename> Loads the Karma database for <channel> from <filename> in the bot's data directory. <channel> is only necessary if the message isn't sent in the channel itself. """ checkAllowShell(irc) self.db.load(channel, filename) irc.replySuccess() load = wrap(load, [('checkCapability', 'owner'), 'channeldb', 'filename'])
class Karma(callbacks.Plugin): callBefore = ('Factoids', 'MoobotFactoids', 'Infobot') def __init__(self, irc): self.__parent = super(Karma, self) self.__parent.__init__(irc) self.db = KarmaDB() def die(self): self.__parent.die() self.db.close() def _normalizeThing(self, thing): assert thing if thing[0] == '(' and thing[-1] == ')': thing = thing[1:-1] return thing def _respond(self, irc, channel): if self.registryValue('response', channel): irc.replySuccess() else: irc.noReply() def _doKarma(self, irc, channel, thing): assert thing[-2:] in ('++', '--') if thing.endswith('++'): thing = thing[:-2] if ircutils.strEqual(thing, irc.msg.nick) and \ not self.registryValue('allowSelfRating', channel): irc.error('You\'re not allowed to adjust your own karma.') elif thing: self.db.increment(channel, self._normalizeThing(thing)) self._respond(irc, channel) else: thing = thing[:-2] if ircutils.strEqual(thing, irc.msg.nick) and \ not self.registryValue('allowSelfRating', channel): irc.error('You\'re not allowed to adjust your own karma.') elif thing: self.db.decrement(channel, self._normalizeThing(thing)) self._respond(irc, channel) def invalidCommand(self, irc, msg, tokens): channel = msg.args[0] if not irc.isChannel(channel): return if tokens[-1][-2:] in ('++', '--'): thing = ' '.join(tokens) self._doKarma(irc, 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 \ not ircmsgs.isCtcp(msg) and \ self.registryValue('allowUnaddressedKarma', channel): irc = callbacks.SimpleProxy(irc, msg) thing = msg.args[1].rstrip() if thing[-2:] in ('++', '--'): self._doKarma(irc, channel, thing) def karma(self, irc, msg, args, channel, things): """[<channel>] [<thing> ...] Returns the karma of <thing>. If <thing> is not given, returns the top N karmas, where N is determined by the config variable supybot.plugins.Karma.rankingDisplay. If one <thing> is given, returns the details of its karma; if more than one <thing> is given, returns the total karma of each of the the things. <channel> is only necessary if the message isn't sent on the channel itself. """ if len(things) == 1: name = things[0] t = self.db.get(channel, name) if t is None: irc.reply(format('%s has neutral karma.', name)) else: (added, subtracted) = t total = added - subtracted if self.registryValue('simpleOutput', channel): s = format('%s: %i', name, total) else: s = format( 'Karma for %q has been increased %n and ' 'decreased %n for a total karma of %s.', name, (added, 'time'), (subtracted, 'time'), total) irc.reply(s) elif len(things) > 1: (L, neutrals) = self.db.gets(channel, things) if L: s = format('%L', [format('%s: %i', *t) for t in L]) if neutrals: neutral = format('. %L %h neutral karma', neutrals, len(neutrals)) s += neutral irc.reply(s + '.') else: 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) top = self.db.top(channel, limit) highest = [ format('%q (%s)', s, t) for (s, t) in self.db.top(channel, limit) ] lowest = [ format('%q (%s)', s, t) for (s, t) in self.db.bottom(channel, limit) ] if not (highest and lowest): irc.error('I have no karma for this channel.') return rank = self.db.rank(channel, msg.nick) if rank is not None: total = self.db.size(channel) rankS = format(' You (%s) are ranked %i out of %i.', msg.nick, rank, total) else: rankS = '' s = format('Highest karma: %L. Lowest karma: %L.%s', highest, lowest, rankS) irc.reply(s) karma = wrap(karma, ['channel', any('something')]) _mostAbbrev = utils.abbrev(['increased', 'decreased', 'active']) def most(self, irc, msg, args, channel, kind): """[<channel>] {increased,decreased,active} Returns the most increased, the most decreased, or the most active (the sum of increased and decreased) karma things. <channel> is only necessary if the message isn't sent in the channel itself. """ L = self.db.most(channel, kind, self.registryValue('mostDisplay', channel)) if L: L = [format('%q: %i', name, i) for (name, i) in L] irc.reply(format('%L', L)) else: irc.error('I have no karma for this channel.') most = wrap(most, ['channel', ('literal', ['increased', 'decreased', 'active'])]) def clear(self, irc, msg, args, channel, name): """[<channel>] <name> Resets the karma of <name> to 0. """ self.db.clear(channel, name) irc.replySuccess() clear = wrap(clear, [('checkChannelCapability', 'op'), 'text']) def dump(self, irc, msg, args, channel, filename): """[<channel>] <filename> Dumps the Karma database for <channel> to <filename> in the bot's data directory. <channel> is only necessary if the message isn't sent in the channel itself. """ self.db.dump(channel, filename) irc.replySuccess() dump = wrap(dump, [('checkCapability', 'owner'), 'channeldb', 'filename']) def load(self, irc, msg, args, channel, filename): """[<channel>] <filename> Loads the Karma database for <channel> from <filename> in the bot's data directory. <channel> is only necessary if the message isn't sent in the channel itself. """ self.db.load(channel, filename) irc.replySuccess() load = wrap(load, [('checkCapability', 'owner'), 'channeldb', 'filename'])
class Karma(callbacks.Plugin): callBefore = ('Factoids', 'MoobotFactoids', 'Infobot') def __init__(self, irc): self.__parent = super(Karma, self) self.__parent.__init__(irc) self.db = KarmaDB() def die(self): self.__parent.die() self.db.close() def _normalizeThing(self, thing): assert thing if thing[0] == '(' and thing[-1] == ')': thing = thing[1:-1] return thing def _respond(self, irc, channel): if self.registryValue('response', channel): irc.replySuccess() else: irc.noReply() def _doKarma(self, irc, channel, thing): assert thing[-2:] in ('++', '--', '+-', '-+') assert thing[:-2] not in ('<', '-', '<!') thing_end = thing[-2:] thing = thing[:-2].strip('\'"') if thing: if ircutils.strEqual(thing, irc.msg.nick) and \ not self.registryValue('allowSelfRating', channel): irc.error('You\'re not allowed to adjust your own karma.') else: if thing_end == '++': self.db.increment(channel, self._normalizeThing(thing)) elif thing_end == '--': self.db.decrement(channel, self._normalizeThing(thing)) else: self.db.increment(channel, self._normalizeThing(thing)) self.db.decrement(channel, self._normalizeThing(thing)) self._respond(irc, channel) # if thing.endswith('++'): # thing = thing[:-2] # thing = thing.strip('\'"') # if ircutils.strEqual(thing, irc.msg.nick) and \ # not self.registryValue('allowSelfRating', channel): # irc.error('You\'re not allowed to adjust your own karma.') # elif thing: # self.db.increment(channel, self._normalizeThing(thing)) # self._respond(irc, channel) # else: # thing = thing[:-2] # thing = thing.strip('\'"') # if ircutils.strEqual(thing, irc.msg.nick) and \ # not self.registryValue('allowSelfRating', channel): # irc.error('You\'re not allowed to adjust your own karma.') # elif thing: # self.db.decrement(channel, self._normalizeThing(thing)) # self._respond(irc, channel) def invalidCommand(self, irc, msg, tokens): channel = msg.args[0] if not irc.isChannel(channel): return # if tokens[-1][-2:] in ('++', '--'): # thing = ' '.join(tokens) # self._doKarma(irc, channel, thing) # let's be a little smarter about grabbing things # <bob> thanks. john++ you're awesome # should only increment 'john' for token in tokens: self._doKarma(irc, 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 \ self.registryValue('allowUnaddressedKarma', channel): irc = callbacks.SimpleProxy(irc, msg) # thing = msg.args[1].rstrip() # if thing[-2:] in ('++', '--'): # self._doKarma(irc, channel, thing) # same here as above in invalidCommand #for token in msg.args[1].split(): for token in smart_split(msg.args[1]): self._doKarma(irc, channel, token) def karma(self, irc, msg, args, channel, things): """[<channel>] [<thing> ...] Returns the karma of <text>. If <thing> is not given, returns the top three and bottom three karmas. If one <thing> is given, returns the details of its karma; if more than one <thing> is given, returns the total karma of each of the things. <channel> is only necessary if the message isn't sent on the channel itself. """ name = ' '.join(things) # if len(things) == 1: # name = things[0] if name: t = self.db.get(channel, name) if t is None: irc.reply(format('%s has neutral karma.', name)) else: (added, subtracted) = t total = added - subtracted if self.registryValue('simpleOutput', channel): s = format('%s: %i', name, total) else: s = format( 'Karma for %q has been increased %n and ' 'decreased %n for a total karma of %s.', name, (added, 'time'), (subtracted, 'time'), total) irc.reply(s) # elif len(things) > 1: # (L, neutrals) = self.db.gets(channel, things) # if L: # s = format('%L', [format('%s: %i', *t) for t in L]) # if neutrals: # neutral = format('. %L %h neutral karma', # neutrals, len(neutrals)) # s += neutral # irc.reply(s + '.') # else: # 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) top = self.db.top(channel, limit) highest = [ format('%q (%s)', s, t) for (s, t) in self.db.top(channel, limit) ] lowest = [ format('%q (%s)', s, t) for (s, t) in self.db.bottom(channel, limit) ] if not (highest and lowest): irc.error('I have no karma for this channel.') return rank = self.db.rank(channel, msg.nick) if rank is not None: total = self.db.size(channel) rankS = format(' You (%s) are ranked %i out of %i.', msg.nick, rank, total) else: rankS = '' s = format('Highest karma: %L. Lowest karma: %L.%s', highest, lowest, rankS) irc.reply(s) karma = wrap(karma, ['channel', any('something')]) _mostAbbrev = utils.abbrev(['increased', 'decreased', 'active']) def most(self, irc, msg, args, channel, kind): """[<channel>] {increased,decreased,active} Returns the most increased, the most decreased, or the most active (the sum of increased and decreased) karma things. <channel> is only necessary if the message isn't sent in the channel itself. """ L = self.db.most(channel, kind, self.registryValue('mostDisplay', channel)) if L: L = [format('%q: %i', name, i) for (name, i) in L] irc.reply(format('%L', L)) else: irc.error('I have no karma for this channel.') most = wrap(most, ['channel', ('literal', ['increased', 'decreased', 'active'])]) def clear(self, irc, msg, args, channel, name): """[<channel>] <name> Resets the karma of <name> to 0. """ self.db.clear(channel, name) irc.replySuccess() clear = wrap(clear, [('checkChannelCapability', 'op'), 'text']) def getName(self, nick, msg, match): addressed = callbacks.addressed(nick, msg) name = callbacks.addressed( nick, ircmsgs.IrcMsg(prefix='', args=(msg.args[0], match.group(1)), msg=msg)) if not name: name = match.group(1) if not addressed: if not self.registryValue('allowUnaddressedKarma'): return '' if not msg.args[1].startswith(match.group(1)): return '' name = match.group(1) elif addressed: if not addressed.startswith(name): return '' name = name.strip('()') return name def dump(self, irc, msg, args, channel, filename): """[<channel>] <filename> Dumps the Karma database for <channel> to <filename> in the bot's data directory. <channel> is only necessary if the message isn't sent in the channel itself. """ self.db.dump(channel, filename) irc.replySuccess() dump = wrap(dump, [('checkCapability', 'owner'), 'channeldb', 'filename']) def load(self, irc, msg, args, channel, filename): """[<channel>] <filename> Loads the Karma database for <channel> from <filename> in the bot's data directory. <channel> is only necessary if the message isn't sent in the channel itself. """ self.db.load(channel, filename) irc.replySuccess() load = wrap(load, [('checkCapability', 'owner'), 'channeldb', 'filename']) def karmawar(self, irc, msg, args, nick, dice): """<nick> [<dice>] Initiate a karma battle with another user """ if not dice: dice = 3 attack_rolls = ["%d" % self.rng.randrange(1, 6) for x in range(3)] irc.reply("%s rolls %d dice: %s" % (nick, dice, commaAndify(attack_rolls))) def_rols = ["%d" % self.rng.randrange(1, 6) for x in range(2)] irc.reply("%s rolls 2 dice: %s" % (nick, commaAndify(def_rolls))) karmawar = wrap(karmawar, ['nickInChannel', optional('nonNegativeInt')])
# POSSIBILITY OF SUCH DAMAGE. ### import re import rssparser import BeautifulSoup import supybot.conf as conf import supybot.utils as utils from supybot.commands import * import supybot.ircutils as ircutils import supybot.callbacks as callbacks unitAbbrevs = utils.abbrev(['Fahrenheit', 'Celsius', 'Centigrade', 'Kelvin']) unitAbbrevs['C'] = 'Celsius' unitAbbrevs['Ce'] = 'Celsius' noLocationError = 'No such location could be found.' class NoLocation(callbacks.Error): pass class Weather(callbacks.Plugin): weatherCommands = {'wunder': 'wunder.wunder', 'rsswunder': 'wunder.rss', 'wunder rss': 'wunder.rss', 'cnn': 'cnn', 'ham': 'ham'} threaded = True headers = {'User-agent': 'Mozilla/5.0 (compatible; U; utils.web python module)'} def callCommand(self, method, irc, msg, *args, **kwargs): try:
class Babelfish(callbacks.Plugin): threaded = True _abbrevs = utils.abbrev(map(str.lower, babelfish.available_languages)) _abbrevs['de'] = 'german' _abbrevs['jp'] = 'japanese' _abbrevs['kr'] = 'korean' _abbrevs['es'] = 'spanish' _abbrevs['pt'] = 'portuguese' _abbrevs['it'] = 'italian' _abbrevs['zh'] = 'chinese_simple' _abbrevs['zt'] = 'chinese_traditional' _abbrevs['nl'] = 'dutch' _abbrevs['el'] = 'greek' for language in babelfish.available_languages: _abbrevs[language] = language def _getLang(self, fromLang, toLang, chan): fromLang = self._abbrevs[fromLang.lower()] toLang = self._abbrevs[toLang.lower()] languages = map(str.lower, self.registryValue('languages', chan)) if fromLang not in languages: fromLang = None if toLang not in languages: toLang = None return (fromLang, toLang) class language(callbacks.Commands): def list(self, irc, msg, args): """takes no arguments Returns the languages that Babelfish can translate to/from. """ irc.reply(format('%L', babelfish.available_languages)) def random(self, irc, msg, args, optlist): """[--allow-english] Returns a random language supported by babelfish. If --allow-english is provided, will include English in the list of possible languages. """ allowEnglish = False for (option, arg) in optlist: if option == 'allow-english': allowEnglish = True languages = conf.get(conf.supybot.plugins.Babelfish.languages, msg.args[0]) if not languages: irc.error('I can\'t speak any other languages.', Raise=True) language = utils.iter.choice(languages) while not allowEnglish and language == 'English': language = utils.iter.choice(languages) irc.reply(language) random = wrap(random, [getopts({'allow-english': ''})]) def translate(self, irc, msg, args, fromLang, toLang, text): """<from-language> [to] <to-language> <text> Returns <text> translated from <from-language> into <to-language>. Beware that translating to or from languages that use multi-byte characters may result in some very odd results. """ chan = msg.args[0] try: (fromLang, toLang) = self._getLang(fromLang, toLang, chan) if not fromLang or not toLang: langs = list(self.registryValue('languages', chan)) if not langs: irc.error('I do not speak any other languages.') return else: irc.error(format('I only speak %L.', langs)) return translation = babelfish.translate(text, fromLang, toLang) irc.reply(utils.web.htmlToText(translation)) except (KeyError, babelfish.LanguageNotAvailableError), e: languages = self.registryValue('languages', chan) if languages: languages = format('Valid languages include %L', sorted(languages)) else: languages = 'I do not speak any other languages.' irc.errorInvalid('language', str(e), languages) except babelfish.BabelizerIOError, e: irc.error(str(e))
class NewKarma(callbacks.Plugin): callBefore = ('Factoids', 'MoobotFactoids', 'Infobot') def __init__(self, irc): self.__parent = super(NewKarma, self) self.__parent.__init__(irc) self.db = KarmaDB() self.alias_db = AliasDB() def die(self): self.__parent.die() self.db.close() self.alias_db.close() def _normalizeThing(self, thing): assert thing if thing[0] == '(' and thing[-1] == ')': thing = thing[1:-1] return thing def _respond(self, irc, channel, message=None): if self.registryValue('response', channel): if message: irc.reply(message, prefixNick=False) else: irc.replySuccess() else: irc.noReply() def _parseKarmaMessage(self, name, total, channel, originalname, direction): if direction == "up": message = self.registryValue('karmaMessageUp', channel) elif direction == "down": message = self.registryValue('karmaMessageDown', channel) elif direction == "none": message = self.registryValue('karmaMessageNone', channel) else: message = "I have no idea what you are talking about." if total == 1 or total == -1: message = message.replace('points', 'point') if originalname: name = "%s (%s)" % (originalname, name) return message.replace('USER', name).replace('TOTAL', str(total)) def _doAlias(self, irc, channel, things): name = things.split('is also known as')[0].split()[-1] alias = things.split('is also known as')[1].split()[0] self.alias_db.alias(channel, name, alias) irc.reply("%s is also %s, got it!" % (name, alias)) def _doUnalias(self, irc, channel, things): name = things.split('is no longer known as')[0].split()[-1] alias = things.split('is no longer known as')[1].split()[0] self.alias_db.unalias(channel, name, alias) irc.reply("Who? I've forgotten that %s was ever %s!" % (name, alias)) def _doKarma(self, irc, channel, things): for thing in things.split(): originalthing = None #if thing.endswith('++'): if "++" in thing: thing = thing.split("++")[0] if thing: #see if what we are incrementing is an alias for someone aliasfor = self.alias_db.get(channel, self._normalizeThing(thing)) if aliasfor: originalthing = thing thing = aliasfor if type(thing) != list: thing = [thing] for athing in thing: #Honor allowSelfRating unless this is a group alias if ircutils.strEqual(athing, irc.msg.nick) and \ not self.registryValue('allowSelfRating', channel) and \ len(thing) == 1: irc.error('You\'re not allowed to adjust your own karma.') else: self.db.increment(channel, self._normalizeThing(athing)) t = self.db.get(channel, athing) if t is None: total = 0 else: (added, subtracted) = t total = added - subtracted if total == 0: self._respond(irc, channel, self._parseKarmaMessage(athing, total, channel, originalthing, "none")) self.db.garbageCollect(channel, athing) else: self._respond(irc, channel, self._parseKarmaMessage(athing, total, channel, originalthing, "up")) #decrement unless some person has "--" in their name in channel elif "--" in thing and thing not in irc.state.channels[channel].users: #Hack for users with "--" in their name being given negative karma if thing[0:-2] in irc.state.channels[channel].users: thing = thing[0:-2] else: thing = thing.split("--")[0] if thing: #see if what we are incrementing is an alias for someone aliasfor = self.alias_db.get(channel, self._normalizeThing(thing)) if aliasfor: originalthing = thing thing = aliasfor if type(thing) != list: thing = [thing] for athing in thing: #Honor allowSelfRating unless this is a group alias if ircutils.strEqual(athing, irc.msg.nick) and \ not self.registryValue('allowSelfRating', channel) and \ len(thing) == 1: irc.error('You\'re not allowed to adjust your own karma.') else: self.db.decrement(channel, self._normalizeThing(athing)) t = self.db.get(channel, athing) if t is None: total = 0 else: (added, subtracted) = t total = added - subtracted if total == 0: self._respond(irc, channel, self._parseKarmaMessage(athing, total, channel, originalthing, "none")) self.db.garbageCollect(channel, athing) else: self._respond(irc, channel, self._parseKarmaMessage(athing, total, channel, originalthing, "down")) def invalidCommand(self, irc, msg, tokens): channel = msg.args[0] if not irc.isChannel(channel): return if tokens[-1][-2:] in ('++', '--'): thing = ' '.join(tokens) self._doKarma(irc, 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 \ not ircmsgs.isCtcp(msg) and \ self.registryValue('allowUnaddressedKarma', channel): irc = callbacks.SimpleProxy(irc, msg) thing = msg.args[1].rstrip() if '++' in thing or '--' in thing: self._doKarma(irc, channel, thing) if 'is also known as' in thing: self._doAlias(irc, channel, thing) if 'is no longer known as' in thing: self._doUnalias(irc, channel, thing) def showaliases(self, irc, msg, args, channel, name): """[<channel>] <word> Lists the karmaaliases for a given word. """ if name: name = name[0] aliases = self.alias_db.get_aliases(channel, name) if aliases: sep = ", " if len(aliases) > 2: aliases[-1] = "and %s" % (aliases[-1]) elif len(aliases) == 2: sep = " and " irc.reply("%s is known as %s." % (name, sep.join(aliases))) else: irc.reply("%s doesn't have any aliases!" % (name)) else: irc.reply("Give me *something*! A nick, a word, anything!") showaliases = wrap(showaliases, ['channel', any('something')]) def karma(self, irc, msg, args, channel, things): """[<channel>] [<thing> ...] Returns the karma of <thing>. If <thing> is not given, returns the top N karmas, where N is determined by the config variable supybot.plugins.Karma.rankingDisplay. If one <thing> is given, returns the details of its karma; if more than one <thing> is given, returns the total karma of each of the the things. <channel> is only necessary if the message isn't sent on the channel itself. """ if len(things) == 1: name = things[0] t = self.db.get(channel, name) if t is None: irc.reply(format('%s has neutral karma.', name)) else: (added, subtracted) = t total = added - subtracted if self.registryValue('simpleOutput', channel): s = format('%s: %i', name, total) else: s = format('Karma for %q has been increased %n and ' 'decreased %n for a total karma of %s.', name, (added, 'time'), (subtracted, 'time'), total) irc.reply(s) elif len(things) > 1: (L, neutrals) = self.db.gets(channel, things) if L: s = format('%L', [format('%s: %i', *t) for t in L]) if neutrals: neutral = format('. %L %h neutral karma', neutrals, len(neutrals)) s += neutral irc.reply(s + '.') else: 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) top = self.db.top(channel, limit) highest = [format('%q (%s)', s, t) for (s, t) in self.db.top(channel, limit)] lowest = [format('%q (%s)', s, t) for (s, t) in self.db.bottom(channel, limit)] if not (highest and lowest): irc.error('I have no karma for this channel.') return rank = self.db.rank(channel, msg.nick) if rank is not None: total = self.db.size(channel) rankS = format(' You (%s) are ranked %i out of %i.', msg.nick, rank, total) else: rankS = '' s = format('Highest karma: %L. Lowest karma: %L.%s', highest, lowest, rankS) irc.reply(s, prefixNick=False) karma = wrap(karma, ['channel', any('something')]) _mostAbbrev = utils.abbrev(['increased', 'decreased', 'active']) def most(self, irc, msg, args, channel, kind): """[<channel>] {increased,decreased,active} Returns the most increased, the most decreased, or the most active (the sum of increased and decreased) karma things. <channel> is only necessary if the message isn't sent in the channel itself. """ L = self.db.most(channel, kind, self.registryValue('mostDisplay', channel)) if L: L = [format('%q: %i', name, i) for (name, i) in L] irc.reply(format('%L', L)) else: irc.error('I have no karma for this channel.') most = wrap(most, ['channel', ('literal', ['increased', 'decreased', 'active'])]) def clear(self, irc, msg, args, channel, name): """[<channel>] <name> Resets the karma of <name> to 0. """ self.db.clear(channel, name) irc.replySuccess() clear = wrap(clear, [('checkChannelCapability', 'op'), 'text']) def dump(self, irc, msg, args, channel, filename): """[<channel>] <filename> Dumps the Karma database for <channel> to <filename> in the bot's data directory. <channel> is only necessary if the message isn't sent in the channel itself. """ self.db.dump(channel, filename) irc.replySuccess() dump = wrap(dump, [('checkCapability', 'owner'), 'channeldb', 'filename']) def load(self, irc, msg, args, channel, filename): """[<channel>] <filename> Loads the Karma database for <channel> from <filename> in the bot's data directory. <channel> is only necessary if the message isn't sent in the channel itself. """ self.db.load(channel, filename) irc.replySuccess() load = wrap(load, [('checkCapability', 'owner'), 'channeldb', 'filename'])
class Karma(callbacks.Plugin): """Provides a simple tracker for setting Karma (thing++, thing--).""" callBefore = ('Factoids', 'MoobotFactoids', 'Infobot') def __init__(self, irc): self.__parent = super(Karma, self) self.__parent.__init__(irc) self.db = KarmaDB() def die(self): self.__parent.die() self.db.close() def _normalizeThing(self, thing): assert thing if thing[0] == '(' and thing[-1] == ')': thing = thing[1:-1] return thing def _respond(self, irc, channel, thing, karma): if self.registryValue('response', channel): irc.reply(_('%(thing)s\'s karma is now %(karma)i') % {'thing': thing, 'karma': karma}) else: irc.noReply() def _doKarma(self, irc, msg, channel, thing): def get_karma_word(thing, suffixes): words = thing.split() karma_words = [word for word in words if word.endswith(tuple(suffixes))] if len(karma_words != 1): return None return karma_words[0] inc = self.registryValue('incrementChars', channel) dec = self.registryValue('decrementChars', channel) inc_word = get_karma_word(thing, inc) dec_word = get_karma_word(thing, dec) # Ignore if there is not an unambigous op if (inc_word is None) != (dec_word is None): return word = inc_word or dec_word cmp_word = self._normalizeThing(ircutils.toLower(word)) # Don't karma if self rating is disabled allow_self_rating = self.registryValue('allowSelfRating', channel) if not allow_self_rating and cmp_word == ircutils.toLower(msg.nick): irc.error(_('You\'re not allowed to adjust your own karma.')) return # Don't karma if the target isn't a nick only_nicks = self.registryValue('onlyNicks', channel) if only_nicks: users = [ircutils.toLower(user) for user in irc.state.channels[channel].users] if not cmp_word in users: return # Execute the karma op karma = '' if inc_word: self.db.increment(channel, cmp_word) karma = self.db.get(channel, cmp_word) else: self.db.decrement(channel, cmp_word) karma = self.db.get(channel, cmp_word) if karma: 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: thing = ' '.join(tokens) self._doKarma(irc, 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 \ not ircmsgs.isCtcp(msg) and \ self.registryValue('allowUnaddressedKarma', channel): irc = callbacks.SimpleProxy(irc, msg) thing = msg.args[1].rstrip() self._doKarma(irc, msg, channel, thing) @internationalizeDocstring def karma(self, irc, msg, args, channel, things): """[<channel>] [<thing> ...] Returns the karma of <thing>. If <thing> is not given, returns the top N karmas, where N is determined by the config variable supybot.plugins.Karma.rankingDisplay. If one <thing> is given, returns the details of its karma; if more than one <thing> is given, returns the total karma of each of the things. <channel> is only necessary if the message isn't sent on the channel itself. """ if len(things) == 1: name = things[0] t = self.db.get(channel, name) if t is None: irc.reply(format(_('%s has neutral karma.'), name)) else: (added, subtracted) = t total = added - subtracted if self.registryValue('simpleOutput', channel): s = format('%s: %i', name, total) else: s = format(_('Karma for %q has been increased %n and ' 'decreased %n for a total karma of %s.'), name, (added, _('time')), (subtracted, _('time')), total) irc.reply(s) elif len(things) > 1: (L, neutrals) = self.db.gets(channel, things) if L: s = format('%L', [format('%s: %i', *t) for t in L]) if neutrals: neutral = format('. %L %h neutral karma', neutrals, len(neutrals)) s += neutral irc.reply(s + '.') else: 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) top = self.db.top(channel, limit) highest = [format('%q (%s)', s, t) for (s, t) in self.db.top(channel, limit)] lowest = [format('%q (%s)', s, t) for (s, t) in self.db.bottom(channel, limit)] if not (highest and lowest): irc.error(_('I have no karma for this channel.')) return rank = self.db.rank(channel, msg.nick) if rank is not None: total = self.db.size(channel) rankS = format(_(' You (%s) are ranked %i out of %i.'), msg.nick, rank, total) else: rankS = '' s = format(_('Highest karma: %L. Lowest karma: %L.%s'), highest, lowest, rankS) irc.reply(s) karma = wrap(karma, ['channel', any('something')]) _mostAbbrev = utils.abbrev(['increased', 'decreased', 'active']) @internationalizeDocstring def most(self, irc, msg, args, channel, kind): """[<channel>] {increased,decreased,active} Returns the most increased, the most decreased, or the most active (the sum of increased and decreased) karma things. <channel> is only necessary if the message isn't sent in the channel itself. """ L = self.db.most(channel, kind, self.registryValue('mostDisplay', channel)) if L: L = [format('%q: %i', name, i) for (name, i) in L] irc.reply(format('%L', L)) else: irc.error(_('I have no karma for this channel.')) most = wrap(most, ['channel', ('literal', ['increased', 'decreased', 'active'])]) @internationalizeDocstring def clear(self, irc, msg, args, channel, name): """[<channel>] [<name>] Resets the karma of <name> to 0. If <name> is not given, resets everything. """ self.db.clear(channel, name or None) irc.replySuccess() clear = wrap(clear, [('checkChannelCapability', 'op'), optional('text')]) @internationalizeDocstring def dump(self, irc, msg, args, channel, filename): """[<channel>] <filename> Dumps the Karma database for <channel> to <filename> in the bot's data directory. <channel> is only necessary if the message isn't sent in the channel itself. """ self.db.dump(channel, filename) irc.replySuccess() dump = wrap(dump, [('checkCapability', 'owner'), 'channeldb', 'filename']) @internationalizeDocstring def load(self, irc, msg, args, channel, filename): """[<channel>] <filename> Loads the Karma database for <channel> from <filename> in the bot's data directory. <channel> is only necessary if the message isn't sent in the channel itself. """ self.db.load(channel, filename) irc.replySuccess() load = wrap(load, [('checkCapability', 'owner'), 'channeldb', 'filename'])
try: # The 3rd party simplejson module was included in Python 2.6 and renamed to # json. Unfortunately, this conflicts with the 3rd party json module. # Luckily, the 3rd party json module has a different interface so we test # to make sure we aren't using it. if simplejson is None or hasattr(simplejson, 'read'): simplejson = utils.python.universalImport('simplejson', 'local.simplejson') except ImportError: raise callbacks.Error, \ 'You need Python2.6 or the simplejson module installed to use ' \ 'this plugin. Download the module at ' \ '<http://undefined.org/python/#simplejson>.' unitAbbrevs = utils.abbrev(['Fahrenheit', 'Celsius', 'Centigrade', 'Kelvin']) unitAbbrevs['C'] = 'Celsius' unitAbbrevs['Ce'] = 'Celsius' noLocationError = 'No such location could be found.' class NoLocation(callbacks.Error): pass class Weather(callbacks.Plugin): weatherCommands = ('wunder', 'wunder rss', 'cnn', 'ham') threaded = True def callCommand(self, method, irc, msg, *args, **kwargs):