~ubuntu-branches/ubuntu/quantal/gozerbot/quantal

« back to all changes in this revision

Viewing changes to build/lib/gozerbot/irc/irc.py

  • Committer: Bazaar Package Importer
  • Author(s): Jeremy Malcolm
  • Date: 2010-09-29 18:20:02 UTC
  • mfrom: (3.1.7 sid)
  • Revision ID: james.westby@ubuntu.com-20100929182002-gi532gnem1vlu6jy
Tags: 0.9.1.3-5
Added python2.5 build dependency. 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# gozerbot/irc.py
 
2
#
 
3
#
 
4
 
 
5
""" an Irc object handles the connection to the irc server .. receiving,
 
6
    sending, connect and reconnect code.
 
7
"""
 
8
 
 
9
__copyright__ = 'this file is in the public domain'
 
10
 
 
11
# gozerbot imports
 
12
from gozerbot.utils.log import rlog
 
13
from gozerbot.utils.exception import handle_exception
 
14
from gozerbot.utils.generic import getrandomnick, toenc, fromenc, strippedtxt
 
15
from gozerbot.utils.generic import fix_format, splittxt, waitforqueue, uniqlist
 
16
from gozerbot.utils.locking import lockdec
 
17
from gozerbot.wait import Wait
 
18
from gozerbot.config import config
 
19
from monitor import saymonitor
 
20
from gozerbot.less import Less
 
21
from gozerbot.ignore import shouldignore
 
22
from gozerbot.persist.pdod import Pdod
 
23
from gozerbot.datadir import datadir
 
24
#from gozerbot.fleet import fleet
 
25
from gozerbot.botbase import BotBase
 
26
from gozerbot.threads.thr import start_new_thread, threaded
 
27
from gozerbot.periodical import periodical
 
28
from gozerbot.morphs import inputmorphs, outputmorphs
 
29
from ircevent import Ircevent
 
30
 
 
31
# basic imports
 
32
import time, thread, socket, threading, os, Queue, random
 
33
 
 
34
# locks
 
35
outlock = thread.allocate_lock()
 
36
outlocked = lockdec(outlock)
 
37
 
 
38
# exceptions
 
39
 
 
40
class AlreadyConnected(Exception):
 
41
 
 
42
    """ already connected exception """
 
43
 
 
44
    pass
 
45
 
 
46
class AlreadyConnecting(Exception):
 
47
 
 
48
    """ bot is already connecting exception """
 
49
 
 
50
    pass
 
51
 
 
52
class Irc(BotBase):
 
53
 
 
54
    """ the irc class, provides interface to irc related stuff. """
 
55
 
 
56
    def __init__(self, name, cfg={}):
 
57
        BotBase.__init__(self, name, cfg)
 
58
        self.type = 'irc'
 
59
        self.wait = Wait()
 
60
        self.outputlock = thread.allocate_lock()
 
61
        self.fsock = None
 
62
        self.oldsock = None
 
63
        self.sock = None
 
64
        if not self.cfg.has_key('nolimiter'):
 
65
            self.nolimiter = 0
 
66
        else:
 
67
            self.nolimiter = self.cfg['nolimiter']
 
68
        self.reconnectcount = 0
 
69
        self.pongcheck = 0
 
70
        self.nickchanged = 0
 
71
        self.noauto433 = 0
 
72
        if not self.state.has_key('alternick'):
 
73
            self.state['alternick'] = self.cfg['alternick']
 
74
        if not self.state.has_key('no-op'):
 
75
            self.state['no-op'] = []
 
76
        self.nrevents = 0
 
77
        self.gcevents = 0
 
78
        self.outqueues = [Queue.Queue() for i in range(10)]
 
79
        self.tickqueue = Queue.Queue()
 
80
        self.nicks401 = []
 
81
        self.stopreadloop = False
 
82
        self.stopoutloop = False
 
83
        if self.port == 0:
 
84
            self.port = 6667
 
85
        self.connectlock = thread.allocate_lock()
 
86
        self.encoding = 'utf-8'
 
87
 
 
88
    def __del__(self):
 
89
        self.exit()
 
90
 
 
91
    def _raw(self, txt):
 
92
 
 
93
        """ send raw text to the server. """
 
94
 
 
95
        if not txt:
 
96
            return
 
97
 
 
98
        rlog(2, self.name + '.sending', txt)
 
99
 
 
100
        try:
 
101
            self.lastoutput = time.time()
 
102
            itxt = toenc(outputmorphs.do(txt), self.encoding)
 
103
            if self.cfg.has_key('ssl') and self.cfg['ssl']:
 
104
                self.sock.write(itxt + '\n')
 
105
            else:
 
106
                self.sock.send(itxt[:500] + '\n')
 
107
        except Exception, ex:
 
108
            # check for broken pipe error .. if so ignore 
 
109
            # used for nonblocking sockets
 
110
            try:
 
111
                (errno, errstr) = ex
 
112
                if errno != 32 and errno != 9:
 
113
                    raise
 
114
                else:
 
115
                    rlog(10, self.name, 'broken pipe/bad socket  error .. ignoring')
 
116
            except:
 
117
                rlog(10, self.name, "ERROR: can't send %s" % str(ex))
 
