Coverage for slidge / core / mixins / disco.py: 96%
105 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-06 05:07 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-06 05:07 +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[
22 Mapping[str, "type[ContactCommand[Any]] | type[MUCCommand[Any]]"]
23 ]
25 def _get_disco_name(self) -> str | None:
26 if self.DISCO_NAME is NotImplemented:
27 return self.xmpp.COMPONENT_NAME
28 return self.DISCO_NAME or self.xmpp.COMPONENT_NAME
30 def features(self) -> list[str]:
31 return []
33 async def extended_features(self) -> list[Form] | None:
34 return None
36 async def get_disco_info(
37 self, jid: OptJid = None, node: str | None = None
38 ) -> DiscoInfo:
39 info = DiscoInfo()
40 if node == "http://jabber.org/protocol/commands":
41 info.add_identity(category="automation", itype="command-list")
42 elif node and node in self.commands:
43 info.add_identity(
44 category="automation",
45 itype="command-node",
46 name=self.commands[node].NAME,
47 )
48 info.add_feature("http://jabber.org/protocol/commands")
49 info.add_feature("jabber:x:data")
50 else:
51 for feature in self.features():
52 info.add_feature(feature)
53 info.add_identity(
54 category=self.DISCO_CATEGORY,
55 itype=self.DISCO_TYPE,
56 name=self._get_disco_name(),
57 lang=self.DISCO_LANG,
58 )
59 if forms := await self.extended_features():
60 for form in forms:
61 info.append(form)
62 return info
64 async def get_caps_ver(self, jid: OptJid = None, node: str | None = None) -> str:
65 info = await self.get_disco_info(jid, node)
66 caps = self.xmpp.plugin["xep_0115"]
67 ver = caps.generate_verstring(info, caps.hash)
68 return ver # type:ignore[no-any-return]
70 async def get_disco_items(self, node: str | None) -> DiscoItems:
71 items = DiscoItems()
72 if node == "http://jabber.org/protocol/commands":
73 for node, command in self.commands.items():
74 items.add_item(jid=self.jid, node=node, name=command.NAME)
76 return items
79class ChatterDiscoMixin(BaseDiscoMixin):
80 AVATAR = True
81 RECEIPTS = True
82 MARKS = True
83 CHAT_STATES = True
84 UPLOAD = True
85 CORRECTION = True
86 REACTION = True
87 RETRACTION = True
88 REPLIES = True
89 INVITATION_RECIPIENT = False
91 DISCO_TYPE = "pc"
92 DISCO_CATEGORY = "client"
93 DISCO_NAME = ""
95 is_participant: bool
97 def features(self) -> list[str]:
98 features = []
99 if self.CHAT_STATES:
100 features.append("http://jabber.org/protocol/chatstates")
101 if self.RECEIPTS:
102 features.append("urn:xmpp:receipts")
103 if self.CORRECTION:
104 features.append("urn:xmpp:message-correct:0")
105 if self.MARKS:
106 features.append("urn:xmpp:chat-markers:0")
107 if self.UPLOAD:
108 features.append("jabber:x:oob")
109 if self.REACTION:
110 features.append("urn:xmpp:reactions:0")
111 if self.RETRACTION:
112 features.append("urn:xmpp:message-retract:0")
113 if self.REPLIES:
114 features.append("urn:xmpp:reply:0")
115 if self.INVITATION_RECIPIENT:
116 features.append("jabber:x:conference")
117 features.append("urn:ietf:params:xml:ns:vcard-4.0")
118 if not self.is_participant:
119 features.append("http://jabber.org/protocol/commands")
120 return features
122 async def extended_features(self) -> list[Form] | None:
123 f = getattr(self, "restricted_emoji_extended_feature", None)
124 if f is None:
125 return None
127 e = await f()
128 if not e:
129 return None
131 return [e]
134class ContactAccountDiscoMixin(BaseDiscoMixin):
135 async def get_disco_info(
136 self, jid: OptJid = None, node: str | None = None
137 ) -> DiscoInfo:
138 if jid and jid.resource:
139 return await super().get_disco_info(jid, node)
140 info = DiscoInfo()
141 info.add_feature("http://jabber.org/protocol/pubsub")
142 info.add_feature("http://jabber.org/protocol/pubsub#retrieve-items")
143 info.add_feature("http://jabber.org/protocol/pubsub#subscribe")
144 info.add_identity(
145 category="account",
146 itype="registered",
147 name=self._get_disco_name(),
148 lang=self.DISCO_LANG,
149 )
150 info.add_identity(
151 category="pubsub",
152 itype="pep",
153 name=self._get_disco_name(),
154 lang=self.DISCO_LANG,
155 )
156 return info