comparison libervia/backend/plugins/plugin_xep_0470.py @ 4270:0d7bb4df2343

Reformatted code base using black.
author Goffi <goffi@goffi.org>
date Wed, 19 Jun 2024 18:44:57 +0200
parents 2109d864a3e7
children
comparison
equal deleted inserted replaced
4269:64a85ce8be70 4270:0d7bb4df2343
91 91
92 def register_attachment_handler( 92 def register_attachment_handler(
93 self, 93 self,
94 name: str, 94 name: str,
95 namespace: str, 95 namespace: str,
96 get_cb: Callable[ 96 get_cb: Callable[[SatXMPPEntity, domish.Element, Dict[str, Any]], None],
97 [SatXMPPEntity, domish.Element, Dict[str, Any]],
98 None],
99 set_cb: Callable[ 97 set_cb: Callable[
100 [SatXMPPEntity, Dict[str, Any], Optional[domish.Element]], 98 [SatXMPPEntity, Dict[str, Any], Optional[domish.Element]],
101 Optional[domish.Element]], 99 Optional[domish.Element],
100 ],
102 ) -> None: 101 ) -> None:
103 """Register callbacks to handle an attachment 102 """Register callbacks to handle an attachment
104 103
105 @param name: name of the element 104 @param name: name of the element
106 @param namespace: namespace of the element 105 @param namespace: namespace of the element
118 key = (name, namespace) 117 key = (name, namespace)
119 if key in self.handlers: 118 if key in self.handlers:
120 raise exceptions.ConflictError( 119 raise exceptions.ConflictError(
121 f"({name}, {namespace}) attachment handlers are already registered" 120 f"({name}, {namespace}) attachment handlers are already registered"
122 ) 121 )
123 self.handlers[(name, namespace)] = { 122 self.handlers[(name, namespace)] = {"get": get_cb, "set": set_cb}
124 "get": get_cb,
125 "set": set_cb
126 }
127 123
128 def get_attachment_node_name(self, service: jid.JID, node: str, item: str) -> str: 124 def get_attachment_node_name(self, service: jid.JID, node: str, item: str) -> str:
129 """Generate name to use for attachment node""" 125 """Generate name to use for attachment node"""
130 target_item_uri = uri.build_xmpp_uri( 126 target_item_uri = uri.build_xmpp_uri(
131 "pubsub", 127 "pubsub", path=service.userhost(), node=node, item=item
132 path=service.userhost(),
133 node=node,
134 item=item
135 ) 128 )
136 return f"{NS_PUBSUB_ATTACHMENTS}/{target_item_uri}" 129 return f"{NS_PUBSUB_ATTACHMENTS}/{target_item_uri}"
137 130
138 def is_attachment_node(self, node: str) -> bool: 131 def is_attachment_node(self, node: str) -> bool:
139 """Return True if node name is an attachment node""" 132 """Return True if node name is an attachment node"""
160 self, 153 self,
161 client: SatXMPPEntity, 154 client: SatXMPPEntity,
162 service: jid.JID, 155 service: jid.JID,
163 node: str, 156 node: str,
164 item: domish.Element, 157 item: domish.Element,
165 data: dict 158 data: dict,
166 ) -> bool: 159 ) -> bool:
167 """trigger to create attachment node on each publication""" 160 """trigger to create attachment node on each publication"""
168 await self.create_attachments_node( 161 await self.create_attachments_node(
169 client, service, node, item["id"], autocreate=True 162 client, service, node, item["id"], autocreate=True
170 ) 163 )
174 self, 167 self,
175 client: SatXMPPEntity, 168 client: SatXMPPEntity,
176 service: jid.JID, 169 service: jid.JID,
177 node: str, 170 node: str,
178 item_id: str, 171 item_id: str,
179 autocreate: bool = False 172 autocreate: bool = False,
180 ): 173 ):
181 """Create node for attachements if necessary 174 """Create node for attachements if necessary
182 175
183 @param service: service of target node 176 @param service: service of target node
184 @param node: node where target item is published 177 @param node: node where target item is published
188 try: 181 try:
189 node_config = await self._p.getConfiguration(client, service, node) 182 node_config = await self._p.getConfiguration(client, service, node)
190 except error.StanzaError as e: 183 except error.StanzaError as e:
191 if e.condition == "item-not-found" and autocreate: 184 if e.condition == "item-not-found" and autocreate:
192 # we auto-create the missing node 185 # we auto-create the missing node
193 await self._p.createNode( 186 await self._p.createNode(client, service, node)
194 client, service, node
195 )
196 node_config = await self._p.getConfiguration(client, service, node) 187 node_config = await self._p.getConfiguration(client, service, node)
197 elif e.condition in ("forbidden", "feature-not-implemented"): 188 elif e.condition in ("forbidden", "feature-not-implemented"):
198 node_config = self._p.make_configuration_form({}) 189 node_config = self._p.make_configuration_form({})
199 else: 190 else:
200 raise e 191 raise e
211 ) 202 )
212 except Exception as e: 203 except Exception as e:
213 log.warning(f"Can't create attachment node {attachment_node}: {e}") 204 log.warning(f"Can't create attachment node {attachment_node}: {e}")
214 205
215 def items_2_attachment_data( 206 def items_2_attachment_data(
216 self, 207 self, client: SatXMPPEntity, items: List[domish.Element]
217 client: SatXMPPEntity,
218 items: List[domish.Element]
219 ) -> List[Dict[str, Any]]: 208 ) -> List[Dict[str, Any]]:
220 """Convert items from attachment node to attachment data""" 209 """Convert items from attachment node to attachment data"""
221 list_data = [] 210 list_data = []
222 for item in items: 211 for item in items:
223 try: 212 try:
247 log.warning( 236 log.warning(
248 "item ID is not a JID, this is not compliant and is ignored: " 237 "item ID is not a JID, this is not compliant and is ignored: "
249 f"{item.toXml}" 238 f"{item.toXml}"
250 ) 239 )
251 continue 240 continue
252 data = { 241 data = {"from": item_id}
253 "from": item_id
254 }
255 for handler in self.handlers.values(): 242 for handler in self.handlers.values():
256 handler["get"](client, attachments_elt, data) 243 handler["get"](client, attachments_elt, data)
257 if len(data) > 1: 244 if len(data) > 1:
258 list_data.append(data) 245 list_data.append(data)
259 return list_data 246 return list_data
263 service_s: str, 250 service_s: str,
264 node: str, 251 node: str,
265 item: str, 252 item: str,
266 senders_s: List[str], 253 senders_s: List[str],
267 extra_s: str, 254 extra_s: str,
268 profile_key: str 255 profile_key: str,
269 ) -> defer.Deferred: 256 ) -> defer.Deferred:
270 client = self.host.get_client(profile_key) 257 client = self.host.get_client(profile_key)
271 extra = data_format.deserialise(extra_s) 258 extra = data_format.deserialise(extra_s)
272 senders = [jid.JID(s) for s in senders_s] 259 senders = [jid.JID(s) for s in senders_s]
273 d = defer.ensureDeferred( 260 d = defer.ensureDeferred(
274 self.get_attachments(client, jid.JID(service_s), node, item, senders) 261 self.get_attachments(client, jid.JID(service_s), node, item, senders)
275 ) 262 )
276 d.addCallback( 263 d.addCallback(
277 lambda ret: 264 lambda ret: (data_format.serialise(ret[0]), data_format.serialise(ret[1]))
278 (data_format.serialise(ret[0]),
279 data_format.serialise(ret[1]))
280 ) 265 )
281 return d 266 return d
282 267
283 async def get_attachments( 268 async def get_attachments(
284 self, 269 self,
285 client: SatXMPPEntity, 270 client: SatXMPPEntity,
286 service: jid.JID, 271 service: jid.JID,
287 node: str, 272 node: str,
288 item: str, 273 item: str,
289 senders: Optional[List[jid.JID]], 274 senders: Optional[List[jid.JID]],
290 extra: Optional[dict] = None 275 extra: Optional[dict] = None,
291 ) -> Tuple[List[Dict[str, Any]], dict]: 276 ) -> Tuple[List[Dict[str, Any]], dict]:
292 """Retrieve data attached to a pubsub item 277 """Retrieve data attached to a pubsub item
293 278
294 @param service: pubsub service where the node is 279 @param service: pubsub service where the node is
295 @param node: pubsub node containing the item 280 @param node: pubsub node containing the item
314 ) 299 )
315 list_data = self.items_2_attachment_data(client, items) 300 list_data = self.items_2_attachment_data(client, items)
316 301
317 return list_data, metadata 302 return list_data, metadata
318 303
319 def _set( 304 def _set(self, attachments_s: str, profile_key: str) -> None:
320 self,
321 attachments_s: str,
322 profile_key: str
323 ) -> None:
324 client = self.host.get_client(profile_key) 305 client = self.host.get_client(profile_key)
325 attachments = data_format.deserialise(attachments_s) or {} 306 attachments = data_format.deserialise(attachments_s) or {}
326 return defer.ensureDeferred(self.set_attachements(client, attachments)) 307 return defer.ensureDeferred(self.set_attachements(client, attachments))
327 308
328 async def apply_set_handler( 309 async def apply_set_handler(
329 self, 310 self,
330 client: SatXMPPEntity, 311 client: SatXMPPEntity,
387 if new_elt is not None: 368 if new_elt is not None:
388 attachments_elt.addChild(new_elt) 369 attachments_elt.addChild(new_elt)
389 return item_elt 370 return item_elt
390 371
391 async def set_attachements( 372 async def set_attachements(
392 self, 373 self, client: SatXMPPEntity, attachments_data: Dict[str, Any]
393 client: SatXMPPEntity,
394 attachments_data: Dict[str, Any]
395 ) -> None: 374 ) -> None:
396 """Set or update attachments 375 """Set or update attachments
397 376
398 Former <attachments> element will be retrieved and updated. Individual 377 Former <attachments> element will be retrieved and updated. Individual
399 attachments replace or update their elements individually, according to the 378 attachments replace or update their elements individually, according to the
412 try: 391 try:
413 service = jid.JID(attachments_data["service"]) 392 service = jid.JID(attachments_data["service"])
414 node = attachments_data["node"] 393 node = attachments_data["node"]
415 item = attachments_data["id"] 394 item = attachments_data["id"]
416 except (KeyError, RuntimeError): 395 except (KeyError, RuntimeError):
417 raise ValueError( 396 raise ValueError('data must have "service", "node" and "id" set')
418 'data must have "service", "node" and "id" set'
419 )
420 attachment_node = self.get_attachment_node_name(service, node, item) 397 attachment_node = self.get_attachment_node_name(service, node, item)
421 try: 398 try:
422 items, __ = await self._p.get_items( 399 items, __ = await self._p.get_items(
423 client, service, attachment_node, item_ids=[client.jid.userhost()] 400 client, service, attachment_node, item_ids=[client.jid.userhost()]
424 ) 401 )
463 @param item: name of target item (used to get attachment node's name) 440 @param item: name of target item (used to get attachment node's name)
464 """ 441 """
465 attachment_node = self.get_attachment_node_name(service, node, item) 442 attachment_node = self.get_attachment_node_name(service, node, item)
466 await self._p.subscribe(client, service, attachment_node) 443 await self._p.subscribe(client, service, attachment_node)
467 444
468
469 def set_timestamp(self, attachment_elt: domish.Element, data: dict) -> None: 445 def set_timestamp(self, attachment_elt: domish.Element, data: dict) -> None:
470 """Check if a ``timestamp`` attribute is set, parse it, and fill data 446 """Check if a ``timestamp`` attribute is set, parse it, and fill data
471 447
472 @param attachments_elt: element where the ``timestamp`` attribute may be set 448 @param attachments_elt: element where the ``timestamp`` attribute may be set
473 @param data: data specific to the attachment (i.e. not the whole microblog data) 449 @param data: data specific to the attachment (i.e. not the whole microblog data)
487 client: SatXMPPEntity, 463 client: SatXMPPEntity,
488 attachments_elt: domish.Element, 464 attachments_elt: domish.Element,
489 data: Dict[str, Any], 465 data: Dict[str, Any],
490 ) -> None: 466 ) -> None:
491 try: 467 try:
492 noticed_elt = next( 468 noticed_elt = next(attachments_elt.elements(NS_PUBSUB_ATTACHMENTS, "noticed"))
493 attachments_elt.elements(NS_PUBSUB_ATTACHMENTS, "noticed")
494 )
495 except StopIteration: 469 except StopIteration:
496 pass 470 pass
497 else: 471 else:
498 noticed_data = { 472 noticed_data = {"noticed": True}
499 "noticed": True
500 }
501 self.set_timestamp(noticed_elt, noticed_data) 473 self.set_timestamp(noticed_elt, noticed_data)
502 data["noticed"] = noticed_data 474 data["noticed"] = noticed_data
503 475
504 def noticed_set( 476 def noticed_set(
505 self, 477 self,
506 client: SatXMPPEntity, 478 client: SatXMPPEntity,
507 data: Dict[str, Any], 479 data: Dict[str, Any],
508 former_elt: Optional[domish.Element] 480 former_elt: Optional[domish.Element],
509 ) -> Optional[domish.Element]: 481 ) -> Optional[domish.Element]:
510 """add or remove a <noticed> attachment 482 """add or remove a <noticed> attachment
511 483
512 if data["noticed"] is True, element is added, if it's False, it's removed, and 484 if data["noticed"] is True, element is added, if it's False, it's removed, and
513 it's not present or None, the former element is kept. 485 it's not present or None, the former element is kept.
515 noticed = data["extra"].get("noticed") 487 noticed = data["extra"].get("noticed")
516 if noticed is None: 488 if noticed is None:
517 return former_elt 489 return former_elt
518 elif noticed: 490 elif noticed:
519 return domish.Element( 491 return domish.Element(
520 (NS_PUBSUB_ATTACHMENTS, "noticed"), 492 (NS_PUBSUB_ATTACHMENTS, "noticed"), attribs={"timestamp": xmpp_date()}
521 attribs = {
522 "timestamp": xmpp_date()
523 }
524 ) 493 )
525 else: 494 else:
526 return None 495 return None
527 496
528 def reactions_get( 497 def reactions_get(
547 516
548 def reactions_set( 517 def reactions_set(
549 self, 518 self,
550 client: SatXMPPEntity, 519 client: SatXMPPEntity,
551 data: Dict[str, Any], 520 data: Dict[str, Any],
552 former_elt: Optional[domish.Element] 521 former_elt: Optional[domish.Element],
553 ) -> Optional[domish.Element]: 522 ) -> Optional[domish.Element]:
554 """update the <reaction> attachment""" 523 """update the <reaction> attachment"""
555 reactions_data = data["extra"].get("reactions") 524 reactions_data = data["extra"].get("reactions")
556 if reactions_data is None: 525 if reactions_data is None:
557 return former_elt 526 return former_elt
558 operation_type = reactions_data.get("operation", "update") 527 operation_type = reactions_data.get("operation", "update")
559 if operation_type == "update": 528 if operation_type == "update":
560 former_reactions = { 529 former_reactions = (
561 str(r) for r in former_elt.elements(NS_PUBSUB_ATTACHMENTS, "reaction") 530 {str(r) for r in former_elt.elements(NS_PUBSUB_ATTACHMENTS, "reaction")}
562 } if former_elt is not None else set() 531 if former_elt is not None
532 else set()
533 )
563 added_reactions = set(reactions_data.get("add") or []) 534 added_reactions = set(reactions_data.get("add") or [])
564 removed_reactions = set(reactions_data.get("remove") or []) 535 removed_reactions = set(reactions_data.get("remove") or [])
565 reactions = list((former_reactions | added_reactions) - removed_reactions) 536 reactions = list((former_reactions | added_reactions) - removed_reactions)
566 elif operation_type == "replace": 537 elif operation_type == "replace":
567 reactions = reactions_data.get("reactions") or [] 538 reactions = reactions_data.get("reactions") or []
568 else: 539 else:
569 raise exceptions.DataError(f"invalid reaction operation: {operation_type!r}") 540 raise exceptions.DataError(f"invalid reaction operation: {operation_type!r}")
570 if reactions: 541 if reactions:
571 reactions_elt = domish.Element( 542 reactions_elt = domish.Element(
572 (NS_PUBSUB_ATTACHMENTS, "reactions"), 543 (NS_PUBSUB_ATTACHMENTS, "reactions"), attribs={"timestamp": xmpp_date()}
573 attribs = {
574 "timestamp": xmpp_date()
575 }
576 ) 544 )
577 for reactions_data in reactions: 545 for reactions_data in reactions:
578 reactions_elt.addElement("reaction", content=reactions_data) 546 reactions_elt.addElement("reaction", content=reactions_data)
579 return reactions_elt 547 return reactions_elt
580 else: 548 else: