Mercurial > libervia-backend
comparison sat/plugins/plugin_xep_0045.py @ 2715:b35c84ea73cf
plugin XEP-0045: MAM implementation for MUC
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 07 Dec 2018 19:13:28 +0100 |
parents | d715d912afac |
children | bb6adaa580ee |
comparison
equal
deleted
inserted
replaced
2714:57eac4fd0ec0 | 2715:b35c84ea73cf |
---|---|
22 from sat.core.log import getLogger | 22 from sat.core.log import getLogger |
23 log = getLogger(__name__) | 23 log = getLogger(__name__) |
24 from twisted.internet import defer | 24 from twisted.internet import defer |
25 from twisted.words.protocols.jabber import jid | 25 from twisted.words.protocols.jabber import jid |
26 from twisted.python import failure | 26 from twisted.python import failure |
27 from dateutil.tz import tzutc | |
28 | 27 |
29 from sat.core import exceptions | 28 from sat.core import exceptions |
30 from sat.memory import memory | 29 from sat.memory import memory |
31 | 30 |
32 import calendar | |
33 import time | 31 import time |
34 import uuid | 32 import uuid |
35 import copy | 33 import copy |
36 | 34 |
37 from wokkel import muc, disco, iwokkel | 35 from wokkel import muc, disco, iwokkel |
38 from sat.tools import xml_tools | 36 from sat.tools import xml_tools |
39 | 37 |
40 from zope.interface import implements | 38 from zope.interface import implements |
39 | |
40 # XXX: mam and rsm come from sat_tmp.wokkel | |
41 from wokkel import rsm | |
42 from wokkel import mam | |
41 | 43 |
42 | 44 |
43 PLUGIN_INFO = { | 45 PLUGIN_INFO = { |
44 C.PI_NAME: "XEP-0045 Plugin", | 46 C.PI_NAME: "XEP-0045 Plugin", |
45 C.PI_IMPORT_NAME: "XEP-0045", | 47 C.PI_IMPORT_NAME: "XEP-0045", |
46 C.PI_TYPE: "XEP", | 48 C.PI_TYPE: "XEP", |
47 C.PI_PROTOCOLS: ["XEP-0045"], | 49 C.PI_PROTOCOLS: ["XEP-0045"], |
48 C.PI_DEPENDENCIES: [], | 50 C.PI_DEPENDENCIES: ["XEP-0359"], |
49 C.PI_RECOMMENDATIONS: [C.TEXT_CMDS], | 51 C.PI_RECOMMENDATIONS: [C.TEXT_CMDS, u"XEP-0313"], |
50 C.PI_MAIN: "XEP_0045", | 52 C.PI_MAIN: "XEP_0045", |
51 C.PI_HANDLER: "yes", | 53 C.PI_HANDLER: "yes", |
52 C.PI_DESCRIPTION: _("""Implementation of Multi-User Chat""") | 54 C.PI_DESCRIPTION: _("""Implementation of Multi-User Chat""") |
53 } | 55 } |
54 | 56 |
60 ENTITY_TYPE_MUC = "MUC" | 62 ENTITY_TYPE_MUC = "MUC" |
61 ROOM_STATE_OCCUPANTS = "occupants" | 63 ROOM_STATE_OCCUPANTS = "occupants" |
62 ROOM_STATE_SELF_PRESENCE = "self-presence" | 64 ROOM_STATE_SELF_PRESENCE = "self-presence" |
63 ROOM_STATE_LIVE = "live" | 65 ROOM_STATE_LIVE = "live" |
64 ROOM_STATES = (ROOM_STATE_OCCUPANTS, ROOM_STATE_SELF_PRESENCE, ROOM_STATE_LIVE) | 66 ROOM_STATES = (ROOM_STATE_OCCUPANTS, ROOM_STATE_SELF_PRESENCE, ROOM_STATE_LIVE) |
67 HISTORY_LEGACY = u"legacy" | |
68 HISTORY_MAM = u"mam" | |
65 | 69 |
66 | 70 |
67 CONFIG_SECTION = u'plugin muc' | 71 CONFIG_SECTION = u'plugin muc' |
68 | 72 |
69 default_conf = {"default_muc": u'sat@chat.jabberfr.org'} | 73 default_conf = {"default_muc": u'sat@chat.jabberfr.org'} |
107 log.info(_(u"Text commands not available")) | 111 log.info(_(u"Text commands not available")) |
108 else: | 112 else: |
109 self.text_cmds.registerTextCommands(self) | 113 self.text_cmds.registerTextCommands(self) |
110 self.text_cmds.addWhoIsCb(self._whois, 100) | 114 self.text_cmds.addWhoIsCb(self._whois, 100) |
111 | 115 |
116 self._mam = self.host.plugins.get(u"XEP-0313") | |
117 self._si = self.host.plugins[u"XEP-0359"] | |
118 | |
112 host.trigger.add("presence_available", self.presenceTrigger) | 119 host.trigger.add("presence_available", self.presenceTrigger) |
113 host.trigger.add("MessageReceived", self.messageReceivedTrigger, priority=1000000) | 120 host.trigger.add("MessageReceived", self.messageReceivedTrigger, priority=1000000) |
121 host.trigger.add("message_parse", self._message_parseTrigger) | |
114 | 122 |
115 def profileConnected(self, client): | 123 def profileConnected(self, client): |
116 def assign_service(service): | 124 def assign_service(service): |
117 client.muc_service = service | 125 client.muc_service = service |
118 return self.getMUCService(client).addCallback(assign_service) | 126 return self.getMUCService(client).addCallback(assign_service) |
127 | |
128 def _message_parseTrigger(self, client, message_elt, data): | |
129 """Add stanza-id from the room if present""" | |
130 if message_elt.getAttribute(u"type") != C.MESS_TYPE_GROUPCHAT: | |
131 return True | |
132 | |
133 # stanza_id will not be filled by parseMessage because the emitter | |
134 # is the room and not our server, so we have to parse it here | |
135 room_jid = data[u"from"].userhostJID() | |
136 stanza_id = self._si.getStanzaId(message_elt, room_jid) | |
137 if stanza_id: | |
138 data[u"extra"][u"stanza_id"] = stanza_id | |
119 | 139 |
120 def messageReceivedTrigger(self, client, message_elt, post_treat): | 140 def messageReceivedTrigger(self, client, message_elt, post_treat): |
121 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: | 141 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: |
122 if message_elt.subject or message_elt.delay: | 142 if message_elt.subject or message_elt.delay: |
123 return False | 143 return False |
124 from_jid = jid.JID(message_elt['from']) | 144 from_jid = jid.JID(message_elt['from']) |
125 room_jid = from_jid.userhostJID() | 145 room_jid = from_jid.userhostJID() |
126 if room_jid in client._muc_client.joined_rooms: | 146 if room_jid in client._muc_client.joined_rooms: |
127 room = client._muc_client.joined_rooms[room_jid] | 147 room = client._muc_client.joined_rooms[room_jid] |
128 if room.state != ROOM_STATE_LIVE: | 148 if room.state != ROOM_STATE_LIVE: |
129 log.warning(_(u"Received non delayed message in a room before its initialisation: state={state}, msg={msg}").format( | 149 if getattr(room, "_history_type", HISTORY_LEGACY) == HISTORY_LEGACY: |
150 # With MAM history, order is different, and we can get live | |
151 # messages before history is complete, so this is not a warning | |
152 # but an expected case. | |
153 # On the other hand, with legacy history, it's not normal. | |
154 log.warning(_( | |
155 u"Received non delayed message in a room before its " | |
156 u"initialisation: state={state}, msg={msg}").format( | |
130 state=room.state, | 157 state=room.state, |
131 msg=message_elt.toXml())) | 158 msg=message_elt.toXml())) |
132 room._cache.append(message_elt) | 159 room._cache.append(message_elt) |
133 return False | 160 return False |
134 else: | 161 else: |
135 log.warning(u"Received groupchat message for a room which has not been joined, ignoring it: {}".format(message_elt.toXml())) | 162 log.warning(u"Received groupchat message for a room which has not been " |
163 u"joined, ignoring it: {}".format(message_elt.toXml())) | |
136 return False | 164 return False |
137 return True | 165 return True |
138 | 166 |
139 def checkRoomJoined(self, client, room_jid): | 167 def checkRoomJoined(self, client, room_jid): |
140 """Check that given room has been joined in current session | 168 """Check that given room has been joined in current session |
195 # FIXME: the current behaviour is to create an instant room | 223 # FIXME: the current behaviour is to create an instant room |
196 # and send the signal only when the room is unlocked | 224 # and send the signal only when the room is unlocked |
197 # a proper configuration management should be done | 225 # a proper configuration management should be done |
198 log.debug(_(u"room locked !")) | 226 log.debug(_(u"room locked !")) |
199 d = client._muc_client.configure(room.roomJID, {}) | 227 d = client._muc_client.configure(room.roomJID, {}) |
200 d.addErrback(lambda dummy: log.error(_(u'Error while configuring the room'))) | 228 d.addErrback(lambda __: log.error(_(u'Error while configuring the room'))) |
201 return room.fully_joined | 229 return room.fully_joined |
202 | 230 |
203 def _joinEb(self, failure, client, room_jid, nick, password): | 231 def _joinEb(self, failure, client, room_jid, nick, password): |
204 """Called when something is going wrong when joining the room""" | 232 """Called when something is going wrong when joining the room""" |
205 try: | 233 try: |
578 self.text_cmds.feedBack(client, feedback, mess_data) | 606 self.text_cmds.feedBack(client, feedback, mess_data) |
579 return False | 607 return False |
580 | 608 |
581 d = self.kick(client, nick, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}) | 609 d = self.kick(client, nick, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}) |
582 | 610 |
583 def cb(dummy): | 611 def cb(__): |
584 feedback_msg = _(u'You have kicked {}').format(nick) | 612 feedback_msg = _(u'You have kicked {}').format(nick) |
585 if len(options) > 1: | 613 if len(options) > 1: |
586 feedback_msg += _(u' for the following reason: {}').format(options[1]) | 614 feedback_msg += _(u' for the following reason: {}').format(options[1]) |
587 self.text_cmds.feedBack(client, feedback_msg, mess_data) | 615 self.text_cmds.feedBack(client, feedback_msg, mess_data) |
588 return True | 616 return True |
607 self.text_cmds.feedBack(client, feedback, mess_data) | 635 self.text_cmds.feedBack(client, feedback, mess_data) |
608 return False | 636 return False |
609 | 637 |
610 d = self.ban(client, entity_jid, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}) | 638 d = self.ban(client, entity_jid, mess_data["to"], {} if len(options) == 1 else {'reason': options[1]}) |
611 | 639 |
612 def cb(dummy): | 640 def cb(__): |
613 feedback_msg = _(u'You have banned {}').format(entity_jid) | 641 feedback_msg = _(u'You have banned {}').format(entity_jid) |
614 if len(options) > 1: | 642 if len(options) > 1: |
615 feedback_msg += _(u' for the following reason: {}').format(options[1]) | 643 feedback_msg += _(u' for the following reason: {}').format(options[1]) |
616 self.text_cmds.feedBack(client, feedback_msg, mess_data) | 644 self.text_cmds.feedBack(client, feedback_msg, mess_data) |
617 return True | 645 return True |
646 self.text_cmds.feedBack(client, feedback, mess_data) | 674 self.text_cmds.feedBack(client, feedback, mess_data) |
647 return False | 675 return False |
648 | 676 |
649 d = self.affiliate(client, entity_jid, mess_data["to"], {'affiliation': affiliation}) | 677 d = self.affiliate(client, entity_jid, mess_data["to"], {'affiliation': affiliation}) |
650 | 678 |
651 def cb(dummy): | 679 def cb(__): |
652 feedback_msg = _(u'New affiliation for %(entity)s: %(affiliation)s').format(entity=entity_jid, affiliation=affiliation) | 680 feedback_msg = _(u'New affiliation for %(entity)s: %(affiliation)s').format(entity=entity_jid, affiliation=affiliation) |
653 self.text_cmds.feedBack(client, feedback_msg, mess_data) | 681 self.text_cmds.feedBack(client, feedback_msg, mess_data) |
654 return True | 682 return True |
655 d.addCallback(cb) | 683 d.addCallback(cb) |
656 return d | 684 return d |
742 class SatMUCClient(muc.MUCClient): | 770 class SatMUCClient(muc.MUCClient): |
743 implements(iwokkel.IDisco) | 771 implements(iwokkel.IDisco) |
744 | 772 |
745 def __init__(self, plugin_parent): | 773 def __init__(self, plugin_parent): |
746 self.plugin_parent = plugin_parent | 774 self.plugin_parent = plugin_parent |
747 self.host = plugin_parent.host | |
748 muc.MUCClient.__init__(self) | 775 muc.MUCClient.__init__(self) |
749 self._changing_nicks = set() # used to keep trace of who is changing nick, | 776 self._changing_nicks = set() # used to keep trace of who is changing nick, |
750 # and to discard userJoinedRoom signal in this case | 777 # and to discard userJoinedRoom signal in this case |
751 print "init SatMUCClient OK" | 778 print "init SatMUCClient OK" |
752 | 779 |
753 @property | 780 @property |
754 def joined_rooms(self): | 781 def joined_rooms(self): |
755 return self._rooms | 782 return self._rooms |
783 | |
784 @property | |
785 def host(self): | |
786 return self.plugin_parent.host | |
787 | |
788 @property | |
789 def client(self): | |
790 return self.parent | |
791 | |
792 @property | |
793 def _mam(self): | |
794 return self.plugin_parent._mam | |
795 | |
796 @property | |
797 def _si(self): | |
798 return self.plugin_parent._si | |
756 | 799 |
757 def changeRoomState(self, room, new_state): | 800 def changeRoomState(self, room, new_state): |
758 """Check that room is in expected state, and change it | 801 """Check that room is in expected state, and change it |
759 | 802 |
760 @param new_state: one of ROOM_STATE_* | 803 @param new_state: one of ROOM_STATE_* |
766 raise exceptions.InternalError("unexpected new room state ({room}): {state}".format( | 809 raise exceptions.InternalError("unexpected new room state ({room}): {state}".format( |
767 room=room.userhost(), | 810 room=room.userhost(), |
768 state=new_state)) | 811 state=new_state)) |
769 expected_state = ROOM_STATES[new_state_idx-1] | 812 expected_state = ROOM_STATES[new_state_idx-1] |
770 if room.state != expected_state: | 813 if room.state != expected_state: |
771 log.warning(_(u"room {room} is not in expected state: room is in state {current_state} while we were expecting {expected_state}").format( | 814 log.error(_( |
815 u"room {room} is not in expected state: room is in state {current_state} " | |
816 u"while we were expecting {expected_state}").format( | |
772 room=room.userhost(), | 817 room=room.userhost(), |
773 current_state=room.state, | 818 current_state=room.state, |
774 expected_state=expected_state)) | 819 expected_state=expected_state)) |
775 room.state = new_state | 820 room.state = new_state |
776 | 821 |
777 def _addRoom(self, room): | 822 def _addRoom(self, room): |
778 super(SatMUCClient, self)._addRoom(room) | 823 super(SatMUCClient, self)._addRoom(room) |
779 room._roster_ok = False # True when occupants list has been fully received | 824 room._roster_ok = False # True when occupants list has been fully received |
780 room.state = ROOM_STATE_OCCUPANTS | 825 room.state = ROOM_STATE_OCCUPANTS |
781 room._history_d = defer.Deferred() # used to send bridge signal once backlog are written in history | |
782 room._history_d.callback(None) | |
783 # FIXME: check if history_d is not redundant with fully_joined | 826 # FIXME: check if history_d is not redundant with fully_joined |
784 room.fully_joined = defer.Deferred() # called when everything is OK | 827 room.fully_joined = defer.Deferred() # called when everything is OK |
785 # cache data until room is ready | 828 # cache data until room is ready |
786 # list of elements which will be re-injected in stream | 829 # list of elements which will be re-injected in stream |
787 room._cache = [] | 830 room._cache = [] |
788 | 831 |
789 @defer.inlineCallbacks | 832 @defer.inlineCallbacks |
790 def join(self, room_jid, nick, password=None): | 833 def _joinLegacy(self, client, room_jid, nick, password): |
791 mess_data_list = yield self.host.memory.historyGet(self.parent.jid.userhostJID(), | 834 """Join room an retrieve history with legacy method""" |
792 room_jid, | 835 mess_data_list = yield self.host.memory.historyGet(room_jid, |
793 1, | 836 client.jid.userhostJID(), |
794 True, | 837 limit=1, |
795 profile=self.parent.profile) | 838 between=True, |
839 profile=client.profile) | |
796 if mess_data_list: | 840 if mess_data_list: |
797 timestamp = mess_data_list[0][1] | 841 timestamp = mess_data_list[0][1] |
798 # we use seconds since last message to get backlog without duplicates | 842 # we use seconds since last message to get backlog without duplicates |
799 # and we remove 1 second to avoid getting the last message again | 843 # and we remove 1 second to avoid getting the last message again |
800 seconds = int(time.time() - timestamp) - 1 | 844 seconds = int(time.time() - timestamp) - 1 |
801 else: | 845 else: |
802 seconds = None | 846 seconds = None |
803 room = yield super(SatMUCClient, self).join(room_jid, nick, muc.HistoryOptions(seconds=seconds), password) | 847 |
848 room = yield super(SatMUCClient, self).join( | |
849 room_jid, nick, muc.HistoryOptions(seconds=seconds), password) | |
850 # used to send bridge signal once backlog are written in history | |
851 room._history_type = HISTORY_LEGACY | |
852 room._history_d = defer.Deferred() | |
853 room._history_d.callback(None) | |
804 defer.returnValue(room) | 854 defer.returnValue(room) |
855 | |
856 @defer.inlineCallbacks | |
857 def _joinMAM(self, client, room_jid, nick, password): | |
858 """Join room and retrieve history using MAM""" | |
859 room = yield super(SatMUCClient, self).join( | |
860 # we don't want any history from room as we'll get it with MAM | |
861 room_jid, nick, muc.HistoryOptions(maxStanzas=0), password=password) | |
862 room._history_type = HISTORY_MAM | |
863 room._history_d = defer.Deferred() | |
864 | |
865 last_mess = yield self.host.memory.historyGet(room_jid, | |
866 None, | |
867 limit=1, | |
868 between=False, | |
869 filters={u'last_stanza_id': True}, | |
870 profile=client.profile) | |
871 if last_mess: | |
872 stanza_id = last_mess[0][-1][u'stanza_id'] | |
873 rsm_req = rsm.RSMRequest(after=stanza_id) | |
874 else: | |
875 log.info(u"We have no MAM archive for room {room_jid}.".format( | |
876 room_jid=room_jid)) | |
877 rsm_req = rsm.RSMRequest(max_=20) | |
878 | |
879 mam_req = mam.MAMRequest(rsm_=rsm_req) | |
880 mam_data = yield self._mam.getArchives(client, mam_req, | |
881 service=room_jid) | |
882 elt_list, rsm_response = mam_data | |
883 | |
884 if not elt_list: | |
885 log.info(_(u"No message received while offline in {room_jid}".format( | |
886 room_jid=room_jid))) | |
887 else: | |
888 log.info( | |
889 _(u"We have received {num_mess} message(s) in {room_jid} while offline.") | |
890 .format(num_mess=len(elt_list), room_jid=room_jid)) | |
891 | |
892 for mess_elt in elt_list: | |
893 try: | |
894 fwd_message_elt = self._mam.getMessageFromResult( | |
895 client, mess_elt, mam_req, service=room_jid) | |
896 except exceptions.DataError: | |
897 continue | |
898 if fwd_message_elt.getAttribute(u"to"): | |
899 log.warning( | |
900 u'Forwarded message element has a "to" attribute while it is ' | |
901 u'forbidden by specifications') | |
902 fwd_message_elt[u"to"] = client.jid.full() | |
903 mess_data = client.messageProt.parseMessage(fwd_message_elt) | |
904 # we attache parsed message data to element, to avoid parsing | |
905 # again in _addToHistory | |
906 fwd_message_elt._mess_data = mess_data | |
907 # and we inject to MUC workflow | |
908 client._muc_client._onGroupChat(fwd_message_elt) | |
909 | |
910 | |
911 # for legacy history, the following steps are done in receivedSubject but for MAM | |
912 # the order is different (we have to join then get MAM archive, so subject | |
913 # is received before archive), so we change state and add the callbacks here. | |
914 self.changeRoomState(room, ROOM_STATE_LIVE) | |
915 room._history_d.addCallbacks(self._historyCb, self._historyEb, [room], | |
916 errbackArgs=[room]) | |
917 | |
918 # callback is done now that all needed Deferred have been added to _history_d | |
919 room._history_d.callback(None) | |
920 | |
921 defer.returnValue(room) | |
922 | |
923 def join(self, room_jid, nick, password=None): | |
924 if (not self._mam or not self.host.hasFeature(self.client, | |
925 mam.NS_MAM, room_jid)): | |
926 return self._joinLegacy(self.client, room_jid, nick, password) | |
927 else: | |
928 return self._joinMAM(self.client, room_jid, nick, password) | |
805 | 929 |
806 ## presence/roster ## | 930 ## presence/roster ## |
807 | 931 |
808 def availableReceived(self, presence): | 932 def availableReceived(self, presence): |
809 """ | 933 """ |
863 if user.nick == room.nick: | 987 if user.nick == room.nick: |
864 # we have received our own nick, this mean that the full room roster was received | 988 # we have received our own nick, this mean that the full room roster was received |
865 self.changeRoomState(room, ROOM_STATE_SELF_PRESENCE) | 989 self.changeRoomState(room, ROOM_STATE_SELF_PRESENCE) |
866 log.debug(u"room {room} joined with nick {nick}".format(room=room.occupantJID.userhost(), nick=user.nick)) | 990 log.debug(u"room {room} joined with nick {nick}".format(room=room.occupantJID.userhost(), nick=user.nick)) |
867 # We set type so we don't have to use a deferred with disco to check entity type | 991 # We set type so we don't have to use a deferred with disco to check entity type |
868 self.host.memory.updateEntityData(room.roomJID, C.ENTITY_TYPE, ENTITY_TYPE_MUC, profile_key=self.parent.profile) | 992 self.host.memory.updateEntityData(room.roomJID, C.ENTITY_TYPE, ENTITY_TYPE_MUC, profile_key=self.client.profile) |
869 elif room.state not in (ROOM_STATE_OCCUPANTS, ROOM_STATE_LIVE): | 993 elif room.state not in (ROOM_STATE_OCCUPANTS, ROOM_STATE_LIVE): |
870 log.warning(u"Received user presence data in a room before its initialisation (current state: {state})," | 994 log.warning(u"Received user presence data in a room before its initialisation (current state: {state})," |
871 "this is not standard! Ignoring it: {room} ({nick})".format( | 995 "this is not standard! Ignoring it: {room} ({nick})".format( |
872 state=room.state, | 996 state=room.state, |
873 room=room.roomJID.userhost(), | 997 room=room.roomJID.userhost(), |
879 try: | 1003 try: |
880 self._changing_nicks.remove(user.nick) | 1004 self._changing_nicks.remove(user.nick) |
881 except KeyError: | 1005 except KeyError: |
882 # this is a new user | 1006 # this is a new user |
883 log.debug(_(u"user {nick} has joined room {room_id}").format(nick=user.nick, room_id=room.occupantJID.userhost())) | 1007 log.debug(_(u"user {nick} has joined room {room_id}").format(nick=user.nick, room_id=room.occupantJID.userhost())) |
884 if not self.host.trigger.point("MUC user joined", room, user, self.parent.profile): | 1008 if not self.host.trigger.point("MUC user joined", room, user, self.client.profile): |
885 return | 1009 return |
886 | 1010 |
887 extra = {'info_type': ROOM_USER_JOINED, | 1011 extra = {'info_type': ROOM_USER_JOINED, |
888 'user_affiliation': user.affiliation, | 1012 'user_affiliation': user.affiliation, |
889 'user_role': user.role, | 1013 'user_role': user.role, |
891 } | 1015 } |
892 if user.entity is not None: | 1016 if user.entity is not None: |
893 extra['user_entity'] = user.entity.full() | 1017 extra['user_entity'] = user.entity.full() |
894 mess_data = { # dict is similar to the one used in client.onMessage | 1018 mess_data = { # dict is similar to the one used in client.onMessage |
895 "from": room.roomJID, | 1019 "from": room.roomJID, |
896 "to": self.parent.jid, | 1020 "to": self.client.jid, |
897 "uid": unicode(uuid.uuid4()), | 1021 "uid": unicode(uuid.uuid4()), |
898 "message": {'': D_(u"=> {} has joined the room").format(user.nick)}, | 1022 "message": {'': D_(u"=> {} has joined the room").format(user.nick)}, |
899 "subject": {}, | 1023 "subject": {}, |
900 "type": C.MESS_TYPE_INFO, | 1024 "type": C.MESS_TYPE_INFO, |
901 "extra": extra, | 1025 "extra": extra, |
902 "timestamp": time.time(), | 1026 "timestamp": time.time(), |
903 } | 1027 } |
904 # FIXME: we disable presence in history as it's taking a lot of space | 1028 # FIXME: we disable presence in history as it's taking a lot of space |
905 # while not indispensable. In the future an option may allow | 1029 # while not indispensable. In the future an option may allow |
906 # to re-enable it | 1030 # to re-enable it |
907 # self.parent.messageAddToHistory(mess_data) | 1031 # self.client.messageAddToHistory(mess_data) |
908 self.parent.messageSendToBridge(mess_data) | 1032 self.client.messageSendToBridge(mess_data) |
909 | 1033 |
910 | 1034 |
911 def userLeftRoom(self, room, user): | 1035 def userLeftRoom(self, room, user): |
912 if not self.host.trigger.point("MUC user left", room, user, self.parent.profile): | 1036 if not self.host.trigger.point("MUC user left", room, user, self.client.profile): |
913 return | 1037 return |
914 if user.nick == room.nick: | 1038 if user.nick == room.nick: |
915 # we left the room | 1039 # we left the room |
916 room_jid_s = room.roomJID.userhost() | 1040 room_jid_s = room.roomJID.userhost() |
917 log.info(_(u"Room ({room}) left ({profile})").format( | 1041 log.info(_(u"Room ({room}) left ({profile})").format( |
918 room = room_jid_s, profile = self.parent.profile)) | 1042 room = room_jid_s, profile = self.client.profile)) |
919 self.host.memory.delEntityCache(room.roomJID, profile_key=self.parent.profile) | 1043 self.host.memory.delEntityCache(room.roomJID, profile_key=self.client.profile) |
920 self.host.bridge.mucRoomLeft(room.roomJID.userhost(), self.parent.profile) | 1044 self.host.bridge.mucRoomLeft(room.roomJID.userhost(), self.client.profile) |
921 elif room.state != ROOM_STATE_LIVE: | 1045 elif room.state != ROOM_STATE_LIVE: |
922 log.warning(u"Received user presence data in a room before its initialisation (current state: {state})," | 1046 log.warning(u"Received user presence data in a room before its initialisation (current state: {state})," |
923 "this is not standard! Ignoring it: {room} ({nick})".format( | 1047 "this is not standard! Ignoring it: {room} ({nick})".format( |
924 state=room.state, | 1048 state=room.state, |
925 room=room.roomJID.userhost(), | 1049 room=room.roomJID.userhost(), |
936 } | 1060 } |
937 if user.entity is not None: | 1061 if user.entity is not None: |
938 extra['user_entity'] = user.entity.full() | 1062 extra['user_entity'] = user.entity.full() |
939 mess_data = { # dict is similar to the one used in client.onMessage | 1063 mess_data = { # dict is similar to the one used in client.onMessage |
940 "from": room.roomJID, | 1064 "from": room.roomJID, |
941 "to": self.parent.jid, | 1065 "to": self.client.jid, |
942 "uid": unicode(uuid.uuid4()), | 1066 "uid": unicode(uuid.uuid4()), |
943 "message": {'': D_(u"<= {} has left the room").format(user.nick)}, | 1067 "message": {'': D_(u"<= {} has left the room").format(user.nick)}, |
944 "subject": {}, | 1068 "subject": {}, |
945 "type": C.MESS_TYPE_INFO, | 1069 "type": C.MESS_TYPE_INFO, |
946 "extra": extra, | 1070 "extra": extra, |
947 "timestamp": time.time(), | 1071 "timestamp": time.time(), |
948 } | 1072 } |
949 # FIXME: disable history, see userJoinRoom comment | 1073 # FIXME: disable history, see userJoinRoom comment |
950 # self.parent.messageAddToHistory(mess_data) | 1074 # self.client.messageAddToHistory(mess_data) |
951 self.parent.messageSendToBridge(mess_data) | 1075 self.client.messageSendToBridge(mess_data) |
952 | 1076 |
953 def userChangedNick(self, room, user, new_nick): | 1077 def userChangedNick(self, room, user, new_nick): |
954 self.host.bridge.mucRoomUserChangedNick(room.roomJID.userhost(), user.nick, new_nick, self.parent.profile) | 1078 self.host.bridge.mucRoomUserChangedNick(room.roomJID.userhost(), user.nick, new_nick, self.client.profile) |
955 | 1079 |
956 def userUpdatedStatus(self, room, user, show, status): | 1080 def userUpdatedStatus(self, room, user, show, status): |
957 self.host.bridge.presenceUpdate(room.roomJID.userhost() + '/' + user.nick, show or '', 0, {C.PRESENCE_STATUSES_DEFAULT: status or ''}, self.parent.profile) | 1081 self.host.bridge.presenceUpdate(room.roomJID.userhost() + '/' + user.nick, show or '', 0, {C.PRESENCE_STATUSES_DEFAULT: status or ''}, self.client.profile) |
958 | 1082 |
959 ## messages ## | 1083 ## messages ## |
960 | 1084 |
961 def receivedGroupChat(self, room, user, body): | 1085 def receivedGroupChat(self, room, user, body): |
962 log.debug(u'receivedGroupChat: room=%s user=%s body=%s' % (room.roomJID.full(), user, body)) | 1086 log.debug(u'receivedGroupChat: room=%s user=%s body=%s' % (room.roomJID.full(), user, body)) |
963 | 1087 |
964 def _addToHistory(self, dummy, user, message): | 1088 def _addToHistory(self, __, user, message): |
965 # we check if message is not in history | 1089 try: |
966 # and raise ConflictError else | 1090 # message can be already parsed (with MAM), in this case mess_data |
967 stamp = message.delay.stamp.astimezone(tzutc()).timetuple() | 1091 # it attached to the element |
968 timestamp = float(calendar.timegm(stamp)) | 1092 mess_data = message.element._mess_data |
969 data = { # dict is similar to the one used in client.onMessage | 1093 except AttributeError: |
970 "from": message.sender, | 1094 mess_data = self.client.messageProt.parseMessage(message.element) |
971 "to": message.recipient, | 1095 if mess_data[u'message'] or mess_data[u'subject']: |
972 "uid": unicode(uuid.uuid4()), | 1096 return self.host.memory.addToHistory(self.client, mess_data) |
973 "type": C.MESS_TYPE_GROUPCHAT, | |
974 "extra": {}, | |
975 "timestamp": timestamp, | |
976 "received_timestamp": unicode(time.time()), | |
977 } | |
978 # FIXME: message and subject don't handle xml:lang | |
979 data['message'] = {'': message.body} if message.body is not None else {} | |
980 data['subject'] = {'': message.subject} if message.subject is not None else {} | |
981 | |
982 if data['message'] or data['subject']: | |
983 return self.host.memory.addToHistory(self.parent, data) | |
984 else: | 1097 else: |
985 return defer.succeed(None) | 1098 return defer.succeed(None) |
986 | 1099 |
987 def _addToHistoryEb(self, failure): | 1100 def _addToHistoryEb(self, failure): |
988 failure.trap(exceptions.CancelError) | 1101 failure.trap(exceptions.CancelError) |
996 @param user(muc.User, None): the user that sent the message | 1109 @param user(muc.User, None): the user that sent the message |
997 None if the message come from the room | 1110 None if the message come from the room |
998 @param message(muc.GroupChat): the parsed message | 1111 @param message(muc.GroupChat): the parsed message |
999 """ | 1112 """ |
1000 if room.state != ROOM_STATE_SELF_PRESENCE: | 1113 if room.state != ROOM_STATE_SELF_PRESENCE: |
1001 log.warning(_(u"received history in unexpected state in room {room} (state: {state})").format( | 1114 log.warning(_( |
1002 room = room.userhost(), | 1115 u"received history in unexpected state in room {room} (state: " |
1003 state = room.state)) | 1116 u"{state})").format(room = room.roomJID.userhost(), |
1117 state = room.state)) | |
1004 room._history_d.addCallback(self._addToHistory, user, message) | 1118 room._history_d.addCallback(self._addToHistory, user, message) |
1005 room._history_d.addErrback(self._addToHistoryEb) | 1119 room._history_d.addErrback(self._addToHistoryEb) |
1006 | 1120 |
1007 ## subject ## | 1121 ## subject ## |
1008 | 1122 |
1028 self.receivedHistory(room, user, message) | 1142 self.receivedHistory(room, user, message) |
1029 | 1143 |
1030 def subject(self, room, subject): | 1144 def subject(self, room, subject): |
1031 return muc.MUCClientProtocol.subject(self, room, subject) | 1145 return muc.MUCClientProtocol.subject(self, room, subject) |
1032 | 1146 |
1033 def _historyCb(self, dummy, room): | 1147 def _historyCb(self, __, room): |
1034 """Called when history have been written to database and subject is received | 1148 """Called when history have been written to database and subject is received |
1035 | 1149 |
1036 this method will finish joining by: | 1150 this method will finish joining by: |
1037 - sending message to bridge | 1151 - sending message to bridge |
1038 - calling fully_joined deferred | 1152 - calling fully_joined deferred |
1039 - sending stanza put in cache | 1153 - sending stanza put in cache |
1040 - cleaning variables not needed anymore | 1154 - cleaning variables not needed anymore |
1041 """ | 1155 """ |
1042 args = self.plugin_parent._getRoomJoinedArgs(room, self.parent.profile) | 1156 args = self.plugin_parent._getRoomJoinedArgs(room, self.client.profile) |
1043 self.host.bridge.mucRoomJoined(*args) | 1157 self.host.bridge.mucRoomJoined(*args) |
1044 room.fully_joined.callback(room) | 1158 room.fully_joined.callback(room) |
1045 del room._history_d | 1159 del room._history_d |
1160 del room._history_type | |
1046 cache = room._cache | 1161 cache = room._cache |
1047 del room._cache | 1162 del room._cache |
1048 for elem in cache: | 1163 for elem in cache: |
1049 self.parent.xmlstream.dispatch(elem) | 1164 self.client.xmlstream.dispatch(elem) |
1050 | 1165 |
1051 def _historyEb(self, failure_, room): | 1166 def _historyEb(self, failure_, room): |
1052 log.error(u"Error while managing history: {}".format(failure_)) | 1167 log.error(u"Error while managing history: {}".format(failure_)) |
1053 self._historyCb(None, room) | 1168 self._historyCb(None, room) |
1054 | 1169 |
1055 def receivedSubject(self, room, user, subject): | 1170 def receivedSubject(self, room, user, subject): |
1056 # when subject is received, we know that we have whole roster and history | 1171 # when subject is received, we know that we have whole roster and history |
1057 # cf. http://xmpp.org/extensions/xep-0045.html#enter-subject | 1172 # cf. http://xmpp.org/extensions/xep-0045.html#enter-subject |
1058 room.subject = subject # FIXME: subject doesn't handle xml:lang | 1173 room.subject = subject # FIXME: subject doesn't handle xml:lang |
1059 if room.state != ROOM_STATE_LIVE: | 1174 if room.state != ROOM_STATE_LIVE: |
1060 self.changeRoomState(room, ROOM_STATE_LIVE) | 1175 if room._history_type == HISTORY_LEGACY: |
1061 room._history_d.addCallbacks(self._historyCb, self._historyEb, [room], errbackArgs=[room]) | 1176 self.changeRoomState(room, ROOM_STATE_LIVE) |
1177 room._history_d.addCallbacks(self._historyCb, self._historyEb, [room], errbackArgs=[room]) | |
1062 else: | 1178 else: |
1063 # the subject has been changed | 1179 # the subject has been changed |
1064 log.debug(_(u"New subject for room ({room_id}): {subject}").format(room_id = room.roomJID.full(), subject = subject)) | 1180 log.debug(_(u"New subject for room ({room_id}): {subject}").format(room_id = room.roomJID.full(), subject = subject)) |
1065 self.host.bridge.mucRoomNewSubject(room.roomJID.userhost(), subject, self.parent.profile) | 1181 self.host.bridge.mucRoomNewSubject(room.roomJID.userhost(), subject, self.client.profile) |
1066 | 1182 |
1067 ## disco ## | 1183 ## disco ## |
1068 | 1184 |
1069 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): | 1185 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): |
1070 return [disco.DiscoFeature(NS_MUC)] | 1186 return [disco.DiscoFeature(NS_MUC)] |