slidge 0.1.0b2__py3-none-any.whl → 0.1.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. slidge/__init__.py +55 -31
  2. slidge/__main__.py +118 -116
  3. slidge/command/__init__.py +28 -0
  4. slidge/command/adhoc.py +258 -0
  5. slidge/command/admin.py +193 -0
  6. slidge/command/base.py +441 -0
  7. slidge/command/categories.py +3 -0
  8. slidge/command/chat_command.py +288 -0
  9. slidge/command/register.py +179 -0
  10. slidge/command/user.py +250 -0
  11. slidge/contact/__init__.py +8 -0
  12. slidge/contact/contact.py +452 -0
  13. slidge/contact/roster.py +192 -0
  14. slidge/core/__init__.py +2 -0
  15. slidge/core/cache.py +183 -0
  16. slidge/core/config.py +216 -0
  17. slidge/core/gateway/__init__.py +3 -0
  18. slidge/core/gateway/base.py +895 -0
  19. slidge/core/gateway/caps.py +63 -0
  20. slidge/core/gateway/delivery_receipt.py +52 -0
  21. slidge/core/gateway/disco.py +80 -0
  22. slidge/core/gateway/mam.py +75 -0
  23. slidge/core/gateway/muc_admin.py +35 -0
  24. slidge/core/gateway/ping.py +66 -0
  25. slidge/core/gateway/presence.py +95 -0
  26. slidge/core/gateway/registration.py +53 -0
  27. slidge/core/gateway/search.py +102 -0
  28. slidge/core/gateway/session_dispatcher.py +789 -0
  29. slidge/core/gateway/vcard_temp.py +130 -0
  30. slidge/core/mixins/__init__.py +19 -0
  31. slidge/core/mixins/attachment.py +506 -0
  32. slidge/core/mixins/avatar.py +167 -0
  33. slidge/core/mixins/base.py +31 -0
  34. slidge/core/mixins/disco.py +130 -0
  35. slidge/core/mixins/lock.py +31 -0
  36. slidge/core/mixins/message.py +398 -0
  37. slidge/core/mixins/message_maker.py +154 -0
  38. slidge/core/mixins/presence.py +217 -0
  39. slidge/core/mixins/recipient.py +43 -0
  40. slidge/core/pubsub.py +282 -116
  41. slidge/core/session.py +595 -372
  42. slidge/group/__init__.py +10 -0
  43. slidge/group/archive.py +125 -0
  44. slidge/group/bookmarks.py +163 -0
  45. slidge/group/participant.py +458 -0
  46. slidge/group/room.py +1103 -0
  47. slidge/migration.py +18 -0
  48. slidge/slixfix/__init__.py +68 -0
  49. slidge/{util/xep_0084 → slixfix/link_preview}/__init__.py +3 -5
  50. slidge/slixfix/link_preview/link_preview.py +17 -0
  51. slidge/slixfix/link_preview/stanza.py +99 -0
  52. slidge/slixfix/roster.py +60 -0
  53. slidge/{util → slixfix}/xep_0077/register.py +14 -2
  54. slidge/slixfix/xep_0077/stanza.py +104 -0
  55. slidge/{util → slixfix}/xep_0100/gateway.py +25 -15
  56. slidge/slixfix/xep_0100/stanza.py +9 -0
  57. slidge/slixfix/xep_0153/__init__.py +10 -0
  58. slidge/slixfix/xep_0153/stanza.py +25 -0
  59. slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
  60. slidge/slixfix/xep_0264/__init__.py +5 -0
  61. slidge/slixfix/xep_0264/stanza.py +36 -0
  62. slidge/slixfix/xep_0264/thumbnail.py +23 -0
  63. slidge/slixfix/xep_0292/__init__.py +5 -0
  64. slidge/slixfix/xep_0292/vcard4.py +100 -0
  65. slidge/slixfix/xep_0313/__init__.py +12 -0
  66. slidge/slixfix/xep_0313/mam.py +262 -0
  67. slidge/slixfix/xep_0313/stanza.py +359 -0
  68. slidge/slixfix/xep_0317/__init__.py +5 -0
  69. slidge/slixfix/xep_0317/hats.py +17 -0
  70. slidge/slixfix/xep_0317/stanza.py +28 -0
  71. slidge/{util → slixfix}/xep_0356_old/privilege.py +9 -7
  72. slidge/slixfix/xep_0424/__init__.py +9 -0
  73. slidge/slixfix/xep_0424/retraction.py +77 -0
  74. slidge/slixfix/xep_0424/stanza.py +28 -0
  75. slidge/slixfix/xep_0490/__init__.py +8 -0
  76. slidge/slixfix/xep_0490/mds.py +47 -0
  77. slidge/slixfix/xep_0490/stanza.py +17 -0
  78. slidge/util/__init__.py +4 -6
  79. slidge/util/archive_msg.py +61 -0
  80. slidge/util/conf.py +206 -0
  81. slidge/util/db.py +57 -76
  82. slidge/util/schema.sql +126 -0
  83. slidge/util/sql.py +508 -0
  84. slidge/util/test.py +215 -25
  85. slidge/util/types.py +177 -4
  86. slidge/util/util.py +225 -59
  87. slidge-0.1.1.dist-info/METADATA +110 -0
  88. slidge-0.1.1.dist-info/RECORD +96 -0
  89. {slidge-0.1.0b2.dist-info → slidge-0.1.1.dist-info}/WHEEL +1 -1
  90. slidge/core/contact.py +0 -891
  91. slidge/core/gateway.py +0 -916
  92. slidge/plugins/discord/__init__.py +0 -90
  93. slidge/plugins/discord/client.py +0 -108
  94. slidge/plugins/discord/session.py +0 -162
  95. slidge/plugins/dummy.py +0 -203
  96. slidge/plugins/facebook.py +0 -493
  97. slidge/plugins/hackernews.py +0 -213
  98. slidge/plugins/mattermost/__init__.py +0 -1
  99. slidge/plugins/mattermost/api.py +0 -280
  100. slidge/plugins/mattermost/gateway.py +0 -365
  101. slidge/plugins/mattermost/websocket.py +0 -252
  102. slidge/plugins/signal/__init__.py +0 -3
  103. slidge/plugins/signal/contact.py +0 -106
  104. slidge/plugins/signal/gateway.py +0 -282
  105. slidge/plugins/signal/session.py +0 -448
  106. slidge/plugins/signal/txt.py +0 -53
  107. slidge/plugins/skype.py +0 -325
  108. slidge/plugins/steam.py +0 -310
  109. slidge/plugins/telegram/__init__.py +0 -5
  110. slidge/plugins/telegram/client.py +0 -228
  111. slidge/plugins/telegram/config.py +0 -12
  112. slidge/plugins/telegram/contact.py +0 -176
  113. slidge/plugins/telegram/gateway.py +0 -150
  114. slidge/plugins/telegram/session.py +0 -256
  115. slidge/util/xep_0030/__init__.py +0 -13
  116. slidge/util/xep_0030/disco.py +0 -811
  117. slidge/util/xep_0030/stanza/__init__.py +0 -7
  118. slidge/util/xep_0030/stanza/info.py +0 -270
  119. slidge/util/xep_0030/stanza/items.py +0 -147
  120. slidge/util/xep_0030/static.py +0 -467
  121. slidge/util/xep_0055/__init__.py +0 -5
  122. slidge/util/xep_0055/search.py +0 -75
  123. slidge/util/xep_0055/stanza.py +0 -10
  124. slidge/util/xep_0077/stanza.py +0 -71
  125. slidge/util/xep_0084/avatar.py +0 -137
  126. slidge/util/xep_0084/stanza.py +0 -104
  127. slidge/util/xep_0115/__init__.py +0 -12
  128. slidge/util/xep_0115/caps.py +0 -379
  129. slidge/util/xep_0115/stanza.py +0 -16
  130. slidge/util/xep_0115/static.py +0 -137
  131. slidge/util/xep_0292/__init__.py +0 -1
  132. slidge/util/xep_0292/stanza.py +0 -167
  133. slidge/util/xep_0292/vcard4.py +0 -75
  134. slidge/util/xep_0333/__init__.py +0 -10
  135. slidge/util/xep_0333/markers.py +0 -96
  136. slidge/util/xep_0333/stanza.py +0 -34
  137. slidge/util/xep_0356/__init__.py +0 -7
  138. slidge/util/xep_0356/permissions.py +0 -35
  139. slidge/util/xep_0356/privilege.py +0 -160
  140. slidge/util/xep_0356/stanza.py +0 -44
  141. slidge/util/xep_0363/__init__.py +0 -16
  142. slidge/util/xep_0363/http_upload.py +0 -215
  143. slidge/util/xep_0363/stanza.py +0 -46
  144. slidge/util/xep_0461/__init__.py +0 -6
  145. slidge/util/xep_0461/reply.py +0 -48
  146. slidge/util/xep_0461/stanza.py +0 -47
  147. slidge-0.1.0b2.dist-info/METADATA +0 -171
  148. slidge-0.1.0b2.dist-info/RECORD +0 -81
  149. /slidge/{plugins/__init__.py → py.typed} +0 -0
  150. /slidge/{util → slixfix}/xep_0077/__init__.py +0 -0
  151. /slidge/{util → slixfix}/xep_0100/__init__.py +0 -0
  152. /slidge/{util → slixfix}/xep_0356_old/__init__.py +0 -0
  153. /slidge/{util → slixfix}/xep_0356_old/stanza.py +0 -0
  154. {slidge-0.1.0b2.dist-info → slidge-0.1.1.dist-info}/LICENSE +0 -0
  155. {slidge-0.1.0b2.dist-info → slidge-0.1.1.dist-info}/entry_points.txt +0 -0