118
                self.reconnect()
 
119
 
 
120
    def _connect(self):
 
121
 
 
122
        """ connect to server/port using nick. """
 
123
 
 
124
        if self.connecting:
 
125
            rlog(10, self.name, 'already connecting')
 
126
            raise AlreadyConnecting()
 
127
 
 
128
        if self.connected:
 
129
            rlog(10, self.name, 'already connected')
 
130
            raise AlreadyConnected()
 
131
 
 
132
        self.stopped = 0
 
133
        self.connecting = True
 
134
        self.connectok.clear()
 
135
        self.connectlock.acquire()
 
136
 
 
137
        # create socket
 
138
        if self.ipv6:
 
139
            rlog(10, self.name, 'creating ipv6 socket')
 
140
            self.oldsock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
 
141
            self.ipv6 = 1
 
142
        else:
 
143
            rlog(10, self.name, 'creating ipv4 socket')
 
144
            self.oldsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
145
 
 
146
        assert(self.oldsock)
 
147
 
 
148
        # optional bind
 
149
        server = self.server
 
150
        elite = self.cfg['bindhost'] or config['bindhost']
 
151
        if elite:
 
152
            try:
 
153
                self.oldsock.bind((elite, 0))
 
154
            except socket.gaierror:
 
155
                rlog(10, self.name, "can't bind to %s" % elite)
 
156
               # resolve the IRC server and pick a random server
 
157
                if not server:
 
158
                    # valid IPv6 ip?
 
159
                    try: socket.inet_pton(socket.AF_INET6, self.server)
 
160
                    except socket.error: pass
 
161
                    else: server = self.server
 
162
                if not server:  
 
163
                    # valid IPv4 ip?
 
164
                    try: socket.inet_pton(socket.AF_INET, self.server)
 
165
                    except socket.error: pass
 
166
                    else: server = self.server
 
167
                if not server:
 
168
                    # valid hostname?
 
169
                    ips = []
 
170
                    try:
 
171
                        for item in socket.getaddrinfo(self.server, None):
 
172
                            if item[0] in [socket.AF_INET, socket.AF_INET6] and item[1] == socket.SOCK_STREAM:
 
173
                                ip = item[4][0]
 
174
                                if ip not in ips: ips.append(ip)
 
175
                    except socket.error: pass
 
176
                    else: server = random.choice(ips)
 
177
 
 
178
        # do the connect .. set timeout to 30 sec upon connecting
 
179
        rlog(10, self.name, 'connecting to %s (%s)' % (server, self.server))
 
180
        self.oldsock.settimeout(15)
 
181
        self.oldsock.connect((server, int(self.port)))
 
182
 
 
183
        # we are connected
 
184
        rlog(10, self.name, 'connection ok')
 
185
        time.sleep(1)
 
186
        self.connected = True
 
187
 
 
188
        # make file socket
 
189
        self.fsock = self.oldsock.makefile("r")
 
190
 
 
191
        # set blocking
 
192
        self.oldsock.setblocking(self.blocking)
 
193
        self.fsock._sock.setblocking(self.blocking)
 
194
 
 
195
        # set socket time out
 
196
        if self.blocking:
 
197
            socktimeout = self.cfg['socktimeout']
 
198
            if not socktimeout:
 
199
                socktimeout = 301.0
 
200
            else:
 
201
                socktimeout = float(socktimeout)
 
202
            self.oldsock.settimeout(socktimeout)
 
203
            self.fsock._sock.settimeout(socktimeout)
 
204
        # enable ssl if set
 
205
        if self.cfg.has_key('ssl') and self.cfg['ssl']:
 
206
            rlog(10, self.name, 'ssl enabled')
 
207
            self.sock = socket.ssl(self.oldsock) 
 
208
        else:
 
209
            self.sock = self.oldsock
 
210
 
 
211
        # try to release the outputlock
 
212
        try:
 
213
            self.outputlock.release()
 
214
        except thread.error:
 
215
            pass
 
216
 
 
217
        # start input and output loops
 
218
        start_new_thread(self._readloop, ())
 
219
        start_new_thread(self._outloop, ())
 
220
 
 
221
        # logon and start monitor
 
222
        self._logon()
 
223
        self.nickchanged = 0
 
224
        self.reconnectcount = 0
 
225
        saymonitor.start()
 
226
        return 1
 
227
 
 
228
    def _readloop(self):
 
229
 
 
230
        """ loop on the socketfile. """
 
231
 
 
232
        self.stopreadloop = 0
 
233
        self.stopped = 0
 
234
        doreconnect = 0
 
235
        timeout = 1
 
236
        rlog(5, self.name, 'starting readloop')
 
237
        prevtxt = ""
 
238
 
 
239
        while not self.stopped and not self.stopreadloop:
 
240
 
 
241
            try:
 
242
                time.sleep(0.01)
 
243
                if self.cfg.has_key('ssl') and self.cfg['ssl']:
 
244
                    intxt = inputmorphs.do(self.sock.read()).split('\n')
 
245
                else:
 
246
                    intxt = inputmorphs.do(self.fsock.readline()).split('\n')
 
247
                # if intxt == "" the other side has disconnected
 
248
                if self.stopreadloop or self.stopped:
 
