Merge branch 'xep-0292' into 'master'

implements XEP-0292 (vCard4 over XMPP)

See merge request poezio/slixmpp!221
This commit is contained in:
Maxime Buquet 2022-11-28 12:05:30 +00:00
commit 656248ede7
6 changed files with 419 additions and 0 deletions

View File

@ -0,0 +1,17 @@
XEP-0292: vCard4 Over XMPP
==========================
.. module:: slixmpp.plugins.xep_0292
.. autoclass:: XEP_0292
:members:
:exclude-members: plugin_init, plugin_end
Stanza elements
---------------
.. automodule:: slixmpp.plugins.xep_0292.stanza
:members:
:undoc-members:

View File

@ -79,6 +79,7 @@ __all__ = [
# 'xep_0270', # XMPP Compliance Suites 2010. Dont automatically load
'xep_0279', # Server IP Check
'xep_0280', # Message Carbons
'xep_0292', # vCard4 Over XMPP
'xep_0297', # Stanza Forwarding
'xep_0300', # Use of Cryptographic Hash Functions in XMPP
# 'xep_0302', # XMPP Compliance Suites 2012. Dont automatically load

View File

@ -0,0 +1,5 @@
from slixmpp.plugins.base import register_plugin
from . import stanza, vcard4
register_plugin(vcard4.XEP_0292)

View File

@ -0,0 +1,167 @@
import datetime
from typing import Optional
from slixmpp import ElementBase, Iq, register_stanza_plugin
NS = "urn:ietf:params:xml:ns:vcard-4.0"
class _VCardElementBase(ElementBase):
namespace = NS
class VCard4(_VCardElementBase):
name = plugin_attrib = "vcard"
interfaces = {"full_name", "given", "surname", "birthday"}
def set_full_name(self, full_name: str):
self["fn"]["text"] = full_name
def get_full_name(self):
return self["fn"]["text"]
def set_given(self, given: str):
self["n"]["given"] = given
def get_given(self):
return self["n"]["given"]
def set_surname(self, surname: str):
self["n"]["surname"] = surname
def get_surname(self):
return self["n"]["surname"]
def set_birthday(self, birthday: datetime.date):
self["bday"]["date"] = birthday
def get_birthday(self):
return self["bday"]["date"]
def add_tel(self, number: str, name: Optional[str] = None):
tel = Tel()
if name:
tel["parameters"]["type_"]["text"] = name
tel["uri"] = f"tel:{number}"
self.append(tel)
def add_address(
self, country: Optional[str] = None, locality: Optional[str] = None
):
adr = Adr()
if locality:
adr["locality"] = locality
if country:
adr["country"] = country
self.append(adr)
def add_nickname(self, nick: str):
el = Nickname()
el["text"] = nick
self.append(el)
def add_note(self, note: str):
el = Note()
el["text"] = note
self.append(el)
def add_impp(self, impp: str):
el = Impp()
el["uri"] = impp
self.append(el)
def add_url(self, url: str):
el = Url()
el["uri"] = url
self.append(el)
def add_email(self, email: str):
el = Email()
el["text"] = email
self.append(el)
class _VCardTextElementBase(_VCardElementBase):
interfaces = {"text"}
sub_interfaces = {"text"}
class Fn(_VCardTextElementBase):
name = plugin_attrib = "fn"
class Nickname(_VCardTextElementBase):
name = plugin_attrib = "nickname"
class Note(_VCardTextElementBase):
name = plugin_attrib = "note"
class _VCardUriElementBase(_VCardElementBase):
interfaces = {"uri"}
sub_interfaces = {"uri"}
class Url(_VCardUriElementBase):
name = plugin_attrib = "url"
class Impp(_VCardUriElementBase):
name = plugin_attrib = "impp"
class Email(_VCardTextElementBase):
name = plugin_attrib = "email"
class N(_VCardElementBase):
name = "n"
plugin_attrib = "n"
interfaces = sub_interfaces = {"given", "surname", "additional"}
class BDay(_VCardElementBase):
name = plugin_attrib = "bday"
interfaces = {"date"}
def set_date(self, date: datetime.date):
d = Date()
d.xml.text = date.strftime("%Y-%m-%d")
self.append(d)
def get_date(self):
for elem in self.xml:
try:
return datetime.date.fromisoformat(elem.text)
except ValueError:
return None
class Date(_VCardElementBase):
name = "date"
class Tel(_VCardUriElementBase):
name = plugin_attrib = "tel"
class Parameters(_VCardElementBase):
name = plugin_attrib = "parameters"
class Type(_VCardTextElementBase):
name = "type"
plugin_attrib = "type_"
class Adr(_VCardElementBase):
name = plugin_attrib = "adr"
interfaces = sub_interfaces = {"locality", "country"}
register_stanza_plugin(Parameters, Type)
register_stanza_plugin(Tel, Parameters)
for p in N, Fn, Nickname, Note, Url, Impp, Email, BDay, Tel, Adr:
register_stanza_plugin(VCard4, p, iterable=True)
register_stanza_plugin(Iq, VCard4)

View File

@ -0,0 +1,111 @@
import logging
from datetime import date
from typing import Optional
from slixmpp import (
JID,
ComponentXMPP,
register_stanza_plugin,
)
from slixmpp.plugins.base import BasePlugin
from . import stanza
class XEP_0292(BasePlugin):
"""
vCard4 over XMPP
Does not implement the IQ semantics that neither movim does gajim implement,
cf https://xmpp.org/extensions/xep-0292.html#self-iq-retrieval and
https://xmpp.org/extensions/xep-0292.html#self-iq-publication
Does not implement the "empty pubsub event item" as a notification mechanism,
that neither gajim nor movim implement
https://xmpp.org/extensions/xep-0292.html#sect-idm45744791178720
Relies on classic pubsub semantics instead.
"""
xmpp: ComponentXMPP
name = "xep_0292"
description = "vCard4 Over XMPP"
dependencies = {"xep_0163", "xep_0060", "xep_0030"}
stanza = stanza
def plugin_init(self):
pubsub_stanza = self.xmpp["xep_0060"].stanza
register_stanza_plugin(pubsub_stanza.Item, stanza.VCard4)
register_stanza_plugin(pubsub_stanza.EventItem, stanza.VCard4)
self.xmpp['xep_0060'].map_node_event(stanza.NS, 'vcard4')
def plugin_end(self):
self.xmpp['xep_0030'].del_feature(feature=stanza.NS)
self.xmpp['xep_0163'].remove_interest(stanza.NS)
def session_bind(self, jid):
self.xmpp['xep_0163'].register_pep('vcard4', stanza.VCard4)
def publish_vcard(
self,
full_name: Optional[str] = None,
given: Optional[str] = None,
surname: Optional[str] = None,
birthday: Optional[date] = None,
nickname: Optional[str] = None,
phone: Optional[str] = None,
note: Optional[str] = None,
url: Optional[str] = None,
email: Optional[str] = None,
country: Optional[str] = None,
locality: Optional[str] = None,
impp: Optional[str] = None,
**pubsubkwargs,
):
"""
Publish a vcard using PEP
"""
vcard = stanza.VCard4()
if impp:
vcard.add_impp(impp)
if nickname:
vcard.add_nickname(nickname)
if full_name:
vcard["full_name"] = full_name
if given:
vcard["given"] = given
if surname:
vcard["surname"] = surname
if birthday:
vcard["birthday"] = birthday
if note:
vcard.add_note(note)
if url:
vcard.add_url(url)
if email:
vcard.add_email(email)
if phone:
vcard.add_tel(phone)
if country and locality:
vcard.add_address(country, locality)
elif country:
vcard.add_address(country, locality)
return self.xmpp["xep_0163"].publish(vcard, id="current", **pubsubkwargs)
def retrieve_vcard(self, jid: JID, **pubsubkwargs):
"""
Retrieve a vcard using PEP
"""
return self.xmpp["xep_0060"].get_item(
jid, stanza.VCard4.namespace, "current", **pubsubkwargs
)
log = logging.getLogger(__name__)

View File

@ -0,0 +1,118 @@
import datetime
from slixmpp import Iq
from slixmpp.test import SlixTest
from slixmpp.plugins.xep_0292 import stanza
REF = """
<iq>
<vcard xmlns='urn:ietf:params:xml:ns:vcard-4.0'>
<fn>
<text>Full Name</text>
</fn>
<n><given>Full</given><surname>Name</surname></n>
<nickname>
<text>some nick</text>
</nickname>
<bday>
<date>1984-05-21</date>
</bday>
<url>
<uri>https://nicoco.fr</uri>
</url>
<note>
<text>About me</text>
</note>
<impp>
<uri>xmpp:test@localhost</uri>
</impp>
<email>
<text>test@gmail.com</text>
</email>
<tel>
<parameters>
<type><text>work</text></type>
</parameters>
<uri>tel:+555</uri>
</tel>
<adr>
<locality>Nice</locality>
<country>France</country>
</adr>
</vcard>
</iq>
"""
class TestVcard(SlixTest):
def test_basic_interfaces(self):
iq = Iq()
x = iq["vcard"]
x["fn"]["text"] = "Full Name"
x["nickname"]["text"] = "some nick"
x["n"]["given"] = "Full"
x["n"]["surname"] = "Name"
x["bday"]["date"] = datetime.date(1984, 5, 21)
x["note"]["text"] = "About me"
x["url"]["uri"] = "https://nicoco.fr"
x["impp"]["uri"] = "xmpp:test@localhost"
x["email"]["text"] = "test@gmail.com"
x["tel"]["uri"] = "tel:+555"
x["tel"]["parameters"]["type_"]["text"] = "work"
x["adr"]["locality"] = "Nice"
x["adr"]["country"] = "France"
self.check(iq, REF, use_values=False)
def test_easy_interface(self):
iq = Iq()
x: stanza.VCard4 = iq["vcard"]
x["full_name"] = "Full Name"
x["given"] = "Full"
x["surname"] = "Name"
x["birthday"] = datetime.date(1984, 5, 21)
x.add_nickname("some nick")
x.add_note("About me")
x.add_url("https://nicoco.fr")
x.add_impp("xmpp:test@localhost")
x.add_email("test@gmail.com")
x.add_tel("+555", "work")
x.add_address("France", "Nice")
self.check(iq, REF, use_values=False)
def test_2_phones(self):
vcard = stanza.VCard4()
tel1 = stanza.Tel()
tel1["parameters"]["type_"]["text"] = "work"
tel1["uri"] = "tel:+555"
tel2 = stanza.Tel()
tel2["parameters"]["type_"]["text"] = "devil"
tel2["uri"] = "tel:+666"
vcard.append(tel1)
vcard.append(tel2)
self.check(
vcard,
"""
<vcard xmlns='urn:ietf:params:xml:ns:vcard-4.0'>
<tel>
<parameters>
<type><text>work</text></type>
</parameters>
<uri>tel:+555</uri>
</tel>
<tel>
<parameters>
<type><text>devil</text></type>
</parameters>
<uri>tel:+666</uri>
</tel>
</vcard>
""",
use_values=False
)