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
# 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]
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)