Mercurial > libervia-backend
comparison sat/plugins/plugin_xep_0045.py @ 3060:730bbed77a89
plugin XEP-0045: join / MAM history improvements:
- fixed use of history_d deferred so MAM history is stored in database as soon as possible
and in right order
- get MAM history with pages of 20 messages instead of 100
- if a message can't be parsed, an error message is logged (with a dump of the stanza),
and the message is ignored, avoiding a crash and the impossibility the join the room at
all
- a new `mucRoomPrepareJoin` signal, containing room jid and profile, is sent when a room
joining is started, this allow better UX as the frontend can show the room immediately,
and lock it with a waiting message until it is fully available. The `mucRoomJoined` is
still sent when the room is fully joined/avaiable, meaning that the frontend can unlock
it and let the user interact with it
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 18 Oct 2019 14:45:07 +0200 |
parents | ab2696e34d29 |
children | 16925f494820 |
comparison
equal
deleted
inserted
replaced
3059:aa728dc7b0ce | 3060:730bbed77a89 |
---|---|
84 | 84 |
85 def __init__(self, host): | 85 def __init__(self, host): |
86 log.info(_("Plugin XEP_0045 initialization")) | 86 log.info(_("Plugin XEP_0045 initialization")) |
87 self.host = host | 87 self.host = host |
88 self._sessions = memory.Sessions() | 88 self._sessions = memory.Sessions() |
89 host.bridge.addMethod("mucJoin", ".plugin", in_sign='ssa{ss}s', out_sign='(bsa{sa{ss}}sss)', method=self._join, async_=True) # return same arguments as mucRoomJoined + a boolean set to True is the room was already joined (first argument) | 89 # return same arguments as mucRoomJoined + a boolean set to True is the room was |
90 host.bridge.addMethod("mucNick", ".plugin", in_sign='sss', out_sign='', method=self._nick) | 90 # already joined (first argument) |
91 host.bridge.addMethod("mucNickGet", ".plugin", in_sign='ss', out_sign='s', method=self._getRoomNick) | 91 host.bridge.addMethod( |
92 host.bridge.addMethod("mucLeave", ".plugin", in_sign='ss', out_sign='', method=self._leave, async_=True) | 92 "mucJoin", ".plugin", in_sign='ssa{ss}s', out_sign='(bsa{sa{ss}}sss)', |
93 host.bridge.addMethod("mucOccupantsGet", ".plugin", in_sign='ss', out_sign='a{sa{ss}}', method=self._getRoomOccupants) | 93 method=self._join, async_=True) |
94 host.bridge.addMethod("mucSubject", ".plugin", in_sign='sss', out_sign='', method=self._subject) | 94 host.bridge.addMethod( |
95 host.bridge.addMethod("mucGetRoomsJoined", ".plugin", in_sign='s', out_sign='a(sa{sa{ss}}ss)', method=self._getRoomsJoined) | 95 "mucNick", ".plugin", in_sign='sss', out_sign='', method=self._nick) |
96 host.bridge.addMethod("mucGetUniqueRoomName", ".plugin", in_sign='ss', out_sign='s', method=self._getUniqueName) | 96 host.bridge.addMethod( |
97 host.bridge.addMethod("mucConfigureRoom", ".plugin", in_sign='ss', out_sign='s', method=self._configureRoom, async_=True) | 97 "mucNickGet", ".plugin", in_sign='ss', out_sign='s', method=self._getRoomNick) |
98 host.bridge.addMethod("mucGetDefaultService", ".plugin", in_sign='', out_sign='s', method=self.getDefaultMUC) | 98 host.bridge.addMethod( |
99 host.bridge.addMethod("mucGetService", ".plugin", in_sign='ss', out_sign='s', method=self._getMUCService, async_=True) | 99 "mucLeave", ".plugin", in_sign='ss', out_sign='', method=self._leave, |
100 host.bridge.addSignal("mucRoomJoined", ".plugin", signature='sa{sa{ss}}sss') # args: room_jid, occupants, user_nick, subject, profile | 100 async_=True) |
101 host.bridge.addSignal("mucRoomLeft", ".plugin", signature='ss') # args: room_jid, profile | 101 host.bridge.addMethod( |
102 host.bridge.addSignal("mucRoomUserChangedNick", ".plugin", signature='ssss') # args: room_jid, old_nick, new_nick, profile | 102 "mucOccupantsGet", ".plugin", in_sign='ss', out_sign='a{sa{ss}}', |
103 host.bridge.addSignal("mucRoomNewSubject", ".plugin", signature='sss') # args: room_jid, subject, profile | 103 method=self._getRoomOccupants) |
104 self.__submit_conf_id = host.registerCallback(self._submitConfiguration, with_data=True) | 104 host.bridge.addMethod( |
105 "mucSubject", ".plugin", in_sign='sss', out_sign='', method=self._subject) | |
106 host.bridge.addMethod( | |
107 "mucGetRoomsJoined", ".plugin", in_sign='s', out_sign='a(sa{sa{ss}}ss)', | |
108 method=self._getRoomsJoined) | |
109 host.bridge.addMethod( | |
110 "mucGetUniqueRoomName", ".plugin", in_sign='ss', out_sign='s', | |
111 method=self._getUniqueName) | |
112 host.bridge.addMethod( | |
113 "mucConfigureRoom", ".plugin", in_sign='ss', out_sign='s', | |
114 method=self._configureRoom, async_=True) | |
115 host.bridge.addMethod( | |
116 "mucGetDefaultService", ".plugin", in_sign='', out_sign='s', | |
117 method=self.getDefaultMUC) | |
118 host.bridge.addMethod( | |
119 "mucGetService", ".plugin", in_sign='ss', out_sign='s', | |
120 method=self._getMUCService, async_=True) | |
121 # called when a room will be joined but must be locked until join is received | |
122 # (room is prepared, history is getting retrieved) | |
123 # args: room_jid, profile | |
124 host.bridge.addSignal( | |
125 "mucRoomPrepareJoin", ".plugin", signature='ss') | |
126 # args: room_jid, occupants, user_nick, subject, profile | |
127 host.bridge.addSignal( | |
128 "mucRoomJoined", ".plugin", signature='sa{sa{ss}}sss') | |
129 # args: room_jid, profile | |
130 host.bridge.addSignal( | |
131 "mucRoomLeft", ".plugin", signature='ss') | |
132 # args: room_jid, old_nick, new_nick, profile | |
133 host.bridge.addSignal( | |
134 "mucRoomUserChangedNick", ".plugin", signature='ssss') | |
135 # args: room_jid, subject, profile | |
136 host.bridge.addSignal( | |
137 "mucRoomNewSubject", ".plugin", signature='sss') | |
138 self.__submit_conf_id = host.registerCallback( | |
139 self._submitConfiguration, with_data=True) | |
105 self._room_join_id = host.registerCallback(self._UIRoomJoinCb, with_data=True) | 140 self._room_join_id = host.registerCallback(self._UIRoomJoinCb, with_data=True) |
106 host.importMenu((D_("MUC"), D_("configure")), self._configureRoomMenu, security_limit=0, help_string=D_("Configure Multi-User Chat room"), type_=C.MENU_ROOM) | 141 host.importMenu( |
142 (D_("MUC"), D_("configure")), self._configureRoomMenu, security_limit=0, | |
143 help_string=D_("Configure Multi-User Chat room"), type_=C.MENU_ROOM) | |
107 try: | 144 try: |
108 self.text_cmds = self.host.plugins[C.TEXT_CMDS] | 145 self.text_cmds = self.host.plugins[C.TEXT_CMDS] |
109 except KeyError: | 146 except KeyError: |
110 log.info(_("Text commands not available")) | 147 log.info(_("Text commands not available")) |
111 else: | 148 else: |
485 log.info(_('{profile} is already in room {room_jid}').format( | 522 log.info(_('{profile} is already in room {room_jid}').format( |
486 profile=client.profile, room_jid = room_jid.userhost())) | 523 profile=client.profile, room_jid = room_jid.userhost())) |
487 return defer.fail(AlreadyJoined(room)) | 524 return defer.fail(AlreadyJoined(room)) |
488 log.info(_("[{profile}] is joining room {room} with nick {nick}").format( | 525 log.info(_("[{profile}] is joining room {room} with nick {nick}").format( |
489 profile=client.profile, room=room_jid.userhost(), nick=nick)) | 526 profile=client.profile, room=room_jid.userhost(), nick=nick)) |
527 self.host.bridge.mucRoomPrepareJoin(room_jid.userhost(), client.profile) | |
490 | 528 |
491 password = options.get("password") | 529 password = options.get("password") |
492 | 530 |
493 d = client._muc_client.join(room_jid, nick, password) | 531 d = client._muc_client.join(room_jid, nick, password) |
494 d.addCallbacks(self._joinCb, self._joinEb, | 532 d.addCallbacks(self._joinCb, self._joinEb, |
909 """Join room and retrieve history using MAM""" | 947 """Join room and retrieve history using MAM""" |
910 room = yield super(SatMUCClient, self).join( | 948 room = yield super(SatMUCClient, self).join( |
911 # we don't want any history from room as we'll get it with MAM | 949 # we don't want any history from room as we'll get it with MAM |
912 room_jid, nick, muc.HistoryOptions(maxStanzas=0), password=password) | 950 room_jid, nick, muc.HistoryOptions(maxStanzas=0), password=password) |
913 room._history_type = HISTORY_MAM | 951 room._history_type = HISTORY_MAM |
914 room._history_d = defer.Deferred() | 952 history_d = room._history_d = defer.Deferred() |
953 # we trigger now the deferred so all callback are processed as soon as possible | |
954 # and in order | |
955 history_d.callback(None) | |
915 | 956 |
916 last_mess = yield self.host.memory.historyGet( | 957 last_mess = yield self.host.memory.historyGet( |
917 room_jid, | 958 room_jid, |
918 None, | 959 None, |
919 limit=1, | 960 limit=1, |
922 'types': C.MESS_TYPE_GROUPCHAT, | 963 'types': C.MESS_TYPE_GROUPCHAT, |
923 'last_stanza_id': True}, | 964 'last_stanza_id': True}, |
924 profile=client.profile) | 965 profile=client.profile) |
925 if last_mess: | 966 if last_mess: |
926 stanza_id = last_mess[0][-1]['stanza_id'] | 967 stanza_id = last_mess[0][-1]['stanza_id'] |
927 rsm_req = rsm.RSMRequest(max_=100, after=stanza_id) | 968 rsm_req = rsm.RSMRequest(max_=20, after=stanza_id) |
928 no_loop=False | 969 no_loop=False |
929 else: | 970 else: |
930 log.info("We have no MAM archive for room {room_jid}.".format( | 971 log.info("We have no MAM archive for room {room_jid}.".format( |
931 room_jid=room_jid)) | 972 room_jid=room_jid)) |
932 # we don't want the whole archive if we have no archive yet | 973 # we don't want the whole archive if we have no archive yet |
959 if fwd_message_elt.getAttribute("to"): | 1000 if fwd_message_elt.getAttribute("to"): |
960 log.warning( | 1001 log.warning( |
961 'Forwarded message element has a "to" attribute while it is ' | 1002 'Forwarded message element has a "to" attribute while it is ' |
962 'forbidden by specifications') | 1003 'forbidden by specifications') |
963 fwd_message_elt["to"] = client.jid.full() | 1004 fwd_message_elt["to"] = client.jid.full() |
964 mess_data = client.messageProt.parseMessage(fwd_message_elt) | 1005 try: |
1006 mess_data = client.messageProt.parseMessage(fwd_message_elt) | |
1007 except Exception as e: | |
1008 log.error( | |
1009 f"Can't parse message, ignoring it: {e}\n" | |
1010 f"{fwd_message_elt.toXml()}" | |
1011 ) | |
1012 continue | |
965 # we attache parsed message data to element, to avoid parsing | 1013 # we attache parsed message data to element, to avoid parsing |
966 # again in _addToHistory | 1014 # again in _addToHistory |
967 fwd_message_elt._mess_data = mess_data | 1015 fwd_message_elt._mess_data = mess_data |
968 # and we inject to MUC workflow | 1016 # and we inject to MUC workflow |
969 client._muc_client._onGroupChat(fwd_message_elt) | 1017 client._muc_client._onGroupChat(fwd_message_elt) |
979 | 1027 |
980 # for legacy history, the following steps are done in receivedSubject but for MAM | 1028 # for legacy history, the following steps are done in receivedSubject but for MAM |
981 # the order is different (we have to join then get MAM archive, so subject | 1029 # the order is different (we have to join then get MAM archive, so subject |
982 # is received before archive), so we change state and add the callbacks here. | 1030 # is received before archive), so we change state and add the callbacks here. |
983 self.changeRoomState(room, ROOM_STATE_LIVE) | 1031 self.changeRoomState(room, ROOM_STATE_LIVE) |
984 room._history_d.addCallbacks(self._historyCb, self._historyEb, [room], | 1032 history_d.addCallbacks(self._historyCb, self._historyEb, [room], |
985 errbackArgs=[room]) | 1033 errbackArgs=[room]) |
986 | 1034 |
987 # callback is done now that all needed Deferred have been added to _history_d | 1035 # we wait for all callbacks to be processed |
988 room._history_d.callback(None) | 1036 yield history_d |
989 | 1037 |
990 defer.returnValue(room) | 1038 defer.returnValue(room) |
991 | 1039 |
992 @defer.inlineCallbacks | 1040 @defer.inlineCallbacks |
993 def join(self, room_jid, nick, password=None): | 1041 def join(self, room_jid, nick, password=None): |
1241 A group chat message has been received from a MUC room. | 1289 A group chat message has been received from a MUC room. |
1242 | 1290 |
1243 There are a few event methods that may get called here. | 1291 There are a few event methods that may get called here. |
1244 L{receivedGroupChat}, L{receivedSubject} or L{receivedHistory}. | 1292 L{receivedGroupChat}, L{receivedSubject} or L{receivedHistory}. |
1245 """ | 1293 """ |
1246 # We override this method to fix subject handling | 1294 # We override this method to fix subject handling (empty strings were discarded) |
1247 # FIXME: remove this merge fixed upstream | 1295 # FIXME: remove this once fixed upstream |
1248 room, user = self._getRoomUser(message) | 1296 room, user = self._getRoomUser(message) |
1249 | 1297 |
1250 if room is None: | 1298 if room is None: |
1251 log.warning("No room found for message: {message}" | 1299 log.warning("No room found for message: {message}" |
1252 .format(message=message.toElement().toXml())) | 1300 .format(message=message.toElement().toXml())) |