comparison bot.py @ 180:102f895347ff

added a required "importance" argument to Bot.error() Signed-off-by: Charly COSTE <changaco@changaco.net>
author Charly COSTE <changaco@changaco.net>
date Sat, 13 Feb 2010 16:32:28 +0100
parents f6c6708c6c0e
children 803e00d72cb7
comparison
equal deleted inserted replaced
179:f6c6708c6c0e 180:102f895347ff
28 del muc 28 del muc
29 29
30 from bridge import Bridge 30 from bridge import Bridge
31 from participant import Participant 31 from participant import Participant
32 import commands 32 import commands
33 import say_levels
33 34
34 35
35 class Bot(threading.Thread): 36 class Bot(threading.Thread):
36 37
37 def __init__(self, jid, password, nickname, admins_jid=[], error_fd=sys.stderr, debug=False): 38 def __init__(self, jid, password, nickname, admins=[], error_fd=sys.stderr, debug=False):
38 threading.Thread.__init__(self) 39 threading.Thread.__init__(self)
39 self.halt = False 40 self.halt = False
40 self.bridges = [] 41 self.bridges = []
41 self.bare_jid = xmpp.protocol.JID(jid=jid) 42 self.bare_jid = xmpp.protocol.JID(jid=jid)
42 self.bare_jid.setResource('') 43 self.bare_jid.setResource('')
43 self.nickname = nickname 44 self.nickname = nickname
44 self.password = password 45 self.password = password
45 self.error_fd = error_fd 46 self.error_fd = error_fd
46 self.debug = debug 47 self.debug = debug
47 self.admins_jid = admins_jid 48 self.admins = admins
48 self.xmpp_connections = {} 49 self.xmpp_connections = {}
49 self.irc = irclib.IRC() 50 self.irc = irclib.IRC()
50 self.irc.bot = self 51 self.irc.bot = self
51 self.irc.add_global_handler('all_events', self._irc_event_handler) 52 self.irc.add_global_handler('all_events', self._irc_event_handler)
52 self.irc_thread = threading.Thread(target=self.irc.process_forever) 53 self.irc_thread = threading.Thread(target=self.irc.process_forever)
53 self.irc_thread.start() 54 self.irc_thread.start()
54 # Open connection with XMPP server 55 # Open connection with XMPP server
55 try: 56 try:
56 self.xmpp_c = self.get_xmpp_connection(self.nickname) 57 self.xmpp_c = self.get_xmpp_connection(self.nickname)
57 except: 58 except:
58 self.error('[Error] XMPP Connection failed') 59 self.error(say_levels.error, 'XMPP Connection failed')
59 raise 60 raise
60 self.xmpp_thread = threading.Thread(target=self._xmpp_loop) 61 self.xmpp_thread = threading.Thread(target=self._xmpp_loop)
61 self.xmpp_thread.start() 62 self.xmpp_thread.start()
62 63
63 64
64 def error(self, s, debug=False, send_to_admins=False): 65 def error(self, importance, message, debug=False, send_to_admins=False):
65 """Output an error message.""" 66 """Output an error message."""
66 if send_to_admins == True: 67 if send_to_admins == True:
67 self._send_message_to_admins(s) 68 self._send_message_to_admins(importance, message)
68 if not debug or debug and self.debug: 69 if importance == -1:
69 self.error_fd.write(s.encode('utf-8')+"\n") 70 return
71 if not debug:
72 self.error_fd.write(self.format_message(importance, message).encode('utf-8')+'\n')
73 if debug and self.debug:
74 self.error_fd.write('='*importance+'> '+message.encode('utf-8')+'\n')
70 75
71 76
72 def _xmpp_loop(self): 77 def _xmpp_loop(self):
73 """[Internal] XMPP infinite loop.""" 78 """[Internal] XMPP infinite loop."""
74 i = 1 79 i = 1
87 if hasattr(c, 'lock'): 92 if hasattr(c, 'lock'):
88 c.lock.acquire() 93 c.lock.acquire()
89 if i == j: 94 if i == j:
90 ping = xmpp.protocol.Iq(typ='get') 95 ping = xmpp.protocol.Iq(typ='get')
91 ping.addChild(name='ping', namespace='urn:xmpp:ping') 96 ping.addChild(name='ping', namespace='urn:xmpp:ping')
92 self.error('=> Debug: sending XMPP ping', debug=True) 97 self.error(1, 'sending XMPP ping', debug=True)
93 c.pings.append(c.send(ping)) 98 c.pings.append(c.send(ping))
94 if hasattr(c, 'Process'): 99 if hasattr(c, 'Process'):
95 c.Process(0.01) 100 c.Process(0.01)
96 c.lock.release() 101 c.lock.release()
97 if i > 5000: 102 if i > 5000:
98 i = 0 103 i = 0
99 except RuntimeError: 104 except RuntimeError:
100 pass 105 pass
101 except (xml.parsers.expat.ExpatError, xmpp.protocol.XMLNotWellFormed): 106 except (xml.parsers.expat.ExpatError, xmpp.protocol.XMLNotWellFormed):
102 self.error('=> Debug: invalid stanza', debug=True) 107 self.error(1, 'invalid stanza', debug=True)
103 self.reopen_xmpp_connection(c) 108 self.reopen_xmpp_connection(c)
104 unlock = True 109 unlock = True
105 except xmpp.Conflict: 110 except xmpp.Conflict:
106 self.error('=> Debug: conflict', debug=True) 111 self.error(1, 'conflict', debug=True)
107 self.reopen_xmpp_connection(c) 112 self.reopen_xmpp_connection(c)
108 unlock = True 113 unlock = True
109 except: 114 except:
110 error = '[Error] Unknown exception on XMPP thread:\n' 115 error = 'Unknown exception on XMPP thread:\n'+traceback.format_exc()
111 error += traceback.format_exc() 116 self.error(say_levels.error, error, send_to_admins=True)
112 self.error(error, send_to_admins=True)
113 unlock = True 117 unlock = True
114 if unlock == True: 118 if unlock == True:
115 c.lock.release() 119 c.lock.release()
116 120
117 121
119 """[Internal] Manage XMPP presence.""" 123 """[Internal] Manage XMPP presence."""
120 124
121 xmpp_c = dispatcher._owner 125 xmpp_c = dispatcher._owner
122 126
123 if xmpp_c.nickname != self.nickname: 127 if xmpp_c.nickname != self.nickname:
124 self.error('=> Debug: Skipping XMPP presence not received on bot connection.', debug=True) 128 self.error(1, 'Skipping XMPP presence not received on bot connection.', debug=True)
125 return 129 return
126 130
127 self.error('==> Debug: Received XMPP presence.', debug=True) 131 self.error(2, 'Received XMPP presence.\n'+presence.__str__(fancy=1), debug=True)
128 self.error(presence.__str__(fancy=1), debug=True)
129 132
130 from_ = xmpp.protocol.JID(presence.getFrom()) 133 from_ = xmpp.protocol.JID(presence.getFrom())
131 bare_jid = unicode(from_.getNode()+'@'+from_.getDomain()) 134 bare_jid = unicode(from_.getNode()+'@'+from_.getDomain())
132 for bridge in self.bridges: 135 for bridge in self.bridges:
133 if bare_jid == bridge.xmpp_room_jid: 136 if bare_jid == bridge.xmpp_room_jid:
151 if reason: 154 if reason:
152 r = reason.getData() 155 r = reason.getData()
153 if r == 'The conference component is shutting down': 156 if r == 'The conference component is shutting down':
154 # MUC server is going down, try to restart the bridges in 1 minute 157 # MUC server is going down, try to restart the bridges in 1 minute
155 bridges = self.findBridges([from_.getDomain()]) 158 bridges = self.findBridges([from_.getDomain()])
156 error_message = '[Warning] The MUC server '+from_.getDomain()+' seems to be going down, the bot will try to recreate all bridges related to this server in 1 minute' 159 m = 'The MUC server '+from_.getDomain()+' seems to be going down, the bot will try to recreate all bridges related to this server in 1 minute'
157 self.restart_bridges_delayed(bridges, 60, error_message) 160 error = (say_levels.warning, m)
158 self.error(presence.__str__(fancy=1).encode('utf-8'), debug=True) 161 self.restart_bridges_delayed(bridges, 60, error)
159 return 162 return
160 elif r == '': 163 elif r == '':
161 r = 'None given' 164 r = 'None given'
162 else: 165 else:
163 r = 'None given' 166 r = 'None given'
164 167
165 # room has been destroyed, stop the bridge 168 # room has been destroyed, stop the bridge
166 self.error('[Error] The MUC room of the bridge '+str(bridge)+' has been destroyed with reason "'+r+'", stopping the bridge', send_to_admins=True) 169 self.error(say_levels.error, 'The MUC room of the bridge '+str(bridge)+' has been destroyed with reason "'+r+'", stopping the bridge', send_to_admins=True)
167 bridge.stop(message='The MUC room of the bridge has been destroyed with reason "'+r+'", stopping the bridge') 170 bridge.stop(message='The MUC room of the bridge has been destroyed with reason "'+r+'", stopping the bridge')
168 171
169 else: 172 else:
170 # presence comes from a participant of the muc 173 # presence comes from a participant of the muc
171 174
186 return 189 return
187 if p.protocol != 'xmpp': 190 if p.protocol != 'xmpp':
188 return 191 return
189 item = x.getTag('item') 192 item = x.getTag('item')
190 if not item: 193 if not item:
191 self.error('=> Debug: bad stanza, no item element', debug=True) 194 self.error(1, 'bad stanza, no item element', debug=True)
192 return 195 return
193 new_nick = item.getAttr('nick') 196 new_nick = item.getAttr('nick')
194 if not new_nick: 197 if not new_nick:
195 self.error('=> Debug: bad stanza, new nick is not given', debug=True) 198 self.error(1, 'bad stanza, new nick is not given', debug=True)
196 return 199 return
197 p.changeNickname(new_nick, 'irc') 200 p.changeNickname(new_nick, 'irc')
198 201
199 elif x and x.getTag('status', attrs={'code': '307'}): 202 elif x and x.getTag('status', attrs={'code': '307'}):
200 # participant was kicked 203 # participant was kicked
223 bridge.removeParticipant('xmpp', p.nickname, s1+s2) 226 bridge.removeParticipant('xmpp', p.nickname, s1+s2)
224 227
225 elif x and x.getTag('status', attrs={'code': '301'}): 228 elif x and x.getTag('status', attrs={'code': '301'}):
226 # participant was banned 229 # participant was banned
227 if p == None: 230 if p == None:
228 m = '[Error] bot got banned from XMPP' 231 bridge.say(say_levels.error, 'bot got banned from XMPP', on_xmpp=False, send_to_admins=True)
229 self.error(m)
230 bridge.say(m, on_xmpp=False)
231 self.removeBridge(bridge) 232 self.removeBridge(bridge)
232 return 233 return
233 if item: 234 if item:
234 reason = item.getTag('reason') 235 reason = item.getTag('reason')
235 actor = item.getTag('actor') 236 actor = item.getTag('actor')
263 # Remote server not found 264 # Remote server not found
264 # Stop bridges that depend on this server 265 # Stop bridges that depend on this server
265 bridges = self.findBridges([from_.getDomain()]) 266 bridges = self.findBridges([from_.getDomain()])
266 error_message = '[Error] XMPP Remote server not found: '+from_.getDomain() 267 error_message = '[Error] XMPP Remote server not found: '+from_.getDomain()
267 self.restart_bridges_delayed(bridges, 60, error_message) 268 self.restart_bridges_delayed(bridges, 60, error_message)
268 self.error(presence.__str__(fancy=1).encode('utf-8'), debug=True)
269 else: 269 else:
270 raise Exception(presence.__str__(fancy=1).encode('utf-8')) 270 raise Exception(presence.__str__(fancy=1).encode('utf-8'))
271 271
272 elif resource != bridge.bot.nickname: 272 elif resource != bridge.bot.nickname:
273 real_jid = None 273 real_jid = None
276 276
277 p = bridge.addParticipant('xmpp', resource, real_jid) 277 p = bridge.addParticipant('xmpp', resource, real_jid)
278 278
279 # if we have the real jid check if the participant is a bot admin 279 # if we have the real jid check if the participant is a bot admin
280 if real_jid and isinstance(p, Participant): 280 if real_jid and isinstance(p, Participant):
281 for jid in self.admins_jid: 281 for admin in self.admins:
282 if xmpp.protocol.JID(jid).bareMatch(real_jid): 282 if xmpp.protocol.JID(admin.jid).bareMatch(real_jid):
283 p.bot_admin = True 283 p.bot_admin = True
284 break 284 break
285 285
286 return 286 return
287 287
294 xmpp_c = dispatcher._owner 294 xmpp_c = dispatcher._owner
295 295
296 # Ignore pongs 296 # Ignore pongs
297 if iq.getType() in ['result', 'error'] and iq.getID() in xmpp_c.pings: 297 if iq.getType() in ['result', 'error'] and iq.getID() in xmpp_c.pings:
298 xmpp_c.pings.remove(iq.getID()) 298 xmpp_c.pings.remove(iq.getID())
299 self.error('=> Debug: received XMPP pong', debug=True) 299 self.error(1, 'received XMPP pong', debug=True)
300 return 300 return
301 301
302 self.error('==> Debug: Received XMPP iq.', debug=True) 302 self.error(2, 'Received XMPP iq.\n'+iq.__str__(fancy=1), debug=True)
303 self.error(iq.__str__(fancy=1), debug=True)
304 303
305 304
306 def _xmpp_message_handler(self, dispatcher, message): 305 def _xmpp_message_handler(self, dispatcher, message):
307 """[Internal] Manage XMPP messages.""" 306 """[Internal] Manage XMPP messages."""
308 307
315 from_bare_jid = unicode(message.getFrom().getNode()+'@'+message.getFrom().getDomain()) 314 from_bare_jid = unicode(message.getFrom().getNode()+'@'+message.getFrom().getDomain())
316 for bridge in self.bridges: 315 for bridge in self.bridges:
317 if from_bare_jid == bridge.xmpp_room_jid: 316 if from_bare_jid == bridge.xmpp_room_jid:
318 # message comes from a room participant 317 # message comes from a room participant
319 318
320 self.error('==> Debug: Received XMPP chat message.', debug=True) 319 self.error(2, 'Received XMPP chat message.\n'+message.__str__(fancy=1), debug=True)
321 self.error(message.__str__(fancy=1), debug=True)
322 320
323 try: 321 try:
324 from_ = bridge.getParticipant(message.getFrom().getResource()) 322 from_ = bridge.getParticipant(message.getFrom().getResource())
325 to_ = bridge.getParticipant(xmpp_c.nickname) 323 to_ = bridge.getParticipant(xmpp_c.nickname)
326 324
329 except Bridge.NoSuchParticipantException: 327 except Bridge.NoSuchParticipantException:
330 if xmpp_c.nickname == self.nickname: 328 if xmpp_c.nickname == self.nickname:
331 r = self.respond(str(message.getBody()), participant=from_) 329 r = self.respond(str(message.getBody()), participant=from_)
332 if isinstance(r, basestring) and len(r) > 0: 330 if isinstance(r, basestring) and len(r) > 0:
333 s = xmpp.protocol.Message(to=message.getFrom(), body=r, typ='chat') 331 s = xmpp.protocol.Message(to=message.getFrom(), body=r, typ='chat')
334 self.error('==> Debug: Sending', debug=True) 332 self.error(2, 'Sending\n'+s.__str__(fancy=1), debug=True)
335 self.error(s.__str__(fancy=1), debug=True)
336 xmpp_c.send(s) 333 xmpp_c.send(s)
337 else: 334 else:
338 self.error('=> Debug: won\'t answer.', debug=True) 335 self.error(1, 'won\'t answer.', debug=True)
339 return 336 return
340 self.error('=> Debug: XMPP chat message not relayed', debug=True) 337 self.error(1, 'XMPP chat message not relayed', debug=True)
341 return 338 return
342 339
343 # message does not come from a room 340 # message does not come from a room
344 if xmpp_c.nickname == self.nickname: 341 if xmpp_c.nickname == self.nickname:
345 self.error('==> Debug: Received XMPP chat message.', debug=True) 342 self.error(2, 'Received XMPP chat message.\n'+message.__str__(fancy=1), debug=True)
346 self.error(message.__str__(fancy=1), debug=True)
347 343
348 # Find out if the message comes from a bot admin 344 # Find out if the message comes from a bot admin
349 bot_admin = False 345 bot_admin = False
350 for jid in self.admins_jid: 346 for admin in self.admins:
351 if xmpp.protocol.JID(jid).bareMatch(message.getFrom()): 347 if xmpp.protocol.JID(admin.jid).bareMatch(message.getFrom()):
352 bot_admin = True 348 bot_admin = True
353 break 349 break
354 350
355 # Respond 351 # Respond
356 r = self.respond(str(message.getBody()), bot_admin=bot_admin) 352 r = self.respond(str(message.getBody()), bot_admin=bot_admin)
357 if isinstance(r, basestring) and len(r) > 0: 353 if isinstance(r, basestring) and len(r) > 0:
358 s = xmpp.protocol.Message(to=message.getFrom(), body=r, typ='chat') 354 s = xmpp.protocol.Message(to=message.getFrom(), body=r, typ='chat')
359 self.error('==> Debug: Sending', debug=True) 355 self.error(2, 'Sending\n'+s.__str__(fancy=1), debug=True)
360 self.error(s.__str__(fancy=1), debug=True)
361 xmpp_c.send(s) 356 xmpp_c.send(s)
362 357
363 else: 358 else:
364 self.error('=> Debug: Ignoring XMPP chat message not received on bot connection.', debug=True) 359 self.error(1, 'Ignoring XMPP chat message not received on bot connection.', debug=True)
365 360
366 elif message.getType() == 'groupchat': 361 elif message.getType() == 'groupchat':
367 # message comes from a room 362 # message comes from a room
368 363
369 for child in message.getChildren(): 364 for child in message.getChildren():
370 if child.getName() == 'delay': 365 if child.getName() == 'delay':
371 # MUC delayed message 366 # MUC delayed message
372 return 367 return
373 368
374 if xmpp_c.nickname != self.nickname: 369 if xmpp_c.nickname != self.nickname:
375 self.error('=> Debug: Ignoring XMPP MUC message not received on bot connection.', debug=True) 370 self.error(1, 'Ignoring XMPP MUC message not received on bot connection.', debug=True)
376 return 371 return
377 372
378 373
379 from_ = xmpp.protocol.JID(message.getFrom()) 374 from_ = xmpp.protocol.JID(message.getFrom())
380 375
381 if unicode(from_.getResource()) == self.nickname: 376 if unicode(from_.getResource()) == self.nickname:
382 self.error('=> Debug: Ignoring XMPP MUC message sent by self.', debug=True) 377 self.error(1, 'Ignoring XMPP MUC message sent by self.', debug=True)
383 return 378 return
384 379
385 room_jid = unicode(from_.getNode()+'@'+from_.getDomain()) 380 room_jid = unicode(from_.getNode()+'@'+from_.getDomain())
386 for bridge in self.bridges: 381 for bridge in self.bridges:
387 if room_jid == bridge.xmpp_room_jid: 382 if room_jid == bridge.xmpp_room_jid:
388 resource = unicode(from_.getResource()) 383 resource = unicode(from_.getResource())
389 if resource == '': 384 if resource == '':
390 # message comes from the room itself 385 # message comes from the room itself
391 self.error('=> Debug: Ignoring XMPP groupchat message sent by the room.', debug=True) 386 self.error(1, 'Ignoring XMPP groupchat message sent by the room.', debug=True)
392 return 387 return
393 else: 388 else:
394 # message comes from a participant of the room 389 # message comes from a participant of the room
395 self.error('==> Debug: Received XMPP groupchat message.', debug=True) 390 self.error(2, 'Received XMPP groupchat message.\n'+message.__str__(fancy=1), debug=True)
396 self.error(message.__str__(fancy=1), debug=True)
397 391
398 try: 392 try:
399 participant = bridge.getParticipant(resource) 393 participant = bridge.getParticipant(resource)
400 except Bridge.NoSuchParticipantException: 394 except Bridge.NoSuchParticipantException:
401 if resource != self.nickname: 395 if resource != self.nickname:
402 self.error('=> Debug: NoSuchParticipantException "'+resource+'" on "'+str(bridge)+'", WTF ?', debug=True) 396 self.error(1, 'NoSuchParticipantException "'+resource+'" on "'+str(bridge)+'", WTF ?', debug=True)
403 return 397 return
404 398
405 participant.sayOnIRC(message.getBody()) 399 participant.sayOnIRC(message.getBody())
406 return 400 return
407 401
420 # let's restart the bot 414 # let's restart the bot
421 self.restart() 415 self.restart()
422 elif err == 'forbidden': 416 elif err == 'forbidden':
423 # we don't have the permission to speak 417 # we don't have the permission to speak
424 # let's remove the bridge and tell admins 418 # let's remove the bridge and tell admins
425 self.error('[Error] Not allowed to speak on the XMPP MUC of bridge '+str(b)+', stopping it', send_to_admins=True) 419 self.error(say_levels.error, 'Not allowed to speak on the XMPP MUC of bridge '+str(b)+', stopping it', send_to_admins=True)
426 b.stop(message='Not allowed to speak on the XMPP MUC, stopping bridge.') 420 b.stop(message='Not allowed to speak on the XMPP MUC, stopping bridge.')
427 else: 421 else:
428 self.error('==> Debug: recevied unknown error message', debug=True) 422 self.error(2, 'recevied unknown error message\n'+message.__str__(fancy=1), debug=True)
429 self.error(message.__str__(fancy=1), debug=True)
430 return 423 return
431 424
432 self.error('==> Debug: recevied unknown error message', debug=True) 425 self.error(2, 'recevied unknown error message\n'+message.__str__(fancy=1), debug=True)
433 self.error(message.__str__(fancy=1), debug=True)
434 426
435 else: 427 else:
436 self.error('==> Debug: Received XMPP message of unknown type "'+str(message.getType())+'".', debug=True) 428 self.error(2, 'Received XMPP message of unknown type "'+str(message.getType())+'".\n'+message.__str__(fancy=1), debug=True)
437 self.error(message.__str__(fancy=1), debug=True)
438 429
439 430
440 def _irc_event_handler(self, connection, event): 431 def _irc_event_handler(self, connection, event):
441 """[Internal] Manage IRC events""" 432 """[Internal] Manage IRC events"""
442 433
448 439
449 # Events we always want to ignore 440 # Events we always want to ignore
450 if 'all' in event.eventtype() or 'motd' in event.eventtype() or event.eventtype() in ['nicknameinuse', 'nickcollision', 'erroneusnickname']: 441 if 'all' in event.eventtype() or 'motd' in event.eventtype() or event.eventtype() in ['nicknameinuse', 'nickcollision', 'erroneusnickname']:
451 return 442 return
452 if event.eventtype() in ['pong', 'privnotice', 'ctcp', 'nochanmodes', 'notexttosend', 'currenttopic', 'topicinfo', '328']: 443 if event.eventtype() in ['pong', 'privnotice', 'ctcp', 'nochanmodes', 'notexttosend', 'currenttopic', 'topicinfo', '328']:
453 self.error('=> Debug: ignoring IRC '+event.eventtype(), debug=True) 444 self.error(1, 'ignoring IRC '+event.eventtype(), debug=True)
454 return 445 return
455 446
456 447
457 nickname = None 448 nickname = None
458 if event.source() != None: 449 if event.source() != None:
465 if connection.really_connected == False: 456 if connection.really_connected == False:
466 if event.target() == connection.nickname: 457 if event.target() == connection.nickname:
467 connection.really_connected = True 458 connection.really_connected = True
468 connection._call_nick_callbacks(None) 459 connection._call_nick_callbacks(None)
469 elif len(connection.nick_callbacks) > 0: 460 elif len(connection.nick_callbacks) > 0:
470 self.error('===> Debug: event target ('+event.target()+') and connection nickname ('+connection.nickname+') don\'t match') 461 self.error(3, 'event target ('+event.target()+') and connection nickname ('+connection.nickname+') don\'t match')
471 connection._call_nick_callbacks('nicknametoolong', arguments=[len(event.target())]) 462 connection._call_nick_callbacks('nicknametoolong', arguments=[len(event.target())])
472 self.error('=> Debug: ignoring '+event.eventtype(), debug=True) 463 self.error(1, 'ignoring '+event.eventtype(), debug=True)
473 return 464 return
474 465
475 466
476 # A string representation of the event 467 # A string representation of the event
477 event_str = 'connection='+connection.__str__()+'\neventtype='+event.eventtype()+'\nsource='+repr(event.source())+'\ntarget='+repr(event.target())+'\narguments='+repr(event.arguments()) 468 event_str = 'connection='+connection.__str__()+'\neventtype='+event.eventtype()+'\nsource='+repr(event.source())+'\ntarget='+repr(event.target())+'\narguments='+repr(event.arguments())
478 debug_str = '==> Debug: Received IRC event.\n'+event_str 469 debug_str = 'Received IRC event.\n'+event_str
479 printed_event = False 470 printed_event = False
480 471
481 472
482 if event.eventtype() in ['pubmsg', 'action', 'privmsg', 'quit', 'part', 'nick', 'kick']: 473 if event.eventtype() in ['pubmsg', 'action', 'privmsg', 'quit', 'part', 'nick', 'kick']:
483 if nickname == None: 474 if nickname == None:
488 if event.eventtype() in ['quit', 'part'] and nickname == self.nickname: 479 if event.eventtype() in ['quit', 'part'] and nickname == self.nickname:
489 return 480 return
490 481
491 if event.eventtype() in ['quit', 'part', 'nick', 'kick']: 482 if event.eventtype() in ['quit', 'part', 'nick', 'kick']:
492 if connection.get_nickname() != self.nickname: 483 if connection.get_nickname() != self.nickname:
493 self.error('=> Debug: ignoring IRC '+event.eventtype()+' not received on bot connection', debug=True) 484 self.error(1, 'ignoring IRC '+event.eventtype()+' not received on bot connection', debug=True)
494 return 485 return
495 else: 486 else:
496 self.error(debug_str, debug=True) 487 self.error(2, debug_str, debug=True)
497 printed_event = True 488 printed_event = True
498 489
499 if event.eventtype() == 'kick' and len(event.arguments()) < 1: 490 if event.eventtype() == 'kick' and len(event.arguments()) < 1:
500 self.error('=> Debug: length of arguments should be greater than 0 for a '+event.eventtype()+' event') 491 self.error(1, 'length of arguments should be greater than 0 for a '+event.eventtype()+' event')
501 return 492 return
502 493
503 if event.eventtype() in ['pubmsg', 'action']: 494 if event.eventtype() in ['pubmsg', 'action']:
504 if connection.get_nickname() != self.nickname: 495 if connection.get_nickname() != self.nickname:
505 self.error('=> Debug: ignoring IRC '+event.eventtype()+' not received on bot connection', debug=True) 496 self.error(1, 'ignoring IRC '+event.eventtype()+' not received on bot connection', debug=True)
506 return 497 return
507 if nickname == self.nickname: 498 if nickname == self.nickname:
508 self.error('=> Debug: ignoring IRC '+event.eventtype()+' sent by self', debug=True) 499 self.error(1, 'ignoring IRC '+event.eventtype()+' sent by self', debug=True)
509 return 500 return
510 501
511 # TODO: lock self.bridges for thread safety 502 # TODO: lock self.bridges for thread safety
512 for bridge in self.bridges: 503 for bridge in self.bridges:
513 if connection.server != bridge.irc_server: 504 if connection.server != bridge.irc_server:
525 if event.target() == None: 516 if event.target() == None:
526 return 517 return
527 518
528 try: 519 try:
529 to_ = bridge.getParticipant(event.target().split('!')[0]) 520 to_ = bridge.getParticipant(event.target().split('!')[0])
530 self.error(debug_str, debug=True) 521 self.error(2, debug_str, debug=True)
531 from_.sayOnXMPPTo(to_.nickname, event.arguments()[0]) 522 from_.sayOnXMPPTo(to_.nickname, event.arguments()[0])
532 return 523 return
533 524
534 except Bridge.NoSuchParticipantException: 525 except Bridge.NoSuchParticipantException:
535 if event.target().split('!')[0] == self.nickname: 526 if event.target().split('!')[0] == self.nickname:
536 # Message is for the bot 527 # Message is for the bot
537 self.error(debug_str, debug=True) 528 self.error(2, debug_str, debug=True)
538 connection.privmsg(from_.nickname, self.respond(event.arguments()[0])) 529 connection.privmsg(from_.nickname, self.respond(event.arguments()[0]))
539 return 530 return
540 else: 531 else:
541 continue 532 continue
542 533
553 bridge.removeParticipant('irc', kicked.nickname, 'Kicked by '+nickname+' with reason: '+event.arguments()[1]) 544 bridge.removeParticipant('irc', kicked.nickname, 'Kicked by '+nickname+' with reason: '+event.arguments()[1])
554 else: 545 else:
555 bridge.removeParticipant('irc', kicked.nickname, 'Kicked by '+nickname+' (no reason was given)') 546 bridge.removeParticipant('irc', kicked.nickname, 'Kicked by '+nickname+' (no reason was given)')
556 return 547 return
557 except Bridge.NoSuchParticipantException: 548 except Bridge.NoSuchParticipantException:
558 self.error('=> Debug: a participant that was not here has been kicked ? WTF ?') 549 self.error(1, 'a participant that was not here has been kicked ? WTF ?')
559 return 550 return
560 else: 551 else:
561 continue 552 continue
562 553
563 554
586 577
587 578
588 # Chan message 579 # Chan message
589 if event.eventtype() in ['pubmsg', 'action']: 580 if event.eventtype() in ['pubmsg', 'action']:
590 if bridge.irc_room == event.target().lower() and bridge.irc_server == connection.server: 581 if bridge.irc_room == event.target().lower() and bridge.irc_server == connection.server:
591 self.error(debug_str, debug=True) 582 self.error(2, debug_str, debug=True)
592 message = event.arguments()[0] 583 message = event.arguments()[0]
593 if event.eventtype() == 'action': 584 if event.eventtype() == 'action':
594 message = '/me '+message 585 message = '/me '+message
595 from_.sayOnXMPP(message) 586 from_.sayOnXMPP(message)
596 return 587 return
602 593
603 594
604 # Handle bannedfromchan 595 # Handle bannedfromchan
605 if event.eventtype() == 'bannedfromchan': 596 if event.eventtype() == 'bannedfromchan':
606 if len(event.arguments()) < 1: 597 if len(event.arguments()) < 1:
607 self.error('=> Debug: length of arguments should be greater than 0 for a '+event.eventtype()+' event') 598 self.error(1, 'length of arguments should be greater than 0 for a '+event.eventtype()+' event')
608 return 599 return
609 600
610 for bridge in self.bridges: 601 for bridge in self.bridges:
611 if connection.server != bridge.irc_server or event.arguments()[0].lower() != bridge.irc_room: 602 if connection.server != bridge.irc_server or event.arguments()[0].lower() != bridge.irc_room:
612 continue 603 continue
613 604
614 if event.target() == self.nickname: 605 if event.target() == self.nickname:
615 self.error('[Error] the nickname "'+event.target()+'" is banned from the IRC chan of bridge "'+str(bridge)+'"') 606 self.error(say_levels.error, 'the nickname "'+event.target()+'" is banned from the IRC chan of bridge "'+str(bridge)+'"')
616 raise Exception('[Error] the nickname "'+event.target()+'" is banned from the IRC chan of bridge "'+str(bridge)+'"') 607 raise Exception('[Error] the nickname "'+event.target()+'" is banned from the IRC chan of bridge "'+str(bridge)+'"')
617 else: 608 else:
618 try: 609 try:
619 banned = bridge.getParticipant(event.target()) 610 banned = bridge.getParticipant(event.target())
620 if banned.irc_connection != 'bannedfromchan': 611 if banned.irc_connection != 'bannedfromchan':
621 banned.irc_connection = 'bannedfromchan' 612 banned.irc_connection = 'bannedfromchan'
622 self.error(debug_str, debug=True) 613 self.error(2, debug_str, debug=True)
623 bridge.say('[Warning] the nickname "'+event.target()+'" is banned from the IRC chan', log=True) 614 bridge.say(say_levels.warning, 'the nickname "'+event.target()+'" is banned from the IRC chan', log=True)
624 else: 615 else:
625 self.error('=> Debug: ignoring '+event.eventtype(), debug=True) 616 self.error(1, 'ignoring '+event.eventtype(), debug=True)
626 except Bridge.NoSuchParticipantException: 617 except Bridge.NoSuchParticipantException:
627 self.error('=> Debug: no such participant. WTF ?') 618 self.error(1, 'no such participant. WTF ?')
628 return 619 return
629 620
630 return 621 return
631 622
632 623
633 # Joining events 624 # Joining events
634 if event.eventtype() in ['namreply', 'join']: 625 if event.eventtype() in ['namreply', 'join']:
635 if connection.get_nickname() != self.nickname: 626 if connection.get_nickname() != self.nickname:
636 self.error('=> Debug: ignoring IRC '+event.eventtype()+' not received on bridge connection', debug=True) 627 self.error(1, 'ignoring IRC '+event.eventtype()+' not received on bridge connection', debug=True)
637 return 628 return
638 629
639 if event.eventtype() == 'namreply': 630 if event.eventtype() == 'namreply':
640 for bridge in self.getBridges(irc_room=event.arguments()[1].lower(), irc_server=connection.server): 631 for bridge in self.getBridges(irc_room=event.arguments()[1].lower(), irc_server=connection.server):
641 for nickname in re.split('(?:^[&@\+%]?|(?: [&@\+%]?)*)', event.arguments()[2].strip()): 632 for nickname in re.split('(?:^[&@\+%]?|(?: [&@\+%]?)*)', event.arguments()[2].strip()):
644 bridge.addParticipant('irc', nickname) 635 bridge.addParticipant('irc', nickname)
645 return 636 return
646 elif event.eventtype() == 'join': 637 elif event.eventtype() == 'join':
647 bridges = self.getBridges(irc_room=event.target().lower(), irc_server=connection.server) 638 bridges = self.getBridges(irc_room=event.target().lower(), irc_server=connection.server)
648 if len(bridges) == 0: 639 if len(bridges) == 0:
649 self.error(debug_str, debug=True) 640 self.error(2, debug_str, debug=True)
650 self.error('===> Debug: no bridge found for "'+event.target().lower()+' at '+connection.server+'"', debug=True) 641 self.error(3, 'no bridge found for "'+event.target().lower()+' at '+connection.server+'"', debug=True)
651 return 642 return
652 for bridge in bridges: 643 for bridge in bridges:
653 bridge.addParticipant('irc', nickname, irc_id=event.source()) 644 bridge.addParticipant('irc', nickname, irc_id=event.source())
654 return 645 return
655 646
656 647
657 if event.eventtype() in ['disconnect', 'kill', 'error']: 648 if event.eventtype() in ['disconnect', 'kill', 'error']:
658 if len(event.arguments()) > 0 and event.arguments()[0] == 'Connection reset by peer': 649 if len(event.arguments()) > 0 and event.arguments()[0] == 'Connection reset by peer':
659 self.error(debug_str, debug=True) 650 self.error(2, debug_str, debug=True)
660 else: 651 else:
661 self.error(debug_str, send_to_admins=True) 652 self.error(2, debug_str, send_to_admins=True)
662 return 653 return
663 654
664 655
665 if event.eventtype() in ['cannotsendtochan', 'notonchannel']: 656 if event.eventtype() in ['cannotsendtochan', 'notonchannel']:
666 self.error(debug_str, debug=True) 657 self.error(2, debug_str, debug=True)
667 bridges = self.getBridges(irc_room=event.arguments()[0], irc_server=connection.server) 658 bridges = self.getBridges(irc_room=event.arguments()[0], irc_server=connection.server)
668 if len(bridges) > 1: 659 if len(bridges) > 1:
669 raise Exception, 'more than one bridge for one irc chan, WTF ?' 660 raise Exception, 'more than one bridge for one irc chan, WTF ?'
670 bridge = bridges[0] 661 bridge = bridges[0]
671 if connection.get_nickname() == self.nickname: 662 if connection.get_nickname() == self.nickname:
677 return 668 return
678 669
679 670
680 # Unhandled events 671 # Unhandled events
681 if not printed_event: 672 if not printed_event:
682 self.error('[Debug] The following IRC event was not handled:\n'+event_str+'\n', send_to_admins=True) 673 self.error(say_levels.debug, 'The following IRC event was not handled:\n'+event_str+'\n', send_to_admins=True)
683 else: 674 else:
684 self.error('=> Debug: event not handled', debug=True) 675 self.error(1, 'event not handled', debug=True)
685 self._send_message_to_admins('[Debug] The following IRC event was not handled:\n'+event_str) 676 self._send_message_to_admins('[Debug] The following IRC event was not handled:\n'+event_str)
686 677
687 678
688 def _send_message_to_admins(self, message): 679 def _send_message_to_admins(self, importance, message):
689 """[Internal] Send XMPP Message to bot admin(s)""" 680 """[Internal] Send XMPP Message to bot admin(s)"""
690 for admin_jid in self.admins_jid: 681 for admin in self.admins:
682 if importance != -1:
683 if admin.say_level == say_levels.nothing or importance < admin.say_level:
684 continue
685 message = self.format_message(importance, message)
686
691 try: 687 try:
692 self.xmpp_c.send(xmpp.protocol.Message(to=admin_jid, body=message, typ='chat')) 688 self.xmpp_c.send(xmpp.protocol.Message(to=admin.jid, body=message, typ='chat'))
693 except: 689 except:
694 pass 690 pass
695 691
696 692
697 def new_bridge(self, xmpp_room, irc_room, irc_server, mode, say_level, irc_port=6667, irc_connection_interval=1, irc_charsets=None): 693 def new_bridge(self, xmpp_room, irc_room, irc_server, mode, say_level, irc_port=6667, irc_connection_interval=1, irc_charsets=None):
708 for s in str_array: 704 for s in str_array:
709 if not s in str(bridge): 705 if not s in str(bridge):
710 bridges.remove(bridge) 706 bridges.remove(bridge)
711 break 707 break
712 return bridges 708 return bridges
709
710
711 def format_message(self, importance, message):
712 if importance < 0 or importance >= len(say_levels.levels):
713 raise Exception('[Internal Error] unknown message importance')
714 return'['+str(say_levels.get(importance))+'] '+message
713 715
714 716
715 def getBridges(self, irc_room=None, irc_server=None, xmpp_room_jid=None): 717 def getBridges(self, irc_room=None, irc_server=None, xmpp_room_jid=None):
716 # TODO: lock self.bridges for thread safety 718 # TODO: lock self.bridges for thread safety
717 bridges = [b for b in self.bridges] 719 bridges = [b for b in self.bridges]
730 732
731 def get_xmpp_connection(self, nickname): 733 def get_xmpp_connection(self, nickname):
732 if self.xmpp_connections.has_key(nickname): 734 if self.xmpp_connections.has_key(nickname):
733 c = self.xmpp_connections[nickname] 735 c = self.xmpp_connections[nickname]
734 c.used_by += 1 736 c.used_by += 1
735 self.error('===> Debug: using existing XMPP connection for "'+nickname+'", now used by '+str(c.used_by)+' bridges', debug=True) 737 self.error(3, 'using existing XMPP connection for "'+nickname+'", now used by '+str(c.used_by)+' bridges', debug=True)
736 return c 738 return c
737 self.error('===> Debug: opening new XMPP connection for "'+nickname+'"', debug=True) 739 self.error(3, 'opening new XMPP connection for "'+nickname+'"', debug=True)
738 c = xmpp.client.Client(self.bare_jid.getDomain(), debug=[]) 740 c = xmpp.client.Client(self.bare_jid.getDomain(), debug=[])
739 c.lock = threading.RLock() 741 c.lock = threading.RLock()
740 c.lock.acquire() 742 c.lock.acquire()
741 self.xmpp_connections[nickname] = c 743 self.xmpp_connections[nickname] = c
742 c.used_by = 1 744 c.used_by = 1
768 for b in self.bridges: 770 for b in self.bridges:
769 for p in b.participants: 771 for p in b.participants:
770 if p.xmpp_c == c: 772 if p.xmpp_c == c:
771 participants.append(p) 773 participants.append(p)
772 p.xmpp_c = None 774 p.xmpp_c = None
773 self.error('===> Debug: reopening XMPP connection for "'+nickname+'"', debug=True) 775 self.error(3, 'reopening XMPP connection for "'+nickname+'"', debug=True)
774 if self.xmpp_connections.has_key(nickname): 776 if self.xmpp_connections.has_key(nickname):
775 self.xmpp_connections.pop(nickname) 777 self.xmpp_connections.pop(nickname)
776 c.send(xmpp.protocol.Presence(typ='unavailable')) 778 c.send(xmpp.protocol.Presence(typ='unavailable'))
777 del c 779 del c
778 c = self.get_xmpp_connection(nickname) 780 c = self.get_xmpp_connection(nickname)
792 return 794 return
793 c = self.xmpp_connections[nickname] 795 c = self.xmpp_connections[nickname]
794 c.lock.acquire() 796 c.lock.acquire()
795 c.used_by -= 1 797 c.used_by -= 1
796 if c.used_by < 1 or force: 798 if c.used_by < 1 or force:
797 self.error('===> Debug: closing XMPP connection for "'+nickname+'"', debug=True) 799 self.error(3, 'closing XMPP connection for "'+nickname+'"', debug=True)
798 self.xmpp_connections.pop(nickname) 800 self.xmpp_connections.pop(nickname)
799 c.send(xmpp.protocol.Presence(typ='unavailable')) 801 c.send(xmpp.protocol.Presence(typ='unavailable'))
800 c.lock.release() 802 c.lock.release()
801 del c 803 del c
802 else: 804 else:
803 c.lock.release() 805 c.lock.release()
804 self.error('===> Debug: XMPP connection for "'+nickname+'" is now used by '+str(c.used_by)+' bridges', debug=True) 806 self.error(3, 'XMPP connection for "'+nickname+'" is now used by '+str(c.used_by)+' bridges', debug=True)
805 807
806 808
807 def removeBridge(self, bridge, message='Removing bridge'): 809 def removeBridge(self, bridge, message='Removing bridge'):
808 self.bridges.remove(bridge) 810 self.bridges.remove(bridge)
809 bridge.stop(message) 811 bridge.stop(message)
830 832
831 # Restart the bridges 833 # Restart the bridges
832 for b in self.bridges: 834 for b in self.bridges:
833 b.init2() 835 b.init2()
834 836
835 self.error('Bot restarted.', send_to_admins=True) 837 self.error(-1, 'Bot restarted.', send_to_admins=True)
836 838
837 839
838 def restart_bridges_delayed(self, bridges, delay, error_message, protocol='xmpp'): 840 def restart_bridges_delayed(self, bridges, delay, error, protocol='xmpp'):
839 if len(bridges) > 0: 841 if len(bridges) > 0:
840 error_message += '\nThese bridges will be stopped:' 842 error[1] += '\nThese bridges will be stopped:'
841 for b in bridges: 843 for b in bridges:
842 error_message += '\n'+str(b) 844 error[1] += '\n'+str(b)
843 845
844 if protocol == 'xmpp': 846 if protocol == 'xmpp':
845 leave_message = 'Could not connect to the MUC server ('+b.xmpp_room_jid+')' 847 leave_message = 'Could not connect to the MUC server ('+b.xmpp_room_jid+')'
846 else: 848 else:
847 leave_message = 'Could not connect to the IRC server ('+b.irc_connection._server_str()+')' 849 leave_message = 'Could not connect to the IRC server ('+b.irc_connection._server_str()+')'
851 b.reconnecting = True 853 b.reconnecting = True
852 self.irc.execute_delayed(delay, b.init2) 854 self.irc.execute_delayed(delay, b.init2)
853 855
854 b.stop(message=leave_message) 856 b.stop(message=leave_message)
855 857
856 self.error(error_message, send_to_admins=True) 858 self.error(error[0], error[1], send_to_admins=True)
857 859
858 860
859 def stop(self, message='Stopping bot'): 861 def stop(self, message='Stopping bot'):
860 for bridge in self.bridges: 862 for bridge in self.bridges:
861 bridge.stop(message=message) 863 bridge.stop(message=message)