~jpds/ubuntu-bots/bugs-via-launchpad-api

1 by Dennis Kaarsemaker
Initial checkin
1
###
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
2
# Copyright (c) 2005-2007 Dennis Kaarsemaker
149 by Jonathan Davies
Implemented anonymous bug reporting support to Bugtracker via the Launchpad
3
# Copyright (C) 2010 Jonathan Davies
1 by Dennis Kaarsemaker
Initial checkin
4
#
5
# This program is free software; you can redistribute it and/or modify
6
# it under the terms of version 2 of the GNU General Public License as
7
# published by the Free Software Foundation.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
###
15
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
16
from supybot.commands import *
1 by Dennis Kaarsemaker
Initial checkin
17
import supybot.utils as utils
21 by Dennis Kaarsemaker
* Completely rewritten encyclopedia plugin
18
import supybot.ircmsgs as ircmsgs
1 by Dennis Kaarsemaker
Initial checkin
19
import supybot.ircutils as ircutils
20
import supybot.callbacks as callbacks
21
import supybot.conf as conf
22
import supybot.registry as registry
21 by Dennis Kaarsemaker
* Completely rewritten encyclopedia plugin
23
import supybot.schedule as schedule
24
165 by Jonathan Davies
Bugtracker/plugin.py: Don't import commands - not used.
25
import re, os, time, imaplib
1 by Dennis Kaarsemaker
Initial checkin
26
import xml.dom.minidom as minidom
27
from htmlentitydefs import entitydefs as entities
28
import email.FeedParser
53 by Dennis Kaarsemaker
Use the SOAP interface at bugs.donarmstrong.com for debbugs
29
import SOAPpy
1 by Dennis Kaarsemaker
Initial checkin
30
151 by Jonathan Davies
Directly import Launchpad API bindings, let supybot report errors.
31
# Launchpad bindings.
32
from launchpadlib.launchpad import Launchpad
33
cachedir = os.path.expanduser("~/.launchpadlib/cache/")
166 by Jonathan Davies
Fall back to auth'ed API login if login_anonymously is not available.
34
35
try:
36
    launchpad = Launchpad.login_anonymously('Supybot: Bugtracker plugin', 'edge',
37
        cachedir)
167 by Jonathan Davies
Catch AttributeError instead.
38
except AttributeError:
166 by Jonathan Davies
Fall back to auth'ed API login if login_anonymously is not available.
39
    launchpad = Launchpad.login_with('Supybot: Bugtracker plugin', 'edge',
40
        cachedir)
149 by Jonathan Davies
Implemented anonymous bug reporting support to Bugtracker via the Launchpad
41
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
42
bad_words = ["fuck","fuk","fucking","fuking","fukin","fuckin","fucked","fuked","fucker","shit","cunt","bastard","nazi","nigger","nigga","cock","bitches","bitch"]
43
44
def makeClean(s):
45
    words = s.split()
46
    for word in words:
47
        if word.lower() in bad_words:
48
            words[words.index(word)] = "<censored>"
49
    return " ".join(words)
50
1 by Dennis Kaarsemaker
Initial checkin
51
def registerBugtracker(name, url='', description='', trackertype=''):
52
    conf.supybot.plugins.Bugtracker.bugtrackers().add(name)
53
    group       = conf.registerGroup(conf.supybot.plugins.Bugtracker.bugtrackers, name)
54
    URL         = conf.registerGlobalValue(group, 'url', registry.String(url, ''))
55
    DESC        = conf.registerGlobalValue(group, 'description', registry.String(description, ''))
56
    TRACKERTYPE = conf.registerGlobalValue(group, 'trackertype', registry.String(trackertype, ''))
57
    if url:
58
        URL.setValue(url)
59
    if description:
60
        DESC.setValue(description)
61
    if trackertype:
62
        if defined_bugtrackers.has_key(trackertype.lower()):
63
            TRACKERTYPE.setValue(trackertype.lower())
64
        else:
65
            raise BugtrackerError("Unknown trackertype: %s" % trackertype)
66
            
67
entre = re.compile('&(\S*?);')
68
def _getnodetxt(node):
69
    L = []
70
    for childnode in node.childNodes:
71
        if childnode.nodeType == childnode.TEXT_NODE:
72
            L.append(childnode.data)
73
    val = ''.join(L)
74
    if node.hasAttribute('encoding'):
75
        encoding = node.getAttribute('encoding')
76
        if encoding == 'base64':
77
            try:
78
                val = val.decode('base64')
79
            except:
80
                val = 'Cannot convert bug data from base64.'
81
    while entre.search(val):
82
        entity = entre.search(val).group(1)
83
        if entity in entities:
84
            val = entre.sub(entities[entity], val)
85
        else:
86
            val = entre.sub('?', val)
87
    return val
88
89
class BugtrackerError(Exception):
90
    """A bugtracker error"""
91
    pass
92
93
class BugNotFoundError(Exception):
94
    """Pity, bug isn't there"""
95
    pass
96
61 by Dennis Kaarsemaker
Fix CVE snarfer for new layout of CVE website
97
cvere = re.compile(r'<th.*?Description.*?<td.*?>(.*?)\s*</td>', re.I | re.DOTALL)
1 by Dennis Kaarsemaker
Initial checkin
98
class Bugtracker(callbacks.PluginRegexp):
99
    """Show a link to a bug report with a brief description"""
100
    threaded = True
101
    callBefore = ['URL']
51 by Dennis Kaarsemaker
Misc fixes
102
    regexps = ['turlSnarfer', 'bugSnarfer', 'oopsSnarfer', 'cveSnarfer']
1 by Dennis Kaarsemaker
Initial checkin
103
104
    def __init__(self, irc):
105
        callbacks.PluginRegexp.__init__(self, irc)
106
        self.db = ircutils.IrcDict()
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
107
        events = []
1 by Dennis Kaarsemaker
Initial checkin
108
        for name in self.registryValue('bugtrackers'):
109
            registerBugtracker(name)
110
            group = self.registryValue('bugtrackers.%s' % name.replace('.','\\.'), value=False)
111
            if group.trackertype() in defined_bugtrackers.keys():
112
                self.db[name] = defined_bugtrackers[group.trackertype()](name, group.url(), group.description())
113
            else:
136 by Terence Simpson
* Fix a couple more typos/strange supybot errors.
114
#                raise BugtrackerError("Unknown trackertype: %s (%s)" % (group.trackertype(), name))
115
                self.log.warning("Unknown trackertype: %s (%s)" % (group.trackertype(), name))
1 by Dennis Kaarsemaker
Initial checkin
116
        self.shorthand = utils.abbrev(self.db.keys())
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
117
118
        # Schedule bug reporting
134 by Terence Simpson
Fix a couple of typos
119
        self.shown = {}
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
120
        if self.registryValue('imap_server') and self.registryValue('reportercache'):
121
            try:
122
                schedule.removeEvent(self.name() + '.bugreporter')
123
            except:
124
                pass
125
            schedule.addPeriodicEvent(lambda: self.reportnewbugs(irc),  60, name=self.name() + '.bugreporter')
87 by Terence Simpson
Made the IMAP code as generic as possible
126
            self.events += [self.name() + '.bugreporter']
