Mercurial > libervia-backend
comparison libervia/backend/plugins/plugin_xep_0424.py @ 4199:7eda7cb8a15c
plugin XEP-0424: fix ID used and recipients
author | Goffi <goffi@goffi.org> |
---|---|
date | Wed, 13 Dec 2023 22:00:25 +0100 |
parents | a1f7040b5a15 |
children |
comparison
equal
deleted
inserted
replaced
4198:b1207332cea2 | 4199:7eda7cb8a15c |
---|---|
41 C.PI_IMPORT_NAME: "XEP-0424", | 41 C.PI_IMPORT_NAME: "XEP-0424", |
42 C.PI_TYPE: "XEP", | 42 C.PI_TYPE: "XEP", |
43 C.PI_MODES: C.PLUG_MODE_BOTH, | 43 C.PI_MODES: C.PLUG_MODE_BOTH, |
44 C.PI_PROTOCOLS: ["XEP-0424"], | 44 C.PI_PROTOCOLS: ["XEP-0424"], |
45 C.PI_DEPENDENCIES: ["XEP-0334", "XEP-0428"], | 45 C.PI_DEPENDENCIES: ["XEP-0334", "XEP-0428"], |
46 C.PI_RECOMMENDATIONS: ["XEP-0045"], | |
46 C.PI_MAIN: "XEP_0424", | 47 C.PI_MAIN: "XEP_0424", |
47 C.PI_HANDLER: "yes", | 48 C.PI_HANDLER: "yes", |
48 C.PI_DESCRIPTION: _("""Implementation Message Retraction"""), | 49 C.PI_DESCRIPTION: _("""Implementation Message Retraction"""), |
49 } | 50 } |
50 | 51 |
65 category_name=CATEGORY, name=NAME, label=_(LABEL) | 66 category_name=CATEGORY, name=NAME, label=_(LABEL) |
66 ) | 67 ) |
67 | 68 |
68 | 69 |
69 class XEP_0424: | 70 class XEP_0424: |
70 | |
71 def __init__(self, host): | 71 def __init__(self, host): |
72 log.info(f"plugin {PLUGIN_INFO[C.PI_NAME]!r} initialization") | 72 log.info(f"plugin {PLUGIN_INFO[C.PI_NAME]!r} initialization") |
73 self.host = host | 73 self.host = host |
74 host.memory.update_params(PARAMS) | 74 host.memory.update_params(PARAMS) |
75 self._h = host.plugins["XEP-0334"] | 75 self._h = host.plugins["XEP-0334"] |
76 self._m = host.plugins.get("XEP-0045") | |
76 host.register_namespace("message-retract", NS_MESSAGE_RETRACT) | 77 host.register_namespace("message-retract", NS_MESSAGE_RETRACT) |
77 host.trigger.add("message_received", self._message_received_trigger, 100) | 78 host.trigger.add("message_received", self._message_received_trigger, 100) |
78 host.bridge.add_method( | 79 host.bridge.add_method( |
79 "message_retract", | 80 "message_retract", |
80 ".plugin", | 81 ".plugin", |
87 def get_handler(self, __): | 88 def get_handler(self, __): |
88 return XEP_0424_handler() | 89 return XEP_0424_handler() |
89 | 90 |
90 def _retract(self, message_id: str, profile: str) -> None: | 91 def _retract(self, message_id: str, profile: str) -> None: |
91 client = self.host.get_client(profile) | 92 client = self.host.get_client(profile) |
92 return defer.ensureDeferred( | 93 return defer.ensureDeferred(self.retract(client, message_id)) |
93 self.retract(client, message_id) | 94 |
94 ) | 95 def send_retract( |
95 | 96 self, client: SatXMPPEntity, peer_jid: jid.JID, retract_id: str, history: History |
96 def retract_by_origin_id( | |
97 self, | |
98 client: SatXMPPEntity, | |
99 peer_jid: jid.JID, | |
100 origin_id: str | |
101 ) -> None: | 97 ) -> None: |
102 """Send a message retraction using origin-id | 98 """Send a message retraction using origin-id |
103 | 99 |
104 [retract] should be prefered: internal ID should be used as it is independant of | 100 [retract] should be prefered: internal ID should be used as it is independant of |
105 XEPs changes. However, in some case messages may not be stored in database | 101 XEPs changes. However, in some case messages may not be stored in database |
106 (notably for some components), and then this method can be used | 102 (notably for some components), and then this method can be used |
107 @param origin_id: origin-id as specified in XEP-0359 | 103 |
104 @param retract_id: ID (origin or stanza according to XEP-0359) of message to | |
105 retract | |
106 @param history: history instance of the message to retract | |
108 """ | 107 """ |
109 message_elt = domish.Element((None, "message")) | 108 message_elt = domish.Element((None, "message")) |
110 message_elt["from"] = client.jid.full() | 109 message_elt["from"] = client.jid.full() |
111 message_elt["to"] = peer_jid.full() | 110 message_elt["to"] = peer_jid.full() |
111 message_elt["type"] = history.type | |
112 retract_elt = message_elt.addElement((NS_MESSAGE_RETRACT, "retract")) | 112 retract_elt = message_elt.addElement((NS_MESSAGE_RETRACT, "retract")) |
113 retract_elt["id"] = origin_id | 113 retract_elt["id"] = retract_id |
114 self.host.plugins["XEP-0428"].add_fallback_elt( | 114 self.host.plugins["XEP-0428"].add_fallback_elt( |
115 message_elt, | 115 message_elt, |
116 "[A message retraction has been requested, but your client doesn't support " | 116 "[A message retraction has been requested, but your client doesn't support " |
117 "it]" | 117 "it]", |
118 ) | 118 ) |
119 self._h.add_hint_elements(message_elt, [self._h.HINT_STORE]) | 119 self._h.add_hint_elements(message_elt, [self._h.HINT_STORE]) |
120 client.send(message_elt) | 120 client.send(message_elt) |
121 | 121 |
122 async def retract_by_history( | 122 async def retract_by_history(self, client: SatXMPPEntity, history: History) -> None: |
123 self, | |
124 client: SatXMPPEntity, | |
125 history: History | |
126 ) -> None: | |
127 """Send a message retraction using History instance | 123 """Send a message retraction using History instance |
128 | 124 |
129 This method is to use instead of [retract] when the history instance is already | 125 This method is to use instead of [retract] when the history instance is already |
130 retrieved. Note that the instance must have messages and subjets loaded | 126 retrieved. Note that the instance must have messages and subjets loaded |
131 @param history: history instance of the message to retract | 127 @param history: history instance of the message to retract |
132 """ | 128 """ |
133 try: | 129 if history.type == C.MESS_TYPE_GROUPCHAT: |
134 origin_id = history.origin_id | 130 is_group_chat = True |
135 except KeyError: | 131 peer_jid = jid.JID(history.source) |
132 retract_id = history.stanza_id | |
133 else: | |
134 is_group_chat = False | |
135 peer_jid = jid.JID(history.dest) | |
136 if self._m is not None and self._m.is_joined_room(client, peer_jid): | |
137 # it's actually a private MUC message, we need the full JID. | |
138 peer_jid = history.dest_jid | |
139 retract_id = history.origin_id | |
140 | |
141 if not retract_id: | |
136 raise exceptions.FeatureNotFound( | 142 raise exceptions.FeatureNotFound( |
137 f"message to retract doesn't have the necessary origin-id, the sending " | 143 "Message to retract doesn't have the necessary ID, the sending client is " |
138 "client is probably not supporting message retraction." | 144 "probably not supporting message retraction." |
139 ) | 145 ) |
140 else: | 146 |
141 if history.type == C.MESS_TYPE_GROUPCHAT: | 147 self.send_retract(client, peer_jid, retract_id, history) |
142 is_group_chat = True | 148 if not is_group_chat: |
143 peer_jid = history.dest_jid | 149 # retraction will happen when <retract> message will be received in the |
144 else: | 150 # chat. |
145 is_group_chat = False | 151 await self.retract_db_history(client, history) |
146 peer_jid = jid.JID(history.dest) | |
147 self.retract_by_origin_id(client, peer_jid, origin_id) | |
148 if not is_group_chat: | |
149 # retraction will happen when <retract> message will be received in the | |
150 # chat. | |
151 await self.retract_db_history(client, history) | |
152 | 152 |
153 async def retract( | 153 async def retract( |
154 self, | 154 self, |
155 client: SatXMPPEntity, | 155 client: SatXMPPEntity, |
156 message_id: str, | 156 message_id: str, |
163 message is not found in database, an exception will be raised | 163 message is not found in database, an exception will be raised |
164 """ | 164 """ |
165 if not message_id: | 165 if not message_id: |
166 raise ValueError("message_id can't be empty") | 166 raise ValueError("message_id can't be empty") |
167 history = await self.host.memory.storage.get( | 167 history = await self.host.memory.storage.get( |
168 client, History, History.uid, message_id, | 168 client, |
169 joined_loads=[History.messages, History.subjects, History.thread] | 169 History, |
170 History.uid, | |
171 message_id, | |
172 joined_loads=[History.messages, History.subjects, History.thread], | |
170 ) | 173 ) |
171 if history is None: | 174 if history is None: |
172 raise exceptions.NotFound( | 175 raise exceptions.NotFound( |
173 f"message to retract not found in database ({message_id})" | 176 f"message to retract not found in database ({message_id})" |
174 ) | 177 ) |
184 # retracted, but if may be bad if the user think it's really deleted | 187 # retracted, but if may be bad if the user think it's really deleted |
185 flag_modified(history, "extra") | 188 flag_modified(history, "extra") |
186 keep_history = await self.host.memory.param_get_a_async( | 189 keep_history = await self.host.memory.param_get_a_async( |
187 NAME, CATEGORY, profile_key=client.profile | 190 NAME, CATEGORY, profile_key=client.profile |
188 ) | 191 ) |
189 old_version: Dict[str, Any] = { | 192 old_version: Dict[str, Any] = {"timestamp": time.time()} |
190 "timestamp": time.time() | |
191 } | |
192 if keep_history: | 193 if keep_history: |
193 old_version.update({ | 194 old_version.update( |
194 "messages": [m.serialise() for m in history.messages], | 195 { |
195 "subjects": [s.serialise() for s in history.subjects], | 196 "messages": [m.serialise() for m in history.messages], |
196 }) | 197 "subjects": [s.serialise() for s in history.subjects], |
198 } | |
199 ) | |
197 | 200 |
198 history.extra.setdefault("old_versions", []).append(old_version) | 201 history.extra.setdefault("old_versions", []).append(old_version) |
199 | 202 |
200 history.messages.clear() | 203 history.messages.clear() |
201 history.subjects.clear() | 204 history.subjects.clear() |
217 | 220 |
218 async def _message_received_trigger( | 221 async def _message_received_trigger( |
219 self, | 222 self, |
220 client: SatXMPPEntity, | 223 client: SatXMPPEntity, |
221 message_elt: domish.Element, | 224 message_elt: domish.Element, |
222 post_treat: defer.Deferred | 225 post_treat: defer.Deferred, |
223 ) -> bool: | 226 ) -> bool: |
224 retract_elt = next(message_elt.elements(NS_MESSAGE_RETRACT, "retract"), None) | 227 retract_elt = next(message_elt.elements(NS_MESSAGE_RETRACT, "retract"), None) |
225 if not retract_elt: | 228 if not retract_elt: |
226 return True | 229 return True |
227 try: | 230 try: |
228 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: | 231 if message_elt.getAttribute("type") == C.MESS_TYPE_GROUPCHAT: |
229 col_id = History.stanza_id | 232 col_id = History.stanza_id |
230 else: | 233 else: |
231 col_id = History.origin_id | 234 col_id = History.origin_id |
232 history = await self.host.memory.storage.get( | 235 history = await self.host.memory.storage.get( |
233 client, History, col_id, retract_elt["id"], | 236 client, |
234 joined_loads=[History.messages, History.subjects, History.thread] | 237 History, |
238 col_id, | |
239 retract_elt["id"], | |
240 joined_loads=[History.messages, History.subjects, History.thread], | |
235 ) | 241 ) |
236 except KeyError: | 242 except KeyError: |
237 log.warning(f"invalid retract element, missing id: {retract_elt.toXml()}") | 243 log.warning(f"invalid retract element, missing id: {retract_elt.toXml()}") |
238 return False | 244 return False |
239 from_jid = jid.JID(message_elt["from"]) | 245 from_jid = jid.JID(message_elt["from"]) |
240 | 246 |
241 if ( | 247 if history is not None and ( |
242 history is not None | 248 (history.type == C.MESS_TYPE_GROUPCHAT and history.source_jid != from_jid) |
243 and history.source_jid.userhostJID() != from_jid.userhostJID() | 249 or ( |
250 history.type != C.MESS_TYPE_GROUPCHAT | |
251 and history.source_jid.userhostJID() != from_jid.userhostJID() | |
252 ) | |
244 ): | 253 ): |
245 log.warning( | 254 log.warning( |
246 f"Received message retraction from {from_jid.full()}, but the message to " | 255 f"Received message retraction from {from_jid.full()}, but the message to " |
247 f"retract is from {history.source_jid.full()}. This maybe a hack " | 256 f"retract is from {history.source_jid.full()}. This maybe a hack " |
248 f"attempt.\n{message_elt.toXml()}" | 257 f"attempt.\n{message_elt.toXml()}" |
255 return False | 264 return False |
256 | 265 |
257 if history is None: | 266 if history is None: |
258 # we check history after the trigger because we may be in a component which | 267 # we check history after the trigger because we may be in a component which |
259 # doesn't store messages in database. | 268 # doesn't store messages in database. |
260 log.warning( | 269 log.warning(f"No message found with given id: {message_elt.toXml()}") |
261 f"No message found with given origin-id: {message_elt.toXml()}" | |
262 ) | |
263 return False | 270 return False |
264 log.info(f"[{client.profile}] retracting message {history.uid!r}") | 271 log.info(f"[{client.profile}] retracting message {history.uid!r}") |
265 await self.retract_db_history(client, history) | 272 await self.retract_db_history(client, history) |
266 return False | 273 return False |
267 | 274 |
268 | 275 |
269 @implementer(disco.IDisco) | 276 @implementer(disco.IDisco) |
270 class XEP_0424_handler(xmlstream.XMPPHandler): | 277 class XEP_0424_handler(xmlstream.XMPPHandler): |
271 | |
272 def getDiscoInfo(self, __, target, nodeIdentifier=""): | 278 def getDiscoInfo(self, __, target, nodeIdentifier=""): |
273 return [disco.DiscoFeature(NS_MESSAGE_RETRACT)] | 279 return [disco.DiscoFeature(NS_MESSAGE_RETRACT)] |
274 | 280 |
275 def getDiscoItems(self, requestor, target, nodeIdentifier=""): | 281 def getDiscoItems(self, requestor, target, nodeIdentifier=""): |
276 return [] | 282 return [] |