slidge/util/test.py CHANGED
@@ -1,14 +1,178 @@
1
+ # type:ignore
1
2
  import tempfile
2
3
  import types
3
4
  from pathlib import Path
4
- from typing import Union
5
+ from typing import Optional, Union
6
+ from xml.dom.minidom import parseString
5
7
 
8
+ import xmldiff.main
9
+ from slixmpp import (
10
+ ElementBase,
11
+ Iq,
12
+ MatcherId,
13
+ MatchXMLMask,
14
+ MatchXPath,
15
+ Message,
16
+ Presence,
17
+ StanzaPath,
18
+ )
19
+ from slixmpp.stanza.error import Error
6
20
  from slixmpp.test import SlixTest, TestTransport
21
+ from slixmpp.xmlstream import highlight, tostring
22
+ from slixmpp.xmlstream.matcher import MatchIDSender
7
23
 
8
- from .. import BaseGateway, BaseSession, LegacyContact, LegacyRoster, user_store
24
+ from slidge import (
25
+ BaseGateway,
26
+ BaseSession,
27
+ LegacyBookmarks,
28
+ LegacyContact,
29
+ LegacyMUC,
30
+ LegacyParticipant,
31
+ LegacyRoster,
32
+ user_store,
33
+ )
9
34
 
35
+ from ..command import Command
36
+ from ..core import config
37
+ from ..core.config import _TimedeltaSeconds
10
38
 
