slidge 0.1.0rc1__py3-none-any.whl → 0.1.2__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (164) hide show
  1. slidge/__init__.py +54 -31
  2. slidge/__main__.py +51 -5
  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 +121 -39
  16. slidge/core/config.py +116 -11
  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 +795 -0
  29. slidge/core/gateway/vcard_temp.py +130 -0
  30. slidge/core/mixins/__init__.py +9 -1
  31. slidge/core/mixins/attachment.py +506 -0
  32. slidge/core/mixins/avatar.py +167 -0
  33. slidge/core/mixins/base.py +6 -19
  34. slidge/core/mixins/disco.py +66 -15
  35. slidge/core/mixins/lock.py +31 -0
  36. slidge/core/mixins/message.py +254 -252
  37. slidge/core/mixins/message_maker.py +154 -0
  38. slidge/core/mixins/presence.py +128 -31
  39. slidge/core/mixins/recipient.py +43 -0
  40. slidge/core/pubsub.py +275 -116
  41. slidge/core/session.py +586 -518
  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_0050 → slixfix/link_preview}/__init__.py +4 -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 +1 -2
  54. slidge/slixfix/xep_0077/stanza.py +104 -0
  55. slidge/{util → slixfix}/xep_0100/gateway.py +17 -12
  56. slidge/slixfix/xep_0153/__init__.py +10 -0
  57. slidge/slixfix/xep_0153/stanza.py +25 -0
  58. slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
  59. slidge/slixfix/xep_0264/__init__.py +5 -0
  60. slidge/slixfix/xep_0264/stanza.py +36 -0
  61. slidge/slixfix/xep_0264/thumbnail.py +23 -0
  62. slidge/slixfix/xep_0292/__init__.py +5 -0
  63. slidge/slixfix/xep_0292/vcard4.py +100 -0
  64. slidge/slixfix/xep_0313/__init__.py +12 -0
  65. slidge/slixfix/xep_0313/mam.py +262 -0
  66. slidge/slixfix/xep_0313/stanza.py +359 -0
  67. slidge/slixfix/xep_0317/__init__.py +5 -0
  68. slidge/slixfix/xep_0317/hats.py +17 -0
  69. slidge/slixfix/xep_0317/stanza.py +28 -0
  70. slidge/{util → slixfix}/xep_0356_old/privilege.py +9 -7
  71. slidge/slixfix/xep_0424/__init__.py +9 -0
  72. slidge/slixfix/xep_0424/retraction.py +77 -0
  73. slidge/slixfix/xep_0424/stanza.py +28 -0
  74. slidge/slixfix/xep_0490/__init__.py +8 -0
  75. slidge/slixfix/xep_0490/mds.py +47 -0
  76. slidge/slixfix/xep_0490/stanza.py +17 -0
  77. slidge/util/__init__.py +4 -6
  78. slidge/util/archive_msg.py +61 -0
  79. slidge/util/conf.py +25 -4
  80. slidge/util/db.py +23 -69
  81. slidge/util/schema.sql +126 -0
  82. slidge/util/sql.py +508 -0
  83. slidge/util/test.py +136 -86
  84. slidge/util/types.py +155 -14
  85. slidge/util/util.py +225 -51
  86. slidge-0.1.2.dist-info/METADATA +111 -0
  87. slidge-0.1.2.dist-info/RECORD +96 -0
  88. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/WHEEL +1 -1
  89. slidge/core/adhoc.py +0 -492
  90. slidge/core/chat_command.py +0 -197
  91. slidge/core/contact.py +0 -441
  92. slidge/core/disco.py +0 -59
  93. slidge/core/gateway.py +0 -899
  94. slidge/core/muc/__init__.py +0 -3
  95. slidge/core/muc/bookmarks.py +0 -74
  96. slidge/core/muc/participant.py +0 -152
  97. slidge/core/muc/room.py +0 -348
  98. slidge/plugins/discord/__init__.py +0 -121
  99. slidge/plugins/discord/client.py +0 -121
  100. slidge/plugins/discord/session.py +0 -172
  101. slidge/plugins/dummy.py +0 -334
  102. slidge/plugins/facebook.py +0 -591
  103. slidge/plugins/hackernews.py +0 -209
  104. slidge/plugins/mattermost/__init__.py +0 -1
  105. slidge/plugins/mattermost/api.py +0 -288
  106. slidge/plugins/mattermost/gateway.py +0 -417
  107. slidge/plugins/mattermost/websocket.py +0 -248
  108. slidge/plugins/signal/__init__.py +0 -4
  109. slidge/plugins/signal/config.py +0 -4
  110. slidge/plugins/signal/contact.py +0 -104
  111. slidge/plugins/signal/gateway.py +0 -379
  112. slidge/plugins/signal/group.py +0 -76
  113. slidge/plugins/signal/session.py +0 -515
  114. slidge/plugins/signal/txt.py +0 -13
  115. slidge/plugins/signal/util.py +0 -32
  116. slidge/plugins/skype.py +0 -310
  117. slidge/plugins/steam.py +0 -400
  118. slidge/plugins/telegram/__init__.py +0 -6
  119. slidge/plugins/telegram/client.py +0 -325
  120. slidge/plugins/telegram/config.py +0 -21
  121. slidge/plugins/telegram/contact.py +0 -154
  122. slidge/plugins/telegram/gateway.py +0 -182
  123. slidge/plugins/telegram/group.py +0 -184
  124. slidge/plugins/telegram/session.py +0 -275
  125. slidge/plugins/telegram/util.py +0 -153
  126. slidge/plugins/whatsapp/__init__.py +0 -6
  127. slidge/plugins/whatsapp/config.py +0 -17
  128. slidge/plugins/whatsapp/contact.py +0 -33
  129. slidge/plugins/whatsapp/event.go +0 -455
  130. slidge/plugins/whatsapp/gateway.go +0 -156
  131. slidge/plugins/whatsapp/gateway.py +0 -69
  132. slidge/plugins/whatsapp/go.mod +0 -17
  133. slidge/plugins/whatsapp/go.sum +0 -22
  134. slidge/plugins/whatsapp/session.go +0 -371
  135. slidge/plugins/whatsapp/session.py +0 -370
  136. slidge/util/xep_0030/__init__.py +0 -13
  137. slidge/util/xep_0030/disco.py +0 -811
  138. slidge/util/xep_0030/stanza/__init__.py +0 -7
  139. slidge/util/xep_0030/stanza/info.py +0 -270
  140. slidge/util/xep_0030/stanza/items.py +0 -147
  141. slidge/util/xep_0030/static.py +0 -467
  142. slidge/util/xep_0050/adhoc.py +0 -631
  143. slidge/util/xep_0050/stanza.py +0 -180
  144. slidge/util/xep_0077/stanza.py +0 -71
  145. slidge/util/xep_0292/__init__.py +0 -1
  146. slidge/util/xep_0292/stanza.py +0 -167
  147. slidge/util/xep_0292/vcard4.py +0 -74
  148. slidge/util/xep_0356/__init__.py +0 -7
  149. slidge/util/xep_0356/permissions.py +0 -35
  150. slidge/util/xep_0356/privilege.py +0 -160
  151. slidge/util/xep_0356/stanza.py +0 -44
  152. slidge/util/xep_0461/__init__.py +0 -6
  153. slidge/util/xep_0461/reply.py +0 -48
  154. slidge/util/xep_0461/stanza.py +0 -80
  155. slidge-0.1.0rc1.dist-info/METADATA +0 -171
  156. slidge-0.1.0rc1.dist-info/RECORD +0 -99
  157. /slidge/{plugins/__init__.py → py.typed} +0 -0
  158. /slidge/{util → slixfix}/xep_0077/__init__.py +0 -0
  159. /slidge/{util → slixfix}/xep_0100/__init__.py +0 -0
  160. /slidge/{util → slixfix}/xep_0100/stanza.py +0 -0
  161. /slidge/{util → slixfix}/xep_0356_old/__init__.py +0 -0
  162. /slidge/{util → slixfix}/xep_0356_old/stanza.py +0 -0
  163. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/LICENSE +0 -0
  164. {slidge-0.1.0rc1.dist-info → slidge-0.1.2.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,458 @@
1
+ import logging
2
+ import string
3
+ import stringprep
4
+ import uuid
5
+ import warnings
6
+ from copy import copy
7
+ from datetime import datetime
8
+ from functools import cached_property
9
+ from typing import TYPE_CHECKING, Optional, Union
10
+
11
+ from slixmpp import JID, InvalidJID, Message, Presence
12
+ from slixmpp.plugins.xep_0045.stanza import MUCAdminItem
13
+ from slixmpp.stringprep import StringprepError, resourceprep
14
+ from slixmpp.types import MessageTypes, OptJid
15
+ from slixmpp.util.stringprep_profiles import StringPrepError, prohibit_output
16
+
17
+ from ..contact import LegacyContact
18
+ from ..core.mixins import ChatterDiscoMixin, MessageMixin, PresenceMixin
19
+ from ..util import SubclassableOnce, strip_illegal_chars
20
+ from ..util.sql import CachedPresence
21
+ from ..util.types import (
22
+ Hat,
23
+ LegacyMessageType,
24
+ MessageOrPresenceTypeVar,
25
+ MucAffiliation,
26
+ MucRole,
27
+ )
28
+
29
+ if TYPE_CHECKING:
30
+ from .room import LegacyMUC
31
+
32
+
33
+ def strip_non_printable(nickname: str):
34
+ new = (
35
+ "".join(x for x in nickname if x in string.printable)
36
+ + f"-slidge-{hash(nickname)}"
37
+ )
38
+ warnings.warn(f"Could not use {nickname} as a nickname, using {new}")
39
+ return new
40
+
41
+
42
+ class LegacyParticipant(
43
+ PresenceMixin,
44
+ MessageMixin,
45
+ ChatterDiscoMixin,
46
+ metaclass=SubclassableOnce,
47
+ ):
48
+ """
49
+ A legacy participant of a legacy group chat.
50
+ """
51
+
52
+ mtype: MessageTypes = "groupchat"
53
+ _can_send_carbon = False
54
+ USE_STANZA_ID = True
55
+ STRIP_SHORT_DELAY = False
56
+
57
+ def __init__(
58
+ self,
59
+ muc: "LegacyMUC",
60
+ nickname: Optional[str] = None,
61
+ is_user=False,
62
+ is_system=False,
63
+ role: MucRole = "participant",
64
+ affiliation: MucAffiliation = "member",
65
+ ):
66
+ super().__init__()
67
+ self._hats = list[Hat]()
68
+ self.muc = muc
69
+ self.session = session = muc.session
70
+ self.user = session.user
71
+ self.xmpp = session.xmpp
72
+ self._role = role
73
+ self._affiliation = affiliation
74
+ self.is_user: bool = is_user
75
+ self.is_system: bool = is_system
76
+
77
+ self._nickname = nickname
78
+
79
+ self.__update_jid(nickname)
80
+ log.debug("Instantiation of: %r", self)
81
+
82
+ self.contact: Optional["LegacyContact"] = None
83
+ # we track if we already sent a presence for this participant.
84
+ # if we didn't, we send it before the first message.
85
+ # this way, event in plugins that don't map "user has joined" events,
86
+ # we send a "join"-presence from the participant before the first message
87
+ self.__presence_sent = False
88
+ self.log = logging.getLogger(f"{self.user.bare_jid}:{self.jid}")
89
+
90
+ def __repr__(self):
91
+ return f"<Participant '{self.nickname}'/'{self.jid}' of '{self.muc}'>"
92
+
93
+ @property
94
+ def affiliation(self):
95
+ return self._affiliation
96
+
97
+ @affiliation.setter
98
+ def affiliation(self, affiliation: MucAffiliation):
99
+ if self._affiliation == affiliation:
100
+ return
101
+ self._affiliation = affiliation
102
+ if not self.__presence_sent:
103
+ return
104
+ self.send_last_presence(force=True, no_cache_online=True)
105
+
106
+ def send_affiliation_change(self):
107
+ # internal use by slidge
108
+ msg = self._make_message()
109
+ msg["muc"]["affiliation"] = self._affiliation
110
+ msg["type"] = "normal"
111
+ if not self.muc.is_anonymous and not self.is_system:
112
+ if self.contact:
113
+ msg["muc"]["jid"] = self.contact.jid
114
+ else:
115
+ warnings.warn(
116
+ f"Private group but no 1:1 JID associated to '{self}'",
117
+ )
118
+ self._send(msg)
119
+
120
+ @property
121
+ def role(self):
122
+ return self._role
123
+
124
+ @role.setter
125
+ def role(self, role: MucRole):
126
+ if self._role == role:
127
+ return
128
+ self._role = role
129
+ if not self.__presence_sent:
130
+ return
131
+ self.send_last_presence(force=True, no_cache_online=True)
132
+
133
+ def set_hats(self, hats: list[Hat]):
134
+ if self._hats == hats:
135
+ return
136
+ self._hats = hats
137
+ if not self.__presence_sent:
138
+ return
139
+ self.send_last_presence(force=True, no_cache_online=True)
140
+
141
+ def __update_jid(self, unescaped_nickname: Optional[str]):
142
+ j: JID = copy(self.muc.jid)
143
+
144
+ if self.is_system:
145
+ self.jid = j
146
+ return
147
+
148
+ nickname = unescaped_nickname
149
+
150
+ if nickname:
151
+ nickname = self._nickname_no_illegal = strip_illegal_chars(nickname)
152
+ else:
153
+ warnings.warn(
154
+ "Only the system participant is allowed to not have a nickname"
155
+ )
156
+ nickname = f"unnamed-{uuid.uuid4()}"
157
+
158
+ assert isinstance(nickname, str)
159
+
160
+ try:
161
+ # workaround for https://codeberg.org/poezio/slixmpp/issues/3480
162
+ prohibit_output(nickname, [stringprep.in_table_a1])
163
+ resourceprep(nickname)
164
+ except (StringPrepError, StringprepError):
165
+ nickname = nickname.encode("punycode").decode()
166
+
167
+ # at this point there still might be control chars
168
+ try:
169
+ j.resource = nickname
170
+ except InvalidJID:
171
+ j.resource = strip_non_printable(nickname)
172
+
173
+ if nickname != unescaped_nickname:
174
+ self.muc._participants_by_escaped_nicknames[nickname] = self # type:ignore
175
+
176
+ self.jid = j
177
+
178
+ def send_configuration_change(self, codes: tuple[int]):
179
+ if not self.is_system:
180
+ raise RuntimeError("This is only possible for the system participant")
181
+ msg = self._make_message()
182
+ msg["muc"]["status_codes"] = codes
183
+ self._send(msg)
184
+
185
+ @property
186
+ def nickname(self):
187
+ return self._nickname
188
+
189
+ @nickname.setter
190
+ def nickname(self, new_nickname: str):
191
+ old = self._nickname
192
+ if new_nickname == old:
193
+ return
194
+
195
+ cache = getattr(self, "_last_presence", None)
196
+ if cache:
197
+ last_seen = cache.last_seen
198
+ kwargs = cache.presence_kwargs
199
+ else:
200
+ last_seen = None
201
+ kwargs = {}
202
+
203
+ kwargs["status_codes"] = {303}
204
+
205
+ p = self._make_presence(ptype="unavailable", last_seen=last_seen, **kwargs)
206
+ # in this order so pfrom=old resource and we actually use the escaped nick
207
+ # in the muc/item/nick element
208
+ self.__update_jid(new_nickname)
209
+ p["muc"]["item"]["nick"] = self.jid.resource
210
+ self._send(p)
211
+
212
+ self._nickname = new_nickname
213
+
214
+ kwargs["status_codes"] = set()
215
+ p = self._make_presence(ptype="available", last_seen=last_seen, **kwargs)
216
+ self.__add_nick_element(p)
217
+ self._send(p)
218
+
219
+ if old:
220
+ self.muc.rename_participant(old, new_nickname)
221
+
222
+ def _make_presence(
223
+ self,
224
+ *,
225
+ last_seen: Optional[datetime] = None,
226
+ status_codes: Optional[set[int]] = None,
227
+ user_full_jid: Optional[JID] = None,
228
+ **presence_kwargs,
229
+ ):
230
+ p = super()._make_presence(last_seen=last_seen, **presence_kwargs)
231
+ p["muc"]["affiliation"] = self.affiliation
232
+ p["muc"]["role"] = self.role
233
+ if self._hats:
234
+ p["hats"].add_hats(self._hats)
235
+ codes = status_codes or set()
236
+ if self.is_user:
237
+ codes.add(110)
238
+ if not self.muc.is_anonymous and not self.is_system:
239
+ if self.is_user:
240
+ if user_full_jid:
241
+ p["muc"]["jid"] = user_full_jid
242
+ else:
243
+ jid = copy(self.user.jid)
244
+ try:
245
+ jid.resource = next(
246
+ iter(self.muc.user_resources) # type:ignore
247
+ )
248
+ except StopIteration:
249
+ jid.resource = "pseudo-resource"
250
+ p["muc"]["jid"] = self.user.jid
251
+ codes.add(100)
252
+ elif self.contact:
253
+ p["muc"]["jid"] = self.contact.jid
254
+ if a := self.contact.get_avatar():
255
+ p["vcard_temp_update"]["photo"] = a.id
256
+ else:
257
+ warnings.warn(
258
+ f"Private group but no 1:1 JID associated to '{self}'",
259
+ )
260
+ if self.is_user and (hash_ := self.session.avatar_hash):
261
+ p["vcard_temp_update"]["photo"] = hash_
262
+ p["muc"]["status_codes"] = codes
263
+ return p
264
+
265
+ @property
266
+ def DISCO_NAME(self):
267
+ return self.nickname
268
+
269
+ def __send_presence_if_needed(
270
+ self, stanza: Union[Message, Presence], full_jid: JID, archive_only: bool
271
+ ):
272
+ if (
273
+ archive_only
274
+ or self.is_system
275
+ or self.is_user
276
+ or self.__presence_sent
277
+ or stanza["subject"]
278
+ ):
279
+ return
280
+ if isinstance(stanza, Message):
281
+ if stanza.get_plugin("muc", check=True):
282
+ return
283
+ self.send_initial_presence(full_jid)
284
+
285
+ @cached_property
286
+ def __occupant_id(self):
287
+ if self.contact:
288
+ return self.contact.jid
289
+ elif self.is_user:
290
+ return "slidge-user"
291
+ elif self.is_system:
292
+ return "room"
293
+ else:
294
+ return str(uuid.uuid4())
295
+
296
+ def _send(
297
+ self,
298
+ stanza: MessageOrPresenceTypeVar,
299
+ full_jid: Optional[JID] = None,
300
+ archive_only=False,
301
+ **send_kwargs,
302
+ ) -> MessageOrPresenceTypeVar:
303
+ stanza["occupant-id"]["id"] = self.__occupant_id
304
+ if isinstance(stanza, Presence):
305
+ if stanza["type"] == "unavailable" and not self.__presence_sent:
306
+ return stanza # type:ignore
307
+ self.__presence_sent = True
308
+ if full_jid:
309
+ stanza["to"] = full_jid
310
+ self.__send_presence_if_needed(stanza, full_jid, archive_only)
311
+ if self.is_user:
312
+ assert stanza.stream is not None
313
+ stanza.stream.send(stanza, use_filters=False)
314
+ else:
315
+ stanza.send()
316
+ else:
317
+ if isinstance(stanza, Message):
318
+ self.muc.archive.add(stanza, self)
319
+ if archive_only:
320
+ return stanza
321
+ for user_full_jid in self.muc.user_full_jids():
322
+ stanza = copy(stanza)
323
+ stanza["to"] = user_full_jid
324
+ self.__send_presence_if_needed(stanza, user_full_jid, archive_only)
325
+ stanza.send()
326
+ return stanza
327
+
328
+ def mucadmin_item(self):
329
+ item = MUCAdminItem()
330
+ item["nick"] = self.nickname
331
+ item["affiliation"] = self.affiliation
332
+ item["role"] = self.role
333
+ if not self.muc.is_anonymous:
334
+ if self.is_user:
335
+ item["jid"] = self.user.bare_jid
336
+ elif self.contact:
337
+ item["jid"] = self.contact.jid.bare
338
+ else:
339
+ warnings.warn(
340
+ (
341
+ f"Public group but no contact JID associated to {self.jid} in"
342
+ f" {self}"
343
+ ),
344
+ )
345
+ return item
346
+
347
+ def __add_nick_element(self, p: Presence):
348
+ if (nick := self._nickname_no_illegal) != self.jid.resource:
349
+ n = self.xmpp.plugin["xep_0172"].stanza.UserNick()
350
+ n["nick"] = nick
351
+ p.append(n)
352
+
353
+ def _get_last_presence(self) -> Optional[CachedPresence]:
354
+ own = super()._get_last_presence()
355
+ if own is None and self.contact:
356
+ return self.contact._get_last_presence()
357
+ return own
358
+
359
+ def send_initial_presence(
360
+ self,
361
+ full_jid: JID,
362
+ nick_change=False,
363
+ presence_id: Optional[str] = None,
364
+ ):
365
+ """
366
+ Called when the user joins a MUC, as a mechanism
367
+ to indicate to the joining XMPP client the list of "participants".
368
+
369
+ Can be called this to trigger a "participant has joined the group" event.
370
+
371
+ :param full_jid: Set this to only send to a specific user XMPP resource.
372
+ :param nick_change: Used when the user joins and the MUC renames them (code 210)
373
+ :param presence_id: set the presence ID. used internally by slidge
374
+ """
375
+ # MUC status codes: https://xmpp.org/extensions/xep-0045.html#registrar-statuscodes
376
+ codes = set()
377
+ if nick_change:
378
+ codes.add(210)
379
+
380
+ if self.is_user:
381
+ # the "initial presence" of the user has to be vanilla, as it is
382
+ # a crucial part of the MUC join sequence for XMPP clients.
383
+ kwargs = {}
384
+ else:
385
+ cache = self._get_last_presence()
386
+ self.log.debug("Join muc, initial presence: %s", cache)
387
+ if cache:
388
+ ptype = cache.ptype
389
+ if ptype == "unavailable":
390
+ return
391
+ kwargs = dict(
392
+ last_seen=cache.last_seen, pstatus=cache.pstatus, pshow=cache.pshow
393
+ )
394
+ else:
395
+ kwargs = {}
396
+ p = self._make_presence(
397
+ status_codes=codes,
398
+ user_full_jid=full_jid,
399
+ **kwargs, # type:ignore
400
+ )
401
+ if presence_id:
402
+ p["id"] = presence_id
403
+ self.__add_nick_element(p)
404
+ self._send(p, full_jid)
405
+
406
+ def leave(self):
407
+ """
408
+ Call this when the participant leaves the room
409
+ """
410
+ self.muc.remove_participant(self)
411
+
412
+ def kick(self):
413
+ """
414
+ Call this when the participant is kicked from the room
415
+ """
416
+ self.muc.remove_participant(self, kick=True)
417
+
418
+ def ban(self):
419
+ """
420
+ Call this when the participant is banned from the room
421
+ """
422
+ self.muc.remove_participant(self, ban=True)
423
+
424
+ def get_disco_info(self, jid: OptJid = None, node: Optional[str] = None):
425
+ if self.contact is not None:
426
+ return self.contact.get_disco_info()
427
+ return super().get_disco_info()
428
+
429
+ def moderate(self, legacy_msg_id: LegacyMessageType, reason: Optional[str] = None):
430
+ m = self.muc.get_system_participant()._make_message()
431
+ m["apply_to"]["id"] = self._legacy_to_xmpp(legacy_msg_id)
432
+ m["apply_to"]["moderated"].enable("retract")
433
+ m["apply_to"]["moderated"]["by"] = self.jid
434
+ if reason:
435
+ m["apply_to"]["moderated"]["reason"] = reason
436
+ self._send(m)
437
+
438
+ def set_room_subject(
439
+ self,
440
+ subject: str,
441
+ full_jid: Optional[JID] = None,
442
+ when: Optional[datetime] = None,
443
+ update_muc=True,
444
+ ):
445
+ if update_muc:
446
+ self.muc._subject = subject # type: ignore
447
+ self.muc.subject_setter = self
448
+ self.muc.subject_date = when
449
+
450
+ msg = self._make_message()
451
+ if when is not None:
452
+ msg["delay"].set_stamp(when)
453
+ msg["delay"]["from"] = self.muc.jid
454
+ msg["subject"] = subject or str(self.muc.name)
455
+ self._send(msg, full_jid)
456
+
457
+
458
+ log = logging.getLogger(__name__)