Mercurial > xib
diff 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 |
line wrap: on
line diff
--- a/bot.py +++ b/bot.py @@ -30,11 +30,12 @@ del muc from bridge import Bridge from participant import Participant import commands +import say_levels class Bot(threading.Thread): - def __init__(self, jid, password, nickname, admins_jid=[], error_fd=sys.stderr, debug=False): + def __init__(self, jid, password, nickname, admins=[], error_fd=sys.stderr, debug=False): threading.Thread.__init__(self) self.halt = False self.bridges = [] @@ -44,7 +45,7 @@ class Bot(threading.Thread): self.password = password self.error_fd = error_fd self.debug = debug - self.admins_jid = admins_jid + self.admins = admins self.xmpp_connections = {} self.irc = irclib.IRC() self.irc.bot = self @@ -55,18 +56,22 @@ class Bot(threading.Thread): try: self.xmpp_c = self.get_xmpp_connection(self.nickname) except: - self.error('[Error] XMPP Connection failed') + self.error(say_levels.error, 'XMPP Connection failed') raise self.xmpp_thread = threading.Thread(target=self._xmpp_loop) self.xmpp_thread.start() - def error(self, s, debug=False, send_to_admins=False): + def error(self, importance, message, debug=False, send_to_admins=False): """Output an error message.""" if send_to_admins == True: - self._send_message_to_admins(s) - if not debug or debug and self.debug: - self.error_fd.write(s.encode('utf-8')+"\n") + self._send_message_to_admins(importance, message) + if importance == -1: + return + if not debug: + self.error_fd.write(self.format_message(importance, message).encode('utf-8')+'\n') + if debug and self.debug: + self.error_fd.write('='*importance+'> '+message.encode('utf-8')+'\n') def _xmpp_loop(self): @@ -89,7 +94,7 @@ class Bot(threading.Thread): if i == j: ping = xmpp.protocol.Iq(typ='get') ping.addChild(name='ping', namespace='urn:xmpp:ping') - self.error('=> Debug: sending XMPP ping', debug=True) + self.error(1, 'sending XMPP ping', debug=True) c.pings.append(c.send(ping)) if hasattr(c, 'Process'): c.Process(0.01) @@ -99,17 +104,16 @@ class Bot(threading.Thread): except RuntimeError: pass except (xml.parsers.expat.ExpatError, xmpp.protocol.XMLNotWellFormed): - self.error('=> Debug: invalid stanza', debug=True) + self.error(1, 'invalid stanza', debug=True) self.reopen_xmpp_connection(c) unlock = True except xmpp.Conflict: - self.error('=> Debug: conflict', debug=True) + self.error(1, 'conflict', debug=True) self.reopen_xmpp_connection(c) unlock = True except: - error = '[Error] Unknown exception on XMPP thread:\n' - error += traceback.format_exc() - self.error(error, send_to_admins=True) + error = 'Unknown exception on XMPP thread:\n'+traceback.format_exc() + self.error(say_levels.error, error, send_to_admins=True) unlock = True if unlock == True: c.lock.release() @@ -121,11 +125,10 @@ class Bot(threading.Thread): xmpp_c = dispatcher._owner if xmpp_c.nickname != self.nickname: - self.error('=> Debug: Skipping XMPP presence not received on bot connection.', debug=True) + self.error(1, 'Skipping XMPP presence not received on bot connection.', debug=True) return - self.error('==> Debug: Received XMPP presence.', debug=True) - self.error(presence.__str__(fancy=1), debug=True) + self.error(2, 'Received XMPP presence.\n'+presence.__str__(fancy=1), debug=True) from_ = xmpp.protocol.JID(presence.getFrom()) bare_jid = unicode(from_.getNode()+'@'+from_.getDomain()) @@ -153,9 +156,9 @@ class Bot(threading.Thread): if r == 'The conference component is shutting down': # MUC server is going down, try to restart the bridges in 1 minute bridges = self.findBridges([from_.getDomain()]) - 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' - self.restart_bridges_delayed(bridges, 60, error_message) - self.error(presence.__str__(fancy=1).encode('utf-8'), debug=True) + 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' + error = (say_levels.warning, m) + self.restart_bridges_delayed(bridges, 60, error) return elif r == '': r = 'None given' @@ -163,7 +166,7 @@ class Bot(threading.Thread): r = 'None given' # room has been destroyed, stop the bridge - self.error('[Error] The MUC room of the bridge '+str(bridge)+' has been destroyed with reason "'+r+'", stopping the bridge', send_to_admins=True) + 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) bridge.stop(message='The MUC room of the bridge has been destroyed with reason "'+r+'", stopping the bridge') else: @@ -188,11 +191,11 @@ class Bot(threading.Thread): return item = x.getTag('item') if not item: - self.error('=> Debug: bad stanza, no item element', debug=True) + self.error(1, 'bad stanza, no item element', debug=True) return new_nick = item.getAttr('nick') if not new_nick: - self.error('=> Debug: bad stanza, new nick is not given', debug=True) + self.error(1, 'bad stanza, new nick is not given', debug=True) return p.changeNickname(new_nick, 'irc') @@ -225,9 +228,7 @@ class Bot(threading.Thread): elif x and x.getTag('status', attrs={'code': '301'}): # participant was banned if p == None: - m = '[Error] bot got banned from XMPP' - self.error(m) - bridge.say(m, on_xmpp=False) + bridge.say(say_levels.error, 'bot got banned from XMPP', on_xmpp=False, send_to_admins=True) self.removeBridge(bridge) return if item: @@ -265,7 +266,6 @@ class Bot(threading.Thread): bridges = self.findBridges([from_.getDomain()]) error_message = '[Error] XMPP Remote server not found: '+from_.getDomain() self.restart_bridges_delayed(bridges, 60, error_message) - self.error(presence.__str__(fancy=1).encode('utf-8'), debug=True) else: raise Exception(presence.__str__(fancy=1).encode('utf-8')) @@ -278,8 +278,8 @@ class Bot(threading.Thread): # if we have the real jid check if the participant is a bot admin if real_jid and isinstance(p, Participant): - for jid in self.admins_jid: - if xmpp.protocol.JID(jid).bareMatch(real_jid): + for admin in self.admins: + if xmpp.protocol.JID(admin.jid).bareMatch(real_jid): p.bot_admin = True break @@ -296,11 +296,10 @@ class Bot(threading.Thread): # Ignore pongs if iq.getType() in ['result', 'error'] and iq.getID() in xmpp_c.pings: xmpp_c.pings.remove(iq.getID()) - self.error('=> Debug: received XMPP pong', debug=True) + self.error(1, 'received XMPP pong', debug=True) return - self.error('==> Debug: Received XMPP iq.', debug=True) - self.error(iq.__str__(fancy=1), debug=True) + self.error(2, 'Received XMPP iq.\n'+iq.__str__(fancy=1), debug=True) def _xmpp_message_handler(self, dispatcher, message): @@ -317,8 +316,7 @@ class Bot(threading.Thread): if from_bare_jid == bridge.xmpp_room_jid: # message comes from a room participant - self.error('==> Debug: Received XMPP chat message.', debug=True) - self.error(message.__str__(fancy=1), debug=True) + self.error(2, 'Received XMPP chat message.\n'+message.__str__(fancy=1), debug=True) try: from_ = bridge.getParticipant(message.getFrom().getResource()) @@ -331,24 +329,22 @@ class Bot(threading.Thread): r = self.respond(str(message.getBody()), participant=from_) if isinstance(r, basestring) and len(r) > 0: s = xmpp.protocol.Message(to=message.getFrom(), body=r, typ='chat') - self.error('==> Debug: Sending', debug=True) - self.error(s.__str__(fancy=1), debug=True) + self.error(2, 'Sending\n'+s.__str__(fancy=1), debug=True) xmpp_c.send(s) else: - self.error('=> Debug: won\'t answer.', debug=True) + self.error(1, 'won\'t answer.', debug=True) return - self.error('=> Debug: XMPP chat message not relayed', debug=True) + self.error(1, 'XMPP chat message not relayed', debug=True) return # message does not come from a room if xmpp_c.nickname == self.nickname: - self.error('==> Debug: Received XMPP chat message.', debug=True) - self.error(message.__str__(fancy=1), debug=True) + self.error(2, 'Received XMPP chat message.\n'+message.__str__(fancy=1), debug=True) # Find out if the message comes from a bot admin bot_admin = False - for jid in self.admins_jid: - if xmpp.protocol.JID(jid).bareMatch(message.getFrom()): + for admin in self.admins: + if xmpp.protocol.JID(admin.jid).bareMatch(message.getFrom()): bot_admin = True break @@ -356,12 +352,11 @@ class Bot(threading.Thread): r = self.respond(str(message.getBody()), bot_admin=bot_admin) if isinstance(r, basestring) and len(r) > 0: s = xmpp.protocol.Message(to=message.getFrom(), body=r, typ='chat') - self.error('==> Debug: Sending', debug=True) - self.error(s.__str__(fancy=1), debug=True) + self.error(2, 'Sending\n'+s.__str__(fancy=1), debug=True) xmpp_c.send(s) else: - self.error('=> Debug: Ignoring XMPP chat message not received on bot connection.', debug=True) + self.error(1, 'Ignoring XMPP chat message not received on bot connection.', debug=True) elif message.getType() == 'groupchat': # message comes from a room @@ -372,14 +367,14 @@ class Bot(threading.Thread): return if xmpp_c.nickname != self.nickname: - self.error('=> Debug: Ignoring XMPP MUC message not received on bot connection.', debug=True) + self.error(1, 'Ignoring XMPP MUC message not received on bot connection.', debug=True) return from_ = xmpp.protocol.JID(message.getFrom()) if unicode(from_.getResource()) == self.nickname: - self.error('=> Debug: Ignoring XMPP MUC message sent by self.', debug=True) + self.error(1, 'Ignoring XMPP MUC message sent by self.', debug=True) return room_jid = unicode(from_.getNode()+'@'+from_.getDomain()) @@ -388,18 +383,17 @@ class Bot(threading.Thread): resource = unicode(from_.getResource()) if resource == '': # message comes from the room itself - self.error('=> Debug: Ignoring XMPP groupchat message sent by the room.', debug=True) + self.error(1, 'Ignoring XMPP groupchat message sent by the room.', debug=True) return else: # message comes from a participant of the room - self.error('==> Debug: Received XMPP groupchat message.', debug=True) - self.error(message.__str__(fancy=1), debug=True) + self.error(2, 'Received XMPP groupchat message.\n'+message.__str__(fancy=1), debug=True) try: participant = bridge.getParticipant(resource) except Bridge.NoSuchParticipantException: if resource != self.nickname: - self.error('=> Debug: NoSuchParticipantException "'+resource+'" on "'+str(bridge)+'", WTF ?', debug=True) + self.error(1, 'NoSuchParticipantException "'+resource+'" on "'+str(bridge)+'", WTF ?', debug=True) return participant.sayOnIRC(message.getBody()) @@ -422,19 +416,16 @@ class Bot(threading.Thread): elif err == 'forbidden': # we don't have the permission to speak # let's remove the bridge and tell admins - self.error('[Error] Not allowed to speak on the XMPP MUC of bridge '+str(b)+', stopping it', send_to_admins=True) + self.error(say_levels.error, 'Not allowed to speak on the XMPP MUC of bridge '+str(b)+', stopping it', send_to_admins=True) b.stop(message='Not allowed to speak on the XMPP MUC, stopping bridge.') else: - self.error('==> Debug: recevied unknown error message', debug=True) - self.error(message.__str__(fancy=1), debug=True) + self.error(2, 'recevied unknown error message\n'+message.__str__(fancy=1), debug=True) return - self.error('==> Debug: recevied unknown error message', debug=True) - self.error(message.__str__(fancy=1), debug=True) + self.error(2, 'recevied unknown error message\n'+message.__str__(fancy=1), debug=True) else: - self.error('==> Debug: Received XMPP message of unknown type "'+str(message.getType())+'".', debug=True) - self.error(message.__str__(fancy=1), debug=True) + self.error(2, 'Received XMPP message of unknown type "'+str(message.getType())+'".\n'+message.__str__(fancy=1), debug=True) def _irc_event_handler(self, connection, event): @@ -450,7 +441,7 @@ class Bot(threading.Thread): if 'all' in event.eventtype() or 'motd' in event.eventtype() or event.eventtype() in ['nicknameinuse', 'nickcollision', 'erroneusnickname']: return if event.eventtype() in ['pong', 'privnotice', 'ctcp', 'nochanmodes', 'notexttosend', 'currenttopic', 'topicinfo', '328']: - self.error('=> Debug: ignoring IRC '+event.eventtype(), debug=True) + self.error(1, 'ignoring IRC '+event.eventtype(), debug=True) return @@ -467,15 +458,15 @@ class Bot(threading.Thread): connection.really_connected = True connection._call_nick_callbacks(None) elif len(connection.nick_callbacks) > 0: - self.error('===> Debug: event target ('+event.target()+') and connection nickname ('+connection.nickname+') don\'t match') + self.error(3, 'event target ('+event.target()+') and connection nickname ('+connection.nickname+') don\'t match') connection._call_nick_callbacks('nicknametoolong', arguments=[len(event.target())]) - self.error('=> Debug: ignoring '+event.eventtype(), debug=True) + self.error(1, 'ignoring '+event.eventtype(), debug=True) return # A string representation of the event event_str = 'connection='+connection.__str__()+'\neventtype='+event.eventtype()+'\nsource='+repr(event.source())+'\ntarget='+repr(event.target())+'\narguments='+repr(event.arguments()) - debug_str = '==> Debug: Received IRC event.\n'+event_str + debug_str = 'Received IRC event.\n'+event_str printed_event = False @@ -490,22 +481,22 @@ class Bot(threading.Thread): if event.eventtype() in ['quit', 'part', 'nick', 'kick']: if connection.get_nickname() != self.nickname: - self.error('=> Debug: ignoring IRC '+event.eventtype()+' not received on bot connection', debug=True) + self.error(1, 'ignoring IRC '+event.eventtype()+' not received on bot connection', debug=True) return else: - self.error(debug_str, debug=True) + self.error(2, debug_str, debug=True) printed_event = True if event.eventtype() == 'kick' and len(event.arguments()) < 1: - self.error('=> Debug: length of arguments should be greater than 0 for a '+event.eventtype()+' event') + self.error(1, 'length of arguments should be greater than 0 for a '+event.eventtype()+' event') return if event.eventtype() in ['pubmsg', 'action']: if connection.get_nickname() != self.nickname: - self.error('=> Debug: ignoring IRC '+event.eventtype()+' not received on bot connection', debug=True) + self.error(1, 'ignoring IRC '+event.eventtype()+' not received on bot connection', debug=True) return if nickname == self.nickname: - self.error('=> Debug: ignoring IRC '+event.eventtype()+' sent by self', debug=True) + self.error(1, 'ignoring IRC '+event.eventtype()+' sent by self', debug=True) return # TODO: lock self.bridges for thread safety @@ -527,14 +518,14 @@ class Bot(threading.Thread): try: to_ = bridge.getParticipant(event.target().split('!')[0]) - self.error(debug_str, debug=True) + self.error(2, debug_str, debug=True) from_.sayOnXMPPTo(to_.nickname, event.arguments()[0]) return except Bridge.NoSuchParticipantException: if event.target().split('!')[0] == self.nickname: # Message is for the bot - self.error(debug_str, debug=True) + self.error(2, debug_str, debug=True) connection.privmsg(from_.nickname, self.respond(event.arguments()[0])) return else: @@ -555,7 +546,7 @@ class Bot(threading.Thread): bridge.removeParticipant('irc', kicked.nickname, 'Kicked by '+nickname+' (no reason was given)') return except Bridge.NoSuchParticipantException: - self.error('=> Debug: a participant that was not here has been kicked ? WTF ?') + self.error(1, 'a participant that was not here has been kicked ? WTF ?') return else: continue @@ -588,7 +579,7 @@ class Bot(threading.Thread): # Chan message if event.eventtype() in ['pubmsg', 'action']: if bridge.irc_room == event.target().lower() and bridge.irc_server == connection.server: - self.error(debug_str, debug=True) + self.error(2, debug_str, debug=True) message = event.arguments()[0] if event.eventtype() == 'action': message = '/me '+message @@ -604,7 +595,7 @@ class Bot(threading.Thread): # Handle bannedfromchan if event.eventtype() == 'bannedfromchan': if len(event.arguments()) < 1: - self.error('=> Debug: length of arguments should be greater than 0 for a '+event.eventtype()+' event') + self.error(1, 'length of arguments should be greater than 0 for a '+event.eventtype()+' event') return for bridge in self.bridges: @@ -612,19 +603,19 @@ class Bot(threading.Thread): continue if event.target() == self.nickname: - self.error('[Error] the nickname "'+event.target()+'" is banned from the IRC chan of bridge "'+str(bridge)+'"') + self.error(say_levels.error, 'the nickname "'+event.target()+'" is banned from the IRC chan of bridge "'+str(bridge)+'"') raise Exception('[Error] the nickname "'+event.target()+'" is banned from the IRC chan of bridge "'+str(bridge)+'"') else: try: banned = bridge.getParticipant(event.target()) if banned.irc_connection != 'bannedfromchan': banned.irc_connection = 'bannedfromchan' - self.error(debug_str, debug=True) - bridge.say('[Warning] the nickname "'+event.target()+'" is banned from the IRC chan', log=True) + self.error(2, debug_str, debug=True) + bridge.say(say_levels.warning, 'the nickname "'+event.target()+'" is banned from the IRC chan', log=True) else: - self.error('=> Debug: ignoring '+event.eventtype(), debug=True) + self.error(1, 'ignoring '+event.eventtype(), debug=True) except Bridge.NoSuchParticipantException: - self.error('=> Debug: no such participant. WTF ?') + self.error(1, 'no such participant. WTF ?') return return @@ -633,7 +624,7 @@ class Bot(threading.Thread): # Joining events if event.eventtype() in ['namreply', 'join']: if connection.get_nickname() != self.nickname: - self.error('=> Debug: ignoring IRC '+event.eventtype()+' not received on bridge connection', debug=True) + self.error(1, 'ignoring IRC '+event.eventtype()+' not received on bridge connection', debug=True) return if event.eventtype() == 'namreply': @@ -646,8 +637,8 @@ class Bot(threading.Thread): elif event.eventtype() == 'join': bridges = self.getBridges(irc_room=event.target().lower(), irc_server=connection.server) if len(bridges) == 0: - self.error(debug_str, debug=True) - self.error('===> Debug: no bridge found for "'+event.target().lower()+' at '+connection.server+'"', debug=True) + self.error(2, debug_str, debug=True) + self.error(3, 'no bridge found for "'+event.target().lower()+' at '+connection.server+'"', debug=True) return for bridge in bridges: bridge.addParticipant('irc', nickname, irc_id=event.source()) @@ -656,14 +647,14 @@ class Bot(threading.Thread): if event.eventtype() in ['disconnect', 'kill', 'error']: if len(event.arguments()) > 0 and event.arguments()[0] == 'Connection reset by peer': - self.error(debug_str, debug=True) + self.error(2, debug_str, debug=True) else: - self.error(debug_str, send_to_admins=True) + self.error(2, debug_str, send_to_admins=True) return if event.eventtype() in ['cannotsendtochan', 'notonchannel']: - self.error(debug_str, debug=True) + self.error(2, debug_str, debug=True) bridges = self.getBridges(irc_room=event.arguments()[0], irc_server=connection.server) if len(bridges) > 1: raise Exception, 'more than one bridge for one irc chan, WTF ?' @@ -679,17 +670,22 @@ class Bot(threading.Thread): # Unhandled events if not printed_event: - self.error('[Debug] The following IRC event was not handled:\n'+event_str+'\n', send_to_admins=True) + self.error(say_levels.debug, 'The following IRC event was not handled:\n'+event_str+'\n', send_to_admins=True) else: - self.error('=> Debug: event not handled', debug=True) + self.error(1, 'event not handled', debug=True) self._send_message_to_admins('[Debug] The following IRC event was not handled:\n'+event_str) - def _send_message_to_admins(self, message): + def _send_message_to_admins(self, importance, message): """[Internal] Send XMPP Message to bot admin(s)""" - for admin_jid in self.admins_jid: + for admin in self.admins: + if importance != -1: + if admin.say_level == say_levels.nothing or importance < admin.say_level: + continue + message = self.format_message(importance, message) + try: - self.xmpp_c.send(xmpp.protocol.Message(to=admin_jid, body=message, typ='chat')) + self.xmpp_c.send(xmpp.protocol.Message(to=admin.jid, body=message, typ='chat')) except: pass @@ -712,6 +708,12 @@ class Bot(threading.Thread): return bridges + def format_message(self, importance, message): + if importance < 0 or importance >= len(say_levels.levels): + raise Exception('[Internal Error] unknown message importance') + return'['+str(say_levels.get(importance))+'] '+message + + def getBridges(self, irc_room=None, irc_server=None, xmpp_room_jid=None): # TODO: lock self.bridges for thread safety bridges = [b for b in self.bridges] @@ -732,9 +734,9 @@ class Bot(threading.Thread): if self.xmpp_connections.has_key(nickname): c = self.xmpp_connections[nickname] c.used_by += 1 - self.error('===> Debug: using existing XMPP connection for "'+nickname+'", now used by '+str(c.used_by)+' bridges', debug=True) + self.error(3, 'using existing XMPP connection for "'+nickname+'", now used by '+str(c.used_by)+' bridges', debug=True) return c - self.error('===> Debug: opening new XMPP connection for "'+nickname+'"', debug=True) + self.error(3, 'opening new XMPP connection for "'+nickname+'"', debug=True) c = xmpp.client.Client(self.bare_jid.getDomain(), debug=[]) c.lock = threading.RLock() c.lock.acquire() @@ -770,7 +772,7 @@ class Bot(threading.Thread): if p.xmpp_c == c: participants.append(p) p.xmpp_c = None - self.error('===> Debug: reopening XMPP connection for "'+nickname+'"', debug=True) + self.error(3, 'reopening XMPP connection for "'+nickname+'"', debug=True) if self.xmpp_connections.has_key(nickname): self.xmpp_connections.pop(nickname) c.send(xmpp.protocol.Presence(typ='unavailable')) @@ -794,14 +796,14 @@ class Bot(threading.Thread): c.lock.acquire() c.used_by -= 1 if c.used_by < 1 or force: - self.error('===> Debug: closing XMPP connection for "'+nickname+'"', debug=True) + self.error(3, 'closing XMPP connection for "'+nickname+'"', debug=True) self.xmpp_connections.pop(nickname) c.send(xmpp.protocol.Presence(typ='unavailable')) c.lock.release() del c else: c.lock.release() - self.error('===> Debug: XMPP connection for "'+nickname+'" is now used by '+str(c.used_by)+' bridges', debug=True) + self.error(3, 'XMPP connection for "'+nickname+'" is now used by '+str(c.used_by)+' bridges', debug=True) def removeBridge(self, bridge, message='Removing bridge'): @@ -832,14 +834,14 @@ class Bot(threading.Thread): for b in self.bridges: b.init2() - self.error('Bot restarted.', send_to_admins=True) + self.error(-1, 'Bot restarted.', send_to_admins=True) - def restart_bridges_delayed(self, bridges, delay, error_message, protocol='xmpp'): + def restart_bridges_delayed(self, bridges, delay, error, protocol='xmpp'): if len(bridges) > 0: - error_message += '\nThese bridges will be stopped:' + error[1] += '\nThese bridges will be stopped:' for b in bridges: - error_message += '\n'+str(b) + error[1] += '\n'+str(b) if protocol == 'xmpp': leave_message = 'Could not connect to the MUC server ('+b.xmpp_room_jid+')' @@ -853,7 +855,7 @@ class Bot(threading.Thread): b.stop(message=leave_message) - self.error(error_message, send_to_admins=True) + self.error(error[0], error[1], send_to_admins=True) def stop(self, message='Stopping bot'):