Package gozerbot :: Module irc
[hide private]
[frames] | no frames]

Source Code for Module gozerbot.irc

  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  __copyright__ = 'this file is in the public domain' 
  9   
 10  from gozerbot.generic import rlog, handle_exception, getrandomnick, toenc, \ 
 11  fix_format, splittxt, waitforqueue, strippedtxt, fromenc, uniqlist, lockdec 
 12  from gozerbot.wait import Wait 
 13  from gozerbot.config import config 
 14  from gozerbot.ircevent import Ircevent 
 15  from gozerbot.monitor import saymonitor 
 16  from gozerbot.less import Less 
 17  from gozerbot.ignore import shouldignore 
 18  from gozerbot.pdod import Pdod 
 19  from gozerbot.datadir import datadir 
 20  from gozerbot.eventhandler import Outputhandler 
 21  from gozerbot.fleet import fleet 
 22  from gozerbot.botbase import BotBase 
 23  from gozerbot.plugins import plugins 
 24  from gozerbot.thr import start_new_thread, threaded 
 25  import time, thread, socket, threading, os, Queue, random 
 26   
 27  outlock = thread.allocate_lock() 
 28  outlocked = lockdec(outlock) 
 29  reconnectlock = thread.allocate_lock() 
 30  reconnectlocked  = lockdec(outlock) 