249
                    doreconnect = 0
 
250
                    break
 
251
                if not intxt or not intxt[0]:
 
252
                    doreconnect = 1
 
253
                    break
 
254
                if prevtxt:
 
255
                    intxt[0] = prevtxt + intxt[0]
 
256
                    prevtxt = ""
 
257
                if intxt[-1] != '':
 
258
                    prevtxt = intxt[-1]
 
259
                    intxt = intxt[:-1]
 
260
                for r in intxt:
 
261
                    r = r.rstrip()
 
262
                    rr = fromenc(r, self.encoding)
 
263
                    if not rr:
 
264
                        continue
 
265
                    res = strippedtxt(rr)
 
266
                    res = rr
 
267
                    rlog(2, self.name, res)
 
268
                    # parse txt read into an ircevent
 
269
                    try:
 
270
                        ievent = Ircevent().parse(self, res)
 
271
                    except Exception, ex:
 
272
                        handle_exception()
 
273
                        continue
 
274
                    # call handle_ievent 
 
275
                    if ievent:
 
276
                        self.handle_ievent(ievent)
 
277
                    timeout = 1
 
278
 
 
279
            except UnicodeError:
 
280
                handle_exception()
 
281
                continue
 
282
 
 
283
            except socket.timeout:
 
284
                # timeout occured .. first time send ping .. reconnect if
 
285
                # second timeout follows
 
286
                if self.stopped:
 
287
                    break
 
288
                timeout += 1
 
289
                if timeout > 2:
 
290
                    doreconnect = 1
 
291
                    rlog(10, self.name, 'no pong received')
 
292
                    break
 
293
                rlog(1, self.name, "socket timeout")
 
294
                pingsend = self.ping()
 
295
                if not pingsend:
 
296
                    doreconnect = 1
 
297
                    break
 
298
                continue
 
299
 
 
300
            except socket.sslerror, ex:
 
301
                # timeout occured .. first time send ping .. reconnect if
 
302
                # second timeout follows
 
303
                if self.stopped or self.stopreadloop:
 
304
                    break
 
305
                if not 'timed out' in str(ex):
 
306
                    handle_exception()
 
307
                    doreconnect = 1
 
308
                    break
 
309
                timeout += 1
 
310
                if timeout > 2:
 
311
                    doreconnect = 1
 
312
                    rlog(10, self.name, 'no pong received')
 
313
                    break
 
314
                rlog(1, self.name, "socket timeout")
 
315
                pingsend = self.ping()
 
316
                if not pingsend:
 
317
                    doreconnect = 1
 
318
                    break
 
319
                continue
 
320
 
 
321
            except IOError, ex:
 
322
                if 'temporarily' in str(ex):
 
323
                    continue
 
324
 
 
325
            except Exception, ex:
 
326
                if self.stopped or self.stopreadloop:
 
327
                    break
 
328
                err = ex
 
329
                try:
 
330
                    (errno, msg) = ex
 
331
                except:
 
332
                    errno = -1
 
333
                    msg = err
 
334
                # check for temp. unavailable error .. raised when using
 
335
                # nonblocking socket .. 35 is FreeBSD 11 is Linux
 
336
                if errno == 35 or errno == 11:
 
337
                    time.sleep(0.5)
 
338
                    continue
 
339
                rlog(10, self.name, "error in readloop: %s" % msg)
 
340
                doreconnect = 1
 
341
                break
 
342
 
 
343
        rlog(5, self.name, 'readloop stopped')
 
344
        self.connectok.clear()
 
345
        self.connected = False
 
346
 
 
347
        # see if we need to reconnect
 
348
        if doreconnect:
 
349
            time.sleep(2)
 
350
            self.reconnect()
 
351
 
 
352
    def _getqueue(self):
 
353
 
 
354
        """ get one of the outqueues. """
 
355
 
 
356
        go = self.tickqueue.get()
 
357
        for index in range(len(self.outqueues)):
 
358
            if not self.outqueues[index].empty():
 
359
                return self.outqueues[index]
 
360
 
 
361
    def putonqueue(self, nr, *args):
 
362
 
 
363
        """ put output onto one of the output queues. """
 
364
 
 
365
        self.outqueues[nr].put_nowait(*args)
 
366
        self.tickqueue.put_nowait('go')
 
367
 
 
368
    def _outloop(self):
 
369
 
 
370
        """ output loop. """
 
371
 
 
372
        rlog(5, self.name, 'starting output loop')
 
373
        self.stopoutloop = 0
 
374
 
 
375
        while not self.stopped and not self.stopoutloop:
 
376
            queue = self._getqueue()
 
377
            if queue:
 
378
                rlog(5, self.name, "outputsizes: %s" % self.outputsizes())
 
379
                try:
 
380
                    res = queue.get_nowait()
 
381
                except Queue.Empty:
 
382
                    continue
 
383
                if not res:
 
384
                    continue
 
385
                try:
 
386
                    (printto, what, who, how, fromm, speed) = res
 
387
                except ValueError:
 
388
                    self.send(res)
 
389
                    continue
 
390
                if not self.stopped and not self.stopoutloop and printto \
 
391
not in self.nicks401:
 
392
                    self.out(printto, what, who, how, fromm, speed)
 
