changeset 103:23416c27b592

New command system Signed-off-by: Charly COSTE <changaco@changaco.net>
author Charly COSTE <changaco@changaco.net>
date Sat, 21 Nov 2009 16:26:09 +0100
parents b3eba9489329
children 60cc60f0058d
files bot.py bridge.py participant.py
diffstat 3 files changed, 264 insertions(+), 54 deletions(-) [+]
line wrap: on
line diff
--- a/bot.py
+++ b/bot.py
@@ -33,13 +33,17 @@ import re
 import sys
 import xml.parsers.expat
 import traceback
+from argparse_modified import *
+import shlex
 
 
 class bot(Thread):
+
+	commands = ['xmpp_participants', 'irc_participants', 'bridges']
+	admin_commands = ['add-bridge', 'add-xmpp-admin', 'halt', 'remove-bridge', 'restart-bot', 'restart-bridge']
 	
 	def __init__(self, jid, password, nickname, admins_jid=[], error_fd=sys.stderr, debug=False):
 		Thread.__init__(self)
-		self.commands = ['!xmpp_participants', '!irc_participants']
 		self.bare_jid = xmpp.protocol.JID(jid=jid)
 		self.bare_jid.setResource('')
 		self.nickname = nickname
@@ -131,7 +135,7 @@ class bot(Thread):
 		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:
+			if bare_jid == bridge.xmpp_room_jid:
 				# presence comes from a muc
 				resource = unicode(from_.getResource())
 				
@@ -143,16 +147,17 @@ class bot(Thread):
 				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 NoSuchParticipantException:
 							p = None
 						
-						x = presence.getTag('x', namespace='http://jabber.org/protocol/muc#user')
-						item = None
-						if x:
-							item = x.getTag('item')
 						if x and x.getTag('status', attrs={'code': '303'}):
 							# participant changed its nickname
 							if p == None:
@@ -227,7 +232,19 @@ class bot(Thread):
 								bridge.removeParticipant('xmpp', resource, presence.getStatus())
 					
 					elif resource != bridge.bot.nickname:
-						bridge.addParticipant('xmpp', resource)
+						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:
+							for jid in self.admins_jid:
+								if xmpp.protocol.JID(jid).bareMatch(real_jid):
+									p.bot_admin = True
+									break
+						
 						return
 					
 				return
@@ -257,13 +274,14 @@ class bot(Thread):
 			return
 		
 		if message.getType() == 'chat':
-			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:
+				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)
@@ -272,10 +290,40 @@ class bot(Thread):
 						
 					except NoSuchParticipantException:
 						if xmpp_c.nickname == self.nickname:
-							xmpp_c.send(xmpp.protocol.Message(to=message.getFrom(), body=self.respond(message.getBody(), participant=from_), typ='chat'))
+							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
@@ -298,7 +346,7 @@ class bot(Thread):
 			
 			room_jid = unicode(from_.getNode()+'@'+from_.getDomain())
 			for bridge in self.bridges:
-				if room_jid == bridge.xmpp_room.room_jid:
+				if room_jid == bridge.xmpp_room_jid:
 					resource = unicode(from_.getResource())
 					if resource == '':
 						# message comes from the room itself
@@ -600,6 +648,17 @@ class bot(Thread):
 		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]
@@ -610,7 +669,7 @@ class bot(Thread):
 			if irc_server != None and bridge.irc_server != irc_server:
 				bridges.remove(bridge)
 				continue
-			if xmpp_room_jid != None and bridge.xmpp_room.room_jid != xmpp_room_jid:
+			if xmpp_room_jid != None and bridge.xmpp_room_jid != xmpp_room_jid:
 				bridges.remove(bridge)
 				continue
 		return bridges
@@ -637,6 +696,8 @@ class bot(Thread):
 		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
 	
@@ -644,6 +705,9 @@ class bot(Thread):
 	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
@@ -659,6 +723,8 @@ class bot(Thread):
 		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
@@ -688,28 +754,146 @@ class bot(Thread):
 		bridge.__del__()
 	
 	
-	def respond(self, message, participant=None):
+	def respond(self, message, participant_=None, bot_admin=False):
 		ret = ''
