Coverage for slidge/core/mixins/disco.py: 96%
105 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-28 18:29 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-07-28 18:29 +0000
1from collections.abc import Mapping
2from typing import TYPE_CHECKING, Any, ClassVar
4from slixmpp.plugins.xep_0004.stanza.form import Form
5from slixmpp.plugins.xep_0030.stanza.info import DiscoInfo
6from slixmpp.plugins.xep_0030.stanza.items import DiscoItems
7from slixmpp.types import OptJid
9from .base import Base
11if TYPE_CHECKING:
12 from slidge.command.base import ContactCommand, MUCCommand
15class BaseDiscoMixin(Base):
16 DISCO_TYPE: str = NotImplemented
17 DISCO_CATEGORY: str = NotImplemented
18 DISCO_NAME: str = NotImplemented
19 DISCO_LANG = None
21 commands: ClassVar[Mapping[str, "type[ContactCommand[Any] | MUCCommand[Any]]"]]
23 def _get_disco_name(self) -> str | None:
24 if self.DISCO_NAME is NotImplemented:
25 return self.xmpp.COMPONENT_NAME
26 return self.DISCO_NAME or self.xmpp.COMPONENT_NAME
28 def features(self) -> list[str]:
29 return []
31 async def extended_features(self) -> list[Form] | None:
32 return None
34 async def get_disco_info(
35 self, jid: OptJid = None, node: str | None = None
36 ) -> DiscoInfo:
37 info = DiscoInfo()
38 if node == "http://jabber.org/protocol/commands":
39 info.add_identity(category="automation", itype="command-list")
40 elif node and node in self.commands:
41 info.add_identity(
42 category="automation",
43 itype="command-node",
44 name=self.commands[node].NAME,
45 )
46 info.add_feature("http://jabber.org/protocol/commands")
47 info.add_feature("jabber:x:data")
48 else:
49 for feature in self.features():
50 info.add_feature(feature)
51 info.add_identity(
52 category=self.DISCO_CATEGORY,
53 itype=self.DISCO_TYPE,
54 name=self._get_disco_name(),
55 lang=self.DISCO_LANG,
56 )
57 if forms := await self.extended_features():
58 for form in forms:
59 info.append(form)
60 return info
62 async def get_caps_ver(self, jid: OptJid = None, node: str | None = None) -> str:
63 info = await self.get_disco_info(jid, node)
64 caps = self.xmpp.plugin["xep_0115"]
65 ver = caps.generate_verstring(info, caps.hash)
66 return ver # type:ignore[no-any-return]
68 async def get_disco_items(self, node: str | None) -> DiscoItems:
69 items = DiscoItems()
70 if node == "http://jabber.org/protocol/commands":
71 for node_, command in self.commands.items():
72 items.add_item(jid=self.jid, node=node_, name=command.NAME)
74 return items
77class ChatterDiscoMixin(BaseDiscoMixin):
78 AVATAR = True
79 RECEIPTS = True
80 MARKS = True
81 CHAT_STATES = True
82 UPLOAD = True
83 CORRECTION = True
84 REACTION = True
85 RETRACTION = True
86 REPLIES = True
87 INVITATION_RECIPIENT = False
89 DISCO_TYPE = "pc"
90 DISCO_CATEGORY = "client"
91 DISCO_NAME = ""
93 is_participant: bool
95 def features(self) -> list[str]:
96 features = []
97 if self.CHAT_STATES:
98 features.append("http://jabber.org/protocol/chatstates")
99 if self.RECEIPTS:
100 features.append("urn:xmpp:receipts")
101 if self.CORRECTION:
102 features.append("urn:xmpp:message-correct:0")
103 if self.MARKS:
104 features.append("urn:xmpp:chat-markers:0")
105 if self.UPLOAD:
106 features.append("jabber:x:oob")
107 if self.REACTION:
108 features.append("urn:xmpp:reactions:0")
109 if self.RETRACTION:
110 features.append("urn:xmpp:message-retract:0")
111 if self.REPLIES:
112 features.append("urn:xmpp:reply:0")
113 if self.INVITATION_RECIPIENT:
114 features.append("jabber:x:conference")
115 features.append("urn:ietf:params:xml:ns:vcard-4.0")
116 if not self.is_participant:
117 features.append("http://jabber.org/protocol/commands")
118 return features
120 async def extended_features(self) -> list[Form] | None:
121 f = getattr(self, "restricted_emoji_extended_feature", None)
122 if f is None:
123 return None
125 e = await f()
126 if not e:
127 return None
129 return [e]
132class ContactAccountDiscoMixin(BaseDiscoMixin):
133 async def get_disco_info(
134 self, jid: OptJid = None, node: str | None = None
135 ) -> DiscoInfo:
136 if jid and jid.resource:
137 return await super().get_disco_info(jid, node)
138 info = DiscoInfo()
139 info.add_feature("http://jabber.org/protocol/pubsub")
140 info.add_feature("http://jabber.org/protocol/pubsub#retrieve-items")
141 info.add_feature("http://jabber.org/protocol/pubsub#subscribe")
142 info.add_identity(
143 category="account",
144 itype="registered",
145 name=self._get_disco_name(),
146 lang=self.DISCO_LANG,
147 )
148 info.add_identity(
149 category="pubsub",
150 itype="pep",
151 name=self._get_disco_name(),
152 lang=self.DISCO_LANG,
153 )
154 return info