Mercurial > xib
view bridge.py @ 22:e2bd4de698e5
Solved an XMPP resource conflict that would have happened when someone on IRC changed its nickname and later its old nickname would be used again. In other words, the bot no longer uses nicknames as XMPP resources.
Signed-off-by: Charly COSTE <changaco@changaco.net>
author | Charly COSTE <changaco@changaco.net> |
---|---|
date | Thu, 20 Aug 2009 17:49:40 +0200 |
parents | 801160b4136f |
children | 4e1f27ea527b |
line wrap: on
line source
#!/usr/bin/env python # -*- coding: utf-8 -*- # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <http://www.gnu.org/licenses/>. import muc xmpp = muc.xmpp del muc from participant import * from encoding import * import traceback import re class NoSuchParticipantException(Exception): pass class bridge: _all = 0 _info = 1 _notice = 2 _warning = 3 _error = 4 _nothing = 5 def __init__(self, owner_bot, xmpp_room_jid, irc_room, irc_server, mode, say_level, 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 if hasattr(self.__class__, '_'+say_level): self.say_level = getattr(self.__class__, '_'+say_level) else: raise Exception('[Error] "'+say_level+'" is not a correct value for a bridge\'s "say_level" attribute') 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 XMPP room try: self.xmpp_room = xmpp.muc(xmpp_room_jid) 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') def _irc_nick_callback(self, error, arguments=[]): if error == None: self.irc_connection.join(self.irc_room) self.bot.error('===> Debug: successfully connected on IRC side of bridge "'+str(self)+'"', debug=True) self.say('[Notice] bridge "'+str(self)+'" is running in '+self.mode+' mode', on_xmpp=False) if error == 'nicknameinuse': 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) self.say('[Notice] bridge "'+str(self)+'" is running in '+self.mode+' mode', on_irc=False) 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): 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: if protocol == 'irc': p.createDuplicateOnXMPP() elif protocol == 'xmpp': p.createDuplicateOnIRC() else: raise Exception('[Internal Error] bad protocol') return except NoSuchParticipantException: pass self.bot.error('===> Debug: adding participant "'+nickname+'" from "'+protocol+'" to bridge "'+str(self)+'"', debug=True) try: p = participant(self, protocol, nickname) except IOError: self.bot.error('===> Debug: IOError while adding participant "'+nickname+'" from "'+protocol+'" to bridge "'+str(self)+'", reconnectiong ...', debug=True) p.xmpp_c.reconnectAndReauth() except: self.bot.error('===> Debug: unknown error while adding participant "'+nickname+'" from "'+protocol+'" to bridge "'+str(self)+'"', debug=True) traceback.print_exc() return self.participants.append(p) if self.mode != 'normal' and protocol == 'xmpp': 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 def getParticipant(self, nickname): """Returns a participant object if there is a participant using nickname in the bridge. Raises a NoSuchParticipantException otherwise.""" for participant_ in self.participants: if participant_.nickname == nickname: return participant_ raise NoSuchParticipantException('there is no participant using the nickname "'+nickname+'" in this bridge') 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 in protocols: participants_nicknames.append('"'+p.nickname+'"') return participants_nicknames 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 == '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 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 message[0] != '[': raise Exception('[Internal Error] message does not start with "["') if self.say_level == self.__class__._nothing: return level = re.findall('^\[(Info|Notice|Warning|Error)\]', message) if len(level) == 0: raise Exception('[Internal Error] unknown message importance "'+re.findall('^\[([^[\]]+)', message)[0]+'"') level = level[0].lower() if getattr(self.__class__, '_'+level) < self.say_level: return if on_xmpp == True: self.xmpp_room.say(message) if on_irc == True: self.irc_connection.privmsg(self.irc_room, auto_encode(message)) def switchFromLimitedToNormalMode(self): if self.mode != 'normal-limited': return 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() def switchFromNormalToLimitedMode(self): if self.mode != 'normal': return self.mode = 'normal-limited' i = 0 for p in self.participants: if p.protocol == 'xmpp': i += 1 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_participants_nicknames_list(protocols=['xmpp']) self.say('[Info] Participants on XMPP: '+' '.join(xmpp_participants_nicknames), on_xmpp=False) def __str__(self): return self.irc_room+'@'+self.irc_server+' <-> '+self.xmpp_room.room_jid def __del__(self): # Delete participants objects for p in self.participants: p.leave('Removing bridge') del p 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')