-		if message.strip() == '!xmpp_participants':
-			if participant == None:
-				for bridge in self.bridges:
-					xmpp_participants_nicknames = bridge.get_participants_nicknames_list(protocols=['xmpp'])
-					ret += '\nparticipants on '+bridge.xmpp_room.room_jid+': '+' '.join(xmpp_participants_nicknames)
+		command = shlex.split(message)
+		args_array = []
+		if len(command) > 1:
+			args_array = command[1:]
+		command = command[0]
+		
+		if isinstance(participant_, participant) and bot_admin != participant_.bot_admin:
+			bot_admin = participant_.bot_admin
+		
+		if command == 'xmpp_participants':
+			if not isinstance(participant_, participant):
+				for b in self.bridges:
+					xmpp_participants_nicknames = b.get_participants_nicknames_list(protocols=['xmpp'])
+					ret += '\nparticipants on '+b.xmpp_room_jid+' ('+str(len(xmpp_participants_nicknames))+'): '+' '.join(xmpp_participants_nicknames)
+				return ret
+			else:
+				xmpp_participants_nicknames = participant_.bridge.get_participants_nicknames_list(protocols=['xmpp'])
+				return 'participants on '+participant_.bridge.xmpp_room_jid+' ('+str(len(xmpp_participants_nicknames))+'): '+' '.join(xmpp_participants_nicknames)
+		
+		elif command == 'irc_participants':
+			if not isinstance(participant_, participant):
+				for b in self.bridges:
+					irc_participants_nicknames = b.get_participants_nicknames_list(protocols=['irc'])
+					ret += '\nparticipants on '+b.irc_room+' at '+b.irc_server+' ('+str(len(irc_participants_nicknames))+'): '+' '.join(irc_participants_nicknames)
 				return ret
 			else:
-				xmpp_participants_nicknames = participant.bridge.get_participants_nicknames_list(protocols=['xmpp'])
-				return 'participants on '+participant.bridge.xmpp_room.room_jid+': '+' '.join(xmpp_participants_nicknames)
-		elif message.strip() == '!irc_participants':
-			if participant == None:
-				for bridge in self.bridges:
-					irc_participants_nicknames = bridge.get_participants_nicknames_list(protocols=['irc'])
-					ret += '\nparticipants on '+bridge.irc_room+' at '+bridge.irc_server+': '+' '.join(irc_participants_nicknames)
+				irc_participants_nicknames = participant_.bridge.get_participants_nicknames_list(protocols=['irc'])
+				return 'participants on '+participant_.bridge.irc_room+' at '+participant_.bridge.irc_server+' ('+str(len(irc_participants_nicknames))+'): '+' '.join(irc_participants_nicknames)
+		
+		elif command == 'bridges':
+			parser = ArgumentParser(prog=command)
+			parser.add_argument('--show-mode', default=False, action='store_true')
+			try:
+				args = parser.parse_args(args_array)
+			except ParseException as e:
+				return '\n'+e.args[1]
+			ret = 'List of bridges:'
+			for i, b in enumerate(self.bridges):
+				ret += '\n'+str(i+1)+' - '+str(b)
+				if args.show_mode:
+					ret += ' - '+b.mode+' mode'
+			return ret
+		
+		elif command in bot.admin_commands:
+			if bot_admin == False:
+				return 'You have to be a bot admin to use this command.'
+			
+			if command == 'add-bridge':
+				parser = ArgumentParser(prog=command)
+				parser.add_argument('xmpp_room_jid', type=str)
+				parser.add_argument('irc_chan', type=str)
+				parser.add_argument('irc_server', type=str)
+				parser.add_argument('--mode', choices=bridge._modes, default='normal')
+				parser.add_argument('--say-level', choices=bridge._say_levels, default='all')
+				parser.add_argument('--irc-port', type=int, default=6667)
+				try:
+					args = parser.parse_args(args_array)
+				except ParseException as e:
+					return '\n'+e.args[1]
+				
+				self.new_bridge(args.xmpp_room_jid, args.irc_chan, args.irc_server, args.mode, args.say_level, irc_port=args.irc_port)
+				
+				return 'Bridge added.'
+			
+			elif command == 'add-xmpp-admin':
+				parser = ArgumentParser(prog=command)
+				parser.add_argument('jid', type=str)
+				try:
+					args = parser.parse_args(args_array)
+				except ParseException as e:
+					return '\n'+e.args[1]
+				self.admins_jid.append(args.jid)
+				for b in self.bridges:
+					for p in b.participants:
+						if p.real_jid != None and xmpp.protocol.JID(args.jid).bareMatch(p.real_jid):
+							p.bot_admin = True
+				
+				return 'XMPP admin added.'
+				
+			elif command == 'restart-bot':
+				self.restart()
+				return 'Bot restarted.'
+			elif command == 'halt':
+				self.__del__()
+			
+			
+			elif command in ['remove-bridge', 'restart-bridge']:
+				# we need to know which bridge the command is for
+				if len(args_array) == 0:
+					if isinstance(participant_, participant):
+						b = participant_.bridge
+					else:
+						return 'You must specify a bridge. '+self.respond('bridges')
+				else:
+					try:
+						bn = int(args_array[0])
+						if bn < 1:
+							raise IndexError
+						b = self.bridges[bn-1]
+					except IndexError:
+						return 'Invalid bridge number "'+str(bn)+'". '+self.respond('bridges')
+					except ValueError:
+						bridges = self.findBridges(args_array)
+						if len(bridges) == 0:
+							return 'No bridge found matching "'+' '.join(args_array)+'". '+self.respond('bridges')
+						elif len(bridges) == 1:
+							b = bridges[0]
+						elif len(bridges) > 1:
+							return 'More than one bridge matches "'+' '.join(args_array)+'", please be more specific. '+self.respond('bridges')
+					
+				if command == 'remove-bridge':
+					self.removeBridge(b)
+					return 'Bridge removed.'
+				elif command == 'restart-bridge':
+					b.restart()
+					return 'Bridge restarted.'
+		
+		else:
+			ret = 'Error: "'+command+'" is not a valid command.\ncommands:  '+'  '.join(bot.commands)
+			if bot_admin == True:
+				return ret+'\n'+'admin commands:  '+'  '.join(bot.admin_commands)
+			else:
 				return ret