11
- class SlidgeTest(SlixTest):
39
+
40
+ class SlixTestPlus(SlixTest):
41
+ def setUp(self):
42
+ super().setUp()
43
+ Error.namespace = "jabber:component:accept"
44
+
45
+ def check(self, stanza, criteria, method="exact", defaults=None, use_values=True):
46
+ """
47
+ Create and compare several stanza objects to a correct XML string.
48
+
49
+ If use_values is False, tests using stanza.values will not be used.
50
+
51
+ Some stanzas provide default values for some interfaces, but
52
+ these defaults can be problematic for testing since they can easily
53
+ be forgotten when supplying the XML string. A list of interfaces that
54
+ use defaults may be provided and the generated stanzas will use the
55
+ default values for those interfaces if needed.
56
+
57
+ However, correcting the supplied XML is not possible for interfaces
58
+ that add or remove XML elements. Only interfaces that map to XML
59
+ attributes may be set using the defaults parameter. The supplied XML
60
+ must take into account any extra elements that are included by default.
61
+
62
+ Arguments:
63
+ stanza -- The stanza object to test.
64
+ criteria -- An expression the stanza must match against.
65
+ method -- The type of matching to use; one of:
66
+ 'exact', 'mask', 'id', 'xpath', and 'stanzapath'.
67
+ Defaults to the value of self.match_method.
68
+ defaults -- A list of stanza interfaces that have default
69
+ values. These interfaces will be set to their
70
+ defaults for the given and generated stanzas to
71
+ prevent unexpected test failures.
72
+ use_values -- Indicates if testing using stanza.values should
73
+ be used. Defaults to True.
74
+ """
75
+ if method is None and hasattr(self, "match_method"):
76
+ method = getattr(self, "match_method")
77
+
78
+ if method != "exact":
79
+ matchers = {
80
+ "stanzapath": StanzaPath,
81
+ "xpath": MatchXPath,
82
+ "mask": MatchXMLMask,
83
+ "idsender": MatchIDSender,
84
+ "id": MatcherId,
85
+ }
86
+ Matcher = matchers.get(method, None)
87
+ if Matcher is None:
88
+ raise ValueError("Unknown matching method.")
89
+ test = Matcher(criteria)
90
+ self.assertTrue(
91
+ test.match(stanza),
92
+ "Stanza did not match using %s method:\n" % method
93
+ + "Criteria:\n%s\n" % str(criteria)
94
+ + "Stanza:\n%s" % str(stanza),
95
+ )
96
+ else:
97
+ stanza_class = stanza.__class__
98
+ # Hack to preserve namespaces instead of having jabber:client
99
+ # everywhere.
100
+ old_ns = stanza_class.namespace
101
+ stanza_class.namespace = stanza.namespace
102
+ if not isinstance(criteria, ElementBase):
103
+ xml = self.parse_xml(criteria)
104
+ else:
105
+ xml = criteria.xml
106
+
107
+ # Ensure that top level namespaces are used, even if they
108
+ # were not provided.
109
+ self.fix_namespaces(stanza.xml)
110
+ self.fix_namespaces(xml)
111
+
112
+ stanza2 = stanza_class(xml=xml)
113
+
114
+ if use_values:
115
+ # Using stanza.values will add XML for any interface that
116
+ # has a default value. We need to set those defaults on
117
+ # the existing stanzas and XML so that they will compare
118
+ # correctly.
119
+ default_stanza = stanza_class()
120
+ if defaults is None:
121
+ known_defaults = {Message: ["type"], Presence: ["priority"]}
122
+ defaults = known_defaults.get(stanza_class, [])
123
+ for interface in defaults:
124
+ stanza[interface] = stanza[interface]
125
+ stanza2[interface] = stanza2[interface]
126
+ # Can really only automatically add defaults for top
127
+ # level attribute values. Anything else must be accounted
128
+ # for in the provided XML string.
129
+ if interface not in xml.attrib:
130
+ if interface in default_stanza.xml.attrib:
131
+ value = default_stanza.xml.attrib[interface]
132
+ xml.attrib[interface] = value
133
+
134
+ values = stanza2.values
135
+ stanza3 = stanza_class()
136
+ stanza3.values = values
137
+
138
+ debug = "Three methods for creating stanzas do not match.\n"
139
+ debug += "Given XML:\n%s\n" % highlight(tostring(xml))
140
+ debug += "Given stanza:\n%s\n" % format_stanza(stanza)
141
+ debug += "Generated stanza:\n%s\n" % highlight(tostring(stanza2.xml))
142
+ debug += "Second generated stanza:\n%s\n" % highlight(
143
+ tostring(stanza3.xml)
144
+ )
145
+ result = self.compare(xml, stanza.xml, stanza2.xml, stanza3.xml)
146
+ else:
147
+ debug = "Two methods for creating stanzas do not match.\n"
148
+ debug += "Given XML:\n%s\n" % highlight(tostring(xml))
149
+ debug += "Given stanza:\n%s\n" % format_stanza(stanza)
150
+ debug += "Generated stanza:\n%s\n" % highlight(tostring(stanza2.xml))
151
+ result = self.compare(xml, stanza.xml, stanza2.xml)
152
+ stanza_class.namespace = old_ns
153
+
154
+ if not result:
155
+ debug += str(
156
+ xmldiff.main.diff_texts(tostring(xml), tostring(stanza.xml))
157
+ )
158
+ if use_values:
159
+ debug += str(
160
+ xmldiff.main.diff_texts(tostring(xml), tostring(stanza2.xml))
161
+ )
162
+ self.assertTrue(result, debug)
163
+
164
+ def next_sent(self, timeout=0.05) -> Optional[Union[Message, Iq, Presence]]:
165
+ self.wait_for_send_queue()
166
+ sent = self.xmpp.socket.next_sent(timeout=timeout)
167
+ if sent is None:
168
+ return None
169
+ xml = self.parse_xml(sent)
170
+ self.fix_namespaces(xml, "jabber:component:accept")
171
+ sent = self.xmpp._build_stanza(xml, "jabber:component:accept")
172
+ return sent
173
+
174
+
175
+ class SlidgeTest(SlixTestPlus):
12
176
  plugin: Union[types.ModuleType, dict]
