# Slixmpp: The Slick XMPP Library
# Copyright (C) 2025 Mathieu Pasquet
# This file is part of Slixmpp.
# See the file LICENSE for copying permission.
from datetime import datetime
from enum import Enum
from slixmpp import Iq, register_stanza_plugin, ElementBase
from slixmpp.plugins.xep_0082 import parse, format_datetime
NS = 'urn:xmpp:cam:0'
[docs]
class ClientType(Enum):
"""
Enum representing the ``type`` attribute of the ``<client/>`` element.
"""
SESSION = 'session'
ACCESS = 'access'
[docs]
class PermissionEnum(Enum):
"""
Enum representing the ``status`` of the ``<permission/>`` element, e.g:
.. code-block:: xml
<permission status="normal/>
"""
UNRESTRICTED = 'unrestricted'
NORMAL = 'normal'
RESTRICTED = 'restricted'
[docs]
class Revoke(ElementBase):
"""
Element used to revoke a device.
"""
namespace = NS
name = 'revoke'
plugin_attrib = 'revoke'
interfaces = {'id'}
[docs]
class List(ElementBase):
"""
Element used to request a list of devices to the server.
"""
namespace = NS
name = 'list'
plugin_attrib = 'list'
interfaces = {}
[docs]
class Clients(ElementBase):
"""List of clients.
Iterate on it to get all substanzas.
.. code-block::
<clients xmlns="urn:xmpp:cam:0">
…
</clients>
"""
namespace = NS
name = 'clients'
plugin_attrib = 'clients'
[docs]
class Client(ElementBase):
"""Client element.
.. code-block:: xml
<client xmlns="urn:xmpp:cam:0" type="session" id="a" connected="true">
…
</client>
Many of the substanzas defined are using overrides to access and set
data from this element, e.g.:
.. code-block:: python
client = Client()
client['user_agent'] = {'software': 'slixmpp', 'device': 'toto'}
will actually create
.. code-block:: xml
<client xmlns="urn:xmpp:cam:0">
<user-agent>
<software>slixmpp</software>
<device>toto</device>
</user-agent>
</client>
"""
namespace = NS
name = 'client'
plugin_attrib = 'client'
interfaces = {
'connected', 'id', 'type', 'first_seen', 'last_seen',
'permission', 'permission_extra',
}
[docs]
def get_type(self) -> ClientType | None:
type_ = self.xml.attrib.get('type', None)
if type_ is not None:
return ClientType(type_)
return None
[docs]
def set_type(self, type_: str | ClientType) -> None:
if isinstance(type_, ClientType):
value = type_.value
else:
ClientType(type_)
value = type_
self.xml.attrib['type'] = value
[docs]
def get_connected(self) -> bool | None:
connected = self.xml.attrib.get('connected', None)
if connected is None or connected.lower() not in ('true', 'false'):
return None
return connected.lower() == 'true'
[docs]
def set_connected(self, connected: bool) -> None:
self.xml.attrib['connected'] = str(connected).lower()
[docs]
def get_first_seen(self) -> datetime | None:
try:
parse(self._get_sub_text('first-seen'))
except:
return None
[docs]
def get_last_seen(self) -> datetime | None:
try:
parse(self._get_sub_text('last-seen'))
except:
return None
[docs]
def set_first_seen(self, time: str | datetime) -> None:
if isinstance(time, str):
time = parse(time)
self._set_sub_text('first-seen', format_datetime(time))
[docs]
def set_last_seen(self, time: str | datetime) -> None:
if isinstance(time, str):
time = parse(time)
self._set_sub_text('last-seen', format_datetime(time))
[docs]
def del_permission(self) -> None:
found = self.xml.findall('{%s}permission' % NS)
if found:
for permission in found:
self.xml.remove(permission)
[docs]
def del_user_agent(self) -> None:
found = self.xml.findall('{%s}user-agent' % NS)
if found:
for agent in found:
self.xml.remove(agent)
[docs]
class UserAgent(ElementBase):
namespace = NS
name = 'user-agent'
plugin_attrib = 'user_agent'
interfaces = {'software', 'uri', 'device'}
sub_interfaces = {'software', 'uri', 'device'}
[docs]
def as_dict(self) -> dict[str, str]:
return {key: self[key] for key in self.sub_interfaces}
[docs]
class Permission(ElementBase):
"""
Permission element.
"""
namespace = NS
name = 'permission'
plugin_attrib = 'permission'
interfaces = {'permission', 'permission_extra'}
overrides = {
'set_permission', 'get_permission',
'set_permission_extra', 'get_permission_extra', 'del_permission_extra',
}
[docs]
def get_permission(self) -> PermissionEnum | None:
return PermissionEnum(self.xml.attrib.get('status'))
[docs]
def set_permission(self, permission: str | PermissionEnum) -> None:
if isinstance(permission, PermissionEnum):
value = permission.value
else:
PermissionEnum(permission)
value = permission
self.xml.attrib['status'] = value
[docs]
class Auth(ElementBase):
"""
Auth element.
.. code-block:: xml
<auth>
<fast/>
<custom-element xmlns="urn:custom"/>
</auth>
"""
namespace = NS
name = 'auth'
plugin_attrib = 'auth'
interfaces = {'password', 'fast', 'all'}
bool_interfaces = {'password', 'fast'}
overrides = {'get_auths'}
[docs]
def get_auths(self) -> dict:
return {
'password': self['password'],
'grant': 'grant' in self,
'fast': self['fast'],
'others': [sub for sub in self],
}
[docs]
class Grant(ElementBase):
"""``<grant/>`` element."""
namespace = NS
name = 'grant'
plugin_attrib = 'grant'
[docs]
def register_plugins() -> None:
register_stanza_plugin(Iq, Clients)
register_stanza_plugin(Iq, List)
register_stanza_plugin(Iq, Revoke)
register_stanza_plugin(Clients, Client, iterable=True)
register_stanza_plugin(Client, UserAgent, overrides=True)
register_stanza_plugin(Client, Permission, overrides=True)
register_stanza_plugin(Client, Auth, overrides=True)
register_stanza_plugin(Grant, Permission, overrides=True)
register_stanza_plugin(Auth, Grant)