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()))