13
177
 
14
178
  class Config:
@@ -18,25 +182,34 @@ class SlidgeTest(SlixTest):
18
182
  port = 5222
19
183
  upload_service = "upload.test"
20
184
  home_dir = Path(tempfile.mkdtemp())
21
- user_jid_validator = ".*@shakespeare.lit"
185
+ user_jid_validator = ".*"
22
186
  admins: list[str] = []
23
187
  no_roster_push = False
24
188
  upload_requester = None
25
- ignore_delay_threshold = 300
189
+ ignore_delay_threshold = _TimedeltaSeconds("300")
26
190
 
27
191
  @classmethod
28
192
  def setUpClass(cls):
29
193
  user_store.set_file(Path(tempfile.mkdtemp()) / "test.db")
194
+ for k, v in vars(cls.Config).items():
195
+ setattr(config, k.upper(), v)
30
196
 
31
197
  def setUp(self):
32
- BaseGateway._subclass = find_subclass(self.plugin, BaseGateway)
33
- BaseSession._subclass = find_subclass(self.plugin, BaseSession)
34
- LegacyRoster._subclass = find_subclass(self.plugin, LegacyRoster, base_ok=True)
35
- LegacyContact._subclass = find_subclass(
36
- self.plugin, LegacyContact, base_ok=True
37
- )
198
+ if hasattr(self, "plugin"):
199
+ BaseGateway._subclass = find_subclass(self.plugin, BaseGateway)
200
+ BaseSession._subclass = find_subclass(self.plugin, BaseSession)
201
+ LegacyRoster._subclass = find_subclass(
202
+ self.plugin, LegacyRoster, base_ok=True
203
+ )
204
+ LegacyContact._subclass = find_subclass(
205
+ self.plugin, LegacyContact, base_ok=True
206
+ )
207
+ LegacyMUC._subclass = find_subclass(self.plugin, LegacyMUC, base_ok=True)
208
+ LegacyBookmarks._subclass = find_subclass(
209
+ self.plugin, LegacyBookmarks, base_ok=True
210
+ )
38
211
 
