Source code for slixmpp.plugins.xep_0511.stanza

"""
Stanza interfaces for XEP-0511: Link Metadata

Usage:

>>> register_plugin()  # automatically done if you use this plugin
>>> msg = Message()
>>> msg["link_metadata"]["about"] = "https://the.link.example.com/what-was-linked-to"
>>> msg["link_metadata"]["title"] = "A cool title"
>>> msg.pretty_print()
<message xmlns="jabber:client">
  <Description xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" rdf:about="https://the.link.example.com/what-was-linked-to">
    <title xmlns="https://ogp.me/ns#">A cool title</title>
  </Description>
</message>
"""

# Slixmpp: The Slick XMPP Library
# Copyright (C) 2012 Nathanael C. Fritz, Lance J.T. Stout
# This file is part of Slixmpp.
# See the file LICENSE for copying permission.

import xml.etree.ElementTree as ET

from slixmpp.stanza.message import Message
from slixmpp.xmlstream import ElementBase, register_stanza_plugin

RDF_NAMESPACE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
OG_NAMESPACE = "https://ogp.me/ns#"


[docs] class LinkMetadata(ElementBase): name = "Description" namespace = RDF_NAMESPACE plugin_attrib = "link_metadata" plugin_multi_attrib = "link_metadatas" interfaces = {"about", "title", "description", "url", "image", "type", "site_name"} def _set_og(self, el: ElementBase, value: str) -> None: el.xml.text = value self.xml.append(el.xml) def _get_og(self, el: type[ElementBase]) -> str | None: child = self.xml.find(f"{{{el.namespace}}}{el.name}") if child is None: return None return child.text
[docs] def set_title(self, v: str) -> None: self._set_og(Title(), v)
[docs] def get_title(self) -> str | None: return self._get_og(Title)
[docs] def set_description(self, v: str) -> None: self._set_og(Description(), v)
[docs] def get_description(self) -> str | None: return self._get_og(Description)
[docs] def set_url(self, v: str) -> None: self._set_og(Url(), v)
[docs] def get_url(self) -> str | None: return self._get_og(Url)
[docs] def set_image(self, v: str) -> None: self._set_og(Image(), v)
[docs] def get_image(self) -> str | None: return self._get_og(Image)
[docs] def set_type(self, v: str) -> None: self._set_og(Type_(), v)
[docs] def get_type(self) -> str | None: return self._get_og(Type_)
[docs] def set_site_name(self, v: str) -> None: self._set_og(SiteName(), v)
[docs] def get_site_name(self) -> str | None: return self._get_og(SiteName)
# The following methods look overly complex, but I did not find anything better. # Maybe the following snippet helps understand what ElementBase does that breaks # the expected behaviour of get(). # >>> msg = ET.Element("{jabber:client}message") # >>> desc = ET.Element("{http://www.w3.org/1999/02/22-rdf-syntax-ns#}Description") # >>> desc.set("{http://www.w3.org/1999/02/22-rdf-syntax-ns#}about", "link") # >>> msg.append(desc) # >>> print(ET.tostring(msg, 'unicode')) # <ns0:message xmlns:ns0="jabber:client" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> # <rdf:Description rdf:about="link" /> # </ns0:message> # The "rdf" prefix is automagically set because it is listed as a well-known prefix in python's stdlib `xml.etree`.
[docs] def get_about(self) -> str | None: about = self.xml.get(f"{{{RDF_NAMESPACE}}}about") if about is not None: return about # When we set 'about' using set_about() below, the code above does not work. # Handling prefix definitions manually is the best I could come up with. for attrib, value in self.xml.attrib.items(): if attrib.startswith("xmlns:") and value == RDF_NAMESPACE: prefix = attrib.removeprefix("xmlns:") return self.xml.get(f"{prefix}:about") return None
[docs] def set_about(self, about: str) -> None: # If we use… # self.xml.set(f"{{{RDF_NAMESPACE}}}about", about) # …the about attrib is not visible in the output of ET.tostring(). # It is a problem, since this is what we send in the stream. # It is probably due to some special handling of namespaces in ElementBase. # The following workaround seems to be OK… self.xml.set("xmlns:rdf", RDF_NAMESPACE) self.xml.set("rdf:about", about)
class _OpenGraphMixin(ElementBase): namespace = OG_NAMESPACE
[docs] class Title(_OpenGraphMixin): name = plugin_attrib = "title"
[docs] class Description(_OpenGraphMixin): name = plugin_attrib = "description"
[docs] class Url(_OpenGraphMixin): name = plugin_attrib = "url"
[docs] class Image(_OpenGraphMixin): name = plugin_attrib = "image"
[docs] class Type_(_OpenGraphMixin): name = plugin_attrib = "type"
[docs] class SiteName(_OpenGraphMixin): name = plugin_attrib = "site_name"
[docs] def register_plugin() -> None: for plugin in Title, Description, Url, Image, Type_, SiteName: register_stanza_plugin(LinkMetadata, plugin) register_stanza_plugin(Message, LinkMetadata, iterable=True)