From 45ae48b53a9d0046ecc13bdf0619922060951f97 Mon Sep 17 00:00:00 2001 From: Krytarik Raido Date: Wed, 16 May 2018 23:45:04 +0200 Subject: [PATCH] Bugtracker: Add Python 3 support (by Mattia Rizzolo) --- Bugtracker/__init__.py | 22 +++--- Bugtracker/plugin.py | 152 ++++++++++++++++++++++------------------- 2 files changed, 94 insertions(+), 80 deletions(-) diff --git a/Bugtracker/__init__.py b/Bugtracker/__init__.py index ad11408..647b4dc 100644 --- a/Bugtracker/__init__.py +++ b/Bugtracker/__init__.py @@ -2,6 +2,7 @@ ### # Copyright (c) 2005-2007 Dennis Kaarsemaker # Copyright (c) 2008-2010 Terence Simpson +# Copyright (c) 2017- Krytarik Raido # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as @@ -21,19 +22,22 @@ This plugin will display bug information when requested. import supybot import supybot.world as world -__version__ = "2.5.1" -__author__ = supybot.Author("Terence Simpson", "tsimpson", "tsimpson@ubuntu.com") -__contributors__ = { - supybot.Author("Dennis Kaarsemaker","Seveas","dennis@kaarsemaker.net"): ['Original Author'] -} -__url__ = 'https://launchpad.net/ubuntu-bots/' +from imp import reload -import config +__version__ = "2.6.0" +__author__ = supybot.Author("Krytarik Raido", "krytarik", "krytarik@tuxgarage.com") +__contributors__ = { + supybot.Author("Dennis Kaarsemaker", "Seveas", "dennis@kaarsemaker.net"): ['Original Author'], + supybot.Author("Terence Simpson", "tsimpson", "tsimpson@ubuntu.com"): ['Original Author'] +} +__url__ = 'https://launchpad.net/ubuntu-bots' + +from . import config reload(config) -import plugin +from . import plugin reload(plugin) if world.testing: - import test + from . import test Class = plugin.Class configure = config.configure diff --git a/Bugtracker/plugin.py b/Bugtracker/plugin.py index f3304fa..42a1677 100644 --- a/Bugtracker/plugin.py +++ b/Bugtracker/plugin.py @@ -2,6 +2,7 @@ ### # Copyright (c) 2005-2007 Dennis Kaarsemaker # Copyright (c) 2008-2010 Terence Simpson +# Copyright (c) 2017- Krytarik Raido # # This program is free software; you can redistribute it and/or modify # it under the terms of version 2 of the GNU General Public License as @@ -24,12 +25,15 @@ 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, time, commands +import re, os, sys, time, subprocess import xml.dom.minidom as minidom -from htmlentitydefs import entitydefs as entities -import email.FeedParser -import SOAPpy +from html.entities import entitydefs as entities +from email.parser import FeedParser +if sys.version_info < (3,0): + import SOAPpy # 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"]) @@ -52,7 +56,7 @@ def registerBugtracker(name, url='', description='', trackertype=''): if description: DESC.setValue(description) if trackertype: - if defined_bugtrackers.has_key(trackertype.lower()): + if trackertype.lower() in defined_bugtrackers: TRACKERTYPE.setValue(trackertype.lower()) else: raise BugtrackerError("Unknown trackertype: %s" % trackertype) @@ -64,7 +68,7 @@ def _getnodetxt(node): if childnode.nodeType == childnode.TEXT_NODE: L.append(childnode.data) if not L: - raise ValueError, "No text nodes" + raise ValueError("No text nodes") val = ''.join(L) if node.hasAttribute('encoding'): encoding = node.getAttribute('encoding') @@ -103,11 +107,11 @@ class Bugtracker(callbacks.PluginRegexp): for name in self.registryValue('bugtrackers'): registerBugtracker(name) group = self.registryValue('bugtrackers.%s' % name.replace('.','\\.'), value=False) - if group.trackertype() in defined_bugtrackers.keys(): + 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)) - self.shorthand = utils.abbrev(self.db.keys()) + self.shorthand = utils.abbrev(list(self.db.keys())) self.shown = {} # # Schedule bug reporting @@ -134,7 +138,7 @@ class Bugtracker(callbacks.PluginRegexp): def is_ok(self, channel, tracker, bug): '''Flood/repeat protection''' now = time.time() - for k in self.shown.keys(): + for k in list(self.shown.keys()): if self.shown[k] < now - self.registryValue('repeatdelay', channel): self.shown.pop(k) if (channel, tracker, bug) not in self.shown: @@ -171,7 +175,7 @@ class Bugtracker(callbacks.PluginRegexp): # # Read all new mail # for m in new_mail: # msg = sc.fetch(m, 'RFC822')[1][0][1] -# fp = email.FeedParser.FeedParser() +# 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() @@ -258,7 +262,7 @@ class Bugtracker(callbacks.PluginRegexp): irc.error("Bugtrackers of type '%s' are not understood" % trackertype) return registerBugtracker(name, url, description, trackertype) - self.shorthand = utils.abbrev(self.db.keys()) + self.shorthand = utils.abbrev(list(self.db.keys())) irc.replySuccess() add = wrap(add, [('checkCapability', 'admin'), 'something', 'something', 'url', additional('text')]) @@ -272,7 +276,7 @@ class Bugtracker(callbacks.PluginRegexp): name = self.shorthand[name.lower()] del self.db[name] self.registryValue('bugtrackers').remove(name) - self.shorthand = utils.abbrev(self.db.keys()) + self.shorthand = utils.abbrev(list(self.db.keys())) irc.replySuccess() except KeyError: s = self.registryValue('replyNoBugtracker', ircutils.isChannel(msg.args[0]) and msg.args[0] or None) @@ -294,7 +298,7 @@ class Bugtracker(callbacks.PluginRegexp): registerBugtracker(newname, group.url(), d, group.trackertype()) del self.db[name] self.registryValue('bugtrackers').remove(name) - self.shorthand = utils.abbrev(self.db.keys()) + self.shorthand = utils.abbrev(list(self.db.keys())) irc.replySuccess() except KeyError: s = self.registryValue('replyNoBugtracker', ircutils.isChannel(msg.args[0]) and msg.args[0] or None) @@ -319,7 +323,7 @@ class Bugtracker(callbacks.PluginRegexp): irc.error(s % name) else: if self.db: - L = self.db.keys() + L = list(self.db.keys()) L.sort() irc.reply(utils.str.commaAndify(L)) else: @@ -355,7 +359,7 @@ class Bugtracker(callbacks.PluginRegexp): # filter out bug number that are 4 numbers, start with '1' and end in '04' or '10 # (let's fix this for 2020 ;) if match.group('bt').lower() == 'ubuntu': - bugids = filter(lambda bugnum: not (len(bugnum) == 4 and bugnum[0] == '1' and bugnum[2:] in ('04', '10')), bugids) + bugids = [bugnum for bugnum in bugids if not (len(bugnum) == 4 and bugnum[0] == '1' and bugnum[2:] in ('04', '10'))] # End HACK if not sure_bug: @@ -364,7 +368,7 @@ class Bugtracker(callbacks.PluginRegexp): bugids = list(set(bugids)) ## remove dups msg.tag('nbugs', nbugs + len(bugids)) - bt = map(lambda x: x.lower(), match.group('bt').split()) + 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] @@ -407,7 +411,7 @@ class Bugtracker(callbacks.PluginRegexp): except BugNotFoundError: if self.registryValue('replyWhenNotFound'): irc.error("%s bug %d could not be found" % (tracker.description, bugid)) - except BugtrackerError, e: + except BugtrackerError as e: # if 'private' in str(e): # irc.reply("Bug %d on http://launchpad.net/bugs/%d is private" % (bugid, bugid)) # return @@ -433,9 +437,9 @@ class Bugtracker(callbacks.PluginRegexp): if not tracker: return report = self.get_bug(channel, tracker, int(match.group('bug')), self.registryValue('showassignee', channel), do_url = False) - except BugtrackerError, e: + except BugtrackerError as e: irc.error(str(e)) - except BugNotFoundError, e: + except BugNotFoundError as e: irc.error("%s bug %s not found" % (tracker, match.group('bug'))) else: for r in report: @@ -463,7 +467,7 @@ class Bugtracker(callbacks.PluginRegexp): if not self.is_ok(channel, 'cve', cve): return url = 'http://cve.mitre.org/cgi-bin/cvename.cgi?name=%s' % cve - cvedata = utils.web.getUrl(url) + cvedata = utils.web.getUrl(url).decode('utf-8') m = cvere.search(cvedata) if m: cve = m.group(1).replace('\n', ' ') @@ -500,7 +504,7 @@ class Bugtracker(callbacks.PluginRegexp): if 'sourceforge.net' in snarfurl: # See below return None - for t in self.db.keys(): + 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) @@ -525,7 +529,7 @@ class Bugtracker(callbacks.PluginRegexp): tracker = Bugzilla().get_tracker(snarfurl) if tracker: self.db[tracker.name] = tracker - self.shorthand = utils.abbrev(self.db.keys()) + self.shorthand = utils.abbrev(list(self.db.keys())) return tracker return None @@ -613,16 +617,16 @@ class Bugzilla(IBugtracker): try: bugxml = utils.web.getUrl(url) zilladom = minidom.parseString(bugxml) - except Exception, e: + except Exception as e: s = 'Could not parse XML returned by %s: %s (%s)' % (self.description, e, url) - raise BugtrackerError, s + raise BugtrackerError(s) 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) - raise BugtrackerError, s + raise BugtrackerError(s) try: title = _getnodetxt(bug_n.getElementsByTagName('short_desc')[0]) status = _getnodetxt(bug_n.getElementsByTagName('bug_status')[0]) @@ -637,9 +641,9 @@ class Bugzilla(IBugtracker): assignee = _getnodetxt(bug_n.getElementsByTagName('assigned_to')[0]) except: pass - except Exception, e: + except Exception as e: s = 'Could not parse XML returned by %s bugzilla: %s (%s)' % (self.description, e, url) - raise BugtrackerError, s + raise BugtrackerError(s) return [(id, component, title, severity, status, assignee, "%s/show_bug.cgi?id=%d" % (self.url, id))] class Issuezilla(Bugzilla): @@ -705,7 +709,7 @@ class Launchpad(IBugtracker): supylog.exception("Unknown exception while accessing the Launchpad API") def _parse(self, task): #Depricated - parser = email.FeedParser.FeedParser() + parser = FeedParser() parser.feed(task) return parser.close() @@ -769,7 +773,7 @@ class Launchpad(IBugtracker): try: bugdata = self.lp.bugs[id] if bugdata.private: - raise BugtrackerError, "This bug is private" + raise BugtrackerError("This bug is private") dup = bugdata.duplicate_of summary_prefix = '' # Used to made dups easier while dup: @@ -784,14 +788,14 @@ class Launchpad(IBugtracker): if tasks.total_size != 1: tasks = list(tasks) try: - tasks.sort(self._sort) + tasks = sorted(tasks, key=cmp_to_key(self._sort)) taskdata = tasks[-1] except ValueError: - tasks = [_ for _ in tasks if _.bug_target_name.endswith(u'(Ubuntu)')] + tasks = [_ for _ in tasks if _.bug_target_name.endswith('(Ubuntu)')] if tasks: if len(tasks) != 1: try: - tasks.sort(self._sort) + tasks = sorted(tasks, key=cmp_to_key(self._sort)) taskdata = tasks[-1] except ValueError: taskdata = bugdata.bug_tasks[bugdata.bug_tasks.total_size - 1] @@ -806,24 +810,24 @@ class Launchpad(IBugtracker): t = taskdata.bug_target_display_name #task name if assignee: # "Diaplay Name (Launchpad ID)" - assignee = u"%s (%s)" % (assignee.display_name, assignee.name) + assignee = "%s (%s)" % (assignee.display_name, assignee.name) else: assignee = '' - except Exception, e: + except Exception as e: if type(e).__name__ == 'HTTPError': # messy, but saves trying to import lazr.restfulclient.errors.HTPError if e.response.status == 404: bugNo = e.content.split(None)[-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 + 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("Could not gather data from %s for bug #%s (%s/bugs/%s). The error has been logged" % (self.description, id, 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("Could not gather data from %s for bug #%s (%s/bugs/%s). The error has been logged" % (self.description, id, self.url, id)) extinfo = "(affected: %d, heat: %d)" % (affected, heat) @@ -832,34 +836,34 @@ class Launchpad(IBugtracker): def get_bug_old(self, id): #Depricated if id == 1: - raise BugtrackerError, "https://bugs.launchpad.net/ubuntu/+bug/1 (Not reporting large bug)" + raise BugtrackerError("https://bugs.launchpad.net/ubuntu/+bug/1 (Not reporting large bug)") try: - bugdata = utils.web.getUrl("%s/bugs/%d/+text" % (self.url,id)) - except Exception, e: + bugdata = utils.web.getUrl("%s/bugs/%d/+text" % (self.url,id)).decode('utf-8') + except Exception as e: if '404' in str(e): 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(s) summary = {} # Trap private bugs if "" in bugdata: - raise BugtrackerError, "This bug is private" + raise BugtrackerError("This bug is private") try: # Split bug data into separate pieces (bug data, task data) data = bugdata.split('\n\n') bugdata = data[0] taskdata = data[1:] - parser = email.FeedParser.FeedParser() + parser = FeedParser() parser.feed(bugdata) bugdata = parser.close() - taskdata = map(self._parse, taskdata) - taskdata.sort(self._old_sort) + taskdata = list(map(self._parse, taskdata)) + taskdata = sorted(taskdata, key=cmp_to_key(self._old_sort)) taskdata = taskdata[-1] - except Exception, e: + 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(s) # Try and find duplicates t = taskdata['task'] if '(' in t: @@ -868,9 +872,9 @@ class Launchpad(IBugtracker): bugNo = bugdata['duplicate-of'] try: data = self.get_bug(int(bugdata['duplicate-of'])) - except Exception, e: + except Exception as e: if '404' in str(e): - 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 is a duplicate of Bug #%s, but it is private. (%s/bugs/%s)' % (id, bugNo, self.url, bugNo)) data = list(data[0]) data[2] = ('duplicate for #%d ' % id) + data[2] return [tuple(data)] @@ -889,6 +893,9 @@ class Launchpad(IBugtracker): # class Debbugs(IBugtracker): def __init__(self, *args, **kwargs): + if not sys.version_info < (3,0): + # XXX python3 does not have SOAPpy, so just quit here (for now) + return IBugtracker.__init__(self, *args, **kwargs) self.soap_proxy = SOAPpy.SOAPProxy("bugs.debian.org/cgi-bin/soap.cgi", "Debbugs/SOAP/Status") self.soap_proxy.soapaction = "Debbugs/SOAP/Status#get_status" @@ -897,9 +904,9 @@ class Debbugs(IBugtracker): bug_url = "http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=%d" % id try: raw = self.soap_proxy.get_status(id) - except Exception, e: + except Exception as e: s = 'Could not parse data returned by %s: %s' % (self.description, e) - raise BugtrackerError, s + raise BugtrackerError(s) if not raw: raise BugNotFoundError raw = raw['item']['value'] @@ -909,12 +916,15 @@ class Debbugs(IBugtracker): else: status = 'Open' return [(id, raw['package'], raw['subject'], raw['severity'], status, '', "%s/%s" % (self.url, id))] - except Exception, e: + 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(s) class Mantis(IBugtracker): def __init__(self, *args, **kwargs): + if not sys.version_info < (3,0): + # XXX python3 does not have SOAPpy, so just quit here (for now) + return IBugtracker.__init__(self, *args, **kwargs) self.soap_proxy = SOAPpy.SOAPProxy(self.url + "/api/soap/mantisconnect.php", "http://futureware.biz/mantisconnect") self.soap_proxy.soapaction = "http://futureware.biz/mantisconnect#mc_issue_get" @@ -923,16 +933,16 @@ class Mantis(IBugtracker): url = self.url + "/view.php?id=%i" % id try: raw = self.soap_proxy.mc_issue_get('', "", id) - except Exception, e: + except Exception as e: s = 'Could not parse data returned by %s: %s (%s)' % (self.description, e, url) - raise BugtrackerError, s + raise BugtrackerError(s) if not raw: raise BugNotFoundError try: return [(id, raw['project']['name'], raw['summary'], raw['priority']['name'], raw['resolution']['name'], '', url)] - except Exception, e: + except Exception as e: s = 'Could not parse data returned by %s bugtracker: %s (%s)' % (self.description, e, url) - raise BugtrackerError, s + raise BugtrackerError(s) # For trac based trackers we get the tab-separated-values format. # The other option is a comma-separated-values format, but if the description @@ -942,12 +952,12 @@ class Trac(IBugtracker): def get_bug(self, id): # This is still a little rough, but it works :) bug_url = "%s/%d" % (self.url, id) try: - raw = utils.web.getUrl("%s?format=tab" % bug_url) - except Exception, e: + raw = utils.web.getUrl("%s?format=tab" % bug_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 + raise BugtrackerError(s) raw = raw.replace("\r\n", '\n') (headers, rest) = raw.split('\n', 1) headers = headers.strip().split('\t') @@ -977,12 +987,12 @@ class WikiForms(IBugtracker): url = "%s/%05d" % (self.url, id) try: - bugdata = utils.web.getUrl(url) - except Exception, e: + 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 + raise BugtrackerError(s) for l in bugdata.split("\n"): l2 = l.lower() if '
importance
' in l2: @@ -1003,10 +1013,10 @@ class Str(IBugtracker): return s url = "%s?L%d" % (self.url, id) try: - bugdata = utils.web.getUrl(url) - except Exception, e: + 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 + raise BugtrackerError(s) for l in bugdata.split("\n"): l2 = l.lower() if 'nowrap>priority:' in l2: @@ -1043,10 +1053,10 @@ class Sourceforge(IBugtracker): def get_bug(self, id): url = self._sf_url % id try: - bugdata = utils.web.getUrl(url) - except Exception, e: + 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 + raise BugtrackerError(s) try: reo = sfre.search(bugdata) status = reo.group('status') @@ -1060,7 +1070,7 @@ class Sourceforge(IBugtracker): # Introspection is quite cool defined_bugtrackers = {} v = vars() -for k in v.keys(): +for k in list(v.keys()): if type(v[k]) == type(IBugtracker) and issubclass(v[k], IBugtracker) and not (v[k] == IBugtracker): defined_bugtrackers[k.lower()] = v[k]