39
- self.xmpp = BaseGateway.get_self_or_unique_subclass()(self.Config)
212
+ self.xmpp = BaseGateway.get_self_or_unique_subclass()()
40
213
 
41
214
  self.xmpp._always_send_everything = True
42
215
 
@@ -67,24 +240,18 @@ class SlidgeTest(SlixTest):
67
240
  # this to True in tests related to those plugins.
68
241
  self.xmpp.use_message_ids = False
69
242
  self.xmpp.use_presence_ids = False
243
+ Error.namespace = "jabber:component:accept"
70
244
 
71
245
  @classmethod
72
246
  def tearDownClass(cls):
73
- BaseSession.reset_subclass()
74
- BaseGateway.reset_subclass()
75
- LegacyRoster.reset_subclass()
76
- LegacyContact.reset_subclass()
247
+ reset_subclasses()
77
248
  user_store._users = None
78
249
 
79
- def next_sent(self):
80
- self.wait_for_send_queue()
81
- sent = self.xmpp.socket.next_sent(timeout=1)
82
- if sent is None:
83
- return None
84
- xml = self.parse_xml(sent)
85
- self.fix_namespaces(xml, "jabber:component:accept")
86
- sent = self.xmpp._build_stanza(xml, "jabber:component:accept")
87
- return sent
250
+
251
+ def format_stanza(stanza):
252
+ return highlight(
253
+ "\n".join(parseString(tostring(stanza.xml)).toprettyxml().split("\n")[1:])
254
+ )
88
255
 