127
            self.log.info('Adding scheduled event "%s.bugreporter"' % self.name())
21 by Dennis Kaarsemaker
* Completely rewritten encyclopedia plugin
128
35 by Dennis Kaarsemaker
adkfasdyfb
129
    def die(self):
130
        try:
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
131
           for event in self.events:
87 by Terence Simpson
Made the IMAP code as generic as possible
132
                self.log.info('Removing scheduled event "%s"' % event)
133
                schedule.removeEvent(event)
135 by Terence Simpson
More typos
134
                schedule.removeEvent(self.name())
35 by Dennis Kaarsemaker
adkfasdyfb
135
        except:
136
            pass
137
21 by Dennis Kaarsemaker
* Completely rewritten encyclopedia plugin
138
    def is_ok(self, channel, tracker, bug):
139
        now = time.time()
140
        for k in self.shown.keys():
35 by Dennis Kaarsemaker
adkfasdyfb
141
            if self.shown[k] < now - self.registryValue('repeatdelay', channel):
21 by Dennis Kaarsemaker
* Completely rewritten encyclopedia plugin
142
                self.shown.pop(k)
143
        if (channel, tracker, bug) not in self.shown:
144
            self.shown[(channel, tracker, bug)] = now
145
            return True
146
        return False
147
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
148
    def is_new(self, tracker, tag, id):
149
        bugreporter_base = self.registryValue('reportercache')
150
        if not os.path.exists(os.path.join(bugreporter_base,tag,tracker.name,str(int(id/1000)),str(id))):
151
            try:
152
                os.makedirs(os.path.join(bugreporter_base,tag,tracker.name,str(int(id/1000))))
153
            except:
154
                pass
155
            fd = open(os.path.join(bugreporter_base,tag,tracker.name,str(int(id/1000)),str(id)),'w')
156
            fd.close()
157
            return True
158
        return False
159
21 by Dennis Kaarsemaker
* Completely rewritten encyclopedia plugin
160
    def reportnewbugs(self,irc):
161
        # Compile list of bugs
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
162
        self.log.info("Checking for new bugs")
21 by Dennis Kaarsemaker
* Completely rewritten encyclopedia plugin
163
        bugs = {}
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
164
        if self.registryValue('imap_ssl'):
165
            sc = imaplib.IMAP4_SSL(self.registryValue('imap_server'))
166
        else:
167
            sc = imaplib.IMAP4(self.registryValue('imap_server'))
168
        sc.login(self.registryValue('imap_user'), self.registryValue('imap_password'))
35 by Dennis Kaarsemaker
adkfasdyfb
169
        sc.select('INBOX')
170
        new_mail = sc.search(None, '(UNSEEN)')[1][0].split()[:20]
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
171
172
        # Read all new mail
35 by Dennis Kaarsemaker
adkfasdyfb
173
        for m in new_mail:
174
            msg = sc.fetch(m, 'RFC822')[1][0][1]
175
            fp = email.FeedParser.FeedParser()
87 by Terence Simpson
Made the IMAP code as generic as possible
176
            sc.store(m, '+FLAGS', "(\Deleted)") # Mark message deleted so we don't have to process it again
35 by Dennis Kaarsemaker
adkfasdyfb
177
            fp.feed(msg)
178
            bug = fp.close()
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
179
            tag = None
87 by Terence Simpson
Made the IMAP code as generic as possible
180
181
            if 'X-Launchpad-Bug' not in bug.keys():
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
182
                self.log.info('Ignoring e-mail with no detectable bug (Not from Launchpad)')            
35 by Dennis Kaarsemaker
adkfasdyfb
183
                continue
87 by Terence Simpson
Made the IMAP code as generic as possible
184
            else:
185
                tag = bug['X-Launchpad-Bug']
186
                if 'distribution=' not in tag and 'product=' not in tag:
187
                    self.log.info('Ignoring e-mail with no detectable bug (no distro/product)')
188
                    continue
189
                else:
190
                    tag = tag.split(';')[0].strip().replace("product=",'').replace("distribution=","")
191
192
            if not tag:
193
                self.log.info('Ignoring e-mail with no detectible bug (bad tag)')
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
194
                
195
            tag = tag[tag.find('+')+1:tag.find('@')]
35 by Dennis Kaarsemaker
adkfasdyfb
196
            if tag not in bugs:
197
                bugs[tag] = {}
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
198
51 by Dennis Kaarsemaker
Misc fixes
199
            # Determine bugtracker type (currently only Launchpad is supported anyway)
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
200
            if bug['X-Launchpad-Bug']:
51 by Dennis Kaarsemaker
Misc fixes
201
                tracker = self.db['launchpad']
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
202
                id = int(bug['Reply-To'].split()[1])
87 by Terence Simpson
Made the IMAP code as generic as possible
203
                subj = bug['Subject'];
204
                if '[NEW]' not in subj: #Not a new bug
205
                    continue
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
206
                if self.is_new(tracker, tag, id):
207
                    component = bug['X-Launchpad-Bug']
208
                    if 'component' in component:
209
                        component = component[component.find('component=')+10:]
210
                        component = component[:component.find(';')].replace('None','')
211
                    else:
212
                        component = ''
35 by Dennis Kaarsemaker
adkfasdyfb
213
                    try:
214
                        if component:
48 by Dennis Kaarsemaker
Fixes in the bugtracker plugin, first attempt at not disturbing a meeting
215
                            bugs[tag][id] = self.get_bug('',tracker, id, False)[0].replace('"','(%s) "' % component, 1)
35 by Dennis Kaarsemaker
adkfasdyfb
216
                        else:
48 by Dennis Kaarsemaker
Fixes in the bugtracker plugin, first attempt at not disturbing a meeting
217
                            bugs[tag][id] = self.get_bug('',tracker, id, False)[0]
51 by Dennis Kaarsemaker
Misc fixes
218
                        if '[apport]' in bugs[tag][id]:
219
                            bugs[tag].pop(id)
35 by Dennis Kaarsemaker
adkfasdyfb
220
                    except:
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
221
                        self.log.info("Unable to get new bug %d" % id)
39 by Dennis Kaarsemaker
Made bantracker releaseable
222
                        pass
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
223
            else:
224
                self.log.info('Ignoring e-mail with no detectable bug')
87 by Terence Simpson
Made the IMAP code as generic as possible
225
226
        reported_bugs = 0
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
227
21 by Dennis Kaarsemaker
* Completely rewritten encyclopedia plugin
228
        for c in irc.state.channels:
87 by Terence Simpson
Made the IMAP code as generic as possible
229
            tags = self.registryValue('bugReporter', channel=c)
230
            if not tags:
231
                continue
232
            for tag in tags.split(','):
233
                if not tag or tag not in bugs.keys():
234
                    continue
235
                for b in sorted(bugs[tag].keys()):
236
                    irc.queueMsg(ircmsgs.privmsg(c,'New bug: #%s' % bugs[tag][b][bugs[tag][b].find('bug ')+4:]))
237
                    reported_bugs = reported_bugs+1
1 by Dennis Kaarsemaker
Initial checkin
238
239
    def add(self, irc, msg, args, name, trackertype, url, description):