-			else:
-				irc_participants_nicknames = participant.bridge.get_participants_nicknames_list(protocols=['irc'])
-				return 'participants on '+participant.bridge.irc_room+' at '+participant.bridge.irc_server+': '+' '.join(irc_participants_nicknames)
-		else:
-			return 'commands: '+' '.join(self.commands)
+	
+	
+	def restart(self):
+		# Stop the bridges
+		for b in self.bridges:
+			b.stop(message='Restarting bridge')
+		
+		# Reopen the bot's XMPP connection
+		self.reopen_xmpp_connection(self.xmpp_c)
+		
+		# Restart the bridges
+		for b in self.bridges:
+			b.init2()
+		
+		sleep(1)
 	
 	
 	def __del__(self):
--- a/bridge.py
+++ b/bridge.py
@@ -37,6 +37,7 @@ class bridge:
 	_warning = 3
 	_error = 4
 	_nothing = 5
+	_say_levels = ['all', 'info', 'notice', 'warning', 'error', 'nothing']
 	_modes = ['normal', 'limited', 'minimal']
 	
 	
@@ -46,6 +47,7 @@ class bridge:
 		self.irc_server = irc_server
 		self.irc_port = irc_port
 		self.irc_room = irc_room.lower()
+		self.xmpp_room_jid = xmpp_room_jid
 		if hasattr(self.__class__, '_'+say_level):
 			self.say_level = getattr(self.__class__, '_'+say_level)
 		else:
@@ -57,9 +59,13 @@ class bridge:
 		
 		self.lock = threading.RLock()
 		
+		self.init2()
+	
+	
+	def init2(self):
 		# Join XMPP room
 		try:
-			self.xmpp_room = xmpp.muc(xmpp_room_jid)
+			self.xmpp_room = xmpp.muc(self.xmpp_room_jid)
 			self.xmpp_room.join(self.bot.xmpp_c, self.bot.nickname, callback=self._xmpp_join_callback)
 		except:
 			self.bot.error('[Error] joining XMPP room failed')
@@ -68,13 +74,13 @@ class bridge:
 		# Join IRC room
 		try:
 			self.irc_connections_limit = -1
