Merge branch 'xep-0292' into 'master'
implements XEP-0292 (vCard4 over XMPP) See merge request poezio/slixmpp!221
This commit is contained in:
commit
656248ede7
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_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. 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