Mercurial > xib
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)