240
        """<name> <type> <url> [<description>]
241
242
        Add a bugtracker <url> to the list of defined bugtrackers. <type> is the
51 by Dennis Kaarsemaker
Misc fixes
243
        type of the tracker (currently only Launchpad, Debbugs, Bugzilla,
95 by Terence Simpson
Big-Old-Update Edition
244
        Issuezilla, Mantis and Trac are known). <name> is the name that will be used to
1 by Dennis Kaarsemaker
Initial checkin
245
        reference the bugzilla in all commands. Unambiguous abbreviations of
246
        <name> will be accepted also.  <description> is the common name for the
247
        bugzilla and will be listed with the bugzilla query; if not given, it
248
        defaults to <name>.
249
        """
250
        name = name.lower()
251
        if not description:
252
            description = name
253
        if url[-1] == '/':
254
            url = url[:-1]
255
        trackertype = trackertype.lower()
256
        if trackertype in defined_bugtrackers:
257
            self.db[name] = defined_bugtrackers[trackertype](name,url,description)
258
        else:
259
            irc.error("Bugtrackers of type '%s' are not understood" % trackertype)
260
            return
261
        registerBugtracker(name, url, description, trackertype)
262
        self.shorthand = utils.abbrev(self.db.keys())
263
        irc.replySuccess()
126 by Terence Simpson
Update code from working branch and fix a couple of bugs
264
    add = wrap(add, [('checkCapability', 'admin'), 'something', 'something', 'url', additional('text')])
1 by Dennis Kaarsemaker
Initial checkin
265
266
    def remove(self, irc, msg, args, name):
267
        """<abbreviation>
268
269
        Remove the bugtracker associated with <abbreviation> from the list of
270
        defined bugtrackers.
271
        """
272
        try:
273
            name = self.shorthand[name.lower()]
274
            del self.db[name]
275
            self.registryValue('bugtrackers').remove(name)
276
            self.shorthand = utils.abbrev(self.db.keys())
277
            irc.replySuccess()
278
        except KeyError:
279
            s = self.registryValue('replyNoBugtracker', msg.args[0])
280
            irc.error(s % name)
126 by Terence Simpson
Update code from working branch and fix a couple of bugs
281
    remove = wrap(remove, [('checkCapability', 'admin'), 'text'])
1 by Dennis Kaarsemaker
Initial checkin
282
41 by Dennis Kaarsemaker
Bugtracker now is much more usable and contains documentation. Backwards
283
    def rename(self, irc, msg, args, oldname, newname, newdesc):
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
284
        """<oldname> <newname>
285
72 by Terence Simpson
Added @help values
286
        Rename the bugtracker associated with <oldname> to <newname>.
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
287
        """
288
        try:
289
            name = self.shorthand[oldname.lower()]
290
            group = self.registryValue('bugtrackers.%s' % name.replace('.','\\.'), value=False)
41 by Dennis Kaarsemaker
Bugtracker now is much more usable and contains documentation. Backwards
291
            d = group.description()
292
            if newdesc:
293
                d = newdesc
294
            self.db[newname] = defined_bugtrackers[group.trackertype()](name,group.url(),d)
295
            registerBugtracker(newname, group.url(), d, group.trackertype())
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
296
            del self.db[name]
297
            self.registryValue('bugtrackers').remove(name)
298
            self.shorthand = utils.abbrev(self.db.keys())
299
            irc.replySuccess()
300
        except KeyError:
301
            s = self.registryValue('replyNoBugtracker', msg.args[0])
302
            irc.error(s % name)
126 by Terence Simpson
Update code from working branch and fix a couple of bugs
303
    rename = wrap(rename, [('checkCapability', 'admin'), 'something','something', additional('text')])
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
304
1 by Dennis Kaarsemaker
Initial checkin
305
    def list(self, irc,  msg, args, name):
306
        """[abbreviation]
307
308
        List defined bugtrackers. If [abbreviation] is specified, list the
309
        information for that bugtracker.
310
        """
311
        if name:
312
            name = name.lower()
313
            try:
314
                name = self.shorthand[name]
315
                (url, description, type) = (self.db[name].url, self.db[name].description,
316
                                            self.db[name].__class__.__name__)
317
                irc.reply('%s: %s, %s [%s]' % (name, description, url, type))
318
            except KeyError:
319
                s = self.registryValue('replyNoBugtracker', msg.args[0])
320
                irc.error(s % name)
321
        else:
322
            if self.db:
323
                L = self.db.keys()
324
                L.sort()
325
                irc.reply(utils.str.commaAndify(L))
326
            else:
327
                irc.reply('I have no defined bugtrackers.')
328
    list = wrap(list, [additional('text')])
329
330
    def bugSnarfer(self, irc, msg, match):
