implements XEP-0292 (vCard4 over XMPP)
This commit is contained in:
parent
7a0fb97083
commit
65636b8cce
6 changed files with 419 additions and 0 deletions
17
docs/api/plugins/xep_0292.rst
Normal file
17
docs/api/plugins/xep_0292.rst
Normal 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:
|
|
@ -79,6 +79,7 @@ __all__ = [
|
||||||
# 'xep_0270', # XMPP Compliance Suites 2010. Don’t automatically load
|
# 'xep_0270', # XMPP Compliance Suites 2010. Don’t automatically load
|
||||||
'xep_0279', # Server IP Check
|
'xep_0279', # Server IP Check
|
||||||
'xep_0280', # Message Carbons
|
'xep_0280', # Message Carbons
|
||||||
|
'xep_0292', # vCard4 Over XMPP
|
||||||
'xep_0297', # Stanza Forwarding
|
'xep_0297', # Stanza Forwarding
|
||||||
'xep_0300', # Use of Cryptographic Hash Functions in XMPP
|
'xep_0300', # Use of Cryptographic Hash Functions in XMPP
|
||||||
# 'xep_0302', # XMPP Compliance Suites 2012. Don’t automatically load
|
# 'xep_0302', # XMPP Compliance Suites 2012. Don’t automatically load
|
||||||
|
|
5
slixmpp/plugins/xep_0292/__init__.py
Normal file
5
slixmpp/plugins/xep_0292/__init__.py
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
from slixmpp.plugins.base import register_plugin
|
||||||
|
|
||||||
|
from . import stanza, vcard4
|
||||||
|
|
||||||
|
register_plugin(vcard4.XEP_0292)
|
167
slixmpp/plugins/xep_0292/stanza.py
Normal file
167
slixmpp/plugins/xep_0292/stanza.py
Normal 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)
|
111
slixmpp/plugins/xep_0292/vcard4.py
Normal file
111
slixmpp/plugins/xep_0292/vcard4.py
Normal 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__)
|
118
tests/test_stanza_xep_0292.py
Normal file
118
tests/test_stanza_xep_0292.py
Normal 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
|
||||||
|
)
|
Loading…
Reference in a new issue