diff --git a/Bugtracker/__init__.py b/Bugtracker/__init__.py index e95d86c..0e18efb 100644 --- a/Bugtracker/__init__.py +++ b/Bugtracker/__init__.py @@ -24,7 +24,7 @@ import supybot.world as world from imp import reload -__version__ = "2.7.0" +__version__ = "2.8.0" __author__ = supybot.Author("Krytarik Raido", "krytarik", "krytarik@tuxgarage.com") __contributors__ = { supybot.Author("Dennis Kaarsemaker", "Seveas", "dennis@kaarsemaker.net"): ['Original Author'], diff --git a/Bugtracker/config.py b/Bugtracker/config.py index ac847f4..83067e9 100644 --- a/Bugtracker/config.py +++ b/Bugtracker/config.py @@ -33,25 +33,25 @@ def configure(advanced): def getRepeatdelay(): output("How many seconds should the bot wait before repeating bug information?") - repeatdelay = something("Enter a number greater or equal to 0", default=Bugtracker.repeatdelay._default) + repeatdelay = something("Enter a number greater or equal to 0.", default=Bugtracker.repeatdelay._default) try: repeatdelay = int(repeatdelay) if repeatdelay < 0: raise TypeError except TypeError: - output("%r is an invalid value, it must be an integer greater or equal to 0" % repeatdelay) + output("Invalid value '%s', it must be an integer greater or equal to 0." % repeatdelay) return getRepeatdelay() else: return repeatdelay - output("Each of the next 3 questions can be set per-channel with the '@Config channel' command") + output("Each of the next 3 questions can be set per-channel with the '@config channel' command.") bugSnarfer = yn("Enable detecting bugs numbers and URL in all channels?", default=Bugtracker.bugSnarfer._default) cveSnarfer = yn("Enable detecting CVE numbers and URL in all channels?", default=Bugtracker.cveSnarfer._default) oopsSnarfer = yn("Enable detecting Launchpad OOPS IDs in all channels?", default=Bugtracker.oopsSnarfer._default) if advanced: - replyNoBugtracker = something("What should the bot reply with when a a user requests information from an unknown bug tracker?", default=Bugtracker.replyNoBugtracker._default) - snarfTarget = something("What should be the default bug tracker used when one isn't specified?", default=Bugtracker.snarfTarget._default) + replyNoBugtracker = something("What should the bot reply with when a user requests information from an unknown bug tracker?", default=Bugtracker.replyNoBugtracker._default) + snarfTarget = something("What should be the default bug tracker used when none is specified?", default=Bugtracker.snarfTarget._default) replyWhenNotFound = yn("Should the bot report when a bug is not found?", default=Bugtracker.replyWhenNotFound._default) repeatdelay = getRepeatdelay() else: @@ -77,7 +77,7 @@ Bugtracker = conf.registerPlugin('Bugtracker') conf.registerChannelValue(Bugtracker, 'bugSnarfer', registry.Boolean(False, """Determines whether the bug snarfer will be - enabled, such that any Bugtracker URLs and bug ### seen in the channel + enabled, such that any bugtracker URLs and bug ### seen in the channel will have their information reported into the channel.""")) conf.registerChannelValue(Bugtracker, 'cveSnarfer', @@ -90,11 +90,8 @@ conf.registerChannelValue(Bugtracker, 'oopsSnarfer', enabled, such that any OOPS ### seen in the channel will have their information reported into the channel.""")) -#conf.registerChannelValue(Bugtracker, 'bugReporter', -# registry.String('', """Report new bugs (experimental)""")) - conf.registerChannelValue(Bugtracker, 'replyNoBugtracker', - registry.String('I don\'t have a bugtracker %s.', """Determines the phrase + registry.String("I have no bugtracker '%s'", """Determines the phrase to use when notifying the user that there is no information about that bugtracker site.""")) @@ -116,20 +113,4 @@ conf.registerChannelValue(Bugtracker, 'showassignee', registry.Boolean(False, """Whether to show the assignee in bug reports""")) conf.registerChannelValue(Bugtracker, 'extended', - registry.Boolean(False, "Show optional extneded bug information, specific to trackers")) - -#conf.registerGlobalValue(Bugtracker, 'reportercache', -# registry.String('', """Name of the basedir for the bugreporter cache""", private=True)) - -#conf.registerGlobalValue(Bugtracker, 'imap_server', -# registry.String('', """IMAP server for bugmail account""",private=True)) - -#conf.registerGlobalValue(Bugtracker, 'imap_user', -# registry.String('', """IMAP user for bugmail account""", private=True)) - -#conf.registerGlobalValue(Bugtracker, 'imap_password', -# registry.String('', """IMAP password for bugmail account""", private=True)) - -#conf.registerGlobalValue(Bugtracker, 'imap_ssl', -# registry.Boolean(False, """Use SSL for imap connections""", private=True)) - + registry.Boolean(False, """Whether to show extended bug information, specific to trackers""")) diff --git a/Bugtracker/plugin.py b/Bugtracker/plugin.py index bd5cd5d..113eaaf 100644 --- a/Bugtracker/plugin.py +++ b/Bugtracker/plugin.py @@ -17,34 +17,19 @@ from supybot.commands import * import supybot.utils as utils -import supybot.ircmsgs as ircmsgs import supybot.ircutils as ircutils +import supybot.ircdb as ircdb import supybot.callbacks as callbacks import supybot.conf as conf import supybot.registry as registry -import supybot.schedule as schedule import supybot.log as supylog -from functools import cmp_to_key - -#import imaplib -import re, os, sys, time, subprocess +import re, os, sys, time import xml.dom.minidom as minidom -from html.entities import entitydefs as entities from email.parser import FeedParser if sys.version_info < (3,0): from SOAPpy.Client import SOAPProxy -# All the words below will be censored when reporting bug information -bad_words = set(["fuck","fuk","fucking","fuking","fukin","fuckin","fucked","fuked","fucker","shit","cunt","bastard","nazi","nigger","nigga","cock","bitches","bitch"]) - -def makeClean(s): - words = s.split() - for word in words: - if word.lower() in bad_words: - words[words.index(word)] = "" - return " ".join(words) - def registerBugtracker(name, url='', description='', trackertype=''): conf.supybot.plugins.Bugtracker.bugtrackers().add(name) group = conf.registerGroup(conf.supybot.plugins.Bugtracker.bugtrackers, name) @@ -56,12 +41,48 @@ def registerBugtracker(name, url='', description='', trackertype=''): if description: DESC.setValue(description) if trackertype: - if trackertype.lower() in defined_bugtrackers: - TRACKERTYPE.setValue(trackertype.lower()) + if trackertype in defined_bugtrackers: + TRACKERTYPE.setValue(trackertype) else: raise BugtrackerError("Unknown trackertype: %s" % trackertype) - -entre = re.compile('&(\S*?);') + +def defaultIgnored(hostmask, recipient): + if not conf.supybot.defaultIgnore(): + return False + if conf.version <= '0.83.4.1' \ + and ircutils.isChannel(recipient): + return False + try: + user = ircdb.users.getUser(hostmask) + except KeyError: + return True + return False + +def checkIgnored(hostmask, recipient): + try: + user = ircdb.users.getUser(hostmask) + if user._checkCapability('owner'): + return False + elif user.ignore: + return True + except KeyError: + pass + if ircdb.ignores.checkIgnored(hostmask): + return True + if ircutils.isChannel(recipient): + c = ircdb.channels.getChannel(recipient) + if c.checkIgnored(hostmask): + return True + return False + +def checkAddressed(text, channel): + if channel: + if text[0] in str(conf.supybot.reply.whenAddressedBy.chars.get(channel)): + return True + elif text[0] in conf.supybot.reply.whenAddressedBy.chars(): + return True + return False + def _getnodetxt(node): L = [] for childnode in node.childNodes: @@ -77,26 +98,14 @@ def _getnodetxt(node): val = val.decode('base64') except: val = 'Cannot convert bug data from base64.' - while entre.search(val): - entity = entre.search(val).group(1) - if entity in entities: - val = entre.sub(entities[entity], val) - else: - val = entre.sub('?', val) - return val + return utils.web.htmlToText(val, tagReplace='') def _getnodeattr(node, attr): if node.hasAttribute(attr): val = node.getAttribute(attr) else: raise ValueError("No such attribute") - while entre.search(val): - entity = entre.search(val).group(1) - if entity in entities: - val = entre.sub(entities[entity], val) - else: - val = entre.sub('?', val) - return val + return utils.web.htmlToText(val, tagReplace='') class BugtrackerError(Exception): """A bugtracker error""" @@ -106,50 +115,30 @@ class BugNotFoundError(Exception): """Pity, bug isn't there""" pass -cvere = re.compile(r'(.*?)\s*', re.I | re.DOTALL) +cvere = re.compile(r']*>Description.*?]*>\s*(?P.*?)\s*', re.I | re.DOTALL) +cverre = re.compile(r']*>\s*(?P.*?)\s*', re.I | re.DOTALL) class Bugtracker(callbacks.PluginRegexp): """Show a link to a bug report with a brief description""" threaded = True - callBefore = ['URL'] - regexps = ['turlSnarfer', 'bugSnarfer', 'oopsSnarfer', 'cveSnarfer'] + callBefore = ('URL') + regexps = ('turlSnarfer', 'bugSnarfer', 'cveSnarfer', 'oopsSnarfer') def __init__(self, irc): - callbacks.PluginRegexp.__init__(self, irc) + self.__parent = super(Bugtracker, self) + self.__parent.__init__(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: self.db[name] = defined_bugtrackers[group.trackertype()](name, group.url(), group.description()) else: - self.log.warning("Bugtracker: Unknown trackertype: %s (%s)" % (group.trackertype(), name)) + supylog.warning("Bugtracker: Unknown trackertype: %s (%s)" % (group.trackertype(), name)) self.shorthand = utils.abbrev(list(self.db.keys())) self.shown = {} -# # Schedule bug reporting -# #TODO: Remove everything below this line -# if self.registryValue('imap_server') and self.registryValue('reportercache'): -# try: -# schedule.removeEvent(self.name() + '.bugreporter') -# except: -# pass -# schedule.addPeriodicEvent(lambda: self.reportnewbugs(irc), 60, name=self.name() + '.bugreporter') -# self.events += [self.name() + '.bugreporter'] -# self.log.info('Bugtracker: Adding scheduled event "%s.bugreporter"' % self.name()) - - def die(self): #TODO: Remove me - pass -# try: -# for event in self.events: -# self.log.info('Bugtracker: Removing scheduled event "%s"' % event) -# schedule.removeEvent(event) -# schedule.removeEvent(self.name()) -# except: -# pass - def is_ok(self, channel, tracker, bug): - '''Flood/repeat protection''' + """Flood/repeat protection""" now = time.time() for k in list(self.shown.keys()): if self.shown[k] < now - self.registryValue('repeatdelay', channel): @@ -159,109 +148,15 @@ class Bugtracker(callbacks.PluginRegexp): return True return False - def is_new(self, tracker, tag, id): #Depricated - pass -# bugreporter_base = self.registryValue('reportercache') -# if not os.path.exists(os.path.join(bugreporter_base,tag,tracker.name,str(int(id/1000)),str(id))): -# try: -# os.makedirs(os.path.join(bugreporter_base,tag,tracker.name,str(int(id/1000)))) -# except: -# pass -# fd = open(os.path.join(bugreporter_base,tag,tracker.name,str(int(id/1000)),str(id)),'w') -# fd.close() -# return True -# return False - - def reportnewbugs(self,irc): #Depricated - pass -# # Compile list of bugs -# self.log.info("Bugtracker: Checking for new bugs") -# bugs = {} -# if self.registryValue('imap_ssl'): -# sc = imaplib.IMAP4_SSL(self.registryValue('imap_server')) -# else: -# sc = imaplib.IMAP4(self.registryValue('imap_server')) -# sc.login(self.registryValue('imap_user'), self.registryValue('imap_password')) -# sc.select('INBOX') -# new_mail = sc.search(None, '(UNSEEN)')[1][0].split()[:20] -# -# # Read all new mail -# for m in new_mail: -# msg = sc.fetch(m, 'RFC822')[1][0][1] -# fp = FeedParser() -# sc.store(m, '+FLAGS', "(\Deleted)") # Mark message deleted so we don't have to process it again -# fp.feed(msg) -# bug = fp.close() -# tag = None -# -# if 'X-Launchpad-Bug' not in bug.keys(): -# self.log.info('Bugtracker: Ignoring e-mail with no detectable bug (Not from Launchpad)') -# continue -# else: -# tag = bug['X-Launchpad-Bug'] -# if 'distribution=' not in tag and 'product=' not in tag: -# self.log.info('Bugtracker: Ignoring e-mail with no detectable bug (no distro/product)') -# continue -# else: -# tag = tag.split(';')[0].strip().replace("product=",'').replace("distribution=","") -# -# if not tag: -# self.log.info('Bugtracker: Ignoring e-mail with no detectible bug (bad tag)') -# -# tag = tag[tag.find('+')+1:tag.find('@')] -# if tag not in bugs: -# bugs[tag] = {} -# -# # Determine bugtracker type (currently only Launchpad is supported anyway) -# if bug['X-Launchpad-Bug']: -# tracker = self.db['launchpad'] -# id = int(bug['Reply-To'].split()[1]) -# subj = bug['Subject']; -# if '[NEW]' not in subj: #Not a new bug -# continue -# if self.is_new(tracker, tag, id): -# component = bug['X-Launchpad-Bug'] -# if 'component' in component: -# component = component[component.find('component=')+10:] -# component = component[:component.find(';')].replace('None','') -# else: -# component = '' -# try: -# if component: -# bugs[tag][id] = self.get_bug('',tracker, id, False)[0].replace('"','(%s) "' % component, 1) -# else: -# bugs[tag][id] = self.get_bug('',tracker, id, False)[0] -# if '[apport]' in bugs[tag][id]: -# bugs[tag].pop(id) -# except: -# self.log.info("Bugtracker: Unable to get new bug %d" % id) -# pass -# else: -# self.log.info('Bugtracker: Ignoring e-mail with no detectable bug') -# -# reported_bugs = 0 -# -# for c in irc.state.channels: -# tags = self.registryValue('bugReporter', channel=c) -# if not tags: -# continue -# for tag in tags.split(','): -# if not tag or tag not in bugs.keys(): -# continue -# for b in sorted(bugs[tag].keys()): -# irc.queueMsg(ircmsgs.privmsg(c,'New bug: #%s' % bugs[tag][b][bugs[tag][b].find('bug ')+4:])) -# reported_bugs = reported_bugs+1 - def add(self, irc, msg, args, name, trackertype, url, description): """ [] - Add a bugtracker to the list of defined bugtrackers. is the - type of the tracker (currently only Launchpad, Debbugs, Bugzilla, - Issuezilla, Mantis and Trac are known). is the name that will be used to - reference the bugzilla in all commands. Unambiguous abbreviations of - will be accepted also. is the common name for the - bugzilla and will be listed with the bugzilla query; if not given, it - defaults to . + Add a bugtracker to the list of defined bugtrackers. Currently + supported types are Launchpad, Debbugs, Bugzilla, Mantis, and Trac. + will be used to reference the bugtracker in all commands. + Unambiguous abbreviations of it will also be accepted. + will be used to reference the bugtracker in the + query result. If not given, it defaults to . """ name = name.lower() if not description: @@ -270,7 +165,7 @@ class Bugtracker(callbacks.PluginRegexp): url = url[:-1] trackertype = trackertype.lower() if trackertype in defined_bugtrackers: - self.db[name] = defined_bugtrackers[trackertype](name,url,description) + self.db[name] = defined_bugtrackers[trackertype](name, url, description) else: irc.error("Bugtrackers of type '%s' are not understood" % trackertype) return @@ -307,7 +202,7 @@ class Bugtracker(callbacks.PluginRegexp): d = group.description() if newdesc: d = newdesc - self.db[newname] = defined_bugtrackers[group.trackertype()](name,group.url(),d) + 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) @@ -316,7 +211,7 @@ class Bugtracker(callbacks.PluginRegexp): except KeyError: s = self.registryValue('replyNoBugtracker', msg.args[0] if ircutils.isChannel(msg.args[0]) else None) irc.error(s % name) - rename = wrap(rename, [('checkCapability', 'admin'), 'something','something', additional('text')]) + rename = wrap(rename, [('checkCapability', 'admin'), 'something', 'something', additional('text')]) def list(self, irc, msg, args, name): """[abbreviation] @@ -328,9 +223,9 @@ class Bugtracker(callbacks.PluginRegexp): name = name.lower() try: name = self.shorthand[name] - (url, description, type) = (self.db[name].url, self.db[name].description, + (url, description, trackertype) = (self.db[name].url, self.db[name].description, self.db[name].__class__.__name__) - irc.reply('%s: %s, %s [%s]' % (name, description, url, type)) + irc.reply('%s: %s, %s [%s]' % (name, description, url, trackertype)) except KeyError: s = self.registryValue('replyNoBugtracker', msg.args[0] if ircutils.isChannel(msg.args[0]) else None) irc.error(s % name) @@ -343,51 +238,48 @@ class Bugtracker(callbacks.PluginRegexp): irc.reply('I have no defined bugtrackers.') list = wrap(list, [additional('text')]) + def inFilter(self, irc, msg): + if not defaultIgnored(msg.prefix, msg.args[0]): + return msg + if checkIgnored(msg.prefix, msg.args[0]): + return msg + if msg.command == 'PRIVMSG': + self.doPrivmsg(irc, msg) + return msg + def bugSnarfer(self, irc, msg, match): - r"""\b(?P(([a-z0-9]+)?\s+bugs?|[a-z0-9]+)):?\s+#?(?P\d+(?!\d*[\-\.]\d+)((,|\s*(and|en|et|und|ir))\s*#?\d+(?!\d*[\-\.]\d+))*)""" + r"(?P[a-z][^\s:]*(\s+bugs?)?):*\s+#?(?P\d+(?!\d*[-.]\d+)(\s*([,\s]+|[,\s]*(and|und|en|et|ir|[&+]+))\s*#?\d+(?!\d*[-.]\d+))*)" channel = msg.args[0] if ircutils.isChannel(msg.args[0]) else None + if checkAddressed(msg.args[1].strip(), channel): + return if not self.registryValue('bugSnarfer', channel): return - nbugs = msg.tagged('nbugs') - if not nbugs: nbugs = 0 + nbugs = msg.tagged('nbugs') or 0 if nbugs >= 5: return - # Don't double on commands - s = str(msg).split(':')[2] - if s and s[0] in str(conf.supybot.reply.whenAddressedBy.chars): - return - - sure_bug = match.group('bt').endswith('bug') or match.group('bt').endswith('bug') - - # Get tracker name - bugids = match.group('bug') - reps = ((' ',''),('#',''),('and',','),('en',','),('et',','),('und',','),('ir',',')) - for r in reps: - bugids = bugids.replace(r[0],r[1]) - bugids = bugids.split(',')[:5-nbugs] + bugids = re.split(r'[^\d]+', match.group('bug'))[:5-nbugs] # Begin HACK - # strings like "ubuntu 1004" and "ubuntu 1010" are false triggers for us - # filter out bug number that are 4 numbers, start with '1' and end in '04' or '10 - # (let's fix this for 2020 ;) + # Strings like "Ubuntu 1004" and "Ubuntu 1610" are false triggers for us if match.group('bt').lower() == 'ubuntu': - bugids = [bugnum for bugnum in bugids if not (len(bugnum) == 4 and bugnum[0] == '1' and bugnum[2:] in ('04', '10'))] + bugids = [x for x in bugids if not re.match(r'^([4-9]|[12][0-9])(04|10)$', x)] # End HACK + # Get tracker name + bt = [x.lower() for x in match.group('bt').split()] + sure_bug = bt[-1] in ('bug', 'bugs') + + bugids = list(set(bugids)) # remove dupes + if not sure_bug: bugids = [x for x in bugids if int(x) > 100] - bugids = list(set(bugids)) ## remove dups - msg.tag('nbugs', nbugs + len(bugids)) - bt = [x.lower() for x in match.group('bt').split()] - # Strip off trailing ':' from the tracker name. Allows for (LP: #nnnnnn) - if bt and bt[0].endswith(':'): - bt[0] = bt[:-1] + name = '' showTracker = True - if len(bt) == 1 and not (bt[0] in ['bug','bugs']): + if len(bt) == 1 and not sure_bug: try: name = bt[0] tracker = self.db[name] @@ -399,147 +291,124 @@ class Bugtracker(callbacks.PluginRegexp): tracker = self.db[name] except: name = '' - pass + if not name: showTracker = False snarfTarget = self.registryValue('snarfTarget', channel) if not snarfTarget: - self.log.warning("Bugtracker: no snarfTarget for Bugtracker") + supylog.warning("Bugtracker: No snarfTarget set") return try: name = self.shorthand[snarfTarget.lower()] + tracker = self.db[name] except: - s = self.registryValue('replyNoBugtracker', name) - irc.error(s % name) - try: - tracker = self.db[name] - except KeyError: - s = self.registryValue('replyNoBugtracker', name) - irc.error(s % name) - else: - for bugid in bugids: - bugid = int(bugid) - try: - report = self.get_bug(channel or msg.nick, tracker, bugid, self.registryValue('showassignee', channel), - self.registryValue('extended', channel), do_tracker=showTracker) - except BugNotFoundError: - if self.registryValue('replyWhenNotFound'): - irc.error("%s bug %d could not be found" % (tracker.description, bugid)) - except BugtrackerError as e: -# if 'private' in str(e): -# irc.reply("Bug %d on https://launchpad.net/bugs/%d is private" % (bugid, bugid)) -# return - if not sure_bug and bugid < 30: - return - irc.error(str(e)) - else: - for r in report: - irc.reply(makeClean(r), prefixNick=False) + s = self.registryValue('replyNoBugtracker', name) + irc.error(s % name) + return + + for bugid in bugids: + bugid = int(bugid) + try: + report = self.get_bug(channel or msg.nick, tracker, bugid, self.registryValue('showassignee', channel), + self.registryValue('extended', channel), do_tracker=showTracker) + except BugNotFoundError: + if self.registryValue('replyWhenNotFound'): + irc.error("Could not find %s bug %d" % (tracker.description, bugid)) + except BugtrackerError as e: + if not sure_bug and bugid < 30: + return + irc.error(str(e)) + else: + for r in report: + irc.reply(r) def turlSnarfer(self, irc, msg, match): - r"(?Phttps?://\S*?)/(?:Bugs/0*|str.php\?L|show_bug.cgi\?id=|bugreport.cgi\?bug=|(?:bugs|\+bug)/|ticket/|tracker/|\S*aid=|bug=)?(?P\d+)(?P&group_id=\d+&at_id=\d+)?" + r"(https?://)?((bugs\.debian\.org|pad\.lv)/|\S+/(show_bug\.cgi\?id=|bugreport\.cgi\?bug=|view\.php\?id=|bug=|bugs/|\+bug/|ticket/))(?P\d+)" channel = msg.args[0] if ircutils.isChannel(msg.args[0]) else None + if checkAddressed(msg.args[1].strip(), channel): + return if not self.registryValue('bugSnarfer', channel): return - nbugs = msg.tagged('nbugs') - if not nbugs: nbugs = 0 + nbugs = msg.tagged('nbugs') or 0 if nbugs >= 5: return msg.tag('nbugs', nbugs+1) + url = match.group(0) + if '://' in url: + url = url[url.rfind('://')+3:] try: - tracker = self.get_tracker(match.group(0),match.group('sfurl')) + tracker = self.get_tracker(url) if not tracker: return report = self.get_bug(channel or msg.nick, tracker, int(match.group('bug')), self.registryValue('showassignee', channel), self.registryValue('extended', channel), do_url=False) except BugtrackerError as e: irc.error(str(e)) - except BugNotFoundError as e: - irc.error("%s bug %s not found" % (tracker, match.group('bug'))) + except BugNotFoundError: + if self.registryValue('replyWhenNotFound'): + irc.error("Could not find %s bug %s" % (tracker.description, match.group('bug'))) else: for r in report: - irc.reply(makeClean(r), prefixNick=False) + irc.reply(r) - # Only useful for launchpad developers + # Only useful to Launchpad developers def oopsSnarfer(self, irc, msg, match): - r"(?:https?://pad.lv/){0}?OOPS-(?P\d*[\dA-Z]{3,})" + r"(https?://\S+[=/])?OOPS-(?P[\dA-Za-z]{6,})" channel = msg.args[0] if ircutils.isChannel(msg.args[0]) else None + if checkAddressed(msg.args[1].strip(), channel): + return if not self.registryValue('bugSnarfer', channel) or not self.registryValue('oopsSnarfer', channel): return - oopsid = match.group(1) - if oopsid.lower() == "tools": - return + oopsid = match.group('oopsid') if not self.is_ok(channel or msg.nick, 'lpoops', oopsid): return - irc.reply('https://oops.canonical.com/?oopsid=OOPS-%s' % oopsid, prefixNick=False) + if not match.group(1): + irc.reply('https://oops.canonical.com/?oopsid=OOPS-%s' % oopsid) def cveSnarfer(self, irc, msg, match): - r"(cve[- ]\d{4}[- ]\d{4,})" + r"(https?://\S+=)?CVE[- ](?P\d{4}[- ]\d{4,})" channel = msg.args[0] if ircutils.isChannel(msg.args[0]) else None + if checkAddressed(msg.args[1].strip(), channel): + return if not self.registryValue('bugSnarfer', channel) or not self.registryValue('cveSnarfer', channel): return - cve = match.group(1).replace(' ','-').upper() - if not self.is_ok(channel or msg.nick, 'cve', cve): + cveid = match.group('cveid').replace(' ','-') + if not self.is_ok(channel or msg.nick, 'cve', cveid): return - url = 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=%s' % cve - cvedata = utils.web.getUrl(url).decode('utf-8') + url = 'https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-%s' % cveid + try: + cvedata = utils.web.getUrl(url).decode('utf-8') + except Exception as e: + raise BugtrackerError('Could not get CVE data: %s (%s)' % (e, url)) m = cvere.search(cvedata) if m: - cve = m.group(1).replace('\n', ' ') + cve = utils.web.htmlToText(m.group('cve'), tagReplace='') if len(cve) > 380: cve = cve[:380] + '...' - irc.reply("%s <%s>" % (cve, url), prefixNick=False) + if not match.group(1): + cve += ' <%s>' % url + irc.reply(cve) + else: + m = cverre.search(cvedata) + if m: + cverr = utils.web.htmlToText(m.group('cverr'), tagReplace='') + irc.reply(cverr) -#TODO: as we will depend on launchpadlib, we should consider using lazr.uri.URI to do URL parsing - 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 + #TODO: As we will depend on launchpadlib, we should consider using lazr.uri.URI to do URL parsing + def get_tracker(self, snarfurl): + # Launchpad URL shortening + snarfurl = re.sub(r'pad\.lv/(bug=)?(?P[0-9]+)', r'launchpad.net/bugs/\g', snarfurl) for t in list(self.db.keys()): tracker = self.db.get(t, None) if not tracker: - self.log.error("No tracker for key %r" % t) + supylog.error("No tracker for key '%s'" % 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: + url = tracker.url[tracker.url.rfind('://')+3:] + if url in snarfurl: 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 + # No tracker found, bummer. Let's try and get one if 'show_bug.cgi' in snarfurl: tracker = Bugzilla().get_tracker(snarfurl) if tracker: @@ -562,9 +431,9 @@ class Bugtracker(callbacks.PluginRegexp): continue if do_tracker: - report = '%s bug %s' % (tracker.description, bid) + report = '%s bug %d' % (tracker.description, bid) else: - report = 'Bug %s' % bid + report = 'Bug %d' % bid if product: report += ' in %s' % product @@ -607,7 +476,8 @@ class IBugtracker: self.name = name self.url = url self.description = description - self.log = supylog # Convenience log wrapper + self.errget = 'Could not get data from %s: %s (%s)' + self.errparse = 'Could not parse data from %s: %s (%s)' def get_bug(self, id): raise BugTrackerError("Bugtracker class does not implement get_bug") @@ -629,37 +499,28 @@ class IBugtracker: class Bugzilla(IBugtracker): def get_tracker(self, url): - url += '&ctype=xml' try: - bugxml = utils.web.getUrl(url) - tree = minidom.parseString(bugxml) - url = str(tree.getElementsByTagName('bugzilla')[0].attributes['urlbase'].childNodes[0].data) - if url[-1] == '/': - url = url[:-1] - name = url[url.find('//') + 2:] - if '/' in name: - name = name[:name.find('/')] - desc = name - registerBugtracker(name, url, desc, 'bugzilla') - tracker = Bugzilla(name, url, desc) - return tracker + match = re.match(r'(?P(?P[^\s/]+).*)/show_bug\.cgi', url) + name = desc = match.group('name') + url = 'https://%s' % match.group('url') +# registerBugtracker(name, url, desc, 'bugzilla') + return Bugzilla(name, url, desc) except: return None def get_bug(self, id): - url = "%s/show_bug.cgi?id=%d&ctype=xml" % (self.url,id) + url = "%s/show_bug.cgi?id=%d&ctype=xml" % (self.url, id) try: bugxml = utils.web.getUrl(url) zilladom = minidom.parseString(bugxml) except Exception as e: - s = 'Could not parse XML returned by %s: %s (%s)' % (self.description, e, url) - raise BugtrackerError(s) + raise BugtrackerError(self.errget % (self.description, e, url)) bug_n = zilladom.getElementsByTagName('bug')[0] if bug_n.hasAttribute('error'): errtxt = bug_n.getAttribute('error') if errtxt == 'NotFound': raise BugNotFoundError - s = 'Error getting %s bug #%s: %s' % (self.description, id, errtxt) + s = 'Error getting %s bug #%d: %s' % (self.description, id, errtxt) raise BugtrackerError(s) try: title = _getnodetxt(bug_n.getElementsByTagName('short_desc')[0]) @@ -678,16 +539,13 @@ class Bugzilla(IBugtracker): except: assignee = '' except Exception as e: - s = 'Could not parse XML returned by %s bugzilla: %s (%s)' % (self.description, e, url) - raise BugtrackerError(s) + raise BugtrackerError(self.errparse % (self.description, e, url)) return [(id, product, title, severity, status, assignee, "%s/show_bug.cgi?id=%d" % (self.url, id), [], [])] -class Issuezilla(Bugzilla): - pass - class Launchpad(IBugtracker): - statuses = ["Unknown", "Invalid", "Opinion", "Won't Fix", "Fix Released", "Fix Committed", "New", "Incomplete", "Confirmed", "Triaged", "In Progress"] - severities = ["Unknown", "Undecided", "Wishlist", "Low", "Medium", "High", "Critical"] + statuses = ("Unknown", "Invalid", "Opinion", "Won't Fix", "Fix Released", "Fix Committed", "New", + "Incomplete", "Confirmed", "Triaged", "In Progress") + severities = ("Unknown", "Undecided", "Wishlist", "Low", "Medium", "High", "Critical") def __init__(self, *args, **kwargs): IBugtracker.__init__(self, *args, **kwargs) @@ -701,81 +559,74 @@ class Launchpad(IBugtracker): # support for /+text in the future in favour of launchpadlib. # Terence Simpson (tsimpson) 2010-04-20 - try: # Attempt to use launchpadlib, python bindings for the Launchpad API + try: from launchpadlib.launchpad import Launchpad - cachedir = os.path.join(conf.supybot.directories.data.tmp(), 'lpcache') - if hasattr(Launchpad, 'login_anonymously'): - self.lp = Launchpad.login_anonymously("Ubuntu Bots - Bugtracker", 'production', cachedir) - else: #NOTE: Most people should have a launchpadlib new enough for .login_anonymously - self.lp = Launchpad.login("Ubuntu Bots - Bugtracker", '', '', 'production', cahedir) + cachedir = os.path.join(conf.supybot.directories.data.tmp(), 'launchpadlib') + self.lp = Launchpad.login_anonymously("Ubuntu Bots - Bugtracker", 'production', cachedir, version='devel') except ImportError: - # Ask for launchpadlib to be installed supylog.warning("Please install python-launchpadlib, the old interface is deprecated") - except Exception: # Something unexpected happened + except Exception: self.lp = None supylog.exception("Unknown exception while accessing the Launchpad API") - def _parse(self, task): #Depricated + def _parse(self, task): # Deprecated parser = FeedParser() parser.feed(task) return parser.close() - def get_bug(self, id): #TODO: Remove this method and rename 'get_new_bug' to 'get_bug' - if self.lp: - return self.get_bug_new(id) - return self.get_bug_old(id) + @classmethod + def _rank(cls, task): + try: + return float('%d.%02d' % (cls.statuses.index(task.status), + cls.severities.index(task.importance))) + except: + return 0 @classmethod - def _sort(cls, task1, task2): - task1_status = task1.status - task1_importance = task1.importance - task2_status = task2.status - task2_importance = task2.importance + def _rank_old(cls, task): + try: + return float('%d.%02d' % (cls.statuses.index(task['status']), + cls.severities.index(task['importance']))) + except: + return 0 - if task1_status not in cls.statuses: - supylog.error("%r is an unknown status for Launchapd, update %s.statuses" % (task1_status, getattr(cls, '__name__', 'Launchpad'))) - if task2_status not in cls.statuses: - supylog.error("%r is an unknown status for Launchapd, update %s.statuses" % (task1_status, getattr(cls, '__name__', 'Launchpad'))) - return -1 - return 1 + @classmethod + def _sort(cls, task1, task2): # Deprecated + try: + if task1.status != task2.status: + if cls.statuses.index(task1.status) < cls.statuses.index(task2.status): + return -1 + return 1 - if task1_importance not in cls.severities: - supylog.error("%r is an unknown status for Launchapd, update %s.severities" % (task1_importance, getattr(cls, '__name__', 'Launchpad'))) - if task2_importance not in cls.severities: - supylog.error("%r is an unknown status for Launchapd, update %s.severities" % (task1_importance, getattr(cls, '__name__', 'Launchpad'))) - return -1 - return 1 - - if task1_status != task2_status: - if cls.statuses.index(task1_status) < cls.statuses.index(task2_status): - return -1 - return 1 - if task1_importance != task2_importance: - if cls.severities.index(task1_importance) < cls.severities.index(task2_importance): - return -1 - return 1 + if task1.importance != task2.importance: + if cls.severities.index(task1.importance) < cls.severities.index(task2.importance): + return -1 + return 1 + except: + return 0 return 0 @classmethod - def _old_sort(cls, task1, task2): #Depricated - # Status sort: + def _sort_old(cls, task1, task2): # Deprecated try: - if task1['status'] not in cls.statuses and task2['status'] in cls.statuses: return -1 - if task1['status'] in cls.statuses and task2['status'] not in cls.statuses: return 1 - if task1['importance'] not in cls.severities and task2['importance'] in cls.severities: return -1 - if task1['importance'] in cls.severities and task2['importance'] not in cls.severities: return 1 - if not (task1['status'] == task2['status']): + if task1['status'] != task2['status']: if cls.statuses.index(task1['status']) < cls.statuses.index(task2['status']): return -1 return 1 - if not (task1['importance'] == task2['importance']): + + if task1['importance'] != task2['importance']: if cls.severities.index(task1['importance']) < cls.severities.index(task2['importance']): return -1 return 1 - except: # Launchpad changed again? + except: return 0 return 0 + def get_bug(self, id): #TODO: Remove this method and rename 'get_bug_new' to 'get_bug' + if self.lp: + return self.get_bug_new(id) + return self.get_bug_old(id) + def get_bug_new(self, id): #TODO: Rename this method to 'get_bug' try: bugdata = self.lp.bugs[id] @@ -792,24 +643,8 @@ class Launchpad(IBugtracker): extinfo.append('heat: %d' % bugdata.heat) tasks = bugdata.bug_tasks - if tasks.total_size != 1: - tasks = list(tasks) - try: - tasks = sorted(tasks, key=cmp_to_key(self._sort)) - taskdata = tasks[-1] - except ValueError: - tasks = [_ for _ in tasks if _.bug_target_name.endswith('(Ubuntu)')] - if tasks: - if len(tasks) != 1: - try: - tasks = sorted(tasks, key=cmp_to_key(self._sort)) - taskdata = tasks[-1] - except ValueError: - taskdata = bugdata.bug_tasks[bugdata.bug_tasks.total_size - 1] - else: - taskdata = tasks[-1] - else: - taskdata = tasks[-1] + if tasks.total_size > 1: + taskdata = sorted(tasks, key=self._rank)[-1] else: taskdata = tasks[0] @@ -823,52 +658,43 @@ class Launchpad(IBugtracker): if e.response.status == 404: bugNo = e.content.split()[-1][2:-1] # extract the real bug number if bugNo != str(id): # A duplicate of a private bug, at least we know it exists - raise BugtrackerError('Bug #%s is a duplicate of bug #%s, but it is private (%s/bugs/%s)' % (id, bugNo, self.url, bugNo)) - raise BugtrackerError("Bug #%s (%s/bugs/%d) is private or doesn't exist" % (id, self.url, id)) # Could be private, could just not exist - - supylog.exception("Error gathering bug data for %s bug #%d" % (self.description, id)) - raise BugtrackerError("Could not gather data from %s for bug #%s (%s/bugs/%s). The error has been logged" % (self.description, id, self.url, id)) + raise BugtrackerError('Bug #%d is a duplicate of bug #%s, but it is private (%s/bugs/%s)' % (id, bugNo, self.url, bugNo)) + raise BugtrackerError("Bug #%d is private or does not exist (%s/bugs/%d)" % (id, self.url, id)) # Could be private, could just not exist + raise BugtrackerError(self.errget % (self.description, e, '%s/bugs/%d' % (self.url, id))) elif isinstance(e, KeyError): raise BugNotFoundError - supylog.exception("Error gathering bug data for %s bug %d" % (self.description, id)) - raise BugtrackerError("Could not gather data from %s for bug #%s (%s/bugs/%s). The error has been logged" % (self.description, id, self.url, id)) + raise BugtrackerError(self.errget % (self.description, e, '%s/bugs/%d' % (self.url, id))) return [(bugdata.id, taskdata.bug_target_display_name, bugdata.title, taskdata.importance, taskdata.status, - assignee, "%s/bugs/%s" % (self.url, bugdata.id), extinfo, duplicate)] - - def get_bug_old(self, id, duplicate=None): #Depricated - if id == 1: - raise BugtrackerError("https://bugs.launchpad.net/ubuntu/+bug/1 (Not reporting large bug)") + assignee, "%s/bugs/%d" % (self.url, bugdata.id), extinfo, duplicate)] + def get_bug_old(self, id, duplicate=None): # Deprecated try: - bugdata = utils.web.getUrl("%s/bugs/%d/+text" % (self.url,id)).decode('utf-8') + bugdata = utils.web.getUrl("%s/bugs/%d/+text" % (self.url, id)).decode('utf-8') except Exception as e: - if '404' in str(e): + if 'HTTP Error 404' in str(e): if duplicate: - raise BugtrackerError('Bug #%s is a duplicate of bug #%s, but it is private (%s/bugs/%s)' % (duplicate, id, self.url, id)) + raise BugtrackerError('Bug #%d is a duplicate of bug #%d, but it is private (%s/bugs/%d)' % (duplicate, id, self.url, id)) else: raise BugNotFoundError - s = 'Could not parse data returned by %s: %s (%s/bugs/%d)' % (self.description, e, self.url, id) - raise BugtrackerError(s) + raise BugtrackerError(self.errget % (self.description, e, '%s/bugs/%d' % (self.url, id))) - # Trap private bugs - if "" in bugdata: - raise BugtrackerError("This bug is private") try: # Split bug data into separate pieces (bug data, task data) data = bugdata.split('\n\nContent-Type:', 1)[0].split('\n\n') bugdata = self._parse(data[0]) if not bugdata['duplicate-of']: taskdata = list(map(self._parse, data[1:])) - taskdata = sorted(taskdata, key=cmp_to_key(self._old_sort)) - taskdata = taskdata[-1] + if len(taskdata) > 1: + taskdata = sorted(taskdata, key=self._rank_old)[-1] + else: + taskdata = taskdata[0] if taskdata['assignee']: assignee = re.sub(r' \([^)]*\)$', '', taskdata['assignee']) else: assignee = '' except Exception as e: - s = 'Could not parse data returned by %s: %s (%s/bugs/%d)' % (self.description, e, self.url, id) - raise BugtrackerError(s) + raise BugtrackerError(self.errparse % (self.description, e, '%s/bugs/%d' % (self.url, id))) # Try and find duplicates if bugdata['duplicate-of']: @@ -877,7 +703,7 @@ class Launchpad(IBugtracker): return [data] return [(id, taskdata['task'], bugdata['title'], taskdata['importance'], taskdata['status'], - assignee, "%s/bugs/%s" % (self.url, id), [], [])] + assignee, "%s/bugs/%d" % (self.url, id), [], [])] # # Debbugs sucks donkeyballs @@ -898,12 +724,11 @@ class Debbugs(IBugtracker): self.soap_proxy = SOAPProxy("%s/cgi-bin/soap.cgi" % self.url, namespace="Debbugs/SOAP") def get_bug(self, id): - bug_url = "%s/cgi-bin/bugreport.cgi?bug=%d" % (self.url, id) + url = "%s/cgi-bin/bugreport.cgi?bug=%d" % (self.url, id) try: raw = self.soap_proxy.get_status(id) except Exception as e: - s = 'Could not parse data returned by %s: %s' % (self.description, e) - raise BugtrackerError(s) + raise BugtrackerError(self.errget % (self.description, e, url)) if not raw: raise BugNotFoundError try: @@ -912,10 +737,9 @@ class Debbugs(IBugtracker): status = 'Fixed' else: status = 'Open' - return [(id, raw['package'], raw['subject'], raw['severity'], status, '', "%s/%s" % (self.url, id), [], [])] + return [(id, raw['package'], raw['subject'], raw['severity'], status, '', "%s/%d" % (self.url, id), [], [])] except Exception as e: - s = 'Could not parse data returned by %s bugtracker: %s (%s)' % (self.description, e, bug_url) - raise BugtrackerError(s) + raise BugtrackerError(self.errparse % (self.description, e, url)) class Mantis(IBugtracker): def __init__(self, *args, **kwargs): @@ -928,33 +752,30 @@ class Mantis(IBugtracker): def get_bug(self, id): url = "%s/view.php?id=%d" % (self.url, id) try: - raw = self.soap_proxy.mc_issue_get('', "", id) + raw = self.soap_proxy.mc_issue_get('', '', id) except Exception as e: - s = 'Could not parse data returned by %s: %s (%s)' % (self.description, e, url) - raise BugtrackerError(s) + raise BugtrackerError(self.errget % (self.description, e, url)) if not raw: raise BugNotFoundError try: return [(id, raw['project']['name'], raw['summary'], raw['severity']['name'], raw['resolution']['name'], '', url, [], [])] except Exception as e: - s = 'Could not parse data returned by %s bugtracker: %s (%s)' % (self.description, e, url) - raise BugtrackerError(s) + raise BugtrackerError(self.errparse % (self.description, e, url)) -# For trac based trackers we get the tab-separated-values format. +# For Trac-based trackers we get the tab-separated-values format. # The other option is a comma-separated-values format, but if the description # has commas, things get tricky. -# This should be more robust than the screen-scraping done previously. +# This should be more robust than the screen scraping done previously. class Trac(IBugtracker): def get_bug(self, id): # This is still a little rough, but it works :) - bug_url = "%s/%d" % (self.url, id) + url = "%s/%d" % (self.url, id) try: - raw = utils.web.getUrl("%s?format=tab" % bug_url).decode('utf-8') + raw = utils.web.getUrl("%s?format=tab" % url).decode('utf-8') except Exception as e: if 'HTTP Error 500' in str(e): raise BugNotFoundError - s = 'Could not parse data returned by %s: %s' % (self.description, e, bug_url) - raise BugtrackerError(s) - raw = raw.replace("\r\n", '\n') + raise BugtrackerError(self.errget % (self.description, e, url)) + raw = raw.replace('\r\n', '\n') (headers, rest) = raw.split('\n', 1) headers = headers.strip().split('\t') rest = rest.strip().split('\t') @@ -972,98 +793,7 @@ class Trac(IBugtracker): severity = rest[headers.index("priority")] if "owner" in headers: assignee = rest[headers.index("owner")] - return [(id, package, title, severity, status, assignee, bug_url, [], [])] - -class WikiForms(IBugtracker): - def get_bug(self, id): - def strip_tags(s): - while '<' in s and '>' in s: - s = str(s[:s.find('<')]) + str(s[s.find('>')+1:]) - return s - - url = "%s/%05d" % (self.url, id) - try: - bugdata = utils.web.getUrl(url).decode('utf-8') - except Exception as e: - if 'HTTP Error 404' in str(e): - raise BugNotFoundError - s = 'Could not parse data returned by %s: %s (%s)' % (self.description, e, url) - raise BugtrackerError(s) - for l in bugdata.split("\n"): - l2 = l.lower() - if '
importance
' in l2: - severity = strip_tags(l[l.find('
')+4:]) - if '
summary
' in l2: - title = strip_tags(l[l.find('
')+4:]) - if '
status
' in l2: - status = strip_tags(l[l.find('
')+4:]) - if '
category
' in l2: - package = strip_tags(l[l.find('
')+4:]) - return [(id, package, title, severity, status, '', "%s/%05d" % (self.url, id), [], [])] - -class Str(IBugtracker): - def get_bug(self, id): - def strip_tags(s): - while '<' in s and '>' in s: - s = str(s[:s.find('<')]) + str(s[s.find('>')+1:]) - return s - url = "%s?L%d" % (self.url, id) - try: - bugdata = utils.web.getUrl(url).decode('utf-8') - except Exception as e: - s = 'Could not parse data returned by %s: %s (%s)' % (self.description, e, url) - raise BugtrackerError(s) - for l in bugdata.split("\n"): - l2 = l.lower() - if 'nowrap>priority:' in l2: - severity = l[l.find(' - ')+3:min(l.find(','),l.find(''))] - if '>application:' in l2: - package = l[l.find('')+4:l.find('')] - if 'nowrap>status:' in l2: - status = l[l.find(' - ')+3:l.find('')] - if 'nowrap>summary:' in l2: - title = l[l.find('')+4:l.find('')] - if 'nowrap>assigned to:' in l2: - assignee = strip_tags(l[l.find('')+4:l.find('')]) - if assignee == 'Unassigned': - assignee = '' - return [(id, package, title, severity, status, assignee, "%s?L%d" % (self.url, id), [], [])] - -sfre = re.compile(r""" - .*? -

\[.*?\]\s*(?P.*?)</h2> - .*? - assigned.*?<br>\s+(?P<assignee>\S+) - .*? - priority.*?(?P<priority>\d+) - .*? - status.*?<br>\s+(?P<status>\S+) - .*? - resolution.*?<br>\s+(?P<resolution>\S+) - .*? - """, re.VERBOSE | re.DOTALL | re.I) -#NOTE: Until sf.net has a way to export formatted bug data, this will remain broken and unmaintained -class Sourceforge(IBugtracker): - _sf_url = 'http://sf.net/support/tracker.php?aid=%d' - def get_bug(self, id): - url = self._sf_url % id - try: - bugdata = utils.web.getUrl(url).decode('utf-8') - except Exception as e: - s = 'Could not parse data returned by %s: %s (%s)' % (self.description, e, url) - raise BugtrackerError(s) - try: - reo = sfre.search(bugdata) - status = reo.group('status') - resolution = reo.group('resolution') - if resolution.lower() != 'none': - status += ': %s' % resolution - assignee = reo.group('assignee') - if assignee.lower() == 'nobody': - assignee = '' - return [(id, '', reo.group('title'), "Pri: %s" % reo.group('priority'), status, assignee, self._sf_url % id, [], [])] - except: - raise BugNotFoundError + return [(id, package, title, severity, status, assignee, url, [], [])] # Introspection is quite cool defined_bugtrackers = {} @@ -1088,9 +818,4 @@ registerBugtracker('debian', 'https://bugs.debian.org', 'Debian', 'debbugs') registerBugtracker('mantis', 'https://www.mantisbt.org/bugs', 'Mantis', 'mantis') registerBugtracker('trac', 'https://trac.edgewall.org/ticket', 'Trac', 'trac') registerBugtracker('django', 'https://code.djangoproject.com/ticket', 'Django', 'trac') -# Outdated -#registerBugtracker('cups', 'http://www.cups.org/str.php', 'CUPS', 'str') -#registerBugtracker('gnewsense', 'http://bugs.gnewsense.org/Bugs', 'gNewSense', 'wikiforms') -#registerBugtracker('sourceforge', 'http://sourceforge.net/tracker/', 'Sourceforge', 'sourceforge') -#registerBugtracker('supybot', 'http://sourceforge.net/tracker/?group_id=58965&atid=489447', 'Supybot', 'sourceforge') Class = Bugtracker