393
            else:
 
394
                time.sleep(0.1)
 
395
 
 
396
        rlog(5, self.name, 'stopping output loop')
 
397
 
 
398
    def _logon(self):
 
399
 
 
400
        """ log on to the network. """
 
401
 
 
402
        # if password is provided send it
 
403
        if self.password:
 
404
            rlog(10, self.name ,'sending password')
 
405
            self._raw("PASS %s" % self.password)
 
406
 
 
407
        # register with irc server
 
408
        rlog(10, self.name, 'registering with %s using nick %s' % \
 
409
(self.server, self.nick))
 
410
        rlog(10, self.name, 'this may take a while')
 
411
 
 
412
        # check for username and realname
 
413
        username = self.nick or self.cfg['username']
 
414
        realname = self.cfg['realname'] or username
 
415
 
 
416
        # first send nick
 
417
        time.sleep(1)
 
418
        self._raw("NICK %s" % self.nick)
 
419
        time.sleep(1)
 
420
 
 
421
        # send USER
 
422
        self._raw("USER %s localhost localhost :%s" % (username, \
 
423
realname))
 
424
 
 
425
        # wait on login
 
426
        self.connectok.wait()
 
427
 
 
428
    def _onconnect(self):
 
429
 
 
430
        """ overload this to run after connect. """
 
431
 
 
432
        pass
 
433
 
 
434
    def _resume(self, data, reto=None):
 
435
 
 
436
        """ resume to server/port using nick. """
 
437
 
 
438
        try:
 
439
            if data['ssl']:
 
440
                self.connectwithjoin()
 
441
                return 1
 
442
        except KeyError:
 
443
            pass
 
444
 
 
445
        try:
 
446
            fd = int(data['fd'])
 
447
        except (TypeError, ValueError):
 
448
            fd = None
 
449
 
 
450
        self.connecting = False # we're already connected
 
451
        self.nick = data['nick']
 
452
        self.orignick = self.nick
 
453
        self.server = str(data['server'])
 
454
        self.port = int(data['port'])
 
455
        self.password = data['password']
 
456
        self.ipv6 = data['ipv6']
 
457
        self.ssl = data['ssl']
 
458
 
 
459
        # create socket
 
460
        if self.ipv6:
 
461
            if fd:
 
462
                rlog(1, self.name, 'resuming ipv6 socket')
 
463
                self.sock = socket.fromfd(fd , socket.AF_INET6, socket.SOCK_STREAM)
 
464
            else:
 
465
                rlog(10, self.name, 'creating ipv6 socket')
 
466
                self.sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
 
467
            self.ipv6 = 1
 
468
        else:
 
469
            if fd:
 
470
                rlog(1, self.name, 'resuming ipv4 socket')
 
471
                self.sock = socket.fromfd(fd, socket.AF_INET, socket.SOCK_STREAM)
 
472
            else:
 
473
                rlog(10, self.name, 'creating ipv4 socket')
 
474
                self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
475
 
 
476
        # do the connect .. set timeout to 30 sec upon connecting
 
477
        rlog(10, self.name, 'resuming to ' + self.server)
 
478
        self.sock.settimeout(30)
 
479
 
 
480
        # we are connected
 
481
        rlog(10, self.name, 'connection ok')
 
482
        self.stopped = 0
 
483
        # make file socket
 
484
        self.fsock = self.sock.makefile("r")
 
485
        # set blocking
 
486
        self.sock.setblocking(self.blocking)
 
487
 
 
488
        # set socket time out
 
489
        if self.blocking:
 
490
            socktimeout = self.cfg['socktimeout']
 
491
            if not socktimeout:
 
492
                socktimeout = 301.0
 
493
            else:
 
494
                socktimeout = float(socktimeout)
 
495
            self.sock.settimeout(socktimeout)
 
496
 
 
497
        # start readloop
 
498
        rlog(0, self.name, 'resuming readloop')
 
499
        start_new_thread(self._readloop, ())
 
500
        start_new_thread(self._outloop, ())
 
501
 
 
502
        # init 
 
503
        self.reconnectcount = 0
 
504
        self.nickchanged = 0
 
505
        self.connecting = False
 
506
 
 
507
        # still there server?
 
508
        self._raw('PING :RESUME %s' % str(time.time()))
 
509
        self.connectok.set()
 
510
        self.connected = True
 
511
        self.reconnectcount = 0
 
512
        if reto:
 
513
            self.say(reto, 'rebooting done')
 
514
        saymonitor.start()
 
515
        return 1
 
516
 
 
517
    def _resumedata(self):
 
518
 
 
519
        """ return data used for resume. """
 
520
        try:
 
521
            fd = self.sock.fileno()
 
522
        except AttributeError, ex:
 
523
            fd = None
 
524
            self.exit()
 
525
        return {self.name: {
 
526
            'nick': self.nick,
 
527
            'server': self.server,
 
528
            'port': self.port,
 
529
            'password': self.password,
 
530
            'ipv6': self.ipv6,
 
531
            'ssl': self.ssl,
 
532
            'fd': fd
 
533
            }}
 
534
 
 
535
 
 
536
    def outputsizes(self):
 
537
 
 
538
        """ return sizes of output queues. """
 
