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)