Coverage for slidge / util / types.py: 94%
119 statements
« prev ^ index » next coverage.py v7.13.0, created at 2026-01-06 15:18 +0000
« prev ^ index » next coverage.py v7.13.0, created at 2026-01-06 15:18 +0000
1"""
2Typing stuff
3"""
5from dataclasses import dataclass, fields
6from datetime import datetime
7from enum import IntEnum
8from pathlib import Path
9from typing import (
10 IO,
11 TYPE_CHECKING,
12 Any,
13 AsyncIterator,
14 Generic,
15 Hashable,
16 Literal,
17 NamedTuple,
18 Optional,
19 TypedDict,
20 TypeVar,
21 Union,
22)
24from slixmpp import Message, Presence
25from slixmpp.types import PresenceShows, PresenceTypes, ResourceDict # noqa: F401
27if TYPE_CHECKING:
28 from ..contact import LegacyContact
29 from ..core.session import BaseSession
30 from ..group import LegacyMUC
31 from ..group.participant import LegacyParticipant
33 AnyBaseSession = BaseSession[Any, Any]
34else:
35 AnyBaseSession = None
38LegacyGroupIdType = TypeVar("LegacyGroupIdType", bound=Hashable)
39"""
40Type of the unique identifier for groups, usually a str or an int,
41but anything hashable should work.
42"""
43LegacyMessageType = TypeVar("LegacyMessageType", bound=Hashable)
44LegacyThreadType = TypeVar("LegacyThreadType", bound=Hashable)
45LegacyUserIdType = TypeVar("LegacyUserIdType", bound=Hashable)
47LegacyContactType = TypeVar("LegacyContactType", bound="LegacyContact[Any]")
48LegacyMUCType = TypeVar("LegacyMUCType", bound="LegacyMUC[Any, Any, Any, Any]")
49LegacyParticipantType = TypeVar("LegacyParticipantType", bound="LegacyParticipant")
51Recipient = Union["LegacyMUC[Any, Any, Any, Any]", "LegacyContact[Any]"]
52RecipientType = TypeVar("RecipientType", bound=Recipient)
53Sender = Union["LegacyContact[Any]", "LegacyParticipant"]
54LegacyFileIdType = Union[int, str]
56ChatState = Literal["active", "composing", "gone", "inactive", "paused"]
57ProcessingHint = Literal["no-store", "markable", "store"]
58Marker = Literal["acknowledged", "received", "displayed"]
59FieldType = Literal[
60 "boolean",
61 "fixed",
62 "text-single",
63 "jid-single",
64 "jid-multi",
65 "list-single",
66 "list-multi",
67 "text-private",
68]
69MucAffiliation = Literal["owner", "admin", "member", "outcast", "none"]
70MucRole = Literal["visitor", "participant", "moderator", "none"]
71# https://xmpp.org/registrar/disco-categories.html#client
72ClientType = Literal[
73 "bot", "console", "game", "handheld", "pc", "phone", "sms", "tablet", "web"
74]
75AttachmentDisposition = Literal["attachment", "inline"]
78@dataclass
79class MessageReference(Generic[LegacyMessageType]):
80 """
81 A "message reply", ie a "quoted message" (:xep:`0461`)
83 At the very minimum, the legacy message ID attribute must be set, but to
84 ensure that the quote is displayed in all XMPP clients, the author must also
85 be set (use the string "user" if the slidge user is the author of the referenced
86 message).
87 The body is used as a fallback for XMPP clients that do not support :xep:`0461`
88 of that failed to find the referenced message.
89 """
91 legacy_id: LegacyMessageType
92 author: Optional[Union[Literal["user"], "LegacyParticipant", "LegacyContact"]] = (
93 None
94 )
95 body: Optional[str] = None
98@dataclass
99class LegacyAttachment:
100 """
101 A file attachment to a message
103 At the minimum, one of the ``path``, ``steam``, ``data`` or ``url`` attribute
104 has to be set
106 To be used with :meth:`.LegacyContact.send_files` or
107 :meth:`.LegacyParticipant.send_files`
108 """
110 path: Optional[Union[Path, str]] = None
111 name: Optional[Union[str]] = None
112 stream: Optional[IO[bytes]] = None
113 aio_stream: Optional[AsyncIterator[bytes]] = None
114 data: Optional[bytes] = None
115 content_type: Optional[str] = None
116 legacy_file_id: Optional[Union[str, int]] = None
117 url: Optional[str] = None
118 caption: Optional[str] = None
119 disposition: Optional[AttachmentDisposition] = None
120 """
121 A caption for this specific image. For a global caption for a list of attachments,
122 use the ``body`` parameter of :meth:`.AttachmentMixin.send_files`
123 """
125 def __post_init__(self) -> None:
126 if all(
127 x is None
128 for x in (self.path, self.stream, self.data, self.url, self.aio_stream)
129 ):
130 raise TypeError("There is not data in this attachment", self)
131 if isinstance(self.path, str):
132 self.path = Path(self.path)
134 def format_for_user(self) -> str:
135 if self.name:
136 name = self.name
137 elif self.path:
138 name = self.path.name # type:ignore[union-attr]
139 elif self.url:
140 name = self.url
141 else:
142 name = ""
144 if self.caption:
145 if name:
146 name = f"{name}: {self.caption}"
147 else:
148 name = self.caption
150 return name
152 def __str__(self):
153 attrs = ", ".join(
154 f"{f.name}={getattr(self, f.name)!r}"
155 for f in fields(self)
156 if getattr(self, f.name) is not None and f.name != "data"
157 )
158 if self.data is not None:
159 data_str = f"data=<{len(self.data)} bytes>"
160 to_join = (attrs, data_str) if attrs else (data_str,)
161 attrs = ", ".join(to_join)
162 return f"Attachment({attrs})"
165class MucType(IntEnum):
166 """
167 The type of group, private, public, anonymous or not.
168 """
170 GROUP = 0
171 """
172 A private group, members-only and non-anonymous, eg a family group.
173 """
174 CHANNEL = 1
175 """
176 A public group, aka an anonymous channel.
177 """
178 CHANNEL_NON_ANONYMOUS = 2
179 """
180 A public group where participants' legacy IDs are visible to everybody.
181 """
184PseudoPresenceShow = Union[PresenceShows, Literal[""]]
187MessageOrPresenceTypeVar = TypeVar(
188 "MessageOrPresenceTypeVar", bound=Union[Message, Presence]
189)
192class LinkPreview(NamedTuple):
193 about: str
194 title: Optional[str]
195 description: Optional[str]
196 url: Optional[str]
197 image: Optional[str]
198 type: Optional[str]
199 site_name: Optional[str]
202class Mention(NamedTuple):
203 contact: "LegacyContact[Any]"
204 start: int
205 end: int
208class Hat(NamedTuple):
209 uri: str
210 title: str
211 hue: float | None = None
214class UserPreferences(TypedDict):
215 sync_avatar: bool
216 sync_presence: bool
219class MamMetadata(NamedTuple):
220 id: str
221 sent_on: datetime
224class HoleBound(NamedTuple):
225 id: int | str
226 timestamp: datetime
229class CachedPresence(NamedTuple):
230 last_seen: Optional[datetime] = None
231 ptype: Optional[PresenceTypes] = None
232 pstatus: Optional[str] = None
233 pshow: Optional[PresenceShows] = None
236class Sticker(NamedTuple):
237 path: Path
238 content_type: Optional[str]
239 hashes: dict[str, str]
242class Avatar(NamedTuple):
243 path: Optional[Path] = None
244 unique_id: Optional[str | int] = None
245 url: Optional[str] = None
246 data: Optional[bytes] = None