view bot.py @ 169:271c35321497

minor fix in bridge.py Signed-off-by: Charly COSTE <changaco@changaco.net>
author Charly COSTE <changaco@changaco.net>
date Sat, 30 Jan 2010 10:04:12 +0100
parents 0fc24e232997
children 489c157d9e82
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 re
import sys
import threading
from time import sleep
import traceback
import xml.parsers.expat

from encoding import *
import irclib
import muc
xmpp = muc.xmpp
del muc

from bridge import Bridge
from participant import Participant
import commands


class Bot(threading.Thread):
	
	def __init__(self, jid, password, nickname, admins_jid=[], error_fd=sys.stderr, debug=False):
		threading.Thread.__init__(self)
		self.halt = False
		self.bare_jid = xmpp.protocol.JID(jid=jid)
		self.bare_jid.setResource('')
		self.nickname = nickname
		self.password = password
		self.error_fd = error_fd
		self.debug = debug
		self.admins_jid = admins_jid
		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 = threading.Thread(target=self.irc.process_forever)
		self.irc_thread.start()
		# Open connection with XMPP server
		try:
			self.xmpp_c = self.get_xmpp_connection(self.nickname)
		except:
			self.error('[Error] XMPP Connection failed')
			raise
		self.xmpp_thread = threading.Thread(target=self._xmpp_loop)
		self.xmpp_thread.start()
	
	
	def error(self, s, debug=False, send_to_admins=False):
		"""Output an error message."""
		if send_to_admins == True:
			self._send_message_to_admins(s)
		if not debug or debug and self.debug:
			try:
				self.error_fd.write(auto_encode(s)+"\n")
			except EncodingException:
				self.error_fd.write('Error message cannot be transcoded.\n')
	
	
	def _xmpp_loop(self):
		"""[Internal] XMPP infinite loop."""
		i = 1
		while True:
			if self.halt:
				s = len(self.xmpp_connections)
				for i in range(s):
					self.close_xmpp_connection(self.xmpp_connections.keys()[s-i-1], force=True)
				break
			unlock = False
			try:
				if len(self.xmpp_connections) == 1:
					sleep(0.5)  # avoid bot connection being locked all the time
				for j, c in enumerate(self.xmpp_connections.itervalues()):
					i += 1
					if hasattr(c, 'lock'):
						c.lock.acquire()
						if i == j:
							ping = xmpp.protocol.Iq(typ='get')
							ping.addChild(name='ping', namespace='urn:xmpp:ping')
							self.error('=> Debug: sending XMPP ping', debug=True)
							c.pings.append(c.send(ping))
						if hasattr(c, 'Process'):
							c.Process(0.01)
						c.lock.release()
					if i > 5000:
						i = 0
			except RuntimeError:
				pass
			except (xml.parsers.expat.ExpatError, xmpp.protocol.XMLNotWellFormed):
				self.error('=> Debug: invalid stanza', debug=True)
				self.reopen_xmpp_connection(c)
				unlock = True
			except xmpp.Conflict:
				self.error('=> Debug: conflict', debug=True)
				self.reopen_xmpp_connection(c)
				unlock = True
			except:
				error = '[Error] Unknown exception on XMPP thread:\n'
				error += traceback.format_exc()
				self.error(error, send_to_admins=True)
				unlock = True
			if unlock == True:
				c.lock.release()
	
	
	def _xmpp_presence_handler(self, dispatcher, presence):
		"""[Internal] Manage XMPP presence."""
		
		xmpp_c = dispatcher._owner
		
		if xmpp_c.nickname != self.nickname:
			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)
		
		from_ = xmpp.protocol.JID(presence.getFrom())
		bare_jid = unicode(from_.getNode()+'@'+from_.getDomain())
		for bridge in self.bridges:
			if bare_jid == bridge.xmpp_room_jid:
				# presence comes from a muc
				resource = unicode(from_.getResource())
				
				if resource == '':
					# presence comes from the muc itself
					pass
				
				elif resource == xmpp_c.nickname:
					# presence comes from self
					x = presence.getTag('x', namespace='http://jabber.org/protocol/muc#user')
					if x:
						d = x.getTag('destroy')
						if d:
							# room was destroyed
							# problem is that this is used by some MUC servers when they shut down or restart
							# considering this lack of semantic we have no choice but to do a check on the reason
							reason = d.getTag('reason')
							if reason:
								r = reason.getData()
								if r == 'The conference component is shutting down':
									# MUC server is going down, try to restart the bridges in 1 minute
									bridges = self.findBridges([from_.getDomain()])
									error_message = '[Warning] The MUC server '+from_.getDomain()+' seems to be going down, the bot will try to recreate all bridges related to this server in 1 minute'
									self.restart_bridges_delayed(bridges, 60, error_message)
									self.error(presence.__str__(fancy=1).encode('utf-8'), debug=True)
									return
								elif r == '':
									r = 'None given'
							else:
								r = 'None given'
							
							# room has been destroyed, stop the bridge
							self.error('[Error] The MUC room of the bridge '+str(bridge)+' has been destroyed with reason "'+r+'", stopping the bridge', send_to_admins=True)
							bridge.stop(message='The MUC room of the bridge has been destroyed with reason "'+r+'", stopping the bridge')
				
				else:
					# presence comes from a participant of the muc
					
					x = presence.getTag('x', namespace='http://jabber.org/protocol/muc#user')
					item = None
					if x:
						item = x.getTag('item')
					
					if presence.getType() == 'unavailable':
						try:
							p = bridge.getParticipant(resource)
						except Bridge.NoSuchParticipantException:
							p = None
						
						if x and x.getTag('status', attrs={'code': '303'}):
							# participant changed its nickname
							if p == None:
								return
							if p.protocol != 'xmpp':
								return
							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')
							
						elif x and x.getTag('status', attrs={'code': '307'}):
							# participant was kicked
							if p == None:
								bridge.xmpp_room.rejoin()
								return
							if isinstance(p.xmpp_c, xmpp.client.Client):
								p.muc.rejoin()
							else:
								if item:
									reason = item.getTag('reason')
									actor = item.getTag('actor')
									if actor and actor.has_attr('jid'):
										kicker = actor.getAttr('jid')
										s1 = 'Kicked by '+kicker
									else:
										s1 = 'Kicked from XMPP'
									if reason:
										s2 = ' with reason: '+reason.getData()
									else:
										s2 = ' (no reason was given)'
								else:
									s1 = 'Kicked from XMPP'
									s2 = ' (no reason was given)'
								
								bridge.removeParticipant('xmpp', p.nickname, s1+s2)
							
						elif x and x.getTag('status', attrs={'code': '301'}):
							# participant was banned
							if p == None:
								m = '[Error] bot got banned from XMPP'
								self.error(m)
								bridge.say(m, on_xmpp=False)
								self.removeBridge(bridge)
								return
							if item:
								reason = item.getTag('reason')
								actor = item.getTag('actor')
								if actor and actor.has_attr('jid'):
									kicker = actor.getAttr('jid')
									s1 = 'Banned by '+kicker
								else:
									s1 = 'Banned from XMPP'
								if reason:
									s2 = ' with reason: '+reason.getData()
								else:
									s2 = ' (no reason was given)'
							else:
								s1 = 'Banned from XMPP'
								s2 = ' (no reason was given)'
							
							bridge.removeParticipant('xmpp', p.nickname, s1+s2)
							
						else:
							# participant left
							if p != None:
								bridge.removeParticipant('xmpp', resource, presence.getStatus())
					
					elif presence.getType() == 'error':
						error = presence.getTag('error')
						if error:
							for c in error.getChildren():
								if c.getNamespace() == 'urn:ietf:params:xml:ns:xmpp-stanzas' and c.getName() != 'text':
									err = error.getAttr('type')+' '+c.getName()
									if err == 'cancel remote-server-not-found':
										# Remote server not found
										# Stop bridges that depend on this server
										bridges = self.findBridges([from_.getDomain()])
										error_message = '[Error] XMPP Remote server not found: '+from_.getDomain()
										self.restart_bridges_delayed(bridges, 60, error_message)
										self.error(presence.__str__(fancy=1).encode('utf-8'), debug=True)
									else:
										raise Exception(presence.__str__(fancy=1).encode('utf-8'))
					
					elif resource != bridge.bot.nickname:
						real_jid = None
						if item and item.has_attr('jid'):
							real_jid = item.getAttr('jid')
						
						p = bridge.addParticipant('xmpp', resource, real_jid)
						
						# if we have the real jid check if the participant is a bot admin
						if real_jid and isinstance(p, Participant):
							for jid in self.admins_jid:
								if xmpp.protocol.JID(jid).bareMatch(real_jid):
									p.bot_admin = True
									break
						
						return
					
				return
	
	
	def _xmpp_iq_handler(self, dispatcher, iq):
		"""[Internal] Manage XMPP IQs."""
		
		xmpp_c = dispatcher._owner
		
		# Ignore pongs
		if iq.getType() in ['result', 'error'] and iq.getID() in xmpp_c.pings:
			xmpp_c.pings.remove(iq.getID())
			self.error('=> Debug: received XMPP pong', debug=True)
			return
		
		self.error('==> Debug: Received XMPP iq.', debug=True)
		self.error(iq.__str__(fancy=1), debug=True)
	
	
	def _xmpp_message_handler(self, dispatcher, message):
		"""[Internal] Manage XMPP messages."""
		
		xmpp_c = dispatcher._owner
		
		if message.getBody() == None:
			return
		
		if message.getType() == 'chat':
			from_bare_jid = unicode(message.getFrom().getNode()+'@'+message.getFrom().getDomain())
			for bridge in self.bridges:
				if from_bare_jid == bridge.xmpp_room_jid:
					# message comes from a room participant
					
					self.error('==> Debug: Received XMPP chat message.', debug=True)
					self.error(message.__str__(fancy=1), debug=True)
					
					try:
						from_ = bridge.getParticipant(message.getFrom().getResource())
						to_ = bridge.getParticipant(xmpp_c.nickname)
						
						from_.sayOnIRCTo(to_.nickname, message.getBody())
						
					except Bridge.NoSuchParticipantException:
						if xmpp_c.nickname == self.nickname:
							r = self.respond(str(message.getBody()), participant=from_)
							if isinstance(r, basestring) and len(r) > 0:
								s = xmpp.protocol.Message(to=message.getFrom(), body=r, typ='chat')
								self.error('==> Debug: Sending', debug=True)
								self.error(s.__str__(fancy=1), debug=True)
								xmpp_c.send(s)
							else:
								self.error('=> Debug: won\'t answer.', debug=True)
							return
						self.error('=> Debug: XMPP chat message not relayed', debug=True)
						return
			
			# message does not come from a room
			if xmpp_c.nickname == self.nickname:
				self.error('==> Debug: Received XMPP chat message.', debug=True)
				self.error(message.__str__(fancy=1), debug=True)
				
				# Find out if the message comes from a bot admin
				bot_admin = False
				for jid in self.admins_jid:
					if xmpp.protocol.JID(jid).bareMatch(message.getFrom()):
						bot_admin = True
						break
				
				# Respond
				r = self.respond(str(message.getBody()), bot_admin=bot_admin)
				if isinstance(r, basestring) and len(r) > 0:
					s = xmpp.protocol.Message(to=message.getFrom(), body=r, typ='chat')
					self.error('==> Debug: Sending', debug=True)
					self.error(s.__str__(fancy=1), debug=True)
					xmpp_c.send(s)
			
			else:
				self.error('=> Debug: Ignoring XMPP chat message not received on bot connection.', debug=True)
		
		elif message.getType() == 'groupchat':
			# message comes from a room
			
			for child in message.getChildren():
				if child.getName() == 'delay':
					# MUC delayed message
					return
			
			if xmpp_c.nickname != self.nickname:
				self.error('=> Debug: Ignoring XMPP MUC message not received on bot connection.', debug=True)
				return
			
			
			from_ = xmpp.protocol.JID(message.getFrom())
			
			if unicode(from_.getResource()) == self.nickname:
				self.error('=> Debug: Ignoring XMPP MUC message sent by self.', debug=True)
				return
			
			room_jid = unicode(from_.getNode()+'@'+from_.getDomain())
			for bridge in self.bridges:
				if room_jid == bridge.xmpp_room_jid:
					resource = unicode(from_.getResource())
					if resource == '':
						# message comes from the room itself
						self.error('=> Debug: Ignoring XMPP groupchat message sent by the room.', debug=True)
						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 Bridge.NoSuchParticipantException:
							if resource != self.nickname:
								self.error('=> Debug: NoSuchParticipantException "'+resource+'" on "'+str(bridge)+'", WTF ?', debug=True)
							return
						
						participant.sayOnIRC(message.getBody())
						return
		
		elif message.getType() == 'error':
			for b in self.bridges:
				if message.getFrom() == b.xmpp_room_jid:
					# message comes from a room
					for c in message.getChildren():
						if c.getName() == 'error':
							for cc in c.getChildren():
								if cc.getNamespace() == 'urn:ietf:params:xml:ns:xmpp-stanzas' and cc.getName() != 'text':
									err = cc.getName()
									if err == 'not-acceptable':
										# we sent a message to a room we are not in
										# probable cause is a MUC server restart
										# let's restart the bot
										self.restart()
									elif err == 'forbidden':
										# we don't have the permission to speak
										# let's remove the bridge and tell admins
										self.error('[Error] Not allowed to speak on the XMPP MUC of bridge '+str(b)+', stopping it', send_to_admins=True)
										b.stop(message='Not allowed to speak on the XMPP MUC, stopping bridge.')
									else:
										self.error('==> Debug: recevied unknown error message', debug=True)
										self.error(message.__str__(fancy=1), debug=True)
					return
			
			self.error('==> Debug: recevied unknown error message', debug=True)
			self.error(message.__str__(fancy=1), debug=True)
		
		else:
			self.error('==> Debug: Received XMPP message of unknown type "'+str(message.getType())+'".', debug=True)
			self.error(message.__str__(fancy=1), debug=True)
	
	
	def _irc_event_handler(self, connection, event):
		"""[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() or event.eventtype() in ['nicknameinuse', 'nickcollision', 'erroneusnickname']:
			return
		if event.eventtype() in ['pong', 'privnotice', 'ctcp', 'nochanmodes', 'notexttosend', 'currenttopic', 'topicinfo', '328']:
			self.error('=> Debug: ignoring IRC '+event.eventtype(), debug=True)
			return
		
		
		nickname = None
		if event.source() != 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:
				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
		
		
		# A string representation of the event
		event_str = '==> Debug: Received IRC event.\nconnection='+connection.__str__()+'\neventtype='+event.eventtype()+'\nsource='+auto_decode(event.source().__str__())+'\ntarget='+auto_decode(event.target().__str__())+'\narguments='+auto_decode(event.arguments().__str__())
		
		
		if event.eventtype() in ['pubmsg', 'action', 'privmsg', 'quit', 'part', 'nick', 'kick']:
			if nickname == None:
				return
			
			handled = False
			
			if event.eventtype() in ['quit', 'part', 'nick', 'kick']:
				if connection.get_nickname() != self.nickname:
					self.error('=> Debug: ignoring IRC '+event.eventtype()+' not received on bot connection', debug=True)
					return
				else:
					self.error(event_str, debug=True)
			
			if event.eventtype() == 'kick' and len(event.arguments()) < 1:
				self.error('=> Debug: length of arguments should be greater than 0 for a '+event.eventtype()+' event')
				return
			
			if event.eventtype() in ['pubmsg', 'action']:
				if connection.get_nickname() != self.nickname:
					self.error('=> Debug: ignoring IRC '+event.eventtype()+' not received on bot connection', debug=True)
					return
				if nickname == self.nickname:
					self.error('=> Debug: ignoring IRC '+event.eventtype()+' sent by self', debug=True)
					return
			
			# TODO: lock self.bridges for thread safety
			for bridge in self.bridges:
				if connection.server != bridge.irc_server:
					continue
				
				try:
					from_ = bridge.getParticipant(nickname)
					
				except Bridge.NoSuchParticipantException:
					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 Bridge.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]))
							return
						else:
							continue
				
				
				# kick handling
				if event.eventtype() == 'kick':
					if event.target().lower() == bridge.irc_room:
						try:
							kicked = bridge.getParticipant(event.arguments()[0])
							if isinstance(kicked.irc_connection, irclib.ServerConnection):
								kicked.irc_connection.join(bridge.irc_room)
							else:
								if len(event.arguments()) > 1:
									bridge.removeParticipant('irc', kicked.nickname, 'Kicked by '+nickname+' with reason: '+event.arguments()[1])
								else:
									bridge.removeParticipant('irc', kicked.nickname, 'Kicked by '+nickname+' (no reason was given)')
							return
						except Bridge.NoSuchParticipantException:
							self.error('=> Debug: a participant that was not here has been kicked ? WTF ?')
							return
					else:
						continue
				
				
				# Leaving events
				if event.eventtype() == 'quit' or event.eventtype() == 'part' and event.target().lower() == bridge.irc_room:
					if event.eventtype() == 'quit' and ( bridge.mode != 'normal' or isinstance(from_.irc_connection, irclib.ServerConnection) ):
						continue
					if len(event.arguments()) > 0:
						leave_message = event.arguments()[0]
					elif event.eventtype() == 'quit':
						leave_message = 'Left server.'
					elif event.eventtype() == 'part':
						leave_message = 'Left channel.'
					else:
						leave_message = ''
					bridge.removeParticipant('irc', from_.nickname, leave_message)
					handled = True
					continue
				
				
				# Nickname change
				if event.eventtype() == 'nick':
					from_.changeNickname(event.target(), 'xmpp')
					handled = True
					continue
				
				
				# Chan message
				if event.eventtype() in ['pubmsg', 'action']:
					if bridge.irc_room == event.target().lower() and bridge.irc_server == connection.server:
						self.error(event_str, debug=True)
						message = event.arguments()[0]
						if event.eventtype() == 'action':
							message = '/me '+message
						from_.sayOnXMPP(message)
						return
					else:
						continue
			
			if handled == False:
				if not event.eventtype() in ['quit', 'part', 'nick', 'kick']:
					self.error(event_str, debug=True)
				self.error('=> Debug: event was not handled', debug=True)
			return
		
		
		# Handle bannedfromchan
		if event.eventtype() == 'bannedfromchan':
			if len(event.arguments()) < 1:
				self.error('=> Debug: length of arguments should be greater than 0 for a '+event.eventtype()+' event')
				return
			
			for bridge in self.bridges:
				if connection.server != bridge.irc_server or event.arguments()[0].lower() != bridge.irc_room:
					continue
				
				if event.target() == self.nickname:
					self.error('[Error] the nickname "'+event.target()+'" is banned from the IRC chan of bridge "'+str(bridge)+'"')
					raise Exception('[Error] the nickname "'+event.target()+'" is banned from the IRC chan of bridge "'+str(bridge)+'"')
				else:
					try:
						banned = bridge.getParticipant(event.target())
						if banned.irc_connection != 'bannedfromchan':
							banned.irc_connection = 'bannedfromchan'
							self.error(event_str, debug=True)
							bridge.say('[Warning] the nickname "'+event.target()+'" is banned from the IRC chan', log=True)
						else:
							self.error('=> Debug: ignoring '+event.eventtype(), debug=True)
					except Bridge.NoSuchParticipantException:
						self.error('=> Debug: no such participant. WTF ?')
						return
			
			return
		
		
		# Joining events
		if event.eventtype() in ['namreply', 'join']:
			if connection.get_nickname() != self.nickname:
				self.error('=> Debug: ignoring IRC '+event.eventtype()+' not received on bridge connection', debug=True)
				return
			
			if event.eventtype() == 'namreply':
				for bridge in self.getBridges(irc_room=event.arguments()[1].lower(), 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().lower(), irc_server=connection.server)
				if len(bridges) == 0:
					self.error(event_str, debug=True)
					self.error('===> Debug: no bridge found for "'+event.target().lower()+' at '+connection.server+'"', debug=True)
					return
				for bridge in bridges:
					bridge.addParticipant('irc', nickname, irc_id=event.source())
				return
		
		
		if event.eventtype() in ['disconnect', 'kill', 'error']:
			if len(event.arguments()) > 0 and event.arguments()[0] == 'Connection reset by peer':
				self.error(event_str, debug=True)
			else:
				self.error(event_str, send_to_admins=True)
			return
		
		
		# Unhandled events
		self.error(event_str+'\n=> Debug: event not handled', debug=True)
	
	
	def _send_message_to_admins(self, message):
		"""[Internal] Send XMPP Message to bot admin(s)"""
		for admin_jid in self.admins_jid:
			try:
				self.xmpp_c.send(xmpp.protocol.Message(to=admin_jid, body=message, typ='chat'))
			except:
				pass
	
	
	def new_bridge(self, xmpp_room, irc_room, irc_server, mode, say_level, irc_port=6667, irc_connection_interval=1):
		"""Create a bridge between xmpp_room and irc_room at irc_server."""
		b = Bridge(self, xmpp_room, irc_room, irc_server, mode, say_level, irc_port=irc_port, irc_connection_interval=irc_connection_interval)
		self.bridges.append(b)
		return b
	
	
	def findBridges(self, str_array):
		# TODO: lock self.bridges for thread safety
		bridges = [b for b in self.bridges]
		for bridge in [b for b in bridges]:
			for s in str_array:
				if not s in str(bridge):
					bridges.remove(bridge)
					break
		return bridges
	
	
	def getBridges(self, irc_room=None, irc_server=None, xmpp_room_jid=None):
		# TODO: lock self.bridges for thread safety
		bridges = [b for b in self.bridges]
		for bridge in [b for b in bridges]:
			if irc_room != None and bridge.irc_room != irc_room:
				bridges.remove(bridge)
				continue
			if irc_server != None and bridge.irc_server != irc_server:
				bridges.remove(bridge)
				continue
			if xmpp_room_jid != None and bridge.xmpp_room_jid != xmpp_room_jid:
				bridges.remove(bridge)
				continue
		return bridges
	
	
	def get_xmpp_connection(self, nickname):
		if self.xmpp_connections.has_key(nickname):
			c = self.xmpp_connections[nickname]
			c.used_by += 1
			self.error('===> Debug: using existing XMPP connection for "'+nickname+'", now used by '+str(c.used_by)+' bridges', debug=True)
			return c
		self.error('===> Debug: opening new XMPP connection for "'+nickname+'"', debug=True)
		c = xmpp.client.Client(self.bare_jid.getDomain(), debug=[])
		c.lock = threading.RLock()
		c.lock.acquire()
		self.xmpp_connections[nickname] = c
		c.used_by = 1
		c.nickname = nickname
		c.mucs = []
		c.pings = []
		c.connect()
		c.auth(self.bare_jid.getNode(), self.password)
		c.RegisterHandler('presence', self._xmpp_presence_handler)
		c.RegisterHandler('iq', self._xmpp_iq_handler)
		c.RegisterHandler('message', self._xmpp_message_handler)
		c.sendInitPresence()
		if nickname == self.nickname:
			c.send(xmpp.protocol.Presence(priority=127))
		c.lock.release()
		return c
	
	
	def reopen_xmpp_connection(self, c):
		if not isinstance(c, xmpp.client.Client):
			return
		bot_connection = False
		if c == self.xmpp_c:
			bot_connection = True
		mucs = c.mucs
		nickname = c.nickname
		used_by = c.used_by
		participants = []
		for b in self.bridges:
			for p in b.participants:
				if p.xmpp_c == c:
					participants.append(p)
					p.xmpp_c = None
		self.error('===> Debug: reopening XMPP connection for "'+nickname+'"', debug=True)
		if self.xmpp_connections.has_key(nickname):
			self.xmpp_connections.pop(nickname)
		c.send(xmpp.protocol.Presence(typ='unavailable'))
		del c
		c = self.get_xmpp_connection(nickname)
		c.used_by = used_by
		if bot_connection:
			self.xmpp_c = c
		for p in participants:
			p.xmpp_c = c
		c.mucs = mucs
		for m in c.mucs:
			m.rejoin()
		return c
	
	
	def close_xmpp_connection(self, nickname, force=False):
		if not self.xmpp_connections.has_key(nickname):
			return
		c = self.xmpp_connections[nickname]
		c.lock.acquire()
		c.used_by -= 1
		if c.used_by < 1 or force:
			self.error('===> Debug: closing XMPP connection for "'+nickname+'"', debug=True)
			self.xmpp_connections.pop(nickname)
			c.send(xmpp.protocol.Presence(typ='unavailable'))
			c.lock.release()
			del c
		else:
			c.lock.release()
			self.error('===> Debug: XMPP connection for "'+nickname+'" is now used by '+str(c.used_by)+' bridges', debug=True)
	
	
	def removeBridge(self, bridge, message='Removing bridge'):
		self.bridges.remove(bridge)
		bridge.stop(message)
	
	
	def respond(self, message, participant=None, bot_admin=False):
		if isinstance(participant, Participant):
			bridge = participant.bridge
			if bot_admin != participant.bot_admin:
				bot_admin = participant.bot_admin
		else:
			bridge = None
		
		return commands.execute(self, message, bot_admin, bridge)
	
	
	def restart(self):
		# Stop the bridges
		for b in self.bridges:
			b.stop(message='Restarting bot')
		
		# Reopen the bot's XMPP connection
		self.reopen_xmpp_connection(self.xmpp_c)
		
		# Restart the bridges
		for b in self.bridges:
			b.init2()
		
		self.error('Bot restarted.', send_to_admins=True)
	
	
	def restart_bridges_delayed(self, bridges, delay, error_message):
		if len(bridges) > 0:
			error_message += '\nThese bridges will be stopped:'
			for b in bridges:
				error_message += '\n'+str(b)
				if hasattr(b, 'reconnecting'):
					leave_message = 'MUC server seems to be down'
				else:
					leave_message = 'MUC server seems to be down, will try to recreate the bridge in '+str(delay)+' seconds'
					self.reconnecting = True
					self.irc.execute_delayed(delay, b.init2)
				b.stop(message=leave_message)
		
		self.error(error_message, send_to_admins=True)
	
	
	def stop(self, message='Stopping bot'):
		for bridge in self.bridges:
			bridge.stop(message=message)
	
	
	def __del__(self):
		for bridge in self.bridges:
			self.removeBridge(bridge, message='Stopping bot')
		self.halt = True