-			self.irc_connection = self.bot.irc.server(irc_server, irc_port, self.bot.nickname)
+			self.irc_connection = self.bot.irc.server(self.irc_server, self.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 and a say_level of "'+say_level+'"')
+		self.bot.error('[Notice] bridge "'+str(self)+'" is running in '+self.mode+' mode and a say_level of "'+self._say_levels[self.say_level]+'"')
 	
 	
 	def _irc_nick_callback(self, error, arguments=[]):
@@ -125,7 +131,7 @@ class bridge:
 			self.bot.removeBridge(self)
 	
 	
-	def addParticipant(self, from_protocol, nickname):
+	def addParticipant(self, from_protocol, nickname, real_jid=None):
 		"""Add a participant to the bridge."""
 		if (from_protocol == 'irc' and nickname == self.irc_connection.get_nickname()) or (from_protocol == 'xmpp' and nickname == self.xmpp_room.nickname):
 			self.bot.error('===> Debug: not adding self ('+self.bot.nickname+') to bridge "'+str(self)+'"', debug=True)
@@ -152,7 +158,7 @@ class bridge:
 		self.lock.acquire()
 		self.bot.error('===> Debug: adding participant "'+nickname+'" from "'+from_protocol+'" to bridge "'+str(self)+'"', debug=True)
 		try:
-			p = participant(self, from_protocol, nickname)
+			p = participant(self, from_protocol, nickname, real_jid=real_jid)
 		except IOError:
 			self.bot.error('===> Debug: IOError while adding participant "'+nickname+'" from "'+from_protocol+'" to bridge "'+str(self)+'", reconnectiong ...', debug=True)
 			p.xmpp_c.reconnectAndReauth()
@@ -270,6 +276,16 @@ class bridge:
 			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 restart(self):
+		"""Restart the bridge"""
+		
+		# Stop the bridge
+		self.stop(message='Restarting bridge')
+		
+		# Recreate the bridge
+		self.init2()
+	
+	
 	def say(self, message, on_irc=True, on_xmpp=True):
 		"""Make the bot say something."""
 		if message[0] != '[':
@@ -288,6 +304,28 @@ class bridge:
 			self.irc_connection.privmsg(self.irc_room, message)
 	
 	
+	def stop(self, message='Stopping bridge'):
+		"""Stop the bridge"""
+		
+		# 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(message)
+		else:
+			self.irc_connection.part(self.irc_room, message=message)
+		
+		# Leave the MUC
+		self.xmpp_room.leave(message=message)
+		self.xmpp_room.__del__()
+		del self.xmpp_room
+		
+		# Delete participants objects
+		for p in self.participants:
+			p.leave(message)
+			del p
+		self.participants = []
+	
+	
 	def switchFromLimitedToNormalMode(self):
 		if self.mode != 'normal-limited':
 			return
@@ -318,22 +356,8 @@ class bridge:
 	
 	
 	def __str__(self):
-		return self.irc_room+'@'+self.irc_server+' <-> '+self.xmpp_room.room_jid
+		return self.irc_room+'@'+self.irc_server+' <-> '+self.xmpp_room_jid
 	
 	
 	def __del__(self):
-		# Delete participants objects
-		for p in self.participants:
-			p.leave('Removing bridge')
-			del p
-		del self.participants
-		
-		# Close IRC connection if not used by an other bridge, just leave the room otherwise
-		self.irc_connection.used_by -= 1
-		if self.irc_connection.used_by < 1:
-			self.irc_connection.close('Removing bridge')
-		else:
-			self.irc_connection.part(self.irc_room, message='Removing bridge')
-		
-		# Leave XMPP room
-		self.xmpp_room.leave('Removing bridge')
\ No newline at end of file
+		self.stop(message='Removing bridge')
--- a/participant.py
+++ b/participant.py
@@ -25,7 +25,9 @@ from time import sleep
 
 
 class participant:
-	def __init__(self, owner_bridge, protocol, nickname):
+	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
@@ -44,7 +46,7 @@ class participant:
 		if isinstance(self.xmpp_c, xmpp.client.Client) or isinstance(self.irc_connection, ServerConnection) or self.bridge.mode == 'minimal' or self.nickname == 'ChanServ':
 			return
 		self.xmpp_c = self.bridge.bot.get_xmpp_connection(self.nickname)
-		self.muc = xmpp.muc(self.bridge.xmpp_room.room_jid)
+		self.muc = xmpp.muc(self.bridge.xmpp_room_jid)
 		self.muc.join(self.xmpp_c, self.nickname, status='From IRC', callback=self._xmpp_join_callback)