31 32 -class AlreadyConnected(Exception):
33 34 """ already connected exception """ 35 36 pass
37
38 -class AlreadyConnecting(Exception):
39 40 """ bot is already connecting exception """ 41 42 pass
43
44 -class Irc(BotBase):
45 46 """ the irc class, provides interface to irc related stuff """ 47
48 - def __init__(self, name='main', owner=[]):
49 BotBase.__init__(self, name, owner) 50 self.type = 'irc' 51 self.wait = Wait() 52 self.outputlock = thread.allocate_lock() 53 self.fsock = None 54 self.oldsock = None 55 self.sock = None 56 self.nolimiter = config['nolimiter'] 57 self.reconnectcount = 0 58 self.pongcheck = 0 59 self.nickchanged = 0 60 self.noauto433 = 0 61 if not self.state.has_key('alternick'): 62 self.state['alternick'] = config['alternick'] 63 if not self.state.has_key('no-op'): 64 self.state['no-op'] = [] 65 if not self.state.has_key('nick'): 66 self.state['nick'] = config['nick'] or 'gb1' 67 self.nick = self.state['nick'] 68 self.nrevents = 0 69 self.gcevents = 0 70 self.outqueues = [Queue.Queue() for i in range(10)] 71 self.whenidle = Queue.Queue() 72 self.nicks401 = [] 73 self.stopreadloop = False 74 self.stopoutloop = False
75
76 - def _raw(self, txt):
77 """ send raw text to the server """ 78 if not txt: 79 return 80 rlog(2, self.name + '.sending', txt) 81 try: 82 if self.ssl: 83 self.sock.write(txt + '\n') 84 else: 85 self.sock.send(txt[:500] + '\n') 86 except Exception, ex: 87 # check for broken pipe error .. if so ignore 88 # used for nonblocking sockets 89 try: 90 (errno, errstr) = ex 91 if errno != 32: 92 raise 93 else: 94 rlog(10, self.name, 'broken pipe error .. ignoring') 95 except: 96 rlog(10, self.name, "ERROR: can't send %s" % str(ex))
97
98 - def _connect(self, nick, server, port, password, ipv6, ssl=0):
99 """ connect to server/port using nick """ 100 if self.connecting: 101 rlog(10, self.name, 'already connecting') 102 raise AlreadyConnecting() 103 if self.connectok.isSet(): 104 rlog(10, self.name, 'already connected') 105 raise AlreadyConnected() 106 self.connecting = True 107 self.connectok.clear() 108 self.nick = str(nick) 109 self.orignick = self.nick 110 self.server = str(server) 111 self.port = int(port) 112 self.password = str(password) 113 self.ipv6 = int(ipv6) 114 self.ssl = int(ssl) 115 # create socket 116 if self.ipv6: 117 rlog(10, self.name, 'creating ipv6 socket') 118 self.oldsock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) 119 self.ipv6 = 1 120 else: 121 rlog(10, self.name, 'creating ipv4 socket') 122 self.oldsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 123 # optional bind 124 elite = config['bindhost'] 125 if elite: 126 try: 127 self.oldsock.bind((elite, 0)) 128 except socket.gaierror: 129 rlog(10, self.name, "can't bind to %s" % elite) 130 # resolve the IRC server and pick a random server 131 server = None 132 if not server: 133 # valid IPv6 ip? 134 try: socket.inet_pton(socket.AF_INET6, self.server) 135 except socket.error: pass 136 else: server = self.server 137 if not server: 138 # valid IPv4 ip? 139 try: socket.inet_pton(socket.AF_INET, self.server) 140 except socket.error: pass 141 else: server = self.server 142 if not server: 143 # valid hostname? 144 ips = [] 145 try: 146 for item in socket.getaddrinfo(self.server, None): 147 if item[0] in [socket.AF_INET, socket.AF_INET6] and item[1] == socket.SOCK_STREAM: 148 ip = item[4][0] 149 if ip not in ips: ips.append(ip) 150 except socket.error: pass 151 else: server = random.choice(ips) 152 # do the connect .. set timeout to 30 sec upon connecting 153 rlog(10, self.name, 'connecting to %s (%s)' % (server, self.server)) 154 self.oldsock.settimeout(30) 155 self.oldsock.connect((server, int(self.port))) 156 # we are connected 157 rlog(10, self.name, 'connection ok') 158 self.stopped = 0 159 # make file socket 160 self.fsock = self.oldsock.makefile("r") 161 # set blocking 162 self.oldsock.setblocking(self.blocking) 163 self.fsock._sock.setblocking(self.blocking) 164 # set socket time out 165 if self.blocking: 166 socktimeout = config['socktimeout'] 167 if not socktimeout: 168 socktimeout = 301.0 169 else: 170 socktimeout = float(socktimeout) 171 self.oldsock.settimeout(socktimeout) 172 self.fsock._sock.settimeout(socktimeout) 173 # start readloop 174 if self.ssl: 175 rlog(10, self.name, 'ssl enabled') 176 self.sock = socket.ssl(self.oldsock) 177 else: 178 self.sock = self.oldsock 179 try: 180 self.outputlock.release() 181 except thread.error: 182 pass 183 start_new_thread(self._readloop, ()) 184 start_new_thread(self._outloop, ()) 185 # execute onconnect file .. code that can be executed after login 186 self._logon() 187 self._onconnect() 188 # init 189 self.nickchanged = 0 190 self.connecting = False 191 self.reconnectcount = 0 192 saymonitor.start() 193 return 1
194
195 - def _readloop(self):
196 """ loop on the socketfile """ 197 self.stopreadloop = 0 198 self.stopped = 0 199 doreconnect = 0 200 timeout = 1 201 rlog(5, self.name, 'starting readloop') 202 prevtxt = "" 203 while not self.stopped and not self.stopreadloop: 204 try: 205 time.sleep(0.01) 206 if self.ssl: 207 intxt = self.sock.read().split('\n') 208 else: 209 intxt = self.fsock.readline().split('\n') 210 # if intxt == "" the other side has disconnected 211 if self.stopreadloop: 212 doreconnect = 0 213 return 214 if not intxt and not self.stopped: 215 doreconnect = 1 216 break 217 if self.stopped: 218 break 219 if prevtxt: 220 intxt[0] = prevtxt + intxt[0] 221 prevtxt = "" 222 if intxt[-1] != '': 223 prevtxt = intxt[-1] 224 intxt = intxt[:-1] 225 for r in intxt: 226 r = r.rstrip() 227 rr = fromenc(r) 228 if not rr: 229 continue 230 res = strippedtxt(rr) 231 res = rr 232 rlog(2, self.name, res) 233 # parse txt read into an ircevent 234 try: 235 ievent = Ircevent().parse(self, res) 236 except Exception, ex: 237 handle_exception() 238 continue 239 # call handle_ievent 240 if ievent: 241 self.handle_ievent(ievent) 242 timeout = 1 243 except UnicodeError: 244 handle_exception() 245 continue 246 except socket.timeout: 247 # timeout occured .. first time send ping .. reconnect if 248 # second timeout follows 249 if self.stopped: 250 break 251 timeout += 1 252 if timeout > 2: 253 doreconnect = 1 254 rlog(10, self.name, 'no pong received') 255 break 256 rlog(1, self.name, "socket timeout") 257 pingsend = self.ping() 258 if not pingsend: 259 doreconnect = 1 260 break 261 continue 262 except socket.sslerror, ex: 263 # timeout occured .. first time send ping .. reconnect if 264 # second timeout follows 265 if self.stopped or self.stopreadloop: 266 break 267 if not 'timed out' in str(ex): 268 handle_exception() 269 doreconnect = 1 270 break 271 timeout += 1 272 if timeout > 2: 273 doreconnect = 1 274 rlog(10, self.name, 'no pong received') 275 break 276 rlog(1, self.name, "socket timeout") 277 pingsend = self.ping() 278 if not pingsend: 279 doreconnect = 1 280 break 281 continue 282 except IOError, ex: 283 if 'temporarily' in str(ex): 284 continue 285 except Exception, ex: 286 if self.stopped or self.stopreadloop: 287 break 288 err = ex 289 try: 290 (errno, msg) = ex 291 except: 292 errno = -1 293 msg = err 294 # check for temp. unavailable error .. raised when using 295 # nonblocking socket .. 35 is FreeBSD 11 is Linux 296 if errno == 35 or errno == 11: 297 time.sleep(0.5) 298 continue 299 rlog(10, self.name, "error in readloop: %s" % msg) 300 doreconnect = 1 301 break 302 rlog(5, self.name, 'readloop stopped') 303 # see if we need to reconnect 304 self.connectok.clear() 305 self.connecting = False 306 self.connected = False 307 if doreconnect: 308 self.reconnect()
309
310 - def _getqueue(self):
311 for index in range(len(self.outqueues)): 312 if not self.outqueues[index].empty(): 313 return self.outqueues[index]
314
315 - def putonqueue(self, nr, *args):
316 self.outqueues[nr].put_nowait(*args)
317
318 - def _outloop(self):
319 rlog(5, self.name, 'starting output loop') 320 self.stopoutloop = 0 321 while not self.stopped and not self.stopoutloop: 322 time.sleep(0.01) 323 queue = self._getqueue() 324 if queue: 325 rlog(5, self.name, "outputsizes: %s" % self.outputsizes()) 326 try: 327 res = queue.get_nowait() 328 except Queue.Empty: 329 continue 330 if not res: 331 continue 332 try: 333 (printto, what, who, how, fromm, speed) = res 334 except ValueError: 335 self.send(res) 336 continue 337 if not self.stopped and not self.stopoutloop and printto \ 338 not in self.nicks401: 339 self.out(printto, what, who, how, fromm, speed) 340 rlog