view participant.py @ 270:7640a3feddf5

catch xmpp.muc.NotConnected in Bridge._say_on_xmpp Signed-off-by: Charly COSTE <changaco@changaco.net>
author Charly COSTE <changaco@changaco.net>
date Tue, 23 Mar 2010 18:38:59 +0100
parents 9a2302e8382b
children d04e40b7be2e
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
from time import sleep

from irclib import ServerNotConnectedError, ServerConnection
import muc
xmpp = muc.xmpp
del muc

import say_levels


class Participant:
	def __init__(self, owner_bridge, protocol, nickname, real_jid=None):
		self.bot_admin = False
		self.real_jid = real_jid
		self.bridge = owner_bridge
		self.protocol = protocol
		self.nickname = nickname
		self.duplicate_nickname = self.nickname
		self.irc_connection = None
		self.xmpp_c = None
		self.muc = None
		self.left = False
		if protocol == 'xmpp' and self.bridge.mode in ['normal', 'bypass']:
			self.create_duplicate_on_irc()
		elif protocol == 'irc' and self.bridge.mode != 'minimal':
			self.create_duplicate_on_xmpp()
	
	
	def _get_new_duplicate_nickname(self):
		new_duplicate_nickname = self.duplicate_nickname
		for i in xrange(5):
			new_duplicate_nickname = new_duplicate_nickname+'_'
			if not self.bridge.has_participant(new_duplicate_nickname):
				return new_duplicate_nickname
		return None
	
	
	def create_duplicate_on_xmpp(self):
		if isinstance(self.xmpp_c, xmpp.client.Client) or isinstance(self.irc_connection, ServerConnection):
			return
		self.xmpp_c = self.bridge.bot.get_xmpp_connection(self.duplicate_nickname)
		self.muc = xmpp.muc(self.bridge.xmpp_room_jid)
		self.join_muc()
	
	
	def join_muc(self):
		self.muc.join(self.xmpp_c, self.duplicate_nickname, status='From IRC', callback=self._xmpp_join_callback)
	
	
	def _xmpp_join_callback(self, errors):
		if len(errors) == 0:
			m = '"'+self.nickname+'" duplicate succesfully created on XMPP side of bridge "'+str(self.bridge)+'"'
			if self.nickname != self.duplicate_nickname:
				m += ' using nickname "'+self.duplicate_nickname+'"'
				self.bridge.say(say_levels.info, '"'+self.nickname+'" will appear as "'+self.duplicate_nickname+'" on XMPP because its real nickname is reserved or contains unauthorized characters')
			self.bridge.bot.error(3, m, debug=True)
		elif self.xmpp_c != 'both':
			for error in errors:
				try:
					raise error
				except xmpp.muc.NicknameConflict as e:
					if xmpp.protocol.JID(e.args[0]).getResource() != self.duplicate_nickname:
						return
					
					if self.bridge.mode == 'bypass':
						new_duplicate_nickname = self._get_new_duplicate_nickname()
						if new_duplicate_nickname != None:
							self.bridge.bot.error(3, '"'+self.duplicate_nickname+'" is already used in the XMPP MUC or reserved on the XMPP server of bridge "'+str(self.bridge)+'", trying "'+new_duplicate_nickname+'"', debug=True)
							if self.duplicate_nickname == self.nickname:
								self.bridge.say(say_levels.info, 'The nickname "'+self.duplicate_nickname+'" is used on both rooms or reserved on the XMPP server')
							self.duplicate_nickname = new_duplicate_nickname
							if isinstance(self.xmpp_c, xmpp.client.Client):
								self.bridge.bot.close_xmpp_connection(self.nickname)
								self.xmpp_c = None
							self.create_duplicate_on_xmpp()
							return
					
					else:
						self.bridge.say(say_levels.warning, 'The nickname "'+self.nickname+'" is used on both rooms or reserved on the XMPP server', log=True)
						if isinstance(self.muc, xmpp.muc):
							self.muc.leave('Changed nickname to "'+self.nickname+'"')
				except xmpp.muc.RoomIsFull:
					self.bridge.say(say_levels.warning, 'XMPP room is full', log=True)
				except xmpp.muc.RemoteServerNotFound:
					self.bridge._RemoteServerNotFound_handler()
				
				self._close_xmpp_connection()
	
	
	def create_duplicate_on_irc(self):
		if isinstance(self.xmpp_c, xmpp.client.Client) or isinstance(self.irc_connection, ServerConnection):
			return
		self.irc_connection = self.bridge.bot.irc.open_connection(self.bridge.irc_server, self.bridge.irc_port, self.duplicate_nickname)
		self.irc_connection.connect(nick_callback=self._irc_nick_callback)
	
	
	def _irc_nick_callback(self, error, arguments=None):
		if not error:
			self.irc_connection.join(self.bridge.irc_room, callback=self._irc_join_callback)
		
		elif self.irc_connection != 'both':
			
			if error in ['nicknameinuse', 'nickcollision']:
				if arguments[0].arguments()[0] != self.duplicate_nickname:
					return
				
				if self.bridge.mode == 'bypass':
					new_duplicate_nickname = self._get_new_duplicate_nickname()
					if new_duplicate_nickname:
						self.bridge.bot.error(3, '"'+self.duplicate_nickname+'" is already used or reserved on the IRC server of bridge "'+str(self.bridge)+'", trying "'+new_duplicate_nickname+'"', debug=True)
						if self.duplicate_nickname == self.nickname:
							self.bridge.say(say_levels.info, 'The nickname "'+self.duplicate_nickname+'" is used or reserved on the IRC server')
						self.duplicate_nickname = new_duplicate_nickname
						if isinstance(self.irc_connection, ServerConnection):
							self.irc_connection.close('')
							self.irc_connection = error
						self.create_duplicate_on_irc()
						return
				
				else:
					self.bridge.say(say_levels.warning, 'The nickname "'+self.nickname+'" is used or reserved on the IRC server', log=True)
			
			elif error == 'erroneusnickname':
				if self.bridge.mode == 'bypass':
					self.duplicate_nickname = re.sub('[^a-zA-Z]', '', self.nickname)
					if isinstance(self.irc_connection, ServerConnection):
						self.irc_connection.close('')
						self.irc_connection = error
					self.create_duplicate_on_irc()
					return
				else:
					self.bridge.say(say_levels.warning, 'The nickname "'+self.nickname+'" contains unauthorized characters and cannot be used in the IRC channel', log=True)
			
			elif error == 'nicknametoolong':
				self.bridge.say(say_levels.warning, 'The nickname "'+self.nickname+'" is too long (limit seems to be '+str(len(self.irc_connection.real_nickname))+') and cannot be used in the IRC channel', log=True)
			
			else:
				self.bridge.say(say_levels.warning, 'unknown error while adding "'+self.nickname+'" to IRC side of bridge', log=True)
			
			if isinstance(self.irc_connection, ServerConnection):
				self.irc_connection.close('')
				self.irc_connection = error
	
	
	def _irc_join_callback(self, channel, error):
		if not error:
			m = '"'+self.nickname+'" duplicate succesfully joined IRC side of bridge "'+str(self.bridge)+'"'
			if self.nickname != self.duplicate_nickname:
				m += ' using nickname "'+self.duplicate_nickname+'"'
				self.bridge.say(say_levels.info, '"'+self.nickname+'" will appear as "'+self.duplicate_nickname+'" on IRC because its real nickname is reserved or contains unauthorized characters')
			self.bridge.bot.error(3, m, debug=True)
		
		elif self.irc_connection != 'both':
			self._close_irc_connection('')
			self.irc_connection = error
	
	
	def set_both_sides(self):
		self.bridge.say(say_levels.warning, 'The nickname "'+self.nickname+'" is used on both sides of the bridge', log=True)
		if isinstance(self.irc_connection, ServerConnection):
			self.irc_connection.close('')
		if self.irc_connection != 'both':
			self.irc_connection = 'both'
		if isinstance(self.muc, xmpp.muc):
			self.muc.leave('')
			self.bridge.bot.close_xmpp_connection(self.nickname)
		if self.xmpp_c != 'both':
			self.xmpp_c = 'both'
	
	
	def change_nickname(self, newnick, on_protocol):
		"""Change participant's nickname."""
		
		p = None
		oldnick = self.nickname
		
		if self.protocol == 'xmpp':
			if on_protocol == 'xmpp':
				self._close_irc_connection('unwanted nick change')
				self.irc_connection = 'unwanted nick change'
			
			else:
				try:
					p = self.bridge.get_participant(newnick)
				except self.bridge.NoSuchParticipantException:
					self.nickname = newnick
					self.duplicate_nickname = newnick
					has_connection = self.bridge.bot.irc.has_connection(self.bridge.irc_server, self.bridge.irc_port, self.duplicate_nickname)
					if isinstance(self.irc_connection, ServerConnection):
						if not has_connection and self.irc_connection.used_by == 1:
							self.irc_connection.nick(newnick, callback=self._irc_nick_callback)
						else:
							self._close_irc_connection('Changed nickname')
							self.create_duplicate_on_irc()
					else:
						if self.irc_connection == 'both':
							self.bridge.add_participant('irc', oldnick)
						self.create_duplicate_on_irc()
					return
		
		elif self.protocol == 'irc':
			if on_protocol == 'irc':
				self._close_xmpp_connection('unwanted nick change')
				self.xmpp_c = 'unwanted nick change'
			
			else:
				try:
					p = self.bridge.get_participant(newnick)
				except self.bridge.NoSuchParticipantException:
					self.nickname = newnick
					self.duplicate_nickname = newnick
					if isinstance(self.muc, xmpp.muc):
						for b in self.bridge.bot.bridges:
							if b.has_participant(oldnick) and b.irc_server != self.bridge.irc_server:
								self.muc.leave(message='Changed nickname to "'+self.nickname+'"')
								self.xmpp_c = None
								self.bridge.bot.close_xmpp_connection(oldnick)
								self.create_duplicate_on_xmpp()
								return
						
						if not self.bridge.bot.xmpp_connections.has_key(newnick):
							if self.bridge.bot.xmpp_connections.has_key(oldnick):
								self.bridge.bot.xmpp_connections.pop(oldnick)
							self.bridge.bot.xmpp_connections[newnick] = self.xmpp_c
						
						self.muc.change_nick(newnick, status='From IRC', callback=self._xmpp_join_callback)
					else:
						if self.xmpp_c == 'both':
							self.bridge.add_participant('xmpp', oldnick)
						self.create_duplicate_on_xmpp()
					return
		
		self.nickname = newnick
		self.duplicate_nickname = newnick
		
		if not isinstance(p, Participant):
			return
		
		if p.nickname == newnick:
			if p.protocol == self.protocol:
				# should never happen
				raise Exception('WTF ?')
			else:
				self.set_both_sides()
		elif p.duplicate_nickname == newnick:
			if p.protocol != self.protocol:
				# should never happen
				raise Exception('WTF ?')
			else:
				if self.protocol == 'xmpp':
					self.irc_connection = p.irc_connection
					p.irc_connection = None
				else:
					self.xmpp_c = p.xmpp_c
					self.muc = p.muc
					p.xmpp_c = None
					p.muc = None
				p.duplicate_nickname = p._get_new_duplicate_nickname()
				p.create_duplicate_on_xmpp()
		else:
			# should never happen
			raise Exception('WTF ?')
	
	
	def say_on_irc(self, message):
		bot_say = False
		if message[:4] == '/me ':
			action = True
			message = message[4:]
		else:
			action = False
		if isinstance(self.irc_connection, ServerConnection):
			try:
				if action:
					self.irc_connection.action(self.bridge.irc_room, message)
				else:
					self.irc_connection.privmsg(self.bridge.irc_room, message)
			except ServerNotConnectedError:
				self.irc_connection.connect()
				bot_say = True
		elif not isinstance(self.xmpp_c, xmpp.client.Client):
			bot_say = True
		if bot_say:
			self.bridge.say_on_behalf(self.nickname, message, 'irc', action=action)
	
	
	def say_on_irc_to(self, to, message):
		error = False
		if isinstance(self.irc_connection, ServerConnection):
			try:
				self.irc_connection.privmsg(to, message)
			except ServerNotConnectedError:
				self.irc_connection.connect()
				error = True
		elif not isinstance(self.xmpp_c, xmpp.client.Client):
			error = True
		
		if error:
			if self.bridge.mode not in ['normal', 'bypass']:
				self.bridge.get_participant(to).say_on_xmpp_to(self.nickname, 'XIB error: Sorry but cross-protocol private messages are disabled in '+self.bridge.mode+' mode.')
			else:
				self.bridge.get_participant(to).say_on_xmpp_to(self.nickname, 'XIB error: Sorry but you cannot send cross-protocol private messages because I don\'t have an IRC duplicate with your nickname.')
	
	
	def say_on_xmpp(self, message, action=False):
		if isinstance(self.muc, xmpp.muc) and self.muc.state == self.muc.JOINED:
			self.muc.say(message, action=action)
		elif not isinstance(self.irc_connection, ServerConnection):
			self.bridge.say_on_behalf(self.nickname, message, 'xmpp', action=action)
	
	
	def say_on_xmpp_to(self, to, message, action=False):
		if isinstance(self.muc, xmpp.muc) and self.muc.state == self.muc.JOINED:
			self.muc.say_to(to, message, action=action)
		elif not isinstance(self.irc_connection, ServerConnection):
			if self.bridge.mode not in ['normal', 'bypass']:
				self.bridge.get_participant(to).say_on_xmpp_to(self.nickname, 'XIB error: Sorry but cross-protocol private messages are disabled in '+self.bridge.mode+' mode.')
			else:
				self.bridge.get_participant(to).say_on_xmpp_to(self.nickname, 'XIB error: Sorry but you cannot send cross-protocol private messages because I don\'t have an XMPP duplicate with your nickname.')
	
	
	def leave(self, message):
		if message == None:
			message = ''
		self.left = True
		self._close_xmpp_connection(message)
		self._close_irc_connection(message)
	
	
	def _close_xmpp_connection(self, message):
		if isinstance(self.muc, xmpp.muc):
			self.muc.leave(message)
			self.xmpp_c = None
			self.bridge.bot.close_xmpp_connection(self.nickname)
	
	
	def _close_irc_connection(self, message):
		if isinstance(self.irc_connection, ServerConnection):
			self.irc_connection.part(self.bridge.irc_room, message=message)
			self.irc_connection.used_by -= 1
			if self.irc_connection.used_by < 1:
				self.irc_connection.close(message)
			self.irc_connection = None
	
	
	def __str__(self):
		r = 'self.protocol='+str(self.protocol)+'\n'+'self.nickname='+str(self.nickname)
		if isinstance(self.irc_connection, ServerConnection):
			r += '\nself.irc_connection='+str(self.irc_connection)+'\n'+'self.irc_connection.logged_in='+str(self.irc_connection.logged_in)
		if isinstance(self.muc, xmpp.muc):
			r += '\nself.muc.state='+str(self.muc.state)
		return r
	
	
	def __del__(self):
		self.leave('')