89
256
 
90
257
  def find_subclass(o, parent, base_ok=False):
@@ -103,3 +270,26 @@ def find_subclass(o, parent, base_ok=False):
103
270
  return parent
104
271
  else:
105
272
  raise RuntimeError
273
+
274
+
275
+ def reset_subclasses():
276
+ """
277
+ Reset registered subclasses between test classes.
278
+
279
+ Needed because these classes are meant to only be subclassed once and raise
280
+ exceptions otherwise.
281
+ """
282
+ BaseSession.reset_subclass()
283
+ BaseGateway.reset_subclass()
284
+ LegacyRoster.reset_subclass()
285
+ LegacyContact.reset_subclass()
286
+ LegacyMUC.reset_subclass()
287
+ LegacyBookmarks.reset_subclass()
288
+ LegacyParticipant.reset_subclass()
289
+ # reset_commands()
290
+
291
+
292
+ def reset_commands():
293
+ Command.subclasses = [
294
+ c for c in Command.subclasses if str(c).startswith("<class 'slidge.core")
295
+ ]
slidge/util/types.py CHANGED
@@ -1,7 +1,180 @@
1
+ """
2
+ Typing stuff
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+ from enum import IntEnum
1
7
  from pathlib import Path
2
- from typing import Union
8
+ from typing import (
9
+ IO,
10
+ TYPE_CHECKING,
11
+ Any,
12
+ Generic,
13
+ Hashable,
14
+ Literal,
15
+ NamedTuple,
16
+ Optional,
17
+ TypedDict,
18
+ TypeVar,
19
+ Union,
20
+ )
21
+
22
+ from slixmpp import Message, Presence
23
+ from slixmpp.types import PresenceShows
24
+
25
+ if TYPE_CHECKING:
26
+ from ..contact import LegacyContact
27
+ from ..core.pubsub import PepItem
28
+ from ..core.session import BaseSession
29
+ from ..group.participant import LegacyMUC, LegacyParticipant
30
+ from .db import GatewayUser
31
+
32
+ AnyBaseSession = BaseSession[Any, Any]
33
+ else:
34
+ AnyBaseSession = None
35
+
36
+
37
+ class URL(str):
38
+ pass
39
+
40
+
41
+ LegacyGroupIdType = TypeVar("LegacyGroupIdType", bound=Hashable)
42
+ """
43
+ Type of the unique identifier for groups, usually a str or an int,
44
+ but anything hashable should work.
45
+ """
46
+ LegacyMessageType = TypeVar("LegacyMessageType", bound=Hashable)
47
+ LegacyThreadType = TypeVar("LegacyThreadType", bound=Hashable)
48
+ LegacyUserIdType = TypeVar("LegacyUserIdType", bound=Hashable)
49
+
50
+ LegacyContactType = TypeVar("LegacyContactType", bound="LegacyContact")
51
+ LegacyMUCType = TypeVar("LegacyMUCType", bound="LegacyMUC")
52
+ LegacyParticipantType = TypeVar("LegacyParticipantType", bound="LegacyParticipant")
3
53
 
54
+ PepItemType = TypeVar("PepItemType", bound="PepItem")
55
+
56
+ Recipient = Union["LegacyMUC", "LegacyContact"]
57
+ RecipientType = TypeVar("RecipientType", bound=Recipient)
58
+ Sender = Union["LegacyContact", "LegacyParticipant"]
4
59
  AvatarType = Union[bytes, str, Path]
5
- LegacyUserIdType = Union[str, int]
6
- LegacyMessageType = Union[str, int]
7
- LegacyContactIdType = Union[str, int]
60
+ LegacyFileIdType = Union[int, str]
61
+ AvatarIdType = Union[LegacyFileIdType, URL]
62
+
63
+ ChatState = Literal["active", "composing", "gone", "inactive", "paused"]
64
+ ProcessingHint = Literal["no-store", "markable", "store"]
65
+ Marker = Literal["acknowledged", "received", "displayed"]
66
+ FieldType = Literal[
67
+ "boolean",
68
+ "fixed",
69
+ "text-single",
70
+ "jid-single",
71
+ "jid-multi",
72
+ "list-single",
73
+ "list-multi",
74
+ "text-private",
75
+ ]
76
+ MucAffiliation = Literal["owner", "admin", "member", "outcast", "none"]
77
+ MucRole = Literal["visitor", "participant", "moderator", "none"]
78
+
79
+
80
+ @dataclass
81
+ class MessageReference(Generic[LegacyMessageType]):
82
+ """
83
+ A "message reply", ie a "quoted message" (:xep:`0461`)
84
+
85
+ At the very minimum, the legacy message ID attribute must be set, but to
86
+ ensure that the quote is displayed in all XMPP clients, the author must also
87
+ be set.
88
+ The body is used as a fallback for XMPP clients that do not support :xep:`0461`
89
+ of that failed to find the referenced message.
90
+ """
91
+
92
+ legacy_id: LegacyMessageType
93
+ author: Optional[Union["GatewayUser", "LegacyParticipant", "LegacyContact"]] = None
94
+ body: Optional[str] = None
95
+
96
+
97
+ @dataclass
98
+ class LegacyAttachment:
99
+ """
100
+ A file attachment to a message
101
+
102
+ At the minimum, one of the ``path``, ``steam``, ``data`` or ``url`` attribute
103
+ has to be set
104
+
105
+ To be used with :meth:`.LegacyContact.send_files` or
106
+ :meth:`.LegacyParticipant.send_files`
107
+ """
108
+
109
+ path: Optional[Union[Path, str]] = None
110
+ name: Optional[Union[str]] = None
111
+ stream: Optional[IO[bytes]] = None
112
+ data: Optional[bytes] = None
113
+ content_type: Optional[str] = None
114
+ legacy_file_id: Optional[Union[str, int]] = None
115
+ url: Optional[str] = None
116
+ caption: Optional[str] = None
117
+ """
118
+ A caption for this specific image. For a global caption for a list of attachments,
119
+ use the ``body`` parameter of :meth:`.AttachmentMixin.send_files`
120
+ """
121
+
122
+ def __post_init__(self):
123
+ if not any(
124
+ x is not None for x in (self.path, self.stream, self.data, self.url)
125
+ ):
126
+ raise TypeError("There is not data in this attachment", self)
127
+
128
+
129
+ class MucType(IntEnum):
130
+ """
131
+ The type of group, private, public, anonymous or not.
132
+ """
133
+
134
+ GROUP = 0
135
+ """
136
+ A private group, members-only and non-anonymous, eg a family group.
137
+ """
138
+ CHANNEL = 1
139
+ """
140
+ A public group, aka an anonymous channel.
141
+ """
142
+ CHANNEL_NON_ANONYMOUS = 2
143
+ """
144
+ A public group where participants' legacy IDs are visible to everybody.
145
+ """
146
+
147
+
148
+ PseudoPresenceShow = Union[PresenceShows, Literal[""]]
149
+
150
+
151
+ class ResourceDict(TypedDict):
152
+ show: PseudoPresenceShow
153
+ status: str
154
+ priority: int
155
+
156
+
157
+ MessageOrPresenceTypeVar = TypeVar(
158
+ "MessageOrPresenceTypeVar", bound=Union[Message, Presence]
159
+ )
160
+
161
+
162
+ class LinkPreview(NamedTuple):
163
+ about: str
164
+ title: Optional[str]
165
+ description: Optional[str]
166
+ url: Optional[str]
167
+ image: Optional[str]
168
+ type: Optional[str]
169
+ site_name: Optional[str]
170
+
171
+
172
+ class Mention(NamedTuple):
173
+ contact: "LegacyContact"
174
+ start: int
175
+ end: int
176
+
177
+
178
+ class Hat(NamedTuple):
179
+ uri: str
180
+ title: str