539
 
 
540
        result = []
 
541
        for q in self.outqueues:
 
542
            result.append(q.qsize())
 
543
        return result
 
544
 
 
545
    def broadcast(self, txt):
 
546
 
 
547
        """ broadcast txt to all joined channels. """
 
548
 
 
549
        for i in self.state['joinedchannels']:
 
550
            self.say(i, txt, speed=-1)
 
551
 
 
552
    def save(self):
 
553
 
 
554
        """ save state data. """
 
555
 
 
556
        self.state.save()
 
557
 
 
558
    def connect(self, reconnect=True):
 
559
 
 
560
        """ connect to server/port using nick .. connect can timeout so catch
 
561
            exception .. reconnect if enabled.
 
562
        """
 
563
 
 
564
        res = 0
 
565
 
 
566
        try:
 
567
            res = self._connect()
 
568
            if res:
 
569
                self.connectok.wait()
 
570
                self._onconnect()
 
571
                self.connecting = False
 
572
                self.connected = True
 
573
                rlog(10, self.name, 'logged on !')
 
574
        except AlreadyConnecting:
 
575
            return 0 
 
576
        except AlreadyConnected:
 
577
            return 0
 
578
        except Exception, ex:
 
579
            self.connectlock.release()
 
580
            if self.stopped:
 
581
                return 0
 
582
            rlog(10, self.name, 'connecting error: %s' % str(ex))
 
583
            if reconnect:
 
584
                self.reconnect()
 
585
                return
 
586
            raise
 
587
 
 
588
        # add bot to the fleet
 
589
        #if not fleet.byname(self.name):
 
590
        #    fleet.addbot(self)
 
591
        self.connectlock.release()
 
592
        return res
 
593
 
 
594
    def shutdown(self):
 
595
 
 
596
        """ shutdown the bot. """
 
597
 
 
598
        rlog(10, self.name, 'shutdown')
 
599
        self.stopoutputloop = 1
 
600
        self.stopped = 1
 
601
        time.sleep(1)
 
602
        self.tickqueue.put_nowait('go')
 
603
        self.close()
 
604
        self.connecting = False
 
605
        self.connected = False
 
606
        self.connectok.clear()
 
607
 
 
608
    def close(self):
 
609
 
 
610
        """ close the connection. """
 
611
 
 
612
        try:
 
613
            if self.cfg.has_key('ssl') and self.cfg['ssl']:
 
614
                self.oldsock.shutdown(2)
 
615
            else:
 
616
                self.sock.shutdown(2)
 
617
        except:
 
618
            pass
 
619
        try:
 
620
            if self.cfg.has_key('ssl') and self.cfg['ssl']:
 
621
                self.oldsock.close()
 
622
            else:
 
623
                self.sock.close()
 
624
            self.fsock.close()
 
625
        except:
 
626
            pass
 
627
 
 
628
    def exit(self):
 
629
 
 
630
        """ exit the bot. """
 
631
 
 
632
        self.stopped = 1
 
633
        self.connected = 0
 
634
        self.shutdown()
 
635
 
 
636
    def reconnect(self):
 
637
 
 
638
        """ reconnect to the irc server. """
 
639
 
 
640
        try:
 
641
            if self.stopped:
 
642
                return 0
 
643
            # determine how many seconds to sleep
 
644
            if self.reconnectcount > 0:
 
645
                reconsleep = self.reconnectcount*15
 
646
                rlog(10, self.name, 'sleeping %s seconds for reconnect' % \
 
647
reconsleep)
 
648
                time.sleep(reconsleep)
 
649
                if self.stopped:
 
650
                    rlog(10, self.name, 'stopped.. not reconnecting')
 
651
                    return 1
 
652
                if self.connected:
 
653
                    rlog(10, self.name, 'already connected .. not reconnecting')
 
654
                    return 1
 
655
            self.reconnectcount += 1
 
656
            self.exit()
 
657
            rlog(10, self.name, 'reconnecting')
 
658
            result = self.connect()
 
659
            return result
 
660
        except Exception, ex:
 
661
            handle_exception()
 
662
 
 
663
    def handle_pong(self, ievent):
 
664
 
 
665
        """ set pongcheck on received pong. """
 
666
 
 
667
        rlog(1, self.name, 'received server pong')
 
668
        self.pongcheck = 1
 
669
 
 
670
    def sendraw(self, txt):
 
671
 
 
672
        """ send raw text to the server. """
 
673
 
 
674
        if self.stopped:
 
675
            return
 
676
        rlog(2, self.name + '.sending', txt)
 
677
        self._raw(txt)
 
678
 
 
679
    def fakein(self, txt):
 
680
 
 
681
        """ do a fake ircevent. """
 
682
 
 
683
        if not txt:
 
684
            return
 
685
        rlog(10, self.name + '.fakein', txt)
 
686
        self.handle_ievent(Ircevent().parse(self, txt))
 
687
 
 
688
    def say(self, printto, what, who=None, how='msg', fromm=None, speed=0):
 
689
 
 
690
        """ say what to printto. """
 
691
 
 
692
        if not printto or not what or printto in self.nicks401:
 
693
            return
 
694
 
 
695
        # if who is set add "who: " to txt
 
