changeset 17:32a35f7eff70

Rewrote/modified many things, multiple bridges should now work and are preferred over multiple bots. Signed-off-by: Charly COSTE <changaco@changaco.net>
author Charly COSTE <changaco@changaco.net>
date Thu, 20 Aug 2009 01:00:54 +0200
parents 0c4a7452d66c
children 3cdf7bb580da
files README bot.py bridge.py example_config.xml irclib.py muc.py participant.py start_bots_from_xml_config.py
diffstat 8 files changed, 564 insertions(+), 353 deletions(-) [+]
line wrap: on
line diff
--- 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:
--- 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
--- 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
--- a/example_config.xml
+++ b/example_config.xml
@@ -2,11 +2,12 @@
 <config>
 	<bot jid='some_bot@example.net' password='do not forget to escape xml entities like &amp;' nickname='xib-bot-nickname' debug='true'>
 		<!-- WARNING: do NOT set debug to "true" if you are sending the bot's stderr to a file, it logs every XMPP stanza and IRC event without controlling the size of the resulting file -->
-		<bridge>
+		<bridge mode='normal'>
 			<xmpp-room jid='dream-world@chat.example.com'/>
 			<irc chan='#dream-world' server='irc.example.org'/>
 		</bridge>
-		<!-- WARNING: multiple bridges is not currently well-implemented, start multiple bots instead -->
 	</bot>
 	<!-- WARNING: do NOT start two bots with the same JID or the same nickname -->
-</config>
\ No newline at end of file
+	<!-- WARNING: two bots connecting to the same XMPP MUC server and/or the same IRC server may conflict, it is better to use multiple bridges -->
+	<!-- NOTICE: even if you use multiple bridges instead of multiple bots, conflicts may still appear if somebody else is running a bot on the same XMPP MUC server and/or the same IRC server -->
+</config>
--- 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)
--- 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=''):
--- 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
 	
--- 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