# HG changeset patch # User Charly COSTE # Date 1250722854 -7200 # Node ID 32a35f7eff70a7d48705759b88cd53f08291b068 # Parent 0c4a7452d66cc687f2c2f65ff8bfb55d4014bc8f Rewrote/modified many things, multiple bridges should now work and are preferred over multiple bots. Signed-off-by: Charly COSTE diff --git a/README b/README --- a/README +++ b/README @@ -41,6 +41,18 @@ To start xib bots just execute "start_bo Copy "example_config.xml" and modify it to fit your needs. +> The different modes of the xib bots: + +Mode is a per-bridge attribute, it can take three values: +- 'normal': +The bot connects on IRC on behalf of XMPP users and connects on XMPP on behalf of IRC users. +In this mode the bot automatically switches from normal to limited mode and back again when the IRC server clones limit is crossed. +- 'limited': +The bot only connects on XMPP on behalf of IRC users but NOT on IRC on behalf of XMPP. +- 'minimal': +The bot does not connect on behalf of the participants. + + > How to log xib bots output: xib does not directly handle logging for now so you have to do it the old school way, for example: diff --git a/bot.py b/bot.py --- a/bot.py +++ b/bot.py @@ -36,27 +36,24 @@ class bot(Thread): def __init__(self, jid, password, nickname, error_fd=sys.stderr, debug=False): Thread.__init__(self) self.commands = ['!xmpp_participants'] + self.bare_jid = xmpp.protocol.JID(jid=jid) + self.bare_jid.setResource('') self.jid = xmpp.protocol.JID(jid=jid) self.nickname = nickname + self.jid.setResource(self.nickname) self.password = password self.error_fd = error_fd self.debug = debug self.bridges = [] + self.xmpp_connections = {} self.irc = irclib.IRC() + self.irc.bot = self self.irc.add_global_handler('all_events', self._irc_event_handler) self.irc_thread = Thread(target=self.irc.process_forever) self.irc_thread.start() # Open connection with XMPP server try: - self.xmpp_c = xmpp.client.Client(self.jid.getDomain(), debug=[]) - self.xmpp_c.connect() - if self.jid.getResource() == '': - self.jid.setResource('xib-bot') - self.xmpp_c.auth(self.jid.getNode(), password, resource=self.jid.getResource()) - self.xmpp_c.RegisterHandler('presence', self._xmpp_presence_handler) - self.xmpp_c.RegisterHandler('iq', self._xmpp_iq_handler) - self.xmpp_c.RegisterHandler('message', self._xmpp_message_handler) - self.xmpp_c.sendInitPresence() + self.xmpp_c = self.get_xmpp_connection(self.jid.getResource()) except: self.error('Error: XMPP Connection failed') raise @@ -65,6 +62,7 @@ class bot(Thread): def error(self, s, debug=False): + """Output an error message.""" if not debug or debug and self.debug: try: self.error_fd.write(auto_encode(s)+"\n") @@ -73,55 +71,70 @@ class bot(Thread): def _xmpp_loop(self): + """[Internal] XMPP infinite loop.""" while True: self.xmpp_c.Process(5) + try: + for c in self.xmpp_connections.itervalues(): + if hasattr(c, 'Process'): + c.Process(5) + else: + sleep(1) + except RuntimeError: + pass def _xmpp_presence_handler(self, xmpp_c, presence): """[Internal] Manage XMPP presence.""" + + if presence.getTo() != self.jid: + self.error('=> Debug: 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) - if presence.getTo() != self.jid: - #self.error('=> Debug: Skipping XMPP presence not received on bot connection.', debug=True) - return from_ = xmpp.protocol.JID(presence.getFrom()) bare_jid = unicode(from_.getNode()+'@'+from_.getDomain()) for bridge in self.bridges: if bare_jid == bridge.xmpp_room.room_jid: # presence comes from a muc resource = unicode(from_.getResource()) + if resource == '': # presence comes from the muc itself # TODO: handle room deletion and muc server reboot pass + else: # presence comes from a participant of the muc try: p = bridge.getParticipant(resource) - if p.protocol in ['xmpp', 'both']: - if presence.getType() == 'unavailable': - x = presence.getTag('x', namespace='http://jabber.org/protocol/muc#user') - if x and x.getTag('status', attrs={'code': '303'}): - # participant changed its nickname - item = x.getTag('item') - if not item: - self.error('Debug: 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) - return - p.changeNickname(new_nick, 'irc') - return - # participant left - bridge.removeParticipant('xmpp', resource, presence.getStatus()) + except NoSuchParticipantException: - if presence.getType() != 'unavailable': - try: - bridge.addParticipant('xmpp', resource) - except Exception: - pass + if presence.getType() != 'unavailable' and resource != bridge.bot.nickname: + bridge.addParticipant('xmpp', resource) + return + + + if p.protocol == 'xmpp' and presence.getType() == 'unavailable': + x = presence.getTag('x', namespace='http://jabber.org/protocol/muc#user') + if x and x.getTag('status', attrs={'code': '303'}): + # participant changed its nickname + item = x.getTag('item') + if not item: + self.error('Debug: 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) + return + p.changeNickname(new_nick, 'irc') + + else: + # participant left + bridge.removeParticipant('xmpp', resource, presence.getStatus()) + return @@ -134,37 +147,42 @@ class bot(Thread): def _xmpp_message_handler(self, xmpp_c, message): """[Internal] Manage XMPP messages.""" if message.getType() == 'chat': - self.error('==> Debug: Received XMPP message.', debug=True) + self.error('==> Debug: Received XMPP chat message.', debug=True) self.error(message.__str__(fancy=1), debug=True) from_bare_jid = unicode(message.getFrom().getNode()+'@'+message.getFrom().getDomain()) for bridge in self.bridges: if from_bare_jid == bridge.xmpp_room.room_jid: # message comes from a room participant + try: from_ = bridge.getParticipant(message.getFrom().getResource()) to_ = bridge.getParticipant(message.getTo().getResource()) + + if from_.protocol == 'xmpp': + from_.sayOnIRCTo(to_.nickname, message.getBody()) + else: + self.error('==> Debug: received XMPP chat message from a non-XMPP participant, WTF ?', debug=True) + except NoSuchParticipantException: if message.getTo() == self.jid: xmpp_c.send(xmpp.protocol.Message(to=message.getFrom(), body=self.respond(message.getBody(), from_), typ='chat')) return self.error('==> Debug: XMPP chat message not relayed, from_bare_jid='+from_bare_jid+' to='+str(message.getTo().getResource())+' from='+message.getFrom().getResource(), debug=True) return - if from_.protocol in ['xmpp', 'both']: - from_.sayOnIRCTo(to_.nickname, message.getBody()) - else: - self.error('==> Debug: received XMPP chat message from a non-XMPP participant, WTF ?', debug=True) elif message.getType() == 'groupchat': # message comes from a room - if message.getTo() != self.jid: - self.error('=> Debug: Skipping XMPP MUC message not received on bot connection.', debug=True) - return + for child in message.getChildren(): if child.getName() == 'delay': - self.error('=> Debug: Skipping XMPP MUC delayed message.', debug=True) + # MUC delayed message return - self.error('==> Debug: Received XMPP message.', debug=True) - self.error(message.__str__(fancy=1), debug=True) + + if message.getTo() != self.jid: + self.error('=> Debug: Ignoring XMPP MUC message not received on bot connection.', debug=True) + return + + from_ = xmpp.protocol.JID(message.getFrom()) room_jid = unicode(from_.getNode()+'@'+from_.getDomain()) for bridge in self.bridges: @@ -172,149 +190,251 @@ class bot(Thread): resource = unicode(from_.getResource()) if resource == '': # message comes from the room itself - pass + self.error('=> Debug: 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) + try: participant_ = bridge.getParticipant(resource) except NoSuchParticipantException: + if resource != self.nickname: + self.error('=> Debug: NoSuchParticipantException "'+resource+'", WTF ?', debug=True) return + if participant_.protocol == 'xmpp': participant_.sayOnIRC(message.getBody()) - elif participant_.protocol == 'both': - bridge.irc_connection.privmsg(bridge.irc_room, '<'+participant_.nickname+'> '+message.getBody()) + else: - self.error('==> Debug: Received XMPP message.', debug=True) + self.error('==> Debug: Received XMPP message of unknown type "'+message.getType()+'".', debug=True) self.error(message.__str__(fancy=1), debug=True) def _irc_event_handler(self, connection, event): - """[internal] Manage IRC events""" - if not connection.bridge in self.bridges: - # Not for us, ignore - return - if 'all' in event.eventtype(): - return - if 'motd' in event.eventtype(): - self.error('=> Debug: ignoring event containing "motd" in the eventtype ('+event.eventtype()+')', debug=True) - return - if event.eventtype() in ['pong', 'privnotice']: - self.error('=> Debug: ignoring '+event.eventtype(), debug=True) - return - if event.eventtype() == 'pubmsg' and connection.get_nickname() != connection.bridge.irc_connection.get_nickname(): - self.error('=> Debug: ignoring IRC pubmsg not received on bridge connection', debug=True) - return + """[Internal] Manage IRC events""" + + # Answer ping if event.eventtype() == 'ping': connection.pong(connection.get_server_name()) return + + + # Events we always want to ignore + if 'all' in event.eventtype() or 'motd' in event.eventtype(): + return + if event.eventtype() in ['pong', 'privnotice', 'ctcp', 'nochanmodes']: + self.error('=> Debug: ignoring '+event.eventtype(), debug=True) + return + + + nickname = None + if '!' in event.source(): + nickname = event.source().split('!')[0] + + + # Events that we want to ignore only in some cases if event.eventtype() in ['umode', 'welcome', 'yourhost', 'created', 'myinfo', 'featurelist', 'luserclient', 'luserop', 'luserchannels', 'luserme', 'n_local', 'n_global', 'endofnames', 'luserunknown', 'luserconns']: if connection.really_connected == False: - connection.really_connected = True - self.error('===> Debug: now really connected', debug=True) - if connection.nick_callback: - connection.nick_callback(None) - else: - self.error('=> Debug: no nick callback for "'+str(event.target())+'"', debug=True) - self.error('connection.nick_callback='+str(connection.nick_callback), debug=True) + if event.target() == connection.nickname: + 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') + connection._call_nick_callbacks('nicknametoolong', arguments=[len(event.target())]) self.error('=> Debug: ignoring '+event.eventtype(), debug=True) return - self.error('==> Debug: Received IRC event.', debug=True) - self.error('server='+connection.get_server_name(), debug=True) - self.error('eventtype='+event.eventtype(), debug=True) - self.error('source='+str(event.source()), debug=True) - self.error('target='+str(event.target()), debug=True) - self.error('arguments='+str(event.arguments()), debug=True) + + + # A string representation of the event + event_str = '==> Debug: Received IRC event.\nconnection='+str(connection)+'\neventtype='+event.eventtype()+'\nsource='+str(event.source())+'\ntarget='+str(event.target())+'\narguments='+str(event.arguments()) + + + if event.eventtype() in ['pubmsg', 'privmsg', 'quit', 'part', 'nick']: + if nickname == None: + return + + # TODO: lock self.bridges for thread safety + for bridge in self.bridges: + try: + from_ = bridge.getParticipant(nickname) + + except NoSuchParticipantException: + self.error('===> Debug: NoSuchParticipantException "'+nickname+'" in bridge "'+str(bridge)+'"', debug=True) + continue + + + # Private message + if event.eventtype() == 'privmsg': + if event.target() == None: + return + + try: + to_ = bridge.getParticipant(event.target().split('!')[0]) + self.error(event_str, debug=True) + from_.sayOnXMPPTo(to_.nickname, event.arguments()[0]) + return + + except NoSuchParticipantException: + if event.target().split('!')[0] == self.nickname: + # Message is for the bot + self.error(event_str, debug=True) + connection.privmsg(from_.nickname, self.respond(event.arguments()[0], from_)) + return + else: + continue + + + # From here we skip if the event was not received on bot connection + if connection.get_nickname() != self.nickname: + self.error('=> Debug: ignoring IRC '+event.eventtype()+' not received on bridge connection', debug=True) + continue + + self.error(event_str, debug=True) + + + # Leaving events + if event.eventtype() == 'quit' or event.eventtype() == 'part' and event.target() == bridge.irc_room: + if from_.protocol == 'irc': + bridge.removeParticipant('irc', from_.nickname, event.arguments()[0]) + continue + + + # Nickname change + if event.eventtype() == 'nick' and from_.protocol == 'irc': + from_.changeNickname(event.target(), 'xmpp') + continue + + + # Chan message + if event.eventtype() == 'pubmsg': + if bridge.irc_room == event.target() and bridge.irc_server == connection.server: + if from_.protocol != 'xmpp': + from_.sayOnXMPP(event.arguments()[0]) + return + else: + continue + + return + + + 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) + return + + if event.eventtype() == 'namreply': + # TODO: lock self.bridges for thread safety + for bridge in self.getBridges(irc_room=event.arguments()[1], irc_server=connection.server): + for nickname in re.split('(?:^[@\+]?|(?: [@\+]?)*)', event.arguments()[2].strip()): + if nickname == '' or nickname == self.nickname: + continue + bridge.addParticipant('irc', nickname) + return + elif event.eventtype() == 'join': + bridges = self.getBridges(irc_room=event.target(), irc_server=connection.server) + if len(bridges) == 0: + self.error('===> Debug: no bridge found for "'+event.target()+' at '+connection.server+'"', debug=True) + return + for bridge in bridges: + bridge.addParticipant('irc', nickname) + return + + + # From here the event is shown + self.error(event_str, debug=True) + if event.eventtype() == 'disconnect': - if connection.get_nickname() == connection.bridge.irc_connection.get_nickname(): - # Lost bridge IRC connection, we must reconnect if we want the bridge to work - self.recreate_bridge(connection.bridge) - return - if connection.bridge.mode == 'normal' and connection.closing == False: - connection.bridge.switchToLimitedMode() - if connection.closing == True: - connection.close() + # TODO: lock self.bridges for thread safety + for bridge in self.bridges: + try: + bridge.getParticipant(connection.get_nickname()) + if bridge.mode == 'normal': + bridge.switchFromNormalToLimitedMode() + except NoSuchParticipantException: + pass return elif event.eventtype() == 'nicknameinuse': - if connection.nick_callback: - connection.nick_callback('nicknameinuse') - else: - self.error('=> Debug: no nick callback for "'+str(event.target())+'"', debug=True) + connection._call_nick_callbacks('nicknameinuse') return elif event.eventtype() == 'erroneusnickname': - if connection.nick_callback: - connection.nick_callback('erroneusnickname') - else: - self.error('=> Debug: no nick callback for "'+str(event.target())+'"', debug=True) - return - elif event.eventtype() == 'namreply': - for nickname in re.split('(?:^[@\+]?|(?: [@\+]?)*)', event.arguments()[2].strip()): - if nickname == '': - continue - try: - connection.bridge.addParticipant('irc', nickname) - except: - pass - return - elif event.eventtype() == 'join': - nickname = event.source().split('!')[0] - if nickname == self.nickname: - pass - else: - try: - connection.bridge.getParticipant(nickname) - except NoSuchParticipantException: - connection.bridge.addParticipant('irc', nickname) + connection._call_nick_callbacks('erroneusnickname') return - try: - if event.source() == None or not '!' in event.source(): - return - from_ = connection.bridge.getParticipant(event.source().split('!')[0]) - if event.eventtype() == 'quit' or event.eventtype() == 'part' and event.target() == connection.bridge.irc_room: - if from_.protocol in ['irc', 'both']: - connection.bridge.removeParticipant('irc', from_.nickname, event.arguments()[0]) - return - if event.eventtype() == 'nick' and from_.protocol in ['irc', 'both']: - from_.changeNickname(event.target(), 'xmpp') - except NoSuchParticipantException: - self.error('===> Debug: NoSuchParticipantException "'+event.source().split('!')[0]+'"', debug=True) - return - if event.eventtype() == 'pubmsg': - if from_.protocol == 'irc' or from_.protocol == 'both': - from_.sayOnXMPP(event.arguments()[0]) - elif event.eventtype() == 'privmsg': - if event.target() == None: - return - try: - to_ = connection.bridge.getParticipant(event.target().split('!')[0]) - except NoSuchParticipantException: - if event.target().split('!')[0] == self.nickname: - connection.privmsg(from_.nickname, self.respond(event.arguments()[0], from_)) - return - if to_.protocol == 'xmpp': - from_.sayOnXMPPTo(to_.nickname, event.arguments()[0]) + + + # Unhandled events + self.error('=> Debug: event not handled', debug=True) - def new_bridge(self, xmpp_room, irc_room, irc_server, irc_port=6667): + def new_bridge(self, xmpp_room, irc_room, irc_server, mode, say_participants_list, irc_port=6667): """Create a bridge between xmpp_room and irc_room at irc_server.""" - b = bridge(self, xmpp_room, irc_room, irc_server, irc_port=irc_port) + b = bridge(self, xmpp_room, irc_room, irc_server, mode, say_participants_list, irc_port=irc_port) self.bridges.append(b) return b - def recreate_bridge(self, bridge): - """Disconnect and reconnect.""" - self.new_bridge(bridge.xmpp_room.room_jid, bridge.irc_room, bridge.irc_server) + def getBridges(self, irc_room=None, irc_server=None, xmpp_room_jid=None): + bridges = [b for b in self.bridges] + if irc_room != None: + for bridge in bridges: + if bridge.irc_room != irc_room: + if bridge in bridges: + bridges.remove(bridge) + if irc_server != None: + for bridge in bridges: + if bridge.irc_server != irc_server: + if bridge in bridges: + bridges.remove(bridge) + if xmpp_room_jid != None: + for bridge in bridges: + if bridge.xmpp_room.room_jid != xmpp_room_jid: + if bridge in bridges: + bridges.remove(bridge) + return bridges + + + def get_xmpp_connection(self, resource): + if self.xmpp_connections.has_key(resource): + c = self.xmpp_connections[resource] + c.used_by += 1 + self.error('===> Debug: using existing XMPP connection for "'+str(self.bare_jid)+'/'+resource+'", now used by '+str(c.used_by)+' bridges', debug=True) + return c + self.error('===> Debug: opening new XMPP connection for "'+str(self.bare_jid)+'/'+resource+'"', debug=True) + c = xmpp.client.Client(self.jid.getDomain(), debug=[]) + self.xmpp_connections[resource] = c + c.used_by = 1 + c.connect() + c.auth(self.jid.getNode(), self.password, resource=resource) + c.RegisterHandler('presence', self._xmpp_presence_handler) + c.RegisterHandler('iq', self._xmpp_iq_handler) + c.RegisterHandler('message', self._xmpp_message_handler) + c.sendInitPresence() + return c + + + def close_xmpp_connection(self, resource): + self.xmpp_connections[resource].used_by -= 1 + if self.xmpp_connections[resource].used_by < 1: + self.error('===> Debug: closing XMPP connection for "'+str(self.bare_jid)+'/'+resource+'"', debug=True) + del self.xmpp_connections[resource] + else: + self.error('===> Debug: XMPP connection for "'+str(self.bare_jid)+'/'+resource+'" is now used by '+str(self.xmpp_connections[resource].used_by)+' bridges', debug=True) + + + def removeBridge(self, bridge): self.bridges.remove(bridge) del bridge def respond(self, message, participant): if message.strip() == '!xmpp_participants': - xmpp_participants_nicknames = participant.bridge.get_xmpp_participants_nicknames_list() + xmpp_participants_nicknames = participant.bridge.get_participants_nicknames_list(protocols=['xmpp']) return 'participants on '+participant.bridge.xmpp_room.room_jid+': '+' '.join(xmpp_participants_nicknames) else: return 'commands: '+' '.join(self.commands) + def __del__(self): - for bridge in bridges: - del bridge \ No newline at end of file + for bridge in self.bridges: + self.removeBridge(bridge) \ No newline at end of file diff --git a/bridge.py b/bridge.py --- a/bridge.py +++ b/bridge.py @@ -26,51 +26,69 @@ class NoSuchParticipantException(Excepti class bridge: - def __init__(self, owner_bot, xmpp_room_jid, irc_room, irc_server, irc_port=6667, mode='normal'): + def __init__(self, owner_bot, xmpp_room_jid, irc_room, irc_server, mode, say_participants_list, irc_port=6667): + """Create a new bridge.""" self.bot = owner_bot self.irc_server = irc_server self.irc_port = irc_port self.irc_room = irc_room + self.say_participants_list = say_participants_list self.participants = [] + if mode not in ['normal', 'limited', 'minimal']: + raise Exception('Error: "'+mode+'" is not a correct value for a bridge\'s "mode" attribute') self.mode = mode - # Join IRC room - self.irc_connections_limit = -1 - self.irc_connection = self.bot.irc.server() - self.irc_connection.nick_callback = self._irc_nick_callback - self.irc_connection.bridge = self - try: - self.irc_connection.connect(irc_server, irc_port, self.bot.nickname) - except: - self.bot.error('Error: joining IRC room failed') - raise - # Join XMPP room try: self.xmpp_room = xmpp.muc(xmpp_room_jid) - self.xmpp_room.join(self.bot.xmpp_c, self.bot.nickname) + self.xmpp_room.join(self.bot.xmpp_c, self.bot.nickname, callback=self._xmpp_join_callback) except: self.bot.error('Error: joining XMPP room failed') raise + + # Join IRC room + try: + self.irc_connection = self.bot.irc.server(irc_server, irc_port, self.bot.nickname) + self.irc_connection.connect(nick_callback=self._irc_nick_callback) + except: + self.bot.error('Error: joining IRC room failed') + raise + self.bot.error('[Notice] bridge "'+str(self)+'" is running in '+self.mode+' mode') + self.say('[Notice] bridge "'+str(self)+'" is running in '+self.mode+' mode') - def _irc_nick_callback(self, error): + def _irc_nick_callback(self, error, arguments=[]): if error == None: self.irc_connection.join(self.irc_room) - self.irc_connection.nick_callback = None self.bot.error('===> Debug: successfully connected on IRC side of bridge "'+str(self)+'"', debug=True) if error == 'nicknameinuse': - self.bot.error('Error: "'+self.bot.nickname+'" is already used in the IRC chan of bridge "'+str(self)+'"') - raise Exception('Error: "'+self.bot.nickname+'" is already used in the IRC chan of bridge "'+str(self)+'"') + self.bot.error('Error: "'+self.bot.nickname+'" is already used in the IRC chan or reserved on the IRC server of bridge "'+str(self)+'"') + raise Exception('Error: "'+self.bot.nickname+'" is already used in the IRC chan or reserved on the IRC server of bridge "'+str(self)+'"') elif error == 'erroneusnickname': self.bot.error('Error: "'+self.bot.nickname+'" got "erroneusnickname" on bridge "'+str(self)+'"') raise Exception('Error: "'+self.bot.nickname+'" got "erroneusnickname" on bridge "'+str(self)+'"') + elif error == 'nicknametoolong': + self.bot.error('Error: "'+self.bot.nickname+'" got "nicknametoolong" on bridge "'+str(self)+'", limit seems to be '+str(arguments[0])) + raise Exception('Error: "'+self.bot.nickname+'" got "nicknametoolong" on bridge "'+str(self)+'", limit seems to be '+str(arguments[0])) + + + def _xmpp_join_callback(self, errors): + """Called by muc._xmpp_presence_handler""" + if len(errors) == 0: + self.bot.error('===> Debug: succesfully connected on XMPP side of bridge "'+str(self)+'"', debug=True) + for error in errors: + try: + raise error + except xmpp.muc.NicknameConflict: + self.bot.error('Error: "'+self.nickname+'" is already used in the XMPP MUC or reserved on the XMPP server of bridge "'+str(self)+'"') + raise Exception('Error: "'+self.nickname+'" is already used in the XMPP MUC or reserved on the XMPP server of bridge "'+str(self)+'"') def addParticipant(self, protocol, nickname): """Add a participant to the bridge.""" if (protocol == 'irc' and nickname == self.irc_connection.get_nickname()) or (protocol == 'xmpp' and nickname == self.xmpp_room.nickname): - raise Exception('Internal Error: cannot add self') + self.bot.error('===> Debug: not adding self ('+self.bot.nickname+') to bridge "'+str(self)+'"', debug=True) + return try: p = self.getParticipant(nickname) if p.protocol != protocol: @@ -87,7 +105,7 @@ class bridge: p = participant(self, protocol, nickname) self.participants.append(p) if self.mode != 'normal' and protocol == 'xmpp': - xmpp_participants_nicknames = self.get_xmpp_participants_nicknames_list() + xmpp_participants_nicknames = self.get_participants_nicknames_list(protocols=['xmpp']) self.say('[Info] Participants on XMPP: '+' '.join(xmpp_participants_nicknames), on_xmpp=False) return p @@ -100,80 +118,106 @@ class bridge: raise NoSuchParticipantException('there is no participant using the nickname "'+nickname+'" in this bridge') - def get_xmpp_participants_nicknames_list(self): - xmpp_participants_nicknames = [] + def get_participants_nicknames_list(self, protocols=['irc', 'xmpp']): + """Returns a list of the nicknames of the bridge's participants that are connected on the XMPP side.""" + participants_nicknames = [] for p in self.participants: - if p.protocol == 'xmpp': - xmpp_participants_nicknames.append(p.nickname) - return xmpp_participants_nicknames + if p.protocol in protocols: + participants_nicknames.append(p.nickname) + return participants_nicknames - def removeParticipant(self, protocol, nickname, leave_message): + def removeParticipant(self, left_protocol, nickname, leave_message): """Remove the participant using nickname from the bridge. Raises a NoSuchParticipantException if nickname is not used in the bridge.""" + + was_on_both = None p = self.getParticipant(nickname) - if p.protocol == 'both': - self.bot.error('===> Debug: "'+nickname+'" was on both sides of bridge "'+str(self)+'" but left '+protocol, debug=True) - if protocol == 'xmpp': - p.protocol = 'irc' - p.createDuplicateOnXMPP() - elif protocol == 'irc': - p.protocol = 'xmpp' - p.createDuplicateOnIRC() - else: - raise Exception('Internal Error: bad protocol') + if p.protocol == 'xmpp': + if left_protocol == 'irc': + was_on_both = True + elif left_protocol == 'xmpp': + if p.irc_connection == None and self.mode == 'normal': + was_on_both = True + p.protocol = 'irc' + p.createDuplicateOnXMPP() + else: + was_on_both = False + + elif p.protocol == 'irc': + if left_protocol == 'xmpp': + was_on_both = True + elif left_protocol == 'irc': + if p.xmpp_c == None and self.mode != 'minimal': + was_on_both = True + p.protocol = 'xmpp' + p.createDuplicateOnIRC() + else: + was_on_both = False + else: + raise Exception('Internal Error: bad protocol') + + if was_on_both == True: + self.bot.error('===> Debug: "'+nickname+'" was on both sides of bridge "'+str(self)+'" but left '+left_protocol, debug=True) + + elif was_on_both == False: self.bot.error('===> Debug: removing participant "'+nickname+'" from bridge "'+str(self)+'"', debug=True) self.participants.remove(p) p.leave(leave_message) + del p i = 0 for p in self.participants: if p.protocol == 'xmpp': i += 1 - if protocol == 'xmpp' and self.irc_connections_limit != -1 and self.irc_connections_limit > i: - self.switchToNormalMode() - del p - if self.mode != 'normal': - xmpp_participants_nicknames = self.get_xmpp_participants_nicknames_list() - self.say('[Info] Participants on XMPP: '+' '.join(xmpp_participants_nicknames), on_xmpp=False) + if left_protocol == 'xmpp': + if self.irc_connections_limit != -1 and self.irc_connections_limit > i: + self.switchFromLimitedToNormalMode() + if self.mode != 'normal' and self.say_participants_list == True: + xmpp_participants_nicknames = self.get_participants_nicknames_list(protocols=['xmpp']) + self.say('[Info] Participants on XMPP: '+' '.join(xmpp_participants_nicknames), on_xmpp=False) + elif left_protocol == 'irc': + if self.mode == 'minimal' and self.say_participants_list == True: + irc_participants_nicknames = self.get_participants_nicknames_list(protocols=['irc']) + self.say('[Info] Participants on IRC: '+' '.join(irc_participants_nicknames), on_irc=False) + + else: + self.bot.error('=> Debug: Bad decision tree, p.protocol='+p.protocol+' left_protocol='+left_protocol+'\np.xmpp_c='+str(p.xmpp_c)+'\np.irc_connection='+str(p.irc_connection), debug=True) def say(self, message, on_irc=True, on_xmpp=True): + """Make the bot say something.""" if on_xmpp == True: self.xmpp_room.say(message) if on_irc == True: self.irc_connection.privmsg(self.irc_room, auto_encode(message)) - def switchToNormalMode(self): - if self.mode == 'normal': + def switchFromLimitedToNormalMode(self): + if self.mode != 'normal-limited': return - prev_mode = self.mode + self.bot.error('===> Bridge is switching to normal mode.') + self.say('[Notice] Bridge is switching to normal mode.') self.mode = 'normal' for p in self.participants: if p.protocol == 'xmpp': p.createDuplicateOnIRC() - elif p.protocol == 'irc' and prev_mode == 'minimal': - p.createDuplicateOnXMPP() - self.bot.error('===> Bridge is switching to normal mode.') - self.say('[Notice] Bridge is switching to normal mode.') - def switchToLimitedMode(self): - if self.mode == 'limited': + def switchFromNormalToLimitedMode(self): + if self.mode != 'normal': return - self.mode = 'limited' + self.mode = 'normal-limited' i = 0 for p in self.participants: if p.protocol == 'xmpp': i += 1 - if p.irc_connection: - p.irc_connection.closing = True - p.irc_connection.disconnect('Bridge is switching to limited mode') + if p.irc_connection != None: + p.irc_connection.close('Bridge is switching to limited mode') p.irc_connection = None self.irc_connections_limit = i self.bot.error('===> Bridge is switching to limited mode. Limit seems to be '+str(self.irc_connections_limit)+' on "'+self.irc_server+'".') self.say('[Warning] Bridge is switching to limited mode, it means that it will be transparent for XMPP users but not for IRC users, this is due to the IRC servers\' per-IP-address connections\' limit number which seems to be '+str(self.irc_connections_limit)+' on "'+self.irc_server+'".') - xmpp_participants_nicknames = self.get_xmpp_participants_nicknames_list() + xmpp_participants_nicknames = self.get_participants_nicknames_list(protocols=['xmpp']) self.say('[Info] Participants on XMPP: '+' '.join(xmpp_participants_nicknames), on_xmpp=False) @@ -186,10 +230,15 @@ class bridge: for p in self.participants: p.leave('Removing bridge') del p - # Leave IRC room - self.irc_connection.quit('Removing bridge') - # Close IRC connection - self.irc_connection.close() + del self.participants + + # Close IRC connection if not used by an other bridge, just leave the room otherwise + self.irc_connection.used_by -= 1 + if self.irc_connection.used_by < 1: + self.irc_connection.close('Removing bridge') + else: + self.irc_connection.part('Removing bridge') del self.irc_connection + # Leave XMPP room self.xmpp_room.leave('Removing bridge') \ No newline at end of file diff --git a/example_config.xml b/example_config.xml --- a/example_config.xml +++ b/example_config.xml @@ -2,11 +2,12 @@ - + - - \ No newline at end of file + + + diff --git a/irclib.py b/irclib.py --- a/irclib.py +++ b/irclib.py @@ -161,10 +161,19 @@ class IRC: self.add_global_handler("ping", _ping_ponger, -42) - def server(self): - """Creates and returns a ServerConnection object.""" + def server(self, server, port, nickname): + """Creates or returns an existing ServerConnection object for nickname at server:port. + + server -- Server name. + + port -- Port number. - c = ServerConnection(self) + nickname -- The nickname.""" + + for c in self.connections: + if c.server == server and c.port == port and c.real_nickname == nickname: + return c + c = ServerConnection(self, server, port, nickname) self.connections.append(c) return c @@ -372,25 +381,28 @@ class ServerConnection(Connection): method on an IRC object. """ - def __init__(self, irclibobj): + def __init__(self, irclibobj, server, port, nickname): Connection.__init__(self, irclibobj) - self.connected = 0 # Not connected yet. + self.connected = False # Not connected yet. self.really_connected = False + self.used_by = 1 self.socket = None self.ssl = None + self.server = server + self.port = port + self.nickname = nickname - def connect(self, server, port, nickname, password=None, username=None, - ircname=None, localaddress="", localport=0, ssl=False, ipv6=False): - """Connect/reconnect to a server. + + def __str__(self): + return self.real_nickname+' at '+self.server+':'+str(self.port) + + + def connect(self, password=None, username=None, + ircname=None, localaddress="", localport=0, ssl=False, ipv6=False, nick_callback=None): + """Connect to the server. Arguments: - server -- Server name. - - port -- Port number. - - nickname -- The nickname. - password -- Password (if any). username -- The username. @@ -409,23 +421,27 @@ class ServerConnection(Connection): Returns the ServerConnection object. """ - if self.connected: - self.disconnect("Changing servers") + if self.connected == True: + self.used_by += 1 + self.irclibobj.bot.error('===> Debug: using existing IRC connection for '+str(self)+', this connection is now used by '+str(self.used_by)+' bridges', debug=True) + self.nick(self.real_nickname, callback=nick_callback) + return - self.closing = False + + self.nick_callbacks = [] self.previous_buffer = "" self.handlers = {} self.real_server_name = "" - self.real_nickname = nickname - self.server = server - self.port = port - self.nickname = nickname - self.username = username or nickname - self.ircname = ircname or nickname + self.real_nickname = self.nickname + self.username = username or self.nickname + self.ircname = ircname or self.nickname self.password = password self.localaddress = localaddress self.localport = localport self.localhost = socket.gethostname() + + self.irclibobj.bot.error('===> Debug: opening new IRC connection for '+str(self), debug=True) + if ipv6: self.socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) else: @@ -439,25 +455,42 @@ class ServerConnection(Connection): self.socket.close() self.socket = None raise ServerConnectionError, "Couldn't connect to socket: %s" % x - self.connected = 1 + self.connected = True if self.irclibobj.fn_to_add_socket: self.irclibobj.fn_to_add_socket(self.socket) # Log on... if self.password: self.pass_(self.password) - self.nick(self.nickname) + self.nick(self.nickname, callback=nick_callback) self.user(self.username, self.ircname) return self - def close(self): + + def _call_nick_callbacks(self, error, arguments=[]): + i = 0 + for f in self.nick_callbacks: + i += 1 + f(error, arguments=arguments) + self.nick_callbacks = [] + if i == 0: + self.irclibobj.bot.error('=> Debug: no nick callback for "'+str(self)+'"', debug=True) + else: + self.irclibobj.bot.error('=> Debug: called '+str(i)+' callback(s) for "'+str(self)+'"', debug=True) + + + def add_nick_callback(self, callback): + self.nick_callbacks.append(callback) + + + def close(self, message): """Close the connection. This method closes the connection permanently; after it has been called, the object is unusable. """ - self.disconnect("Closing object") + self.disconnect(message) self.irclibobj._remove_connection(self) def _get_socket(self): @@ -656,7 +689,7 @@ class ServerConnection(Connection): if not self.connected: return - self.connected = 0 + self.connected = False self.quit(message) @@ -665,6 +698,7 @@ class ServerConnection(Connection): except socket.error, x: pass self.socket = None + self.irclibobj self._handle_event(Event("disconnect", self.server, "", [message])) def globops(self, text): @@ -730,8 +764,13 @@ class ServerConnection(Connection): """Send a NAMES command.""" self.send_raw("NAMES" + (channels and (" " + ",".join(channels)) or "")) - def nick(self, newnick): + def nick(self, newnick, callback=None): """Send a NICK command.""" + if callback != None: + self.add_nick_callback(callback) + if ' ' in newnick: + self._call_nick_callbacks('erroneusnickname') + return self.send_raw("NICK " + newnick) def notice(self, target, text): @@ -868,7 +907,7 @@ class DCCConnection(Connection): """ def __init__(self, irclibobj, dcctype): Connection.__init__(self, irclibobj) - self.connected = 0 + self.connected = False self.passive = 0 self.dcctype = dcctype self.peeraddress = None @@ -895,7 +934,7 @@ class DCCConnection(Connection): self.socket.connect((self.peeraddress, self.peerport)) except socket.error, x: raise DCCConnectionError, "Couldn't connect to socket: %s" % x - self.connected = 1 + self.connected = True if self.irclibobj.fn_to_add_socket: self.irclibobj.fn_to_add_socket(self.socket) return self @@ -932,7 +971,7 @@ class DCCConnection(Connection): if not self.connected: return - self.connected = 0 + self.connected = False try: self.socket.close() except socket.error, x: @@ -950,7 +989,7 @@ class DCCConnection(Connection): conn, (self.peeraddress, self.peerport) = self.socket.accept() self.socket.close() self.socket = conn - self.connected = 1 + self.connected = True if DEBUG: print "DCC connection from %s:%d" % ( self.peeraddress, self.peerport) diff --git a/muc.py b/muc.py --- a/muc.py +++ b/muc.py @@ -43,7 +43,7 @@ class muc: self.xmpp_c = xmpp_c self.callback = callback self.xmpp_c.RegisterHandler('presence', self._xmpp_presence_handler) - self.xmpp_c.send(xmpp.protocol.Presence(to=self.jid, status=status)) + self.xmpp_c.send(xmpp.protocol.Presence(to=self.jid, status=status, payload=[xmpp.simplexml.Node(tag='x', attrs={'xmlns': 'http://jabber.org/protocol/muc'}, payload=[xmpp.simplexml.Node(tag='history', attrs={'maxchars': '0'})])])) def _xmpp_presence_handler(self, xmpp_c, presence): @@ -98,12 +98,12 @@ class muc: self.xmpp_c.send(xmpp.protocol.Message(to=self.room_jid+'/'+to, typ='chat', body=message)) - def change_nick(self, nickname, callback=None): + def change_nick(self, nickname, status=None, callback=None): """Change nickname""" self.jid = self.room_jid+'/'+nickname self.callback = callback self.xmpp_c.RegisterHandler('presence', self._xmpp_presence_handler) - self.xmpp_c.send(xmpp.protocol.Presence(to=self.jid)) + self.xmpp_c.send(xmpp.protocol.Presence(to=self.jid, status=status)) def leave(self, message=''): diff --git a/participant.py b/participant.py --- a/participant.py +++ b/participant.py @@ -31,127 +31,100 @@ class participant: self.nickname = nickname self.irc_connection = None self.xmpp_c = None + self.muc = None if protocol == 'xmpp': self.createDuplicateOnIRC() elif protocol == 'irc': self.createDuplicateOnXMPP() else: raise Exception('Internal Error: bad protocol') - quit(1) def createDuplicateOnXMPP(self): - if self.xmpp_c != None or self.irc_connection != None or self.protocol == 'both' or self.bridge.mode == 'minimal': + if self.xmpp_c != None or self.irc_connection != None or self.bridge.mode == 'minimal': return - self.xmpp_c = xmpp.client.Client(self.bridge.bot.jid.getDomain(), debug=[]) - self.xmpp_c.connect() - self.xmpp_c.auth(self.bridge.bot.jid.getNode(), self.bridge.bot.password, resource=self.nickname) - self.xmpp_c.RegisterHandler('presence', self.bridge.bot._xmpp_presence_handler) - self.xmpp_c.RegisterHandler('iq', self.bridge.bot._xmpp_iq_handler) - self.xmpp_c.RegisterHandler('message', self.bridge.bot._xmpp_message_handler) - self.xmpp_thread = Thread(target=self._xmpp_loop) - self.xmpp_thread.start() - self.xmpp_c.sendInitPresence() + self.xmpp_c = self.bridge.bot.get_xmpp_connection(self.nickname) self.muc = xmpp.muc(self.bridge.xmpp_room.room_jid) self.muc.join(self.xmpp_c, self.nickname, status='From IRC', callback=self._xmpp_join_callback) - def createDuplicateOnIRC(self): - if self.irc_connection != None or self.xmpp_c != None or self.protocol == 'both' or self.bridge.mode != 'normal': - return - if ' ' in self.nickname: - self.bridge.bot.error('===> Debug: "'+self.nickname+'" contains a white space character, duplicate cannot be created on the IRC chan of bridge "'+str(self.bridge)+'"', debug=True) - self.bridge.say('[Warning] The nickname "'+self.nickname+'" contains a white space character, duplicate cannot be created on IRC, please avoid that if possible') - return - sleep(1) # try to prevent "reconnecting too fast" shit - self.irc_connection = self.bridge.bot.irc.server() - self.irc_connection.bridge = self.bridge - self.irc_connection.nick_callback = self._irc_nick_callback - self.irc_connection.connect(self.bridge.irc_server, self.bridge.irc_port, self.nickname) - - - def _irc_nick_callback(self, error): - if error == None: - self.irc_connection.join(self.bridge.irc_room) - self.irc_connection.nick_callback = None - self.bridge.bot.error('===> Debug: "'+self.nickname+'" duplicate succesfully created on IRC side of bridge "'+str(self.bridge)+'"', debug=True) - elif self.protocol != 'both': - if error == 'nicknameinuse': - self.bridge.bot.error('===> Debug: "'+self.nickname+'" is already used in the IRC chan of bridge "'+str(self.bridge)+'"', debug=True) - self.bridge.say('[Warning] The nickname "'+self.nickname+'" is used on both rooms or reserved on the IRC server, please avoid that if possible') - self.protocol = 'both' - self.irc_connection.closing = True - self.irc_connection.disconnect() - self.irc_connection = None - elif error == 'erroneusnickname': - self.bridge.bot.error('===> Debug: "'+self.nickname+'" got "erroneusnickname" on bridge "'+str(self.bridge)+'"', debug=True) - self.bridge.say('[Warning] The nickname "'+self.nickname+'" contains non-ASCII characters and cannot be used in the IRC channel, please avoid that if possible') - self.irc_connection.closing = True - self.irc_connection.disconnect() - self.irc_connection = None - - def _xmpp_join_callback(self, errors): if len(errors) == 0: self.bridge.bot.error('===> Debug: "'+self.nickname+'" duplicate succesfully created on XMPP side of bridge "'+str(self.bridge)+'"', debug=True) - elif self.protocol != 'both': + else: for error in errors: try: raise error except xmpp.muc.NicknameConflict: self.bridge.bot.error('===> Debug: "'+self.nickname+'" is already used in the XMPP MUC or reserved on the XMPP server of bridge "'+str(self.bridge)+'"', debug=True) self.bridge.say('[Warning] The nickname "'+self.nickname+'" is used on both rooms or reserved on the XMPP server, please avoid that if possible') - self.protocol = 'both' + self.bridge.bot.close_xmpp_connection(self.nickname) self.xmpp_c = None - def _xmpp_loop(self): - while True: - if self.xmpp_c != None: - self.xmpp_c.Process(5) - else: - sleep(5) + def createDuplicateOnIRC(self): + if self.irc_connection != None or self.xmpp_c != None or self.bridge.mode != 'normal': + return + sleep(1) # try to prevent "reconnecting too fast" shit + self.irc_connection = self.bridge.bot.irc.server(self.bridge.irc_server, self.bridge.irc_port, self.nickname) + self.irc_connection.connect(nick_callback=self._irc_nick_callback) + self.irc_connection.join(self.bridge.irc_room) + + + def _irc_nick_callback(self, error, arguments=[]): + if error == None: + self.irc_connection.join(self.bridge.irc_room) + self.bridge.bot.error('===> Debug: "'+self.nickname+'" duplicate succesfully created on IRC side of bridge "'+str(self.bridge)+'"', debug=True) + else: + if error == 'nicknameinuse': + self.bridge.bot.error('===> Debug: "'+self.nickname+'" is already used in the IRC chan of bridge "'+str(self.bridge)+'"', debug=True) + self.bridge.say('[Warning] The nickname "'+self.nickname+'" is used on both rooms or reserved on the IRC server, please avoid that if possible') + self.irc_connection.close('') + self.irc_connection = None + elif error == 'erroneusnickname': + self.bridge.bot.error('===> Debug: "'+self.nickname+'" got "erroneusnickname" on bridge "'+str(self.bridge)+'"', debug=True) + self.bridge.say('[Warning] The nickname "'+self.nickname+'" contains unauthorized characters and cannot be used in the IRC channel, please avoid that if possible') + self.irc_connection.close('') + self.irc_connection = None + elif error == 'nicknametoolong': + self.bridge.bot.error('===> Debug: "'+self.nickname+'" got "nicknametoolong" on bridge "'+str(self.bridge)+'"', debug=True) + self.bridge.say('[Warning] The nickname "'+self.nickname+'" is too long (limit seems to be '+str(arguments[0])+') and cannot be used in the IRC channel, please avoid that if possible') + self.irc_connection.close('') + self.irc_connection = None def changeNickname(self, newnick, on_protocol): if self.protocol == 'xmpp': if on_protocol == 'xmpp': - raise Exception('Internal Error: wanted to change nickname on bad protocol') - self.nickname = newnick - if ' ' in self.nickname: - self.bridge.bot.error('===> Debug: "'+self.nickname+'" contains a white space character, duplicate cannot be created on the IRC chan of bridge "'+str(self.bridge)+'"', debug=True) - self.bridge.say('[Warning] The nickname "'+self.nickname+'" contains a white space character, duplicate cannot be created on IRC, please avoid that if possible') + self.bridge.removeParticipant('irc', self.nickname, '') + self.bridge.addParticipant('irc', newnick) + + else: + self.nickname = newnick if self.irc_connection != None: - self.irc_connection.closing = True - self.irc_connection.disconnect() - self.irc_connection = None - return - if self.irc_connection != None: - self.irc_connection.nick(newnick) - else: - self.createDuplicateOnIRC() + self.irc_connection.nick(newnick, callback=self._irc_nick_callback) + else: + self.createDuplicateOnIRC() + elif self.protocol == 'irc': if on_protocol == 'irc': - raise Exception('Internal Error: wanted to change nickname on bad protocol') - self.nickname = newnick - if self.muc: - self.muc.change_nick(newnick, callback=self._xmpp_join_callback) - else: - self.createDuplicateOnXMPP() - elif self.protocol == 'both': - if on_protocol == 'irc': self.bridge.removeParticipant('xmpp', self.nickname, '') self.bridge.addParticipant('xmpp', newnick) - elif on_protocol == 'xmpp': - self.bridge.removeParticipant('irc', self.nickname, '') - self.bridge.addParticipant('irc', newnick) + + else: + self.nickname = newnick + if self.muc != None: + self.muc.change_nick(newnick, status='From IRC', callback=self._xmpp_join_callback) + else: + self.createDuplicateOnXMPP() def sayOnIRC(self, message): + if self.protocol == 'irc': + raise Exception('Internal Error: "'+self.nickname+'" comes from IRC') + try: - if self.protocol == 'irc': - raise Exception('Internal Error: "'+self.nickname+'" comes from IRC') - elif self.protocol == 'both' or self.irc_connection == None: + if self.irc_connection == None: self.bridge.irc_connection.privmsg(self.bridge.irc_room, '<'+self.nickname+'> '+message) else: self.irc_connection.privmsg(self.bridge.irc_room, message) @@ -162,7 +135,8 @@ class participant: def sayOnIRCTo(self, to, message): if self.protocol == 'irc': raise Exception('Internal Error: "'+self.nickname+'" comes from IRC') - elif self.irc_connection == None: + + if self.irc_connection == None: if self.bridge.mode != 'normal': self.bridge.getParticipant(to).sayOnXMPPTo(self.nickname, 'Sorry but cross-protocol private messages are disabled in limited mode.') else: @@ -177,23 +151,24 @@ class participant: def sayOnXMPP(self, message): if self.protocol == 'xmpp': raise Exception('Internal Error: "'+self.nickname+'" comes from XMPP') - elif self.protocol == 'both' or self.xmpp_c == None: - self.bridge.xmpp_room.say('<'+self.nickname+'> '+auto_decode(message)) - else: - try: + + try: + if self.xmpp_c == None: + self.bridge.xmpp_room.say('<'+self.nickname+'> '+auto_decode(message)) + else: self.muc.say(auto_decode(message)) - except EncodingException: - self.bridge.say('[Warning] "'+self.nickname+'" is sending messages using an unknown encoding') + except EncodingException: + self.bridge.say('[Warning] "'+self.nickname+'" is sending messages using an unknown encoding') def sayOnXMPPTo(self, to, message): if self.protocol == 'xmpp': raise Exception('Internal Error: "'+self.nickname+'" comes from XMPP') - else: - try: - self.muc.sayTo(to, auto_decode(message)) - except EncodingException: - self.bridge.say('[Warning] "'+self.nickname+'" is sending messages using an unknown encoding') + + try: + self.muc.sayTo(to, auto_decode(message)) + except EncodingException: + self.bridge.say('[Warning] "'+self.nickname+'" is sending messages using an unknown encoding') def leave(self, message): @@ -201,9 +176,11 @@ class participant: message = '' if self.xmpp_c != None: self.muc.leave(message) + self.bridge.bot.close_xmpp_connection(self.nickname) if self.irc_connection != None: - self.irc_connection.closing = True - self.irc_connection.disconnect(message) + self.irc_connection.used_by -= 1 + if self.irc_connection.used_by < 1: + self.irc_connection.close(message) self.irc_connection = None self.nickname = None diff --git a/start_bots_from_xml_config.py b/start_bots_from_xml_config.py --- a/start_bots_from_xml_config.py +++ b/start_bots_from_xml_config.py @@ -21,9 +21,9 @@ from bot import bot from time import sleep from xml.dom.minidom import parse import sys +import traceback -bots = [] try: if len(sys.argv) > 1: @@ -40,22 +40,35 @@ for bot_el in config.getElementsByTagNam print 'Error: you cannot have two bots using the same JID' quit(2) bots_jids.append(bot_el.getAttribute('jid')) -for bot_el in config.getElementsByTagName('bot'): - debug = False - if bot_el.hasAttribute('debug'): - if bot_el.getAttribute('debug') == 'true': - debug = True - bot_ = bot(bot_el.getAttribute('jid'), bot_el.getAttribute('password'), bot_el.getAttribute('nickname'), debug=debug) - bots.append(bot_) - for bridge_el in bot_el.getElementsByTagName('bridge'): - xmpp_room = bridge_el.getElementsByTagName('xmpp-room')[0] - irc = bridge_el.getElementsByTagName('irc')[0] - bridge_ = bot_.new_bridge(xmpp_room.getAttribute('jid'), irc.getAttribute('chan'), irc.getAttribute('server')) try: + bots = [] + for bot_el in config.getElementsByTagName('bot'): + debug = False + if bot_el.hasAttribute('debug'): + if bot_el.getAttribute('debug') == 'true': + debug = True + bot_ = bot(bot_el.getAttribute('jid'), bot_el.getAttribute('password'), bot_el.getAttribute('nickname'), debug=debug) + bots.append(bot_) + for bridge_el in bot_el.getElementsByTagName('bridge'): + xmpp_room = bridge_el.getElementsByTagName('xmpp-room')[0] + irc = bridge_el.getElementsByTagName('irc')[0] + say_participants_list = True + if bridge_el.hasAttribute('say_participants_list'): + if bridge_el.getAttribute('say_participants_list') == 'false': + say_participants_list = False + if bridge_el.hasAttribute('mode'): + mode = bridge_el.getAttribute('mode') + else: + mode = 'normal' + bridge_ = bot_.new_bridge(xmpp_room.getAttribute('jid'), irc.getAttribute('chan'), irc.getAttribute('server'), mode, say_participants_list) + + while True: sleep(1) except: - del bots + for bot in bots: + del bot + traceback.print_exc() quit(3) \ No newline at end of file