696
        if not 'socket' in repr(printto):
 
697
            if who:
 
698
                what = "%s: %s" % (who, what)
 
699
            if speed > 9:
 
700
                speed = 9
 
701
            self.putonqueue(9-speed, (printto, what, who, how, fromm, speed))
 
702
            return
 
703
 
 
704
        # do the sending
 
705
        try:
 
706
            printto.send(what + '\n')
 
707
            time.sleep(0.001)
 
708
        except Exception, ex :
 
709
            if "Broken pipe" in str(ex) or "Bad file descriptor" in str(ex):
 
710
                return
 
711
            handle_exception()
 
712
 
 
713
    def out(self, printto, what, who=None, how='msg', fromm=None, speed=5):
 
714
 
 
715
        """ output the first 375 chars .. put the rest into cache. """
 
716
 
 
717
        # convert the data to the encoding
 
718
        try:
 
719
            what = toenc(what.rstrip())
 
720
        except Exception, ex:
 
721
            rlog(10, self.name, "can't output: %s" % str(ex))
 
722
            return
 
723
        if not what:
 
724
            return
 
725
 
 
726
        # split up in parts of 375 chars overflowing on word boundaries
 
727
        txtlist = splittxt(what)
 
728
        size = 0
 
729
 
 
730
        # send first block
 
731
        self.output(printto, txtlist[0], how, who, fromm)
 
732
 
 
733
        # see if we need to store output in less cache
 
734
        result = ""
 
735
        if len(txtlist) > 2:
 
736
            if not fromm:
 
737
                self.less.add(printto, txtlist[1:])
 
738
            else:
 
739
                self.less.add(fromm, txtlist[1:])
 
740
            size = len(txtlist) - 2
 
741
            result = txtlist[1:2][0]
 
742
            if size:
 
743
                result += " (+%s)" % size
 
744
        else:
 
745
            if len(txtlist) == 2:
 
746
                result = txtlist[1]
 
747
 
 
748
        # send second block
 
749
        if result:
 
750
            self.output(printto, result, how, who, fromm)
 
751
 
 
752
    def output(self, printto, what, how='msg' , who=None, fromm=None):
 
753
 
 
754
        """ first output .. then call saymonitor. """
 
755
 
 
756
        self.outputnolog(printto, what, how, who, fromm)
 
757
        saymonitor.put(self.name, printto, what, who, how, fromm)
 
758
        
 
759
    def outputnolog(self, printto, what, how, who=None, fromm=None):
 
760
 
 
761
        """ do output to irc server .. rate limit to 3 sec. """
 
762
 
 
763
        if fromm and shouldignore(fromm):
 
764
            return
 
765
 
 
766
        try:
 
767
            what = fix_format(what)
 
768
            if what:
 
769
                if how == 'msg':
 
770
                    self.privmsg(printto, what)
 
771
                elif how == 'notice':
 
772
                    self.notice(printto, what)
 
773
                elif how == 'ctcp':
 
774
                    self.ctcp(printto, what)
 
775
        except Exception, ex:
 
776
            handle_exception()
 
777
 
 
778
    def donick(self, nick, setorig=0, save=0, whois=0):
 
779
 
 
780
        """ change nick .. optionally set original nick and/or save to config.  """
 
781
 
 
782
        if not nick:
 
783
            return
 
784
 
 
785
        # disable auto 433 nick changing
 
786
        self.noauto433 = 1
 
787
 
 
788
        # set up wait for NICK command and issue NICK
 
789
        queue = Queue.Queue()
 
790
        nick = nick[:16]
 
791
        self.wait.register('NICK', self.nick[:16], queue, 12)
 
792
        self._raw('NICK %s\n' % nick)
 
793
        result = waitforqueue(queue, 5)
 
794
 
 
795
        # reenable 433 auto nick changing
 
796
        self.noauto433 = 0
 
797
        if not result:
 
798
            return 0
 
799
        self.nick = nick
 
800
 
 
801
        # send whois
 
802
        if whois:
 
803
            self.whois(nick)
 
804
 
 
805
        # set original
 
806
        if setorig:
 
807
            self.orignick = nick
 
808
 
 
809
        # save nick to state and config file
 
810
        if save:
 
811
            self.state['nick'] = nick
 
812
            self.state.save()
 
813
            self.cfg.set('nick', nick)
 
814
            self.cfg.save()
 
815
        return 1
 
816
 
 
817
    def join(self, channel, password=None):
 
818
 
 
819
        """ join channel with optional password. """
 
820
 
 
821
        if not channel:
 
822
            return
 
823
 
 
824
        # do join with password
 
825
        if password:
 
826
            self._raw('JOIN %s %s' % (channel, password))
 
827
            try:
 
828
                self.channels[channel.lower()]['key'] = password
 
829
                self.channels.save()
 
830
            except KeyError:
 
831
                pass
 
832
        else:
 
833
            # do pure join
 
834
            self._raw('JOIN %s' % channel)
 
835
 
 
836
    def part(self, channel):
 
837
 
 
838
        """ leave channel. """
 
839
 
 
840
        if not channel:
 
841
            return
 
842
        self._raw('PART %s' % channel)
 
843
 
 
844
        try:
 
845
            self.state['joinedchannels'].remove(channel)
 