144 by Terence Simpson
Tweak regex for bugSnarfer in Bugtracker (LP: #148777)
331
        r"""\b(?P<bt>(([a-z0-9]+)?\s+bugs?|[a-z0-9]+))\s+#?(?P<bug>\d+(?!\d*[\-\.]\d+)((,|\s*(and|en|et|und|ir))\s*#?\d+(?!\d*[\-\.]\d+))*)"""
28 by Dennis Kaarsemaker
Just keep fixing
332
        if msg.args[0][0] == '#' and not self.registryValue('bugSnarfer', msg.args[0]):
1 by Dennis Kaarsemaker
Initial checkin
333
            return
43 by Dennis Kaarsemaker
More fixes everywhere
334
        nbugs = msg.tagged('nbugs')
335
        if not nbugs: nbugs = 0
336
        if nbugs >= 5:
337
            return
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
338
1 by Dennis Kaarsemaker
Initial checkin
339
        # Don't double on commands
340
        s = str(msg).split(':')[2]
92 by Terence Simpson
Bugtracker: Small fix
341
        if s and s[0] in str(conf.supybot.reply.whenAddressedBy.chars):
1 by Dennis Kaarsemaker
Initial checkin
342
            return
343
        sure_bug = match.group('bt').endswith('bug') or match.group('bt').endswith('bug')
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
344
        
1 by Dennis Kaarsemaker
Initial checkin
345
        # Get tracker name
346
        bugids = match.group('bug')
21 by Dennis Kaarsemaker
* Completely rewritten encyclopedia plugin
347
        reps = ((' ',''),('#',''),('and',','),('en',','),('et',','),('und',','),('ir',','))
1 by Dennis Kaarsemaker
Initial checkin
348
        for r in reps:
349
            bugids = bugids.replace(r[0],r[1])
43 by Dennis Kaarsemaker
More fixes everywhere
350
        bugids = bugids.split(',')[:5-nbugs]
48 by Dennis Kaarsemaker
Fixes in the bugtracker plugin, first attempt at not disturbing a meeting
351
        if not sure_bug:
352
            bugids = [x for x in bugids if int(x) > 100]
43 by Dennis Kaarsemaker
More fixes everywhere
353
        msg.tag('nbugs', nbugs + len(bugids))
1 by Dennis Kaarsemaker
Initial checkin
354
        bt = map(lambda x: x.lower(), match.group('bt').split())
355
        name = ''
356
        if len(bt) == 1 and not (bt[0] in ['bug','bugs']):
357
            try:
358
                name = bt[0].lower()
359
                tracker = self.db[name]
360
            except:
361
                return
362
        elif len(bt) == 2:
363
            try:
364
                name = bt[0].lower()
365
                tracker = self.db[name]
366
            except:
367
                name = ''
368
                pass
369
        if not name:
35 by Dennis Kaarsemaker
adkfasdyfb
370
            snarfTarget = self.registryValue('snarfTarget', msg.args[0]).lower()
1 by Dennis Kaarsemaker
Initial checkin
371
            if not snarfTarget:
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
372
                self.log.warning("no snarfTarget for Bugtracker")
1 by Dennis Kaarsemaker
Initial checkin
373
                return
374
            try:
375
                name = self.shorthand[snarfTarget.lower()]
376
            except:
377
               s = self.registryValue('replyNoBugtracker', name)
378
               irc.error(s % name)
379
        try:
380
            tracker = self.db[name]
381
        except KeyError:
382
            s = self.registryValue('replyNoBugtracker', name)
383
            irc.error(s % name)
384
        else:
385
            for bugid in bugids:
386
                bugid = int(bugid)
387
                try:
48 by Dennis Kaarsemaker
Fixes in the bugtracker plugin, first attempt at not disturbing a meeting
388
                    report = self.get_bug(msg.args[0],tracker,bugid,self.registryValue('showassignee', msg.args[0]))
41 by Dennis Kaarsemaker
Bugtracker now is much more usable and contains documentation. Backwards
389
                except BugNotFoundError:
43 by Dennis Kaarsemaker
More fixes everywhere
390
                    if self.registryValue('replyWhenNotFound'):
391
                        irc.error("%s bug %d could not be found" % (tracker.description, bugid))
1 by Dennis Kaarsemaker
Initial checkin
392
                except BugtrackerError, e:
27 by Dennis Kaarsemaker
Yes, more features!
393
                    if 'private' in str(e):
394
                        irc.reply("Bug %d on http://launchpad.net/bugs/%d is private" % (bugid, bugid))
395
                        return
1 by Dennis Kaarsemaker
Initial checkin
396
                    if not sure_bug and bugid < 30:
397
                        return
398
                    irc.error(str(e))
399
                else:
35 by Dennis Kaarsemaker
adkfasdyfb
400
                    for r in report:
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
401
                        irc.reply(makeClean(r), prefixNick=False)
1 by Dennis Kaarsemaker
Initial checkin
402
403
    def turlSnarfer(self, irc, msg, match):
43 by Dennis Kaarsemaker
More fixes everywhere
404
        r"(?P<tracker>https?://\S*?)/(Bugs/0*|str.php\?L|show_bug.cgi\?id=|bugreport.cgi\?bug=|(bugs|\+bug)/|ticket/|tracker/|\S*aid=)(?P<bug>\d+)(?P<sfurl>&group_id=\d+&at_id=\d+)?"
28 by Dennis Kaarsemaker
Just keep fixing
405
        if msg.args[0][0] == '#' and not self.registryValue('bugSnarfer', msg.args[0]):
1 by Dennis Kaarsemaker
Initial checkin
406
            return
43 by Dennis Kaarsemaker
More fixes everywhere
407
        nbugs = msg.tagged('nbugs')
408
        if not nbugs: nbugs = 0
409
        if nbugs >= 5:
410
            return
411
        msg.tag('nbugs', nbugs+1)
1 by Dennis Kaarsemaker
Initial checkin
412
        try:
413
            tracker = self.get_tracker(match.group(0),match.group('sfurl'))
414
            if not tracker:
21 by Dennis Kaarsemaker
* Completely rewritten encyclopedia plugin
415
                return
48 by Dennis Kaarsemaker
Fixes in the bugtracker plugin, first attempt at not disturbing a meeting
416
            report = self.get_bug(msg.args[0],tracker,int(match.group('bug')),self.registryValue('showassignee', msg.args[0]), do_url = False)
1 by Dennis Kaarsemaker
Initial checkin
417
        except BugtrackerError, e:
418
            irc.error(str(e))
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
419
        except BugNotFoundError, e:
420
            irc.error("%s bug %s not found" % (tracker, match.group('bug')))
1 by Dennis Kaarsemaker
Initial checkin
421
        else:
35 by Dennis Kaarsemaker
adkfasdyfb
422
            for r in report:
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
423
                irc.reply(makeClean(r), prefixNick=False)
1 by Dennis Kaarsemaker
Initial checkin
424
    turlSnarfer = urlSnarfer(turlSnarfer)
425
426
    # Only useful for launchpad developers
427
    def oopsSnarfer(self, irc, msg, match):
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
428
        r"OOPS-(?P<oopsid>\d*[\dA-Z]+)"
51 by Dennis Kaarsemaker
Misc fixes
429
        if msg.args[0][0] == '#' and not self.registryValue('bugSnarfer', msg.args[0]):
430
            return
1 by Dennis Kaarsemaker
Initial checkin
431
        oopsid = match.group(1)
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
432
        if oopsid.lower() == "tools":
433
            return
434
        irc.reply("https://lp-oops.canonical.com/oops.py/?oopsid=%s" % oopsid, prefixNick=False)
435
        #irc.reply("https://devpad.canonical.com/~jamesh/oops.cgi/%s" % oopsid, prefixNick=False)
51 by Dennis Kaarsemaker
Misc fixes
436
437
    def cveSnarfer(self, irc, msg, match):
438
        r"(cve[- ]\d{4}[- ]\d{4})"
439
        if msg.args[0][0] == '#' and not self.registryValue('bugSnarfer', msg.args[0]):
440
            return
441
        cve = match.group(1).replace(' ','-').upper()
442
        url = 'http://cve.mitre.org/cgi-bin/cvename.cgi?name=%s' % cve
443
        cvedata = utils.web.getUrl(url)
444
        m = cvere.search(cvedata)
445
        if m:
446
            cve = m.group(1).replace('\n', ' ')
447
            irc.reply("%s (%s)" % (cve,url))
448
449
    def cveUrlSnarfer(self, irc, msg, match):
450
        pass
1 by Dennis Kaarsemaker
Initial checkin
451
452
    def get_tracker(self,snarfurl,sfdata):
44 by Dennis Kaarsemaker
Fixy fixy!
453
        snarfurl = snarfurl.replace('sf.net','sourceforge.net')
27 by Dennis Kaarsemaker
Yes, more features!
454
        snarfhost = snarfurl.replace('http://','').replace('https://','')
455
        if '/' in snarfurl:
456
            snarfhost = snarfhost[:snarfhost.index('/')]
1 by Dennis Kaarsemaker
Initial checkin
457
        for t in self.db.keys():
458
            tracker = self.db[t]
459
            url = tracker.url.replace('http://','').replace('https://','')
460
            if 'sourceforge.net' in url:
461
                # Try to find the correct sf tracker
462
                if str(sfdata) in tracker.url:
463
                    return tracker
464
            if '/' in url:
465
                url = url[:url.index('/')]
27 by Dennis Kaarsemaker
Yes, more features!
466
            if url in snarfhost:
1 by Dennis Kaarsemaker
Initial checkin
467
                return tracker
468
        if 'sourceforge.net' in snarfurl:
469
            return self.db['sourceforge']
470
        # No tracker found, bummer. Let's try and add one
471
        if 'show_bug.cgi' in snarfurl:
472
            tracker = Bugzilla().get_tracker(snarfurl)
473
            if tracker:
474
                self.db[tracker.name] = tracker
475
                self.shorthand = utils.abbrev(self.db.keys())
476
                return tracker
477
        return None
478
48 by Dennis Kaarsemaker
Fixes in the bugtracker plugin, first attempt at not disturbing a meeting
479
    def get_bug(self, channel, tracker, id, do_assignee, do_url = True):
35 by Dennis Kaarsemaker
adkfasdyfb
480
        reports = []
481
        for r in tracker.get_bug(id):
48 by Dennis Kaarsemaker
Fixes in the bugtracker plugin, first attempt at not disturbing a meeting
482
            if not self.is_ok(channel, tracker, r[0]):
483
                continue
35 by Dennis Kaarsemaker
adkfasdyfb
484
            (bid, product, title, severity, status, assignee, url) = r
485
            severity = severity[0].upper() + severity[1:].lower()
486
            status = status[0].upper() + status[1:].lower()
487
            if not do_url:
488
                url = ''
489
            if product:
490
                reports.append("%s bug %s in %s \"%s\" [%s,%s] %s" % (tracker.description, bid, product, 
491
                                                                      title, severity, status, url))
492
            else:
493
                reports.append("%s bug %s \"%s\" [%s,%s] %s" % (tracker.description, bid, title, severity, status, url))
494
            if do_assignee and assignee:
495
                reports[-1] = reports[-1] + (" - Assigned to %s" % assignee)
496
        return reports
1 by Dennis Kaarsemaker
Initial checkin
497
498
# Define all bugtrackers
499
class IBugtracker:
500
    def __init__(self, name=None, url=None, description=None):
501
        self.name        = name
502
        self.url         = url
503
        self.description = description
504
505
    def get_bug(self, id):
506
        raise BugTrackerError("Bugtracker class does not implement get_bug")
507
508
    def get_tracker(self, url):
509
        raise BugTrackerError("Bugtracker class does not implement get_tracker")
510
511
class Bugzilla(IBugtracker):
512
    def get_tracker(self, url):
513
        url = url.replace('show_bug','xml')
514
        try:
515
            bugxml = utils.web.getUrl(url)
516
            tree = minidom.parseString(bugxml)
517
            url  = str(tree.getElementsByTagName('bugzilla')[0].attributes['urlbase'].childNodes[0].data)
518
            if url[-1] == '/':
519
                url = url[:-1]
520
            name = url[url.find('//') + 2:]
521
            if '/' in name:
522
                name = name[:name.find('/')]
523
            desc = name
524
            registerBugtracker(name, url, desc, 'bugzilla')
525
            tracker = Bugzilla(name, url, desc)
526
            return tracker
527
        except:
528
            return None
529
    def get_bug(self, id):
530
        url = "%s/xml.cgi?id=%d" % (self.url,id)
531
        try:
532
            bugxml = utils.web.getUrl(url)
533
            zilladom = minidom.parseString(bugxml)
534
        except Exception, e:
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
535
            s = 'Could not parse XML returned by %s: %s (%s)' % (self.description, e, url)
1 by Dennis Kaarsemaker
Initial checkin
536
            raise BugtrackerError, s
537
        bug_n = zilladom.getElementsByTagName('bug')[0]
538
        if bug_n.hasAttribute('error'):
539
            errtxt = bug_n.getAttribute('error')
41 by Dennis Kaarsemaker
Bugtracker now is much more usable and contains documentation. Backwards
540
            if errtxt == 'NotFound':
541
                raise BugNotFoundError
1 by Dennis Kaarsemaker
Initial checkin
542
            s = 'Error getting %s bug #%s: %s' % (self.description, id, errtxt)
543
            raise BugtrackerError, s
544
        try:
545
            title = _getnodetxt(bug_n.getElementsByTagName('short_desc')[0])
546
            status = _getnodetxt(bug_n.getElementsByTagName('bug_status')[0])
547
            try:
548
                status += ": " + _getnodetxt(bug_n.getElementsByTagName('resolution')[0])
549
            except:
550
                pass
551
            component = _getnodetxt(bug_n.getElementsByTagName('component')[0])
552
            severity = _getnodetxt(bug_n.getElementsByTagName('bug_severity')[0])
41 by Dennis Kaarsemaker
Bugtracker now is much more usable and contains documentation. Backwards
553
            assignee = '(unavailable)'
554
            try:
555
                assignee = _getnodetxt(bug_n.getElementsByTagName('assigned_to')[0])
556
            except:
557
                pass
1 by Dennis Kaarsemaker
Initial checkin
558
        except Exception, e:
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
559
            s = 'Could not parse XML returned by %s bugzilla: %s (%s)' % (self.description, e, url)
1 by Dennis Kaarsemaker
Initial checkin
560
            raise BugtrackerError, s
35 by Dennis Kaarsemaker
adkfasdyfb
561
        return [(id, component, title, severity, status, assignee, "%s/show_bug.cgi?id=%d" % (self.url, id))]
1 by Dennis Kaarsemaker
Initial checkin
562
563
class Issuezilla(IBugtracker):
564
    def get_bug(self, id):
565
        url = "%s/xml.cgi?id=%d" % (self.url,id)
566
        try:
567
            bugxml = utils.web.getUrl(url)
568
            zilladom = minidom.parseString(bugxml)
569
        except Exception, e:
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
570
            s = 'Could not parse XML returned by %s: %s (%s)' % (self.description, e, url)
1 by Dennis Kaarsemaker
Initial checkin
571
            raise BugtrackerError, s
572
        bug_n = zilladom.getElementsByTagName('issue')[0]
573
        if not (bug_n.getAttribute('status_code') == '200'):
41 by Dennis Kaarsemaker
Bugtracker now is much more usable and contains documentation. Backwards
574
            if bug_n.getAttribute('status_message') == 'NotFound':
575
                raise BugNotFoundError
1 by Dennis Kaarsemaker
Initial checkin
576
            s = 'Error getting %s bug #%s: %s' % (self.description, id, bug_n.getAttribute('status_message'))
577
            raise BugtrackerError, s
578
        try:
579
            title = _getnodetxt(bug_n.getElementsByTagName('short_desc')[0])
580
            status = _getnodetxt(bug_n.getElementsByTagName('issue_status')[0])
581
            try:
582
                status += ": " + _getnodetxt(bug_n.getElementsByTagName('resolution')[0])
583
            except:
584
                pass
585
            component = _getnodetxt(bug_n.getElementsByTagName('component')[0])
586
            severity = _getnodetxt(bug_n.getElementsByTagName('issue_type')[0])
35 by Dennis Kaarsemaker
adkfasdyfb
587
            assignee = _getnodetxt(bug_n.getElementsByTagName('assigned_to')[0])
1 by Dennis Kaarsemaker
Initial checkin
588
        except Exception, e:
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
589
            s = 'Could not parse XML returned by %s bugzilla: %s (%s)' % (self.description, e, url)
1 by Dennis Kaarsemaker
Initial checkin
590
            raise BugtrackerError, s
35 by Dennis Kaarsemaker
adkfasdyfb
591
        return [(id, component, title, severity, status, assignee, "%s/show_bug.cgi?id=%d" % (self.url, id))]
1 by Dennis Kaarsemaker
Initial checkin
592
51 by Dennis Kaarsemaker
Misc fixes
593
class Launchpad(IBugtracker):
1 by Dennis Kaarsemaker
Initial checkin
594
    def get_bug(self, id):
595
        try:
156 by Jonathan Davies
Removed debug code.
596
            #print "Looking up bug #%s..." % id
154 by Jonathan Davies
Mass clean-up of code to the state that it actually works.
597
            bug_data = launchpad.bugs[int(id)]
149 by Jonathan Davies
Implemented anonymous bug reporting support to Bugtracker via the Launchpad
598
        except KeyError:
154 by Jonathan Davies
Mass clean-up of code to the state that it actually works.
599
            raise BugtrackerError, "Bug #%s not found." % id
159 by Jonathan Davies
Really catch private bugs.
600
        except:
168 by Jonathan Davies
Display bug heat stuff.
601
            print Exception.message
602
603
        try:
604
            bug_data.private
605
        except:
149 by Jonathan Davies
Implemented anonymous bug reporting support to Bugtracker via the Launchpad
606
            raise BugtrackerError, "Bug #%s is private." % id
607
154 by Jonathan Davies
Mass clean-up of code to the state that it actually works.
608
        bug_title = bug_data.title
162 by Jonathan Davies
Display how many users a bug affects.
609
        people_affected = bug_data.users_affected_count_with_dupes
158 by Jonathan Davies
Read task data of a bug from the last task.
610
        bug_tasks_number = len(bug_data.bug_tasks) - 1
611
        # Read task data from last task.
612
        bug_task = list(bug_data.bug_tasks)[bug_tasks_number]
154 by Jonathan Davies
Mass clean-up of code to the state that it actually works.
613
        bug_project = bug_task.bug_target_name
614
        bug_importance = bug_task.importance
615
        bug_status = bug_task.status
616
        bug_assignee = ""
163 by Jonathan Davies
Report how many duplicates a bug report has.
617
        bug_duplicates = len(bug_data.duplicates)
168 by Jonathan Davies
Display bug heat stuff.
618
        bug_heat = bug_data.heat
619
154 by Jonathan Davies
Mass clean-up of code to the state that it actually works.
620
        if bug_task.assignee:
621
            bug_assignee = bug_task.assignee.name
168 by Jonathan Davies
Display bug heat stuff.
622
160 by Jonathan Davies
Catch bugs with more than one task and report the number of tasks.
623
        if bug_tasks_number > 0:
624
            if bug_tasks_number == 1:
161 by Jonathan Davies
Improved wording of more projects reply.
625
                bug_project += " (and %d other project)" % bug_tasks_number
160 by Jonathan Davies
Catch bugs with more than one task and report the number of tasks.
626
            else:
161 by Jonathan Davies
Improved wording of more projects reply.
627
                bug_project += " (and %d other projects)" % bug_tasks_number
168 by Jonathan Davies
Display bug heat stuff.
628
35 by Dennis Kaarsemaker
adkfasdyfb
629
        # Try and find duplicates
149 by Jonathan Davies
Implemented anonymous bug reporting support to Bugtracker via the Launchpad
630
        if bug_data.duplicate_of is not None:
154 by Jonathan Davies
Mass clean-up of code to the state that it actually works.
631
            dup_bug = launchpad.bugs[id].duplicate_of
160 by Jonathan Davies
Catch bugs with more than one task and report the number of tasks.
632
            return [(id, bug_project,
633
                    bug_title + (' (dup-of: %d)' % dup_bug.id), bug_importance,
634
                    bug_status, bug_assignee,
635
                    "%s/bugs/%s" % (self.url, id))] + self.get_bug(dup_bug.id)
149 by Jonathan Davies
Implemented anonymous bug reporting support to Bugtracker via the Launchpad
636
162 by Jonathan Davies
Display how many users a bug affects.
637
        if people_affected > 0:
164 by Jonathan Davies
Added a : here.
638
            bug_title += " (affects: %d)" % people_affected
163 by Jonathan Davies
Report how many duplicates a bug report has.
639
640
        if bug_duplicates > 0:
641
            bug_title += " (dups: %d)" % bug_duplicates
162 by Jonathan Davies
Display how many users a bug affects.
642
168 by Jonathan Davies
Display bug heat stuff.
643
        if bug_heat != 0:
644
            bug_title += " (heat: %d)" % bug_heat
645
160 by Jonathan Davies
Catch bugs with more than one task and report the number of tasks.
646
        return [(id, bug_project, bug_title, bug_importance, bug_status,
647
            bug_assignee, "%s/bugs/%s" % (self.url, id))]
1 by Dennis Kaarsemaker
Initial checkin
648
            
649
# <rant>
650
# Debbugs sucks donkeyballs
651
# * HTML pages are inconsistent
652
# * Parsing mboxes gets incorrect with cloning perversions (eg with bug 330000)
21 by Dennis Kaarsemaker
* Completely rewritten encyclopedia plugin
653
# * No sane way of accessing bug reports in a machine readable way (bts2ldap
654
#   has no search on bugid)
655
# * The damn thing allow incomplete bugs, eg bugs without severity set. WTF?!?
1 by Dennis Kaarsemaker
Initial checkin
656
#
53 by Dennis Kaarsemaker
Use the SOAP interface at bugs.donarmstrong.com for debbugs
657
# Fortunately bugs.donarmstrong.com has a SOAP interface which we can use.
1 by Dennis Kaarsemaker
Initial checkin
658
# </rant>
659
class Debbugs(IBugtracker):
53 by Dennis Kaarsemaker
Use the SOAP interface at bugs.donarmstrong.com for debbugs
660
    def __init__(self, *args, **kwargs):
661
        IBugtracker.__init__(self, *args, **kwargs)
66 by Dennis Kaarsemaker
Misc fixes
662
        self.soap_proxy = SOAPpy.SOAPProxy("bugs.debian.org/cgi-bin/soap.cgi", "Debbugs/SOAP/Status")
53 by Dennis Kaarsemaker
Use the SOAP interface at bugs.donarmstrong.com for debbugs
663
        self.soap_proxy.soapaction = "Debbugs/SOAP/Status#get_status"
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
664
1 by Dennis Kaarsemaker
Initial checkin
665
    def get_bug(self, id):
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
666
        bug_url = "http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=%d" % id
1 by Dennis Kaarsemaker
Initial checkin
667
        try:
53 by Dennis Kaarsemaker
Use the SOAP interface at bugs.donarmstrong.com for debbugs
668
            raw = self.soap_proxy.get_status(id)
1 by Dennis Kaarsemaker
Initial checkin
669
        except Exception, e:
670
            s = 'Could not parse data returned by %s: %s' % (self.description, e)
671
            raise BugtrackerError, s
53 by Dennis Kaarsemaker
Use the SOAP interface at bugs.donarmstrong.com for debbugs
672
        if not raw:
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
673
            raise BugNotFoundError
53 by Dennis Kaarsemaker
Use the SOAP interface at bugs.donarmstrong.com for debbugs
674
        raw = raw['item']['value']
1 by Dennis Kaarsemaker
Initial checkin
675
        try:
53 by Dennis Kaarsemaker
Use the SOAP interface at bugs.donarmstrong.com for debbugs
676
            if len(raw['fixed_versions']):
677
                status = 'Fixed'
678
            else:
679
                status = 'Open'
680
            return [(id, raw['package'], raw['subject'], raw['severity'], status, '', "%s/%s" % (self.url, id))]
1 by Dennis Kaarsemaker
Initial checkin
681
        except Exception, e:
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
682
            s = 'Could not parse data returned by %s bugtracker: %s (%s)' % (self.description, e, bug_url)
1 by Dennis Kaarsemaker
Initial checkin
683
            raise BugtrackerError, s
684
95 by Terence Simpson
Big-Old-Update Edition
685
class Mantis(IBugtracker):
686
    def __init__(self, *args, **kwargs):
687
        IBugtracker.__init__(self, *args, **kwargs)
688
        self.soap_proxy = SOAPpy.SOAPProxy(self.url + "/api/soap/mantisconnect.php", "http://futureware.biz/mantisconnect")
689
        self.soap_proxy.soapaction = "http://futureware.biz/mantisconnect#mc_issue_get"
690
691
    def get_bug(self, id):
692
        url = self.url + "/view.php?id=%i" % id
693
        try:
694
            raw = self.soap_proxy.mc_issue_get('', "", id)
695
        except Exception, e:
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
696
            s = 'Could not parse data returned by %s: %s (%s)' % (self.description, e, url)
95 by Terence Simpson
Big-Old-Update Edition
697
            raise BugtrackerError, s
698
        if not raw:
699
            raise BugNotFoundError
700
        try:
701
            return [(id, raw['project']['name'], raw['summary'], raw['priority']['name'], raw['resolution']['name'], '', url)]
702
        except Exception, e:
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
703
            s = 'Could not parse data returned by %s bugtracker: %s (%s)' % (self.description, e, url)
95 by Terence Simpson
Big-Old-Update Edition
704
            raise BugtrackerError, s
705
132 by Terence Simpson
Bugtracker/plugin.py: Update Trac class to use the "Tab-delimited Text" source rather than screen-scraping
706
# For trac based trackers we get the tab-separated-values format.
707
# The other option is a comma-separated-values format, but if the description
708
# has commas, things get tricky.
709
# This should be more robust than the screen-scraping done previously.
1 by Dennis Kaarsemaker
Initial checkin
710
class Trac(IBugtracker):
132 by Terence Simpson
Bugtracker/plugin.py: Update Trac class to use the "Tab-delimited Text" source rather than screen-scraping
711
    def get_bug(self, id): # This is still a little rough, but it works :)
712
        bug_url = "%s/%d" % (self.url, id)
1 by Dennis Kaarsemaker
Initial checkin
713
        try:
132 by Terence Simpson
Bugtracker/plugin.py: Update Trac class to use the "Tab-delimited Text" source rather than screen-scraping
714
            raw = utils.web.getUrl("%s?format=tab" % bug_url)
1 by Dennis Kaarsemaker
Initial checkin
715
        except Exception, e:
41 by Dennis Kaarsemaker
Bugtracker now is much more usable and contains documentation. Backwards
716
            if 'HTTP Error 500' in str(e):
717
                raise BugNotFoundError
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
718
            s = 'Could not parse data returned by %s: %s' % (self.description, e, bug_url)
1 by Dennis Kaarsemaker
Initial checkin
719
            raise BugtrackerError, s
132 by Terence Simpson
Bugtracker/plugin.py: Update Trac class to use the "Tab-delimited Text" source rather than screen-scraping
720
        raw = raw.replace("\r\n", '\n')
721
        (headers, rest) = raw.split('\n', 1)
722
        headers = headers.strip().split('\t')
723
        rest = rest.strip().split('\t')
131 by Terence Simpson
Bugtracker/plugin.py: Temporary fix for Trac
724
        title = status = package = severity = assignee = "Unknown"
132 by Terence Simpson
Bugtracker/plugin.py: Update Trac class to use the "Tab-delimited Text" source rather than screen-scraping
725
        if "summary" in headers:
726
            title = rest[headers.index("summary")]
727
        if "status" in headers:
728
            status = rest[headers.index("status")]
729
        if "component" in headers:
730
            package = rest[headers.index("component")]
731
        if "severity" in headers:
732
            severity = rest[headers.index("severity")]
733
        if "owner" in headers:
734
            assingee = rest[headers.index("owner")]
735
        if severity == "Unknown" and "priority" in headers:
736
            severity = rest[headers.index("priority")]
737
738
        return [(id, package, title, severity, status, assignee, bug_url)]
31 by Dennis Kaarsemaker
This project is now officially the one with the worst commitlog :)
739
        
