Mercurial > xib
comparison bot.py @ 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 | 29d3b85c6286 |
children | d8acff763731 |
comparison
equal
deleted
inserted
replaced
102:b3eba9489329 | 103:23416c27b592 |
---|---|
31 from time import sleep | 31 from time import sleep |
32 import re | 32 import re |
33 import sys | 33 import sys |
34 import xml.parsers.expat | 34 import xml.parsers.expat |
35 import traceback | 35 import traceback |
36 from argparse_modified import * | |
37 import shlex | |
36 | 38 |
37 | 39 |
38 class bot(Thread): | 40 class bot(Thread): |
41 | |
42 commands = ['xmpp_participants', 'irc_participants', 'bridges'] | |
43 admin_commands = ['add-bridge', 'add-xmpp-admin', 'halt', 'remove-bridge', 'restart-bot', 'restart-bridge'] | |
39 | 44 |
40 def __init__(self, jid, password, nickname, admins_jid=[], error_fd=sys.stderr, debug=False): | 45 def __init__(self, jid, password, nickname, admins_jid=[], error_fd=sys.stderr, debug=False): |
41 Thread.__init__(self) | 46 Thread.__init__(self) |
42 self.commands = ['!xmpp_participants', '!irc_participants'] | |
43 self.bare_jid = xmpp.protocol.JID(jid=jid) | 47 self.bare_jid = xmpp.protocol.JID(jid=jid) |
44 self.bare_jid.setResource('') | 48 self.bare_jid.setResource('') |
45 self.nickname = nickname | 49 self.nickname = nickname |
46 self.password = password | 50 self.password = password |
47 self.error_fd = error_fd | 51 self.error_fd = error_fd |
129 self.error(presence.__str__(fancy=1), debug=True) | 133 self.error(presence.__str__(fancy=1), debug=True) |
130 | 134 |
131 from_ = xmpp.protocol.JID(presence.getFrom()) | 135 from_ = xmpp.protocol.JID(presence.getFrom()) |
132 bare_jid = unicode(from_.getNode()+'@'+from_.getDomain()) | 136 bare_jid = unicode(from_.getNode()+'@'+from_.getDomain()) |
133 for bridge in self.bridges: | 137 for bridge in self.bridges: |
134 if bare_jid == bridge.xmpp_room.room_jid: | 138 if bare_jid == bridge.xmpp_room_jid: |
135 # presence comes from a muc | 139 # presence comes from a muc |
136 resource = unicode(from_.getResource()) | 140 resource = unicode(from_.getResource()) |
137 | 141 |
138 if resource == '': | 142 if resource == '': |
139 # presence comes from the muc itself | 143 # presence comes from the muc itself |
140 # TODO: handle room deletion and muc server reboot | 144 # TODO: handle room deletion and muc server reboot |
141 pass | 145 pass |
142 | 146 |
143 else: | 147 else: |
144 # presence comes from a participant of the muc | 148 # presence comes from a participant of the muc |
149 | |
150 x = presence.getTag('x', namespace='http://jabber.org/protocol/muc#user') | |
151 item = None | |
152 if x: | |
153 item = x.getTag('item') | |
145 | 154 |
146 if presence.getType() == 'unavailable': | 155 if presence.getType() == 'unavailable': |
147 try: | 156 try: |
148 p = bridge.getParticipant(resource) | 157 p = bridge.getParticipant(resource) |
149 except NoSuchParticipantException: | 158 except NoSuchParticipantException: |
150 p = None | 159 p = None |
151 | 160 |
152 x = presence.getTag('x', namespace='http://jabber.org/protocol/muc#user') | |
153 item = None | |
154 if x: | |
155 item = x.getTag('item') | |
156 if x and x.getTag('status', attrs={'code': '303'}): | 161 if x and x.getTag('status', attrs={'code': '303'}): |
157 # participant changed its nickname | 162 # participant changed its nickname |
158 if p == None: | 163 if p == None: |
159 return | 164 return |
160 if p.protocol != 'xmpp': | 165 if p.protocol != 'xmpp': |
225 # participant left | 230 # participant left |
226 if p != None: | 231 if p != None: |
227 bridge.removeParticipant('xmpp', resource, presence.getStatus()) | 232 bridge.removeParticipant('xmpp', resource, presence.getStatus()) |
228 | 233 |
229 elif resource != bridge.bot.nickname: | 234 elif resource != bridge.bot.nickname: |
230 bridge.addParticipant('xmpp', resource) | 235 real_jid = None |
236 if item and item.has_attr('jid'): | |
237 real_jid = item.getAttr('jid') | |
238 | |
239 p = bridge.addParticipant('xmpp', resource, real_jid) | |
240 | |
241 # if we have the real jid check if the participant is a bot admin | |
242 if real_jid: | |
243 for jid in self.admins_jid: | |
244 if xmpp.protocol.JID(jid).bareMatch(real_jid): | |
245 p.bot_admin = True | |
246 break | |
247 | |
231 return | 248 return |
232 | 249 |
233 return | 250 return |
234 | 251 |
235 | 252 |
255 | 272 |
256 if message.getBody() == None: | 273 if message.getBody() == None: |
257 return | 274 return |
258 | 275 |
259 if message.getType() == 'chat': | 276 if message.getType() == 'chat': |
260 self.error('==> Debug: Received XMPP chat message.', debug=True) | |
261 self.error(message.__str__(fancy=1), debug=True) | |
262 from_bare_jid = unicode(message.getFrom().getNode()+'@'+message.getFrom().getDomain()) | 277 from_bare_jid = unicode(message.getFrom().getNode()+'@'+message.getFrom().getDomain()) |
263 for bridge in self.bridges: | 278 for bridge in self.bridges: |
264 if from_bare_jid == bridge.xmpp_room.room_jid: | 279 if from_bare_jid == bridge.xmpp_room_jid: |
265 # message comes from a room participant | 280 # message comes from a room participant |
281 | |
282 self.error('==> Debug: Received XMPP chat message.', debug=True) | |
283 self.error(message.__str__(fancy=1), debug=True) | |
266 | 284 |
267 try: | 285 try: |
268 from_ = bridge.getParticipant(message.getFrom().getResource()) | 286 from_ = bridge.getParticipant(message.getFrom().getResource()) |
269 to_ = bridge.getParticipant(xmpp_c.nickname) | 287 to_ = bridge.getParticipant(xmpp_c.nickname) |
270 | 288 |
271 from_.sayOnIRCTo(to_.nickname, message.getBody()) | 289 from_.sayOnIRCTo(to_.nickname, message.getBody()) |
272 | 290 |
273 except NoSuchParticipantException: | 291 except NoSuchParticipantException: |
274 if xmpp_c.nickname == self.nickname: | 292 if xmpp_c.nickname == self.nickname: |
275 xmpp_c.send(xmpp.protocol.Message(to=message.getFrom(), body=self.respond(message.getBody(), participant=from_), typ='chat')) | 293 r = self.respond(str(message.getBody()), participant_=from_) |
294 if isinstance(r, basestring) and len(r) > 0: | |
295 s = xmpp.protocol.Message(to=message.getFrom(), body=r, typ='chat') | |
296 self.error('==> Debug: Sending', debug=True) | |
297 self.error(s.__str__(fancy=1), debug=True) | |
298 xmpp_c.send(s) | |
299 else: | |
300 self.error('=> Debug: won\'t answer.', debug=True) | |
276 return | 301 return |
277 self.error('=> Debug: XMPP chat message not relayed', debug=True) | 302 self.error('=> Debug: XMPP chat message not relayed', debug=True) |
278 return | 303 return |
304 | |
305 # message does not come from a room | |
306 if xmpp_c.nickname == self.nickname: | |
307 self.error('==> Debug: Received XMPP chat message.', debug=True) | |
308 self.error(message.__str__(fancy=1), debug=True) | |
309 | |
310 # Find out if the message comes from a bot admin | |
311 bot_admin = False | |
312 for jid in self.admins_jid: | |
313 if xmpp.protocol.JID(jid).bareMatch(message.getFrom()): | |
314 bot_admin = True | |
315 break | |
316 | |
317 # Respond | |
318 r = self.respond(str(message.getBody()), bot_admin=bot_admin) | |
319 if isinstance(r, basestring) and len(r) > 0: | |
320 s = xmpp.protocol.Message(to=message.getFrom(), body=r, typ='chat') | |
321 self.error('==> Debug: Sending', debug=True) | |
322 self.error(s.__str__(fancy=1), debug=True) | |
323 xmpp_c.send(s) | |
324 | |
325 else: | |
326 self.error('=> Debug: Ignoring XMPP chat message not received on bot connection.', debug=True) | |
279 | 327 |
280 elif message.getType() == 'groupchat': | 328 elif message.getType() == 'groupchat': |
281 # message comes from a room | 329 # message comes from a room |
282 | 330 |
283 for child in message.getChildren(): | 331 for child in message.getChildren(): |
296 self.error('=> Debug: Ignoring XMPP MUC message sent by self.', debug=True) | 344 self.error('=> Debug: Ignoring XMPP MUC message sent by self.', debug=True) |
297 return | 345 return |
298 | 346 |
299 room_jid = unicode(from_.getNode()+'@'+from_.getDomain()) | 347 room_jid = unicode(from_.getNode()+'@'+from_.getDomain()) |
300 for bridge in self.bridges: | 348 for bridge in self.bridges: |
301 if room_jid == bridge.xmpp_room.room_jid: | 349 if room_jid == bridge.xmpp_room_jid: |
302 resource = unicode(from_.getResource()) | 350 resource = unicode(from_.getResource()) |
303 if resource == '': | 351 if resource == '': |
304 # message comes from the room itself | 352 # message comes from the room itself |
305 self.error('=> Debug: Ignoring XMPP groupchat message sent by the room.', debug=True) | 353 self.error('=> Debug: Ignoring XMPP groupchat message sent by the room.', debug=True) |
306 return | 354 return |
598 b = bridge(self, xmpp_room, irc_room, irc_server, mode, say_level, irc_port=irc_port) | 646 b = bridge(self, xmpp_room, irc_room, irc_server, mode, say_level, irc_port=irc_port) |
599 self.bridges.append(b) | 647 self.bridges.append(b) |
600 return b | 648 return b |
601 | 649 |
602 | 650 |
651 def findBridges(self, str_array): | |
652 # TODO: lock self.bridges for thread safety | |
653 bridges = [b for b in self.bridges] | |
654 for bridge in [b for b in bridges]: | |
655 for s in str_array: | |
656 if not s in str(bridge): | |
657 bridges.remove(bridge) | |
658 break | |
659 return bridges | |
660 | |
661 | |
603 def getBridges(self, irc_room=None, irc_server=None, xmpp_room_jid=None): | 662 def getBridges(self, irc_room=None, irc_server=None, xmpp_room_jid=None): |
604 # TODO: lock self.bridges for thread safety | 663 # TODO: lock self.bridges for thread safety |
605 bridges = [b for b in self.bridges] | 664 bridges = [b for b in self.bridges] |
606 for bridge in [b for b in bridges]: | 665 for bridge in [b for b in bridges]: |
607 if irc_room != None and bridge.irc_room != irc_room: | 666 if irc_room != None and bridge.irc_room != irc_room: |
608 bridges.remove(bridge) | 667 bridges.remove(bridge) |
609 continue | 668 continue |
610 if irc_server != None and bridge.irc_server != irc_server: | 669 if irc_server != None and bridge.irc_server != irc_server: |
611 bridges.remove(bridge) | 670 bridges.remove(bridge) |
612 continue | 671 continue |
613 if xmpp_room_jid != None and bridge.xmpp_room.room_jid != xmpp_room_jid: | 672 if xmpp_room_jid != None and bridge.xmpp_room_jid != xmpp_room_jid: |
614 bridges.remove(bridge) | 673 bridges.remove(bridge) |
615 continue | 674 continue |
616 return bridges | 675 return bridges |
617 | 676 |
618 | 677 |
635 c.auth(self.bare_jid.getNode(), self.password) | 694 c.auth(self.bare_jid.getNode(), self.password) |
636 c.RegisterHandler('presence', self._xmpp_presence_handler) | 695 c.RegisterHandler('presence', self._xmpp_presence_handler) |
637 c.RegisterHandler('iq', self._xmpp_iq_handler) | 696 c.RegisterHandler('iq', self._xmpp_iq_handler) |
638 c.RegisterHandler('message', self._xmpp_message_handler) | 697 c.RegisterHandler('message', self._xmpp_message_handler) |
639 c.sendInitPresence() | 698 c.sendInitPresence() |
699 if nickname == self.nickname: | |
700 c.send(xmpp.protocol.Presence(priority=127)) | |
640 c.lock.release() | 701 c.lock.release() |
641 return c | 702 return c |
642 | 703 |
643 | 704 |
644 def reopen_xmpp_connection(self, c): | 705 def reopen_xmpp_connection(self, c): |
645 if not isinstance(c, xmpp.client.Client): | 706 if not isinstance(c, xmpp.client.Client): |
646 return | 707 return |
708 bot_connection = False | |
709 if c == self.xmpp_c: | |
710 bot_connection = True | |
647 mucs = c.mucs | 711 mucs = c.mucs |
648 nickname = c.nickname | 712 nickname = c.nickname |
649 used_by = c.used_by | 713 used_by = c.used_by |
650 participants = [] | 714 participants = [] |
651 for b in self.bridges: | 715 for b in self.bridges: |
657 self.xmpp_connections.pop(nickname) | 721 self.xmpp_connections.pop(nickname) |
658 c.send(xmpp.protocol.Presence(typ='unavailable')) | 722 c.send(xmpp.protocol.Presence(typ='unavailable')) |
659 del c | 723 del c |
660 c = self.get_xmpp_connection(nickname) | 724 c = self.get_xmpp_connection(nickname) |
661 c.used_by = used_by | 725 c.used_by = used_by |
726 if bot_connection: | |
727 self.xmpp_c = c | |
662 for p in participants: | 728 for p in participants: |
663 p.xmpp_c = c | 729 p.xmpp_c = c |
664 c.mucs = mucs | 730 c.mucs = mucs |
665 for m in c.mucs: | 731 for m in c.mucs: |
666 m.rejoin() | 732 m.rejoin() |
686 def removeBridge(self, bridge): | 752 def removeBridge(self, bridge): |
687 self.bridges.remove(bridge) | 753 self.bridges.remove(bridge) |
688 bridge.__del__() | 754 bridge.__del__() |
689 | 755 |
690 | 756 |
691 def respond(self, message, participant=None): | 757 def respond(self, message, participant_=None, bot_admin=False): |
692 ret = '' | 758 ret = '' |
693 if message.strip() == '!xmpp_participants': | 759 command = shlex.split(message) |
694 if participant == None: | 760 args_array = [] |
695 for bridge in self.bridges: | 761 if len(command) > 1: |
696 xmpp_participants_nicknames = bridge.get_participants_nicknames_list(protocols=['xmpp']) | 762 args_array = command[1:] |
697 ret += '\nparticipants on '+bridge.xmpp_room.room_jid+': '+' '.join(xmpp_participants_nicknames) | 763 command = command[0] |
764 | |
765 if isinstance(participant_, participant) and bot_admin != participant_.bot_admin: | |
766 bot_admin = participant_.bot_admin | |
767 | |
768 if command == 'xmpp_participants': | |
769 if not isinstance(participant_, participant): | |
770 for b in self.bridges: | |
771 xmpp_participants_nicknames = b.get_participants_nicknames_list(protocols=['xmpp']) | |
772 ret += '\nparticipants on '+b.xmpp_room_jid+' ('+str(len(xmpp_participants_nicknames))+'): '+' '.join(xmpp_participants_nicknames) | |
698 return ret | 773 return ret |
699 else: | 774 else: |
700 xmpp_participants_nicknames = participant.bridge.get_participants_nicknames_list(protocols=['xmpp']) | 775 xmpp_participants_nicknames = participant_.bridge.get_participants_nicknames_list(protocols=['xmpp']) |
701 return 'participants on '+participant.bridge.xmpp_room.room_jid+': '+' '.join(xmpp_participants_nicknames) | 776 return 'participants on '+participant_.bridge.xmpp_room_jid+' ('+str(len(xmpp_participants_nicknames))+'): '+' '.join(xmpp_participants_nicknames) |
702 elif message.strip() == '!irc_participants': | 777 |
703 if participant == None: | 778 elif command == 'irc_participants': |
704 for bridge in self.bridges: | 779 if not isinstance(participant_, participant): |
705 irc_participants_nicknames = bridge.get_participants_nicknames_list(protocols=['irc']) | 780 for b in self.bridges: |
706 ret += '\nparticipants on '+bridge.irc_room+' at '+bridge.irc_server+': '+' '.join(irc_participants_nicknames) | 781 irc_participants_nicknames = b.get_participants_nicknames_list(protocols=['irc']) |
782 ret += '\nparticipants on '+b.irc_room+' at '+b.irc_server+' ('+str(len(irc_participants_nicknames))+'): '+' '.join(irc_participants_nicknames) | |
707 return ret | 783 return ret |
708 else: | 784 else: |
709 irc_participants_nicknames = participant.bridge.get_participants_nicknames_list(protocols=['irc']) | 785 irc_participants_nicknames = participant_.bridge.get_participants_nicknames_list(protocols=['irc']) |
710 return 'participants on '+participant.bridge.irc_room+' at '+participant.bridge.irc_server+': '+' '.join(irc_participants_nicknames) | 786 return 'participants on '+participant_.bridge.irc_room+' at '+participant_.bridge.irc_server+' ('+str(len(irc_participants_nicknames))+'): '+' '.join(irc_participants_nicknames) |
787 | |
788 elif command == 'bridges': | |
789 parser = ArgumentParser(prog=command) | |
790 parser.add_argument('--show-mode', default=False, action='store_true') | |
791 try: | |
792 args = parser.parse_args(args_array) | |
793 except ParseException as e: | |
794 return '\n'+e.args[1] | |
795 ret = 'List of bridges:' | |
796 for i, b in enumerate(self.bridges): | |
797 ret += '\n'+str(i+1)+' - '+str(b) | |
798 if args.show_mode: | |
799 ret += ' - '+b.mode+' mode' | |
800 return ret | |
801 | |
802 elif command in bot.admin_commands: | |
803 if bot_admin == False: | |
804 return 'You have to be a bot admin to use this command.' | |
805 | |
806 if command == 'add-bridge': | |
807 parser = ArgumentParser(prog=command) | |
808 parser.add_argument('xmpp_room_jid', type=str) | |
809 parser.add_argument('irc_chan', type=str) | |
810 parser.add_argument('irc_server', type=str) | |
811 parser.add_argument('--mode', choices=bridge._modes, default='normal') | |
812 parser.add_argument('--say-level', choices=bridge._say_levels, default='all') | |
813 parser.add_argument('--irc-port', type=int, default=6667) | |
814 try: | |
815 args = parser.parse_args(args_array) | |
816 except ParseException as e: | |
817 return '\n'+e.args[1] | |
818 | |
819 self.new_bridge(args.xmpp_room_jid, args.irc_chan, args.irc_server, args.mode, args.say_level, irc_port=args.irc_port) | |
820 | |
821 return 'Bridge added.' | |
822 | |
823 elif command == 'add-xmpp-admin': | |
824 parser = ArgumentParser(prog=command) | |
825 parser.add_argument('jid', type=str) | |
826 try: | |
827 args = parser.parse_args(args_array) | |
828 except ParseException as e: | |
829 return '\n'+e.args[1] | |
830 self.admins_jid.append(args.jid) | |
831 for b in self.bridges: | |
832 for p in b.participants: | |
833 if p.real_jid != None and xmpp.protocol.JID(args.jid).bareMatch(p.real_jid): | |
834 p.bot_admin = True | |
835 | |
836 return 'XMPP admin added.' | |
837 | |
838 elif command == 'restart-bot': | |
839 self.restart() | |
840 return 'Bot restarted.' | |
841 elif command == 'halt': | |
842 self.__del__() | |
843 | |
844 | |
845 elif command in ['remove-bridge', 'restart-bridge']: | |
846 # we need to know which bridge the command is for | |
847 if len(args_array) == 0: | |
848 if isinstance(participant_, participant): | |
849 b = participant_.bridge | |
850 else: | |
851 return 'You must specify a bridge. '+self.respond('bridges') | |
852 else: | |
853 try: | |
854 bn = int(args_array[0]) | |
855 if bn < 1: | |
856 raise IndexError | |
857 b = self.bridges[bn-1] | |
858 except IndexError: | |
859 return 'Invalid bridge number "'+str(bn)+'". '+self.respond('bridges') | |
860 except ValueError: | |
861 bridges = self.findBridges(args_array) | |
862 if len(bridges) == 0: | |
863 return 'No bridge found matching "'+' '.join(args_array)+'". '+self.respond('bridges') | |
864 elif len(bridges) == 1: | |
865 b = bridges[0] | |
866 elif len(bridges) > 1: | |
867 return 'More than one bridge matches "'+' '.join(args_array)+'", please be more specific. '+self.respond('bridges') | |
868 | |
869 if command == 'remove-bridge': | |
870 self.removeBridge(b) | |
871 return 'Bridge removed.' | |
872 elif command == 'restart-bridge': | |
873 b.restart() | |
874 return 'Bridge restarted.' | |
875 | |
711 else: | 876 else: |
712 return 'commands: '+' '.join(self.commands) | 877 ret = 'Error: "'+command+'" is not a valid command.\ncommands: '+' '.join(bot.commands) |
878 if bot_admin == True: | |
879 return ret+'\n'+'admin commands: '+' '.join(bot.admin_commands) | |
880 else: | |
881 return ret | |
882 | |
883 | |
884 def restart(self): | |
885 # Stop the bridges | |
886 for b in self.bridges: | |
887 b.stop(message='Restarting bridge') | |
888 | |
889 # Reopen the bot's XMPP connection | |
890 self.reopen_xmpp_connection(self.xmpp_c) | |
891 | |
892 # Restart the bridges | |
893 for b in self.bridges: | |
894 b.init2() | |
895 | |
896 sleep(1) | |
713 | 897 |
714 | 898 |
715 def __del__(self): | 899 def __del__(self): |
716 for bridge in self.bridges: | 900 for bridge in self.bridges: |
717 self.removeBridge(bridge) | 901 self.removeBridge(bridge) |