846
            self.state.save()
 
847
        except (KeyError, ValueError):
 
848
            pass
 
849
 
 
850
    def who(self, who):
 
851
 
 
852
        """ send who query. """
 
853
 
 
854
        if not who:
 
855
            return
 
856
        self.putonqueue(6, 'WHO %s' % who.strip())
 
857
 
 
858
    def names(self, channel):
 
859
 
 
860
        """ send names query. """
 
861
 
 
862
        if not channel:
 
863
            return
 
864
        self.putonqueue(6, 'NAMES %s' % channel)
 
865
 
 
866
    def whois(self, who):
 
867
 
 
868
        """ send whois query. """
 
869
 
 
870
        if not who:
 
871
            return
 
872
        self.putonqueue(6, 'WHOIS %s' % who)
 
873
 
 
874
    def privmsg(self, printto, what):
 
875
 
 
876
        """ send privmsg to irc server. """
 
877
 
 
878
        if not printto or not what:
 
879
            return
 
880
        self.send('PRIVMSG %s :%s' % (printto, what))
 
881
 
 
882
    def send(self, txt):
 
883
 
 
884
        """ send text to irc server. """
 
885
 
 
886
        if not txt:
 
887
            return
 
888
 
 
889
        if self.stopped:
 
890
            return
 
891
 
 
892
        try:
 
893
            self.outputlock.acquire()
 
894
            now = time.time()
 
895
            timetosleep = 5 - (now - self.lastoutput)
 
896
            if timetosleep > 0 and not self.nolimiter:
 
897
                rlog(0, self.name, 'flood protect')
 
898
                time.sleep(timetosleep)
 
899
            txt = toenc(strippedtxt(txt))
 
900
            txt = txt.rstrip()
 
901
            self._raw(txt)
 
902
            try:
 
903
                self.outputlock.release()
 
904
            except:
 
905
                pass
 
906
            self.lastoutput = time.time()
 
907
        except Exception, ex:
 
908
            try:
 
909
                self.outputlock.release()
 
910
            except:
 
911
                pass
 
912
            if not self.blocking and 'broken pipe' in str(ex).lower():
 
913
                rlog(11, self.name, 'broken pipe error .. ignoring')
 
914
            else:
 
915
                rlog(11, self.name, 'send error: %s' % str(ex))
 
916
                self.reconnect()
 
917
                return
 
918
            
 
919
    def voice(self, channel, who):
 
920
 
 
921
        """ give voice. """
 
922
 
 
923
        if not channel or not who:
 
924
            return
 
925
        self.putonqueue(9, 'MODE %s +v %s' % (channel, who))
 
926
 
 
927
    def doop(self, channel, who):
 
928
 
 
929
        """ give ops. """
 
930
 
 
931
        if not channel or not who:
 
932
            return
 
933
        self._raw('MODE %s +o %s' % (channel, who))
 
934
 
 
935
    def delop(self, channel, who):
 
936
 
 
937
        """ de-op user. """
 
938
 
 
939
        if not channel or not who:
 
940
            return
 
941
        self._raw('MODE %s -o %s' % (channel, who))
 
942
 
 
943
    def quit(self, reason='http://gozerbot.org'):
 
944
 
 
945
        """ send quit message. """
 
946
 
 
947
        rlog(10, self.name, 'sending quit')
 
948
        try:
 
949
            self._raw('QUIT :%s' % reason)
 
950
        except IOError:
 
951
            pass
 
952
 
 
953
    def notice(self, printto, what):
 
954
 
 
955
        """ send notice. """
 
956
 
 
957
        if not printto or not what:
 
958
            return
 
959
        self.send('NOTICE %s :%s' % (printto, what))
 
960
 
 
961
    def ctcp(self, printto, what):
 
962
 
 
963
        """ send ctcp privmsg. """
 
964
 
 
965
        if not printto or not what:
 
966
            return
 
967
        self.send("PRIVMSG %s :\001%s\001" % (printto, what))
 
968
 
 
969
    def ctcpreply(self, printto, what):
 
970
 
 
971
        """ send ctcp notice. """
 
972
 
 
973
        if not printto or not what:
 
974
            return
 
975
        self.putonqueue(2, "NOTICE %s :\001%s\001" % (printto, what))
 
976
 
 
977
    def action(self, printto, what):
 
978
 
 
979
        """ do action. """
 
980
 
 
981
        if not printto or not what:
 
982
            return
 
983
        self.putonqueue(9, "PRIVMSG %s :\001ACTION %s\001" % (printto, what))
 
984
 
 
985
    def handle_ievent(self, ievent):
 
986
 
 
987
        """ handle ircevent .. dispatch to 'handle_command' method. """ 
 
988
 
 
989
        try:
 
990
            if ievent.cmnd == 'JOIN' or ievent.msg:
 
991
                if ievent.nick.lower() in self.nicks401:
 
992
                    self.nicks401.remove(ievent.nick)
 
993
                    rlog(10, self.name, '%s joined .. unignoring')
 
994
            # see if the irc object has a method to handle the ievent
 
995
            method = getattr(self,'handle_' + ievent.cmnd.lower())
 
996
            # try to call method
 
997
            if method:
 
998
                try:
 