740
class WikiForms(IBugtracker):
741
    def get_bug(self, id):
742
        def strip_tags(s):
743
            while '<' in s and '>' in s:
744
                s = str(s[:s.find('<')]) + str(s[s.find('>')+1:])
745
            return s
746
747
        url = "%s/%05d" % (self.url, id)
748
        try:
749
            bugdata = utils.web.getUrl(url)
750
        except Exception, e:
41 by Dennis Kaarsemaker
Bugtracker now is much more usable and contains documentation. Backwards
751
            if 'HTTP Error 404' in str(e):
752
                raise BugNotFoundError
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
753
            s = 'Could not parse data returned by %s: %s (%s)' % (self.description, e, url)
31 by Dennis Kaarsemaker
This project is now officially the one with the worst commitlog :)
754
            raise BugtrackerError, s
755
        for l in bugdata.split("\n"):
756
            l2 = l.lower()
757
            if '<dt>importance</dt>' in l2:
758
                severity = 'Importance ' + strip_tags(l[l.find('<dd>')+4:])
759
            if '<dt>summary</dt>' in l2:
760
                title = strip_tags(l[l.find('<dd>')+4:])
761
            if '<dt>status</dt>' in l2:
762
                status = strip_tags(l[l.find('<dd>')+4:])
763
            if '<dt>category</dt>' in l2:
