~ubuntu-branches/ubuntu/utopic/gozerbot/utopic

« back to all changes in this revision

Viewing changes to gozerbot/jabberbot.py

  • Committer: Bazaar Package Importer
  • Author(s): Jeremy Malcolm
  • Date: 2009-09-14 09:00:29 UTC
  • mfrom: (1.1.4 upstream) (3.1.5 sid)
  • Revision ID: james.westby@ubuntu.com-20090914090029-uval0ekt72kmklxw
Tags: 0.9.1.3-3
Changed dependency on python-setuptools to python-pkg-resources
(Closes: #546435) 

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# gozerbot/jabberbot.py
2
 
#
3
 
#
4
 
 
5
 
""" jabber bot definition """
6
 
 
7
 
__copyright__ = 'this file is in the public domain'
8
 
__revision__ = '$Id: bot.py 75 2005-09-12 16:33:00Z bart $'
9
 
 
10
 
from gozerbot.eventhandler import commandhandler
11
 
from gozerbot.users import users
12
 
from gozerbot.monitor import jabbermonitor
13
 
from gozerbot.wait import Jabberwait, Jabbererrorwait
14
 
from gozerbot.generic import rlog, handle_exception, lockdec, waitforqueue, \
15
 
toenc, fromenc, jabberstrip
16
 
from gozerbot.config import config
17
 
from gozerbot.plugins import plugins
18
 
from gozerbot.jabbermsg import Jabbermsg
19
 
from gozerbot.jabberpresence import Jabberpresence
20
 
from gozerbot.pdod import Pdod
21
 
from gozerbot.dol import Dol
22
 
from gozerbot.datadir import datadir
23
 
from gozerbot.channels import Channels
24
 
from gozerbot.less import Less
25
 
from gozerbot.ignore import shouldignore
26
 
from gozerbot.callbacks import jcallbacks
27
 
from gozerbot.thr import start_new_thread
28
 
from gozerbot.fleet import fleet
29
 
from gozerbot.runner import runner
30
 
from gozerbot.botbase import BotBase
31
 
from xmpp.simplexml import Node
32
 
import xmpp, time, Queue, os, threading, thread, types, xml
33
 
 
34
 
jabberoutlock = thread.allocate_lock()
35
 
jabberinlock = thread.allocate_lock()
36
 
outlocked = lockdec(jabberoutlock)
37
 
inlocked = lockdec(jabberinlock)
38
 
 
39
 
class Jabberbot(BotBase):
40
 
 
41
 
    """ jabber bot class """
42
 
 
43
 
    def __init__(self, name='jabbermain', owner=[]):
44
 
        BotBase.__init__(self, name, owner)
45
 
        self.type = 'jabber'
46
 
        self.outqueue = Queue.Queue()
47
 
        self.host = None
48
 
        self.user = None
49
 
        self.sock = None
50
 
        self.jid = None
51
 
        self.username = None
52
 
        self.me = None
53
 
        self.server = None
54
 
        self.lastin = None
55
 
        self.test = 0
56
 
        self.connecttime = 0
57
 
        self.connection = None
58
 
        self.privwait = Jabberwait()
59
 
        self.errorwait = Jabbererrorwait()
60
 
        self.jabber = True
61
 
        self.connectok = threading.Event()
62
 
        self.jids = {}
63
 
        self.topics = {}
64
 
        self.timejoined = {}
65
 
        if not self.state.has_key('ratelimit'):
66
 
            self.state['ratelimit'] = 0
67
 
        
68
 
    def _resumedata(self): # stateful reboot request, just shutdown
69
 
        self.exit()
70
 
        return {self.name: [self.host, self.user, self.password, self.port]}
71
 
 
72
 
    def _doprocess(self):
73
 
        """ process loop """
74
 
        while not self.stopped:
75
 
            try:
76
 
                time.sleep(0.0001)
77
 
                res = self.connection.Process()
78
 
                if res:
79
 
                    self.lastin = time.time()
80
 
            except xmpp.StreamError, ex:
81
 
                if u'Disconnected' in str(ex):
82
 
                    rlog(10, self.name, str(ex))
83
 
                    self.reconnect()
84
 
            except xml.parsers.expat.ExpatError, ex:
85
 
                if u'not well-formed' in str(ex):
86
 
                    rlog(10, self.name, str(ex))
87
 
                    continue
88
 
            except Exception, ex:
89
 
                if not self.stopped:
90
 
                    handle_exception()
91
 
                    time.sleep(2)
92
 
                else:
93
 
                    return
94
 
 
95
 
    def _outputloop(self):
96
 
        rlog(10, self.name, 'starting outputloop')
97
 
        while not self.stopped:
98
 
            what = self.outqueue.get()
99
 
            if self.stopped or what == None:
100
 
                 break
101
 
            self.rawsend(what)
102
 
            sleeptime = config['jabberoutsleep']
103
 
            if sleeptime:
104
 
                time.sleep(sleeptime)
105
 
            else:
106
 
                time.sleep(0.1)
107
 
        rlog(10, self.name, 'stopping outputloop')
108
 
 
109
 
    def _keepalive(self):
110
 
        """ keepalive method .. send empty string to self every 3 minutes """
111
 
        nrsec = 0
112
 
        while not self.stopped:
113
 
            time.sleep(1)
114
 
            nrsec += 1
115
 
            if nrsec < 180:
116
 
                continue
117
 
            else:
118
 
                nrsec = 0
119
 
            self.say(self.me, "")
120
 
 
121
 
    def _keepchannelsalive(self):
122
 
        """ channels keep alive method """
123
 
        nrsec = 0
124
 
        while not self.stopped:
125
 
            time.sleep(1)
126
 
            nrsec += 1
127
 
            if nrsec < 600:
128
 
                continue
129
 
            for i in self.state['joinedchannels']:
130
 
                self.say(i, "")
131
 
 
132
 
    def _connect(self, host, user, password, port=5222):
133
 
        """ connect to server .. start read loop """
134
 
        self.host = host
135
 
        self.port = port
136
 
        self.user = user
137
 
        self.password = password
138
 
        if not '@' in user:
139
 
            rlog(100, self.name, 'user needs to be in username@host format')
140
 
            return
141
 
        self.username = user.split('@')[0]
142
 
        self.me = user
143
 
        self.jid = xmpp.JID(user)
144
 
        self.server = self.jid.getDomain()
145
 
        self.nick = self.username
146
 
        self.password = password
147
 
        rlog(10, self.name, 'connecting to %s' % self.host)
148
 
#        self.connection = xmpp.Client(self.server, debug=['always', 'nodebuilder'])
149
 
        self.connection = xmpp.Client(self.server, debug=[])
150
 
        self.connection.connect((self.host, self.port))
151
 
        rlog(10, self.name, 'doing auth')
152
 
        auth = self.connection.auth(self.username, self.password, \
153
 
'gozerbot')
154
 
        if not auth:
155
 
            rlog(10, self.name, 'auth for %s failed .. trying register' \
156
 
% self.username)
157
 
            info = {'username': self.username, 'password': self.password}
158
 
            xmpp.features.getRegInfo(self.connection, self.host, info)
159
 
            if not xmpp.features.register(self.connection, self.host, info):
160
 
                rlog(100, self.name, "can't register")
161
 
                return
162
 
            else:
163
 
                self.connection = xmpp.Client(self.server, debug=[])
164
 
                self.connection.connect((self.host, self.port))
165
 
                auth = self.connection.auth(self.username, self.password, \
166
 
'gozerbot')
167
 
                rlog(100, self.name, "register succeded")
168
 
        self.connecttime = time.time()
169
 
        rlog(100, self.name, 'connected! type: %s' % \
170
 
self.connection.connected)
171
 
        self.connection.RegisterHandler('message', self.messageHandler)
172
 
        self.connection.RegisterHandler('presence', self.presenceHandler)
173
 
        self.connection.RegisterHandler('iq', self.iqHandler)
174
 
        self.connection.UnregisterDisconnectHandler(\
175
 
self.connection.DisconnectHandler)
176
 
        self.connection.RegisterDisconnectHandler(self.disconnectHandler)
177
 
        self.connection.UnregisterHandlerOnce = self.UnregisterHandlerOnce
178
 
        self.stopped = 0
179
 
        jabbermonitor.start()
180
 
        start_new_thread(self._doprocess, ())
181
 
        start_new_thread(self._keepalive, ())
182
 
        start_new_thread(self._outputloop, ())
183
 
        #start_new_thread(self._keepchannelsalive, ())
184
 
        self.connection.sendInitPresence()
185
 
        self.connection.getRoster()
186
 
        self.connectok.set()
187
 
        return 1
188
 
 
189
 
    def connect(self, host, user, password, port=5222, reconnect=True):
190
 
        res = 0
191
 
        try:
192
 
            res = self._connect(host, user, password, port)
193
 
        #except AttributeError:
194
 
        #    rlog(10, self.name, "%s denied the connection" % self.host)
195
 
        #    return
196
 
        except Exception, ex:
197
 
            if self.stopped:
198
 
                return 0
199
 
            rlog(10, self.name, str(ex))
200
 
            #handle_exception()
201
 
            if reconnect:
202
 
                return self.reconnect()
203
 
        if res and not fleet.byname(self.name):
204
 
            fleet.addbot(self)
205
 
        return res
206
 
 
207
 
    def joinchannels(self):
208
 
        """ join channels """
209
 
        time.sleep(5)
210
 
        for i in self.state['joinedchannels']:
211
 
            key = self.channels.getkey(i)
212
 
            nick = self.channels.getnick(i)
213
 
            result = self.join(i, key, nick)
214
 
            if result == 1:
215
 
                rlog(10, self.name, 'joined %s' % i)
216
 
            else:
217
 
                rlog(10, self.name, 'failed to join %s: %s' % (i, result))
218
 
 
219
 
 
220
 
    def broadcast(self, txt):
221
 
        for i in self.state['joinedchannels']:
222
 
            self.say(i, txt)
223
 
 
224
 
    def sendpresence(self, to):
225
 
        """ send presence """
226
 
        presence = xmpp.Presence(to=to)
227
 
        presence.setFrom(self.me)
228
 
        self.send(presence)
229
 
 
230
 
 
231
 
    def iqHandler(self, conn, node):
232
 
        """ handle iq stanza's """
233
 
        rlog(2, self.name + '-Iq', str(node))
234
 
        node.cmnd = 'Iq'
235
 
        node.conn = conn
236
 
        jcallbacks.check(self, node)
237
 
 
238
 
    def messageHandler(self, conn, msg):
239
 
        """ message handler """
240
 
        if self.test:
241
 
            return
242
 
        if 'jabber:x:delay' in str(msg):
243
 
            return
244
 
        m = Jabbermsg(msg)
245
 
        m.toirc(self)
246
 
        if m.groupchat and m.getSubject():
247
 
            self.topiccheck(m)
248
 
            return
249
 
        if self.privwait.check(m):
250
 
            return
251
 
        if not m.txt:
252
 
            return
253
 
        if self.me in m.userhost:
254
 
            return 0
255
 
        if m.groupchat and self.nick == m.resource:
256
 
            return 0
257
 
        go = 1
258
 
        try:
259
 
            cc = self.channels[m.channel]['cc']
260
 
        except (TypeError, KeyError):
261
 
            cc = config['defaultcc'] or '!'
262
 
        try:
263
 
            channick = self.channels[m.channel]['nick']
264
 
        except (TypeError, KeyError):
265
 
            channick = self.nick
266
 
        if m.groupchat and not m.txt[0] in cc:
267
 
            go = 0
268
 
        if m.txt.startswith("%s: " % channick):
269
 
            m.txt = m.txt.replace("%s: " % channick, "")
270
 
            go = 1
271
 
        elif m.txt.startswith("%s, " % channick):
272
 
            m.txt = m.txt.replace("%s, " % channick, "")
273
 
            go = 1
274
 
        if m.txt[0] in cc:
275
 
            m.txt = m.txt[1:]
276
 
        if go and not 'dojcoll' in str(m.id):
277
 
            try:
278
 
                if plugins.woulddispatch(self, m):
279
 
                    m.usercmnd = True
280
 
                plugins.trydispatch(self, m)
281
 
            except:
282
 
                handle_exception()
283
 
        nm = Jabbermsg(msg)
284
 
        nm.copyin(m)
285
 
        jcallbacks.check(self, nm)
286
 
        if nm.getType() == 'error':
287
 
            err = nm.getErrorCode()
288
 
            if err:
289
 
                rlog(10, self.name + '.error', "%s => %s: %s" % (nm.getFrom(),\
290
 
 err, nm.getError()))
291
 
                rlog(10, self.name + '.error', str(nm))
292
 
            self.errorwait.check(nm)
293
 
 
294
 
    def presenceHandler(self, conn, pres):
295
 
        """ overloaded presence handler """
296
 
        p = Jabberpresence(pres)
297
 
        p.toirc(self)
298
 
        frm = p.getFrom()
299
 
        nickk = ""
300
 
        nick = frm.getResource()
301
 
        if nick:
302
 
            self.userhosts[nick] = str(frm)
303
 
            nickk = nick
304
 
        jid = None
305
 
        for i in p.getPayload():
306
 
            try:
307
 
                if i.getName() == 'x':
308
 
                    for j in i.getPayload():
309
 
                        if j.getName() == 'item':
310
 
                            attrs = j.getAttrs()
311
 
                            if attrs.has_key('jid'):
312
 
                                jid = xmpp.JID(attrs['jid'])
313
 
            except AttributeError:
314
 
                continue
315
 
        if nickk and jid:
316
 
            channel = frm.getStripped()
317
 
            if not self.jids.has_key(channel):
318
 
                self.jids[channel] = {}
319
 
            self.jids[channel][nickk] = jid
320
 
            self.userhosts[nickk.lower()] = str(jid)
321
 
            rlog(0, 'jabberbot', 'setting jid of %s (%s) to %s' % (nickk, \
322
 
 channel, jid))
323
 
        if p.type == 'subscribe':
324
 
            fromm = p.getFrom()
325
 
            self.send(xmpp.Presence(to=fromm, typ='subscribed'))
326
 
            self.send(xmpp.Presence(to=fromm, typ='subscribe'))
327
 
        nick = p.resource
328
 
        if p.type != 'unavailable':
329
 
            self.userchannels.adduniq(nick, p.channel)
330
 
            p.joined = True
331
 
        elif self.me in p.userhost:
332
 
            try:
333
 
                del self.jids[p.channel]
334
 
                rlog(10, 'jabberbot', 'removed %s channel jids' % p.channel)
335
 
            except KeyError:
336
 
                pass
337
 
        else:
338
 
            try:
339
 
                del self.jids[p.channel][p.nick]
340
 
                rlog(10, 'jabberbot', 'removed %s jid' % p.nick)
341
 
            except KeyError:
342
 
                pass
343
 
        p.conn = conn
344
 
        jcallbacks.check(self, p)
345
 
        if p.getType() == 'error':
346
 
            err = p.getErrorCode()
347
 
            if err:
348
 
                rlog(10, self.name + '.error', "%s => %s: %s" % (p.getFrom(),\
349
 
 err, p.getError()))
350
 
                rlog(10, self.name + '.error', str(p))
351
 
            self.errorwait.check(p)
352
 
 
353
 
    def reconnect(self):
354
 
        rlog(100, self.name, 'reconnecting .. sleeping 15 seconds')
355
 
        self.exit()
356
 
        time.sleep(15)
357
 
        newbot = Jabberbot(self.name)
358
 
        if newbot.connect(self.host, self.user, self.password, self.port):
359
 
            newbot.joinchannels()
360
 
        fleet.replace(self.name, newbot)
361
 
        return 1
362
 
 
363
 
    def disconnectHandler(self):
364
 
        """ overloaded disconnect handler """
365
 
        rlog(100, self.name, "disconnected")
366
 
        if not self.stopped:
367
 
            self.reconnect()
368
 
 
369
 
    def send(self, what):
370
 
        self.outqueue.put(toenc(what))
371
 
        jabbermonitor.put(self, what)
372
 
 
373
 
    @outlocked
374
 
    def rawsend(self, what):
375
 
        """ send via jabber.Client and check for output monitor """
376
 
        try:
377
 
            rlog(2, '%s-send' % self.name, str(what))
378
 
            if self.connection.isConnected():
379
 
                self.connection.send(what)
380
 
            jabbermonitor.put(self, what)
381
 
        except:
382
 
            handle_exception()
383
 
 
384
 
    def sendnocb(self, what):
385
 
        """ send via jabber.Client and without checking for output monitor """
386
 
        if self.stopped:
387
 
            return
388
 
        try:
389
 
            time.sleep(self.state['ratelimit'])
390
 
            if self.connection.isConnected():
391
 
                self.connection.send(what)
392
 
            rlog(2, '%s-send' % self.name, str(what))
393
 
        except:
394
 
            handle_exception()
395
 
 
396
 
    def action(self, printto, txt, fromm=None, groupchat=True):
397
 
        """ action txt to printto """
398
 
        txt = "/me " + txt
399
 
        if self.google:
400
 
            fromm = self.me
401
 
        if printto in self.state['joinedchannels'] and groupchat:
402
 
            message = xmpp.Message(to=printto, body=txt, typ='groupchat')
403
 
        else:
404
 
            message = xmpp.Message(to=printto, body=txt)
405
 
        if fromm:
406
 
            message.setFrom(fromm)
407
 
        self.send(message)
408
 
        
409
 
    def say(self, printto, txt, fromm=None, groupchat=True, speed=5):
410
 
        """ say txt to printto """
411
 
        txt = jabberstrip(txt)
412
 
        if self.google:
413
 
            fromm = self.me
414
 
        if printto in self.state['joinedchannels'] and groupchat:
415
 
            message = xmpp.Message(to=printto, body=txt, typ='groupchat')
416
 
        else:
417
 
            message = xmpp.Message(to=printto, body=txt, typ='chat')
418
 
        if fromm:
419
 
            message.setFrom(fromm)
420
 
        self.send(message)
421
 
 
422
 
    def saynocb(self, printto, txt, fromm=None, groupchat=True, speed=5):
423
 
        """ say txt to printto """
424
 
        txt = jabberstrip(txt)
425
 
        if self.google:
426
 
            fromm = self.me
427
 
        if printto in self.state['joinedchannels'] and groupchat:
428
 
            message = xmpp.Message(to=printto, body=txt, typ='groupchat')
429
 
        else:
430
 
            message = xmpp.Message(to=printto, body=txt, typ='chat')
431
 
        self.sendnocb(message)
432
 
 
433
 
    def wait(self, msg, txt):
434
 
        """ wait for user response """
435
 
        msg.reply(txt)
436
 
        queue = Queue.Queue()
437
 
        self.privwait.register(msg, queue)
438
 
        result = queue.get()
439
 
        if result:
440
 
            return result.getBody()
441
 
 
442
 
    def save(self):
443
 
        """ save bot's state """
444
 
        self.state.save()
445
 
 
446
 
    def quit(self):
447
 
        """ send unavailable presence """
448
 
        try:
449
 
            presence = xmpp.Presence()
450
 
        except ValueError:
451
 
            return
452
 
        presence.setType('unavailable')
453
 
        for i in self.state['joinedchannels']:
454
 
            presence.setTo(i)
455
 
            self.send(presence)
456
 
        time.sleep(1)
457
 
        presence = xmpp.Presence()
458
 
        presence.setType('unavailable')
459
 
        self.send(presence)
460
 
 
461
 
    def exit(self):
462
 
        """ exit the bot """
463
 
        self.quit()
464
 
        self.stopped = 1
465
 
        time.sleep(2)
466
 
        self.outqueue.put_nowait(None)
467
 
        self.save()
468
 
        rlog(10, self.name, 'exit')
469
 
 
470
 
    def join(self, channel, password=None, nick=None):
471
 
        """ join conference """
472
 
        try:
473
 
            if not nick:
474
 
                nick = channel.split('/')[1]
475
 
        except IndexError:
476
 
            nick = self.nick
477
 
        channel = channel.split('/')[0]
478
 
        if not self.channels.has_key(channel):
479
 
            # init channel data
480
 
            self.channels.setdefault(channel, {})
481
 
        # setup error wait
482
 
        q = Queue.Queue()
483
 
        self.errorwait.register("409", q, 3)
484
 
        self.errorwait.register("401", q, 3)
485
 
        self.errorwait.register("400", q, 3)
486
 
        # do the actual join
487
 
        presence = xmpp.Presence(to=channel + '/' + nick)
488
 
        #presence.setFrom(self.me)
489
 
        if password:
490
 
            passnode = Node('password')
491
 
            passnode.addData(password)
492
 
            presence.addChild(name='x', namespace='http://jabber.org/protocol/muc', \
493
 
payload=[passnode, ])
494
 
        self.send(presence)
495
 
        errorobj = waitforqueue(q, 3)
496
 
        if errorobj:
497
 
            err = errorobj[0].error
498
 
            rlog(10, self.name, 'error joining %s: %s' % (channel, err))
499
 
            return err
500
 
        self.timejoined[channel] = time.time()
501
 
        chan = self.channels[channel]
502
 
        # if password is provided set it
503
 
        chan['nick'] = nick
504
 
        if password:
505
 
            chan['key'] = password
506
 
        # check for control char .. if its not there init to !
507
 
        if not chan.has_key('cc'):
508
 
            chan['cc'] = config['defaultcc'] or '!'
509
 
        if not chan.has_key('perms'):
510
 
            chan['perms'] = []
511
 
        self.channels.save()
512
 
        if channel not in self.state['joinedchannels']:
513
 
            self.state['joinedchannels'].append(channel)
514
 
        self.state.save()
515
 
        return 1
516
 
 
517
 
    def part(self, channel):
518
 
        """ leace conference """
519
 
        presence = xmpp.Presence(to=channel)
520
 
        presence.setFrom(self.me)
521
 
        presence.setType('unavailable')
522
 
        self.send(presence)
523
 
        if channel in self.state['joinedchannels']:
524
 
            self.state['joinedchannels'].remove(channel)
525
 
        self.state.save()
526
 
        return 1
527
 
 
528
 
    def outputnolog(self, printto, what, how, who=None, fromm=None):
529
 
        """ doe output but don't log it """
530
 
        if fromm and shouldignore(fromm):
531
 
            return
532
 
        self.saynocb(printto, what)
533
 
 
534
 
    def topiccheck(self, msg):
535
 
        """ chek if topic is set """
536
 
        if msg.groupchat:
537
 
            try:
538
 
                topic = msg.getSubject()
539
 
                if not topic:
540
 
                    return None
541
 
                self.topics[msg.channel] = (topic, msg.userhost, time.time())
542
 
                rlog(10, self.name, 'topic of %s set to %s' % \
543
 
(msg.channel, topic))
544
 
            except AttributeError:
545
 
                return None
546
 
 
547
 
    def settopic(self, channel, txt):
548
 
        """ set topic """
549
 
        pres = xmpp.Message(to=channel, subject=txt)
550
 
        pres.setType('groupchat')
551
 
        self.send(pres)
552
 
 
553
 
    def gettopic(self, channel):
554
 
        """ get topic """
555
 
        try:
556
 
            topic = self.topics[channel]
557
 
            return topic
558
 
        except KeyError:
559
 
            return None
560
 
 
561
 
    def UnregisterHandlerOnce(self, a, b, xmlns=None):
562
 
        """ hack to work around missing method """
563
 
        print a, b
564
 
 
565
 
    def sendraw(self, msg):
566
 
        rlog(2, '%s-sendraw' % self.name, str(msg))
567
 
        if self.connection:
568
 
            try:
569
 
                if self.connection.__dict__.has_key('TCPsocket'):
570
 
                    self.connection.TCPsocket.send(msg)
571
 
                else:
572
 
                    self.connection.Connection.send(msg)
573
 
            except:
574
 
                handle_exception()