999
                    method(ievent)
 
1000
                except:
 
1001
                    handle_exception()
 
1002
        except AttributeError:
 
1003
            # no command method to handle event
 
1004
            pass
 
1005
        try:
 
1006
            # see if there are wait callbacks
 
1007
            self.wait.check(ievent)
 
1008
        except:
 
1009
            handle_exception()
 
1010
 
 
1011
    def handle_432(self, ievent):
 
1012
 
 
1013
        """ erroneous nick. """
 
1014
 
 
1015
        self.handle_433(ievent)
 
1016
 
 
1017
    def handle_433(self, ievent):
 
1018
 
 
1019
        """ handle nick already taken. """
 
1020
 
 
1021
        if self.noauto433:
 
1022
            return
 
1023
        nick = ievent.arguments[1]
 
1024
        # check for alternick
 
1025
        alternick = self.state['alternick']
 
1026
        if alternick and not self.nickchanged:
 
1027
            rlog(10, self.name, 'using alternick %s' % alternick)
 
1028
            self.donick(alternick)
 
1029
            self.nickchanged = 1
 
1030
            return
 
1031
        # use random nick
 
1032
        randomnick = getrandomnick()
 
1033
        self._raw("NICK %s" % randomnick)
 
1034
        self.nick = randomnick
 
1035
        rlog(100, self.name, 'ALERT: nick %s already in use/unavailable .. \
 
1036
using randomnick %s' % (nick, randomnick))
 
1037
        self.nickchanged = 1
 
1038
 
 
1039
    def handle_ping(self, ievent):
 
1040
 
 
1041
        """ send pong response. """
 
1042
 
 
1043
        if not ievent.txt:
 
1044
            return
 
1045
        self._raw('PONG :%s' % ievent.txt)
 
1046
 
 
1047
    def handle_001(self, ievent):
 
1048
 
 
1049
        """ we are connected.  """
 
1050
 
 
1051
        self.connectok.set()
 
1052
        self.connected = True
 
1053
        periodical.addjob(15, 1, self.whois, self, self.nick)
 
1054
 
 
1055
    def handle_privmsg(self, ievent):
 
1056
 
 
1057
        """ check if msg is ctcp or not .. return 1 on handling. """
 
1058
 
 
1059
        if ievent.txt and ievent.txt[0] == '\001':
 
1060
            self.handle_ctcp(ievent)
 
1061
            return 1
 
1062
 
 
1063
    def handle_notice(self, ievent):
 
1064
 
 
1065
        """ handle notice event .. check for version request. """
 
1066
 
 
1067
        if ievent.txt and ievent.txt.find('VERSION') != -1:
 
1068
            self.say(ievent.nick, self.cfg['version'], None, 'notice')
 
1069
            return 1
 
1070
 
 
1071
    def handle_ctcp(self, ievent):
 
1072
 
 
1073
        """ handle client to client request .. version and ping. """
 
1074
 
 
1075
        if ievent.txt.find('VERSION') != -1:
 
1076
            self.ctcpreply(ievent.nick, 'VERSION %s' % self.cfg['version'])
 
1077
 
 
1078
        if ievent.txt.find('PING') != -1:
 
1079
            try:
 
1080
                pingtime = ievent.txt.split()[1]
 
1081
                pingtime2 = ievent.txt.split()[2]
 
1082
                if pingtime:
 
1083
                    self.ctcpreply(ievent.nick, 'PING ' + pingtime + ' ' + \
 
1084
pingtime2)
 
1085
            except IndexError:
 
1086
                pass
 
1087
 
 
1088
    def handle_error(self, ievent):
 
1089
 
 
1090
        """ show error. """
 
1091
 
 
1092
        if ievent.txt.startswith('Closing'):
 
1093
            rlog(10, self.name, ievent.txt)
 
1094
        else:
 
1095
            rlog(10, self.name + '.ERROR', "%s - %s" % (ievent.arguments, \
 
1096
ievent.txt))
 
1097
 
 
1098
    def ping(self):
 
1099
 
 
1100
        """ ping the irc server. """
 
1101
 
 
1102
        rlog(1, self.name, 'sending ping')
 
1103
        try:
 
1104
            self.putonqueue(1, 'PING :%s' % self.server)
 
1105
            return 1
 
1106
        except Exception, ex:
 
1107
            rlog(10, self.name, "can't send ping: %s" % str(ex))
 
1108
            return 0
 
1109
 
 
1110
    def handle_401(self, ievent):
 
1111
 
 
1112
        """ handle 401 .. nick not available. """
 
1113
 
 
1114
        try:
 
1115
            nick = ievent.arguments[1]
 
1116
            if nick not in self.nicks401:
 
1117
                rlog(10, self.name, '401 on %s .. ignoring' % nick)
 
1118
                self.nicks401.append(nick)
 
1119
        except:
 
1120
            pass
 
1121
 
 
1122
    def handle_700(self, ievent):
 
1123
 
 
1124
        """ handle 700 .. encoding request of the server. """
 
1125
 
 
1126
        try:
 
1127
            self.encoding = ievent.arguments[1]
 
1128
            rlog(10, self.name, '700 encoding now is %s' % self.encoding)
 
1129
        except:
 
1130
            pass