764
                package = strip_tags(l[l.find('<dd>')+4:])
35 by Dennis Kaarsemaker
adkfasdyfb
765
        return [(id, package, title, severity, status, '', "%s/%05d" % (self.url, id))]
41 by Dennis Kaarsemaker
Bugtracker now is much more usable and contains documentation. Backwards
766
767
class Str(IBugtracker):
768
    def get_bug(self, id):
769
        def strip_tags(s):
770
            while '<' in s and '>' in s:
771
                s = str(s[:s.find('<')]) + str(s[s.find('>')+1:])
772
            return s
773
        url = "%s?L%d" % (self.url, id)
774
        try:
775
            bugdata = utils.web.getUrl(url)
776
        except Exception, e:
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
777
            s = 'Could not parse data returned by %s: %s (%s)' % (self.description, e, url)
41 by Dennis Kaarsemaker
Bugtracker now is much more usable and contains documentation. Backwards
778
            raise BugtrackerError, s
779
        for l in bugdata.split("\n"):
780
            l2 = l.lower()
781
            if 'nowrap>priority:</th>' in l2:
782
                severity = 'Priority ' + l[l.find(' - ')+3:min(l.find(','),l.find('</td>'))]
783
            if '>application:</th>' in l2:
784
                package = l[l.find('<td>')+4:l.find('</td>')]
785
            if 'nowrap>status:</th>' in l2:
786
                status = l[l.find(' - ')+3:l.find('</td>')]
787
            if 'nowrap>summary:</th>' in l2:
788
                title = l[l.find('<td>')+4:l.find('</td>')]
789
            if 'nowrap>assigned to:</th>' in l2:
790
                assignee = strip_tags(l[l.find('<td>')+4:l.find('</td>')])
791
                if assignee == 'Unassigned':
792
                    assignee = 'nobody'
42 by Dennis Kaarsemaker
Encyclopedia made neat.
793
        return [(id, package, title, severity, status, assignee, "%s?L%d" % (self.url, id))]
41 by Dennis Kaarsemaker
Bugtracker now is much more usable and contains documentation. Backwards
794
        
1 by Dennis Kaarsemaker
Initial checkin
795
796
sfre = re.compile(r"""
797
                  .*?
798
                  <h2>\[.*?\]\s*(?P<title>.*?)</h2>
35 by Dennis Kaarsemaker
adkfasdyfb
799
                  .*?
43 by Dennis Kaarsemaker
More fixes everywhere
800
                  assigned.*?<br>\s+(?P<assignee>\S+)
1 by Dennis Kaarsemaker
Initial checkin
801
                  .*?
43 by Dennis Kaarsemaker
More fixes everywhere
802
                  priority.*?(?P<priority>\d+)
1 by Dennis Kaarsemaker
Initial checkin
803
                  .*?
43 by Dennis Kaarsemaker
More fixes everywhere
804
                  status.*?<br>\s+(?P<status>\S+)
1 by Dennis Kaarsemaker
Initial checkin
805
                  .*?
43 by Dennis Kaarsemaker
More fixes everywhere
806
                  resolution.*?<br>\s+(?P<resolution>\S+)
1 by Dennis Kaarsemaker
Initial checkin
807
                  .*?
808
                  """, re.VERBOSE | re.DOTALL | re.I)
809
class Sourceforge(IBugtracker):
810
    _sf_url = 'http://sf.net/support/tracker.php?aid=%d'
811
    def get_bug(self, id):
812
        url = self._sf_url % id
813
        try:
814
            bugdata = utils.web.getUrl(url)
815
        except Exception, e:
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
816
            s = 'Could not parse data returned by %s: %s (%s)' % (self.description, e, url)
1 by Dennis Kaarsemaker
Initial checkin
817
            raise BugtrackerError, s
818
        try:
819
            reo = sfre.search(bugdata)
820
            status = reo.group('status')
821
            resolution = reo.group('resolution')
822
            if not (resolution.lower() == 'none'):
823
                status += ' ' + resolution
35 by Dennis Kaarsemaker
adkfasdyfb
824
            return [(id, None, reo.group('title'), "Pri: %s" % reo.group('priority'), status, reo.group('assignee'),self._sf_url % id)]
1 by Dennis Kaarsemaker
Initial checkin
825
        except:
40 by Dennis Kaarsemaker
Made bugtracker plugin more usable
826
            raise BugNotFoundError
1 by Dennis Kaarsemaker
Initial checkin
827
828
# Introspection is quite cool
829
defined_bugtrackers = {}
830
v = vars()
831
for k in v.keys():
832
    if type(v[k]) == type(IBugtracker) and issubclass(v[k], IBugtracker) and not (v[k] == IBugtracker):
833
        defined_bugtrackers[k.lower()] = v[k]
834
835
registerBugtracker('mozilla', 'http://bugzilla.mozilla.org', 'Mozilla', 'bugzilla')
133 by Terence Simpson
Resync all running ubottu code to bzr branch, they should now be in sync again
836
#registerBugtracker('ubuntu', 'http://bugzilla.ubuntu.com', 'Ubuntu', 'bugzilla')
93 by Terence Simpson
Make sure 'ubuntu ###' looks at launchpad for the bug, not bugzilla.ubuntu.com
837
registerBugtracker('ubuntu', 'https://launchpad.net', 'Ubuntu', 'launchpad')
1 by Dennis Kaarsemaker
Initial checkin
838
registerBugtracker('gnome', 'http://bugzilla.gnome.org', 'Gnome', 'bugzilla')
839
registerBugtracker('gnome2', 'http://bugs.gnome.org', 'Gnome', 'bugzilla')
840
registerBugtracker('kde', 'http://bugs.kde.org', 'KDE', 'bugzilla')
841
registerBugtracker('ximian', 'http://bugzilla.ximian.com', 'Ximian', 'bugzilla')
842
registerBugtracker('freedesktop', 'http://bugzilla.freedesktop.org', 'Freedesktop', 'bugzilla')
843
registerBugtracker('freedesktop2', 'http://bugs.freedesktop.org', 'Freedesktop', 'bugzilla')
844
registerBugtracker('openoffice', 'http://openoffice.org/issues', 'OpenOffice.org', 'issuezilla')
51 by Dennis Kaarsemaker
Misc fixes
845
registerBugtracker('launchpad', 'https://launchpad.net', 'Launchpad', 'launchpad')
846
registerBugtracker('lp', 'https://launchpad.net', 'Launchpad', 'launchpad')
847
registerBugtracker('malone', 'https://launchpad.net', 'Launchpad', 'launchpad')
1 by Dennis Kaarsemaker
Initial checkin
848
registerBugtracker('debian', 'http://bugs.debian.org', 'Debian', 'debbugs')
43 by Dennis Kaarsemaker
More fixes everywhere
849
registerBugtracker('trac', 'http://trac.edgewall.org/ticket', 'Trac', 'trac')
1 by Dennis Kaarsemaker
Initial checkin
850
registerBugtracker('django', 'http://code.djangoproject.com/ticket', 'Django', 'trac')
41 by Dennis Kaarsemaker
Bugtracker now is much more usable and contains documentation. Backwards
851
registerBugtracker('cups', 'http://www.cups.org/str.php', 'CUPS', 'str')
852
registerBugtracker('gnewsense', 'http://bugs.gnewsense.org/Bugs', 'gNewSense', 'wikiforms')
1 by Dennis Kaarsemaker
Initial checkin
853
registerBugtracker('supybot', 'http://sourceforge.net/tracker/?group_id=58965&atid=489447', 'Supybot', 'sourceforge')
95 by Terence Simpson
Big-Old-Update Edition
854
registerBugtracker('mantis', "http://www.mantisbt.org/bugs", "Mantis", 'mantis')
41 by Dennis Kaarsemaker
Bugtracker now is much more usable and contains documentation. Backwards
855
# Don't delete this one
1 by Dennis Kaarsemaker
Initial checkin
856
registerBugtracker('sourceforge', 'http://sourceforge.net/tracker/', 'Sourceforge', 'sourceforge')
857
Class = Bugtracker