slidge 0.1.3__py3-none-any.whl → 0.2.0__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. slidge/__init__.py +3 -5
  2. slidge/__main__.py +2 -197
  3. slidge/__version__.py +5 -0
  4. slidge/command/adhoc.py +40 -17
  5. slidge/command/admin.py +24 -12
  6. slidge/command/base.py +10 -8
  7. slidge/command/categories.py +13 -3
  8. slidge/command/chat_command.py +29 -2
  9. slidge/command/register.py +32 -16
  10. slidge/command/user.py +106 -13
  11. slidge/contact/contact.py +254 -50
  12. slidge/contact/roster.py +124 -53
  13. slidge/core/config.py +19 -13
  14. slidge/core/dispatcher/__init__.py +3 -0
  15. slidge/core/{gateway → dispatcher}/caps.py +12 -8
  16. slidge/core/{gateway → dispatcher}/disco.py +10 -18
  17. slidge/core/dispatcher/message/__init__.py +10 -0
  18. slidge/core/dispatcher/message/chat_state.py +40 -0
  19. slidge/core/dispatcher/message/marker.py +62 -0
  20. slidge/core/dispatcher/message/message.py +397 -0
  21. slidge/core/dispatcher/muc/__init__.py +12 -0
  22. slidge/core/dispatcher/muc/admin.py +98 -0
  23. slidge/core/{gateway → dispatcher/muc}/mam.py +25 -17
  24. slidge/core/dispatcher/muc/misc.py +121 -0
  25. slidge/core/dispatcher/muc/owner.py +96 -0
  26. slidge/core/{gateway → dispatcher/muc}/ping.py +11 -17
  27. slidge/core/dispatcher/presence.py +176 -0
  28. slidge/core/dispatcher/registration.py +85 -0
  29. slidge/core/{gateway → dispatcher}/search.py +9 -16
  30. slidge/core/dispatcher/session_dispatcher.py +84 -0
  31. slidge/core/dispatcher/util.py +174 -0
  32. slidge/core/{gateway/vcard_temp.py → dispatcher/vcard.py} +35 -19
  33. slidge/core/{gateway/base.py → gateway.py} +176 -153
  34. slidge/core/mixins/__init__.py +11 -1
  35. slidge/core/mixins/attachment.py +106 -67
  36. slidge/core/mixins/avatar.py +94 -25
  37. slidge/core/mixins/base.py +10 -4
  38. slidge/core/mixins/db.py +18 -0
  39. slidge/core/mixins/disco.py +0 -10
  40. slidge/core/mixins/lock.py +10 -8
  41. slidge/core/mixins/message.py +11 -195
  42. slidge/core/mixins/message_maker.py +17 -9
  43. slidge/core/mixins/message_text.py +211 -0
  44. slidge/core/mixins/presence.py +17 -4
  45. slidge/core/pubsub.py +114 -288
  46. slidge/core/session.py +101 -40
  47. slidge/db/__init__.py +4 -0
  48. slidge/db/alembic/__init__.py +0 -0
  49. slidge/db/alembic/env.py +64 -0
  50. slidge/db/alembic/old_user_store.py +183 -0
  51. slidge/db/alembic/script.py.mako +26 -0
  52. slidge/db/alembic/versions/09f27f098baa_add_missing_attributes_in_room.py +36 -0
  53. slidge/db/alembic/versions/15b0bd83407a_remove_bogus_unique_constraints_on_room_.py +85 -0
  54. slidge/db/alembic/versions/2461390c0af2_store_contacts_caps_verstring_in_db.py +36 -0
  55. slidge/db/alembic/versions/29f5280c61aa_store_subject_setter_in_room.py +37 -0
  56. slidge/db/alembic/versions/2b1f45ab7379_store_room_subject_setter_by_nickname.py +41 -0
  57. slidge/db/alembic/versions/3071e0fa69d4_add_contact_client_type.py +52 -0
  58. slidge/db/alembic/versions/45c24cc73c91_add_bob.py +42 -0
  59. slidge/db/alembic/versions/5bd48bfdffa2_lift_room_legacy_id_constraint.py +61 -0
  60. slidge/db/alembic/versions/82a4af84b679_add_muc_history_filled.py +48 -0
  61. slidge/db/alembic/versions/8b993243a536_add_vcard_content_to_contact_table.py +43 -0
  62. slidge/db/alembic/versions/8d2ced764698_rely_on_db_to_store_contacts_rooms_and_.py +139 -0
  63. slidge/db/alembic/versions/aa9d82a7f6ef_db_creation.py +101 -0
  64. slidge/db/alembic/versions/abba1ae0edb3_store_avatar_legacy_id_in_the_contact_.py +79 -0
  65. slidge/db/alembic/versions/b33993e87db3_move_everything_to_persistent_db.py +214 -0
  66. slidge/db/alembic/versions/b64b1a793483_add_source_and_legacy_id_for_archived_.py +52 -0
  67. slidge/db/alembic/versions/c4a8ec35a0e8_per_room_user_nick.py +34 -0
  68. slidge/db/alembic/versions/e91195719c2c_store_users_avatars_persistently.py +26 -0
  69. slidge/db/avatar.py +205 -0
  70. slidge/db/meta.py +72 -0
  71. slidge/db/models.py +405 -0
  72. slidge/db/store.py +1257 -0
  73. slidge/group/archive.py +58 -14
  74. slidge/group/bookmarks.py +89 -65
  75. slidge/group/participant.py +107 -40
  76. slidge/group/room.py +402 -213
  77. slidge/main.py +202 -0
  78. slidge/migration.py +45 -1
  79. slidge/slixfix/__init__.py +31 -1
  80. slidge/{core/gateway → slixfix}/delivery_receipt.py +1 -1
  81. slidge/slixfix/roster.py +13 -4
  82. slidge/slixfix/xep_0292/vcard4.py +1 -87
  83. slidge/util/archive_msg.py +2 -1
  84. slidge/util/db.py +4 -228
  85. slidge/util/test.py +91 -4
  86. slidge/util/types.py +39 -4
  87. slidge/util/util.py +45 -2
  88. {slidge-0.1.3.dist-info → slidge-0.2.0.dist-info}/METADATA +10 -5
  89. slidge-0.2.0.dist-info/RECORD +131 -0
  90. slidge-0.2.0.dist-info/entry_points.txt +3 -0
  91. slidge/core/cache.py +0 -183
  92. slidge/core/gateway/__init__.py +0 -3
  93. slidge/core/gateway/muc_admin.py +0 -35
  94. slidge/core/gateway/presence.py +0 -95
  95. slidge/core/gateway/registration.py +0 -53
  96. slidge/core/gateway/session_dispatcher.py +0 -804
  97. slidge/util/schema.sql +0 -126
  98. slidge/util/sql.py +0 -508
  99. slidge-0.1.3.dist-info/RECORD +0 -96
  100. slidge-0.1.3.dist-info/entry_points.txt +0 -3
  101. {slidge-0.1.3.dist-info → slidge-0.2.0.dist-info}/LICENSE +0 -0
  102. {slidge-0.1.3.dist-info → slidge-0.2.0.dist-info}/WHEEL +0 -0
@@ -1,804 +0,0 @@
1
- import logging
2
- from copy import copy
3
- from typing import TYPE_CHECKING, Awaitable, Callable, Optional, Union
4
-
5
- from slixmpp import JID, CoroutineCallback, Iq, Message, Presence, StanzaPath
6
- from slixmpp.exceptions import IqError, XMPPError
7
- from slixmpp.plugins.xep_0004 import Form
8
- from slixmpp.plugins.xep_0084.stanza import Info
9
-
10
- from ... import LegacyContact
11
- from ...group.room import LegacyMUC
12
- from ...util.sql import db
13
- from ...util.types import LinkPreview, Recipient, RecipientType
14
- from ...util.util import (
15
- dict_to_named_tuple,
16
- merge_resources,
17
- remove_emoji_variation_selector_16,
18
- )
19
- from .. import config
20
- from ..session import BaseSession
21
-
22
- if TYPE_CHECKING:
23
- from .base import BaseGateway
24
-
25
- HandlerType = Callable[[Union[Presence, Message]], Awaitable[None]]
26
-
27
-
28
- class Ignore(BaseException):
29
- pass
30
-
31
-
32
- class SessionDispatcher:
33
- def __init__(self, xmpp: "BaseGateway"):
34
- self.xmpp = xmpp
35
- self.http = xmpp.http
36
-
37
- xmpp.register_handler(
38
- CoroutineCallback(
39
- "MUCModerate",
40
- StanzaPath("iq/apply_to/moderate"),
41
- _exceptions_to_xmpp_errors(self.on_user_moderation), # type:ignore
42
- )
43
- )
44
- xmpp.register_handler(
45
- CoroutineCallback(
46
- "MUCSetAffiliation",
47
- StanzaPath("iq@type=set/mucadmin_query"),
48
- _exceptions_to_xmpp_errors(self.on_user_set_affiliation), # type:ignore
49
- )
50
- )
51
- xmpp.register_handler(
52
- CoroutineCallback(
53
- "muc#admin",
54
- StanzaPath("iq@type=get/mucowner_query"),
55
- _exceptions_to_xmpp_errors(self.on_muc_owner_query), # type: ignore
56
- )
57
- )
58
- xmpp.register_handler(
59
- CoroutineCallback(
60
- "muc#admin",
61
- StanzaPath("iq@type=set/mucowner_query"),
62
- _exceptions_to_xmpp_errors(self.on_muc_owner_set), # type: ignore
63
- )
64
- )
65
-
66
- for event in (
67
- "legacy_message",
68
- "marker_displayed",
69
- "presence",
70
- "chatstate_active",
71
- "chatstate_inactive",
72
- "chatstate_composing",
73
- "chatstate_paused",
74
- "message_correction",
75
- "reactions",
76
- "message_retract",
77
- "groupchat_join",
78
- "groupchat_message",
79
- "groupchat_direct_invite",
80
- "groupchat_subject",
81
- "avatar_metadata_publish",
82
- "message_displayed_synchronization_publish",
83
- ):
84
- xmpp.add_event_handler(
85
- event, _exceptions_to_xmpp_errors(getattr(self, "on_" + event))
86
- )
87
-
88
- async def __get_session(
89
- self, stanza: Union[Message, Presence, Iq], timeout: Optional[int] = 10
90
- ) -> BaseSession:
91
- xmpp = self.xmpp
92
- if stanza.get_from().server == xmpp.boundjid.bare:
93
- log.debug("Ignoring echo")
94
- raise Ignore
95
- if (
96
- isinstance(stanza, Message)
97
- and stanza.get_type() == "chat"
98
- and stanza.get_to() == xmpp.boundjid.bare
99
- ):
100
- log.debug("Ignoring message to component")
101
- raise Ignore
102
- session = xmpp.get_session_from_stanza(stanza)
103
- await session.wait_for_ready(timeout)
104
- if isinstance(stanza, Message) and _ignore(session, stanza):
105
- raise Ignore
106
- return session
107
-
108
- def __ack(self, msg: Message):
109
- if not self.xmpp.PROPER_RECEIPTS:
110
- self.xmpp.delivery_receipt.ack(msg)
111
-
112
- async def __get_session_entity_thread(
113
- self, msg: Message
114
- ) -> tuple["BaseSession", Recipient, Union[int, str]]:
115
- session = await self.__get_session(msg)
116
- e: Recipient = await _get_entity(session, msg)
117
- legacy_thread = await _xmpp_to_legacy_thread(session, msg, e)
118
- return session, e, legacy_thread
119
-
120
- async def on_legacy_message(self, msg: Message):
121
- """
122
- Meant to be called from :class:`BaseGateway` only.
123
-
124
- :param msg:
125
- :return:
126
- """
127
- # we MUST not use `if m["replace"]["id"]` because it adds the tag if not
128
- # present. this is a problem for MUC echoed messages
129
- if msg.get_plugin("replace", check=True) is not None:
130
- # ignore last message correction (handled by a specific method)
131
- return
132
- if msg.get_plugin("apply_to", check=True) is not None:
133
- # ignore message retraction (handled by a specific method)
134
- return
135
- if msg.get_plugin("reactions", check=True) is not None:
136
- # ignore message reaction fallback.
137
- # the reaction itself is handled by self.react_from_msg().
138
- return
139
- if msg.get_plugin("retract", check=True) is not None:
140
- # ignore message retraction fallback.
141
- # the retraction itself is handled by self.on_retract
142
- return
143
-
144
- session, entity, thread = await self.__get_session_entity_thread(msg)
145
-
146
- e: Recipient = await _get_entity(session, msg)
147
- log.debug("Entity %r", e)
148
-
149
- if msg.get_plugin("oob", check=True) is not None:
150
- url = msg["oob"]["url"]
151
- else:
152
- url = None
153
-
154
- text = msg["body"]
155
-
156
- reply_to = None
157
- reply_fallback = None
158
- if msg.get_plugin("reply", check=True):
159
- try:
160
- reply_to_msg_xmpp_id = _xmpp_msg_id_to_legacy(
161
- session, msg["reply"]["id"]
162
- )
163
- except XMPPError:
164
- session.log.debug(
165
- "Could not determine reply-to legacy msg ID, sending quote instead."
166
- )
167
- text = msg["body"]
168
- reply_fallback = None
169
- reply_to_msg_xmpp_id = None
170
- else:
171
- reply_to_jid = JID(msg["reply"]["to"])
172
- if msg["type"] == "chat":
173
- if reply_to_jid.bare != session.user.jid.bare:
174
- try:
175
- reply_to = await session.contacts.by_jid(reply_to_jid)
176
- except XMPPError:
177
- pass
178
- elif msg["type"] == "groupchat":
179
- nick = reply_to_jid.resource
180
- try:
181
- muc = await session.bookmarks.by_jid(reply_to_jid)
182
- except XMPPError:
183
- pass
184
- else:
185
- if nick != muc.user_nick:
186
- reply_to = await muc.get_participant(
187
- reply_to_jid.resource, store=False
188
- )
189
- if msg.get_plugin("fallback", check=True) and (
190
- isinstance(e, LegacyMUC) or e.REPLIES
191
- ):
192
- text = msg["fallback"].get_stripped_body(
193
- self.xmpp["xep_0461"].namespace
194
- )
195
- try:
196
- reply_fallback = msg["reply"].get_fallback_body()
197
- except AttributeError:
198
- pass
199
- else:
200
- reply_to_msg_xmpp_id = None
201
- reply_to = None
202
-
203
- if msg.get_plugin("link_previews", check=True):
204
- pass
205
-
206
- kwargs = dict(
207
- reply_to_msg_id=reply_to_msg_xmpp_id,
208
- reply_to_fallback_text=reply_fallback,
209
- reply_to=reply_to,
210
- thread=thread,
211
- )
212
-
213
- if not url and isinstance(e, LegacyMUC):
214
- kwargs["mentions"] = await e.parse_mentions(text)
215
-
216
- if previews := msg["link_previews"]:
217
- kwargs["link_previews"] = [
218
- dict_to_named_tuple(p, LinkPreview) for p in previews
219
- ]
220
-
221
- if url:
222
- async with self.http.get(url) as response:
223
- if response.status >= 400:
224
- session.log.warning(
225
- (
226
- "OOB url cannot be downloaded: %s, sending the URL as text"
227
- " instead."
228
- ),
229
- response,
230
- )
231
- legacy_msg_id = await session.on_text(e, url, **kwargs)
232
- else:
233
- legacy_msg_id = await session.on_file(
234
- e, url, http_response=response, **kwargs
235
- )
236
- elif text:
237
- legacy_msg_id = await session.on_text(e, text, **kwargs)
238
- else:
239
- log.debug("Ignoring %s", msg.get_id())
240
- return
241
-
242
- if isinstance(e, LegacyMUC):
243
- await e.echo(msg, legacy_msg_id)
244
- if legacy_msg_id is not None:
245
- session.muc_sent_msg_ids[legacy_msg_id] = msg.get_id()
246
- else:
247
- self.__ack(msg)
248
- if legacy_msg_id is not None:
249
- session.sent[legacy_msg_id] = msg.get_id()
250
- if session.MESSAGE_IDS_ARE_THREAD_IDS and (t := msg["thread"]):
251
- session.threads[t] = legacy_msg_id
252
-
253
- async def on_groupchat_message(self, msg: Message):
254
- await self.on_legacy_message(msg)
255
-
256
- async def on_message_correction(self, msg: Message):
257
- session, entity, thread = await self.__get_session_entity_thread(msg)
258
- xmpp_id = msg["replace"]["id"]
259
- if isinstance(entity, LegacyMUC):
260
- legacy_id = session.muc_sent_msg_ids.inverse.get(xmpp_id)
261
- else:
262
- legacy_id = _xmpp_msg_id_to_legacy(session, xmpp_id)
263
-
264
- if isinstance(entity, LegacyMUC):
265
- mentions = await entity.parse_mentions(msg["body"])
266
- else:
267
- mentions = None
268
-
269
- if previews := msg["link_previews"]:
270
- link_previews = [dict_to_named_tuple(p, LinkPreview) for p in previews]
271
- else:
272
- link_previews = []
273
-
274
- if legacy_id is None:
275
- log.debug("Did not find legacy ID to correct")
276
- new_legacy_msg_id = await session.on_text(
277
- entity,
278
- "Correction:" + msg["body"],
279
- thread=thread,
280
- mentions=mentions,
281
- link_previews=link_previews,
282
- )
283
- elif (
284
- not msg["body"].strip()
285
- and config.CORRECTION_EMPTY_BODY_AS_RETRACTION
286
- and entity.RETRACTION
287
- ):
288
- await session.on_retract(entity, legacy_id, thread=thread)
289
- new_legacy_msg_id = None
290
- elif entity.CORRECTION:
291
- new_legacy_msg_id = await session.on_correct(
292
- entity,
293
- msg["body"],
294
- legacy_id,
295
- thread=thread,
296
- mentions=mentions,
297
- link_previews=link_previews,
298
- )
299
- else:
300
- session.send_gateway_message(
301
- "Last message correction is not supported by this legacy service. "
302
- "Slidge will send your correction as new message."
303
- )
304
- if (
305
- config.LAST_MESSAGE_CORRECTION_RETRACTION_WORKAROUND
306
- and entity.RETRACTION
307
- and legacy_id is not None
308
- ):
309
- if legacy_id is not None:
310
- session.send_gateway_message(
311
- "Slidge will attempt to retract the original message you wanted"
312
- " to edit."
313
- )
314
- await session.on_retract(entity, legacy_id, thread=thread)
315
-
316
- new_legacy_msg_id = await session.on_text(
317
- entity,
318
- "Correction: " + msg["body"],
319
- thread=thread,
320
- mentions=mentions,
321
- link_previews=link_previews,
322
- )
323
-
324
- if isinstance(entity, LegacyMUC):
325
- if new_legacy_msg_id is not None:
326
- session.muc_sent_msg_ids[new_legacy_msg_id] = msg.get_id()
327
- await entity.echo(msg, new_legacy_msg_id)
328
- else:
329
- self.__ack(msg)
330
- if new_legacy_msg_id is not None:
331
- session.sent[new_legacy_msg_id] = msg.get_id()
332
-
333
- async def on_message_retract(self, msg: Message):
334
- session, entity, thread = await self.__get_session_entity_thread(msg)
335
- if not entity.RETRACTION:
336
- raise XMPPError(
337
- "bad-request",
338
- "This legacy service does not support message retraction.",
339
- )
340
- xmpp_id: str = msg["retract"]["id"]
341
- legacy_id = _xmpp_msg_id_to_legacy(session, xmpp_id)
342
- if legacy_id:
343
- await session.on_retract(entity, legacy_id, thread=thread)
344
- if isinstance(entity, LegacyMUC):
345
- await entity.echo(msg, None)
346
- else:
347
- log.debug("Ignored retraction from user")
348
- self.__ack(msg)
349
-
350
- async def on_marker_displayed(self, msg: Message):
351
- session = await self.__get_session(msg)
352
-
353
- e: Recipient = await _get_entity(session, msg)
354
- legacy_thread = await _xmpp_to_legacy_thread(session, msg, e)
355
- displayed_msg_id = msg["displayed"]["id"]
356
- if not isinstance(e, LegacyMUC) and self.xmpp.MARK_ALL_MESSAGES:
357
- to_mark = e.get_msg_xmpp_id_up_to(displayed_msg_id) # type: ignore
358
- if to_mark is None:
359
- session.log.debug("Can't mark all messages up to %s", displayed_msg_id)
360
- to_mark = [displayed_msg_id]
361
- else:
362
- to_mark = [displayed_msg_id]
363
- for xmpp_id in to_mark:
364
- await session.on_displayed(
365
- e, _xmpp_msg_id_to_legacy(session, xmpp_id), legacy_thread
366
- )
367
- if isinstance(e, LegacyMUC):
368
- await e.echo(msg, None)
369
-
370
- async def on_chatstate_active(self, msg: Message):
371
- if msg["body"]:
372
- # if there is a body, it's handled in self.on_legacy_message()
373
- return
374
- session, entity, thread = await self.__get_session_entity_thread(msg)
375
- await session.on_active(entity, thread)
376
-
377
- async def on_chatstate_inactive(self, msg: Message):
378
- session, entity, thread = await self.__get_session_entity_thread(msg)
379
- await session.on_inactive(entity, thread)
380
-
381
- async def on_chatstate_composing(self, msg: Message):
382
- session, entity, thread = await self.__get_session_entity_thread(msg)
383
- await session.on_composing(entity, thread)
384
-
385
- async def on_chatstate_paused(self, msg: Message):
386
- session, entity, thread = await self.__get_session_entity_thread(msg)
387
- await session.on_paused(entity, thread)
388
-
389
- async def on_reactions(self, msg: Message):
390
- session, entity, thread = await self.__get_session_entity_thread(msg)
391
- react_to: str = msg["reactions"]["id"]
392
-
393
- special_msg = session.SPECIAL_MSG_ID_PREFIX and react_to.startswith(
394
- session.SPECIAL_MSG_ID_PREFIX
395
- )
396
-
397
- if special_msg:
398
- legacy_id = react_to
399
- else:
400
- legacy_id = _xmpp_msg_id_to_legacy(session, react_to)
401
-
402
- if not legacy_id:
403
- log.debug("Ignored reaction from user")
404
- raise XMPPError(
405
- "internal-server-error",
406
- "Could not convert the XMPP msg ID to a legacy ID",
407
- )
408
-
409
- emojis = [
410
- remove_emoji_variation_selector_16(r["value"]) for r in msg["reactions"]
411
- ]
412
- error_msg = None
413
- entity = entity
414
-
415
- if not special_msg:
416
- if entity.REACTIONS_SINGLE_EMOJI and len(emojis) > 1:
417
- error_msg = "Maximum 1 emoji/message"
418
-
419
- if not error_msg and (subset := await entity.available_emojis(legacy_id)):
420
- if not set(emojis).issubset(subset):
421
- error_msg = f"You can only react with the following emojis: {''.join(subset)}"
422
-
423
- if error_msg:
424
- session.send_gateway_message(error_msg)
425
- if not isinstance(entity, LegacyMUC):
426
- # no need to carbon for groups, we just don't echo the stanza
427
- entity.react(legacy_id, carbon=True) # type: ignore
428
- await session.on_react(entity, legacy_id, [], thread=thread)
429
- raise XMPPError("not-acceptable", text=error_msg)
430
-
431
- await session.on_react(entity, legacy_id, emojis, thread=thread)
432
- if isinstance(entity, LegacyMUC):
433
- await entity.echo(msg, None)
434
- else:
435
- self.__ack(msg)
436
-
437
- multi = db.attachment_get_associated_xmpp_ids(react_to)
438
- if not multi:
439
- return
440
-
441
- if isinstance(entity, LegacyMUC):
442
- for xmpp_id in multi:
443
- mc = copy(msg)
444
- mc["reactions"]["id"] = xmpp_id
445
- await entity.echo(mc)
446
- elif isinstance(entity, LegacyContact):
447
- for xmpp_id in multi:
448
- entity.react(legacy_id, emojis, xmpp_id=xmpp_id, carbon=True)
449
-
450
- async def on_presence(self, p: Presence):
451
- if p.get_plugin("muc_join", check=True):
452
- # handled in on_groupchat_join
453
- # without this early return, since we switch from and to in this
454
- # presence stanza, on_groupchat_join ends up trying to instantiate
455
- # a MUC with the user's JID, which in turn leads to slidge sending
456
- # a (error) presence from=the user's JID, which terminates the
457
- # XML stream.
458
- return
459
-
460
- session = await self.__get_session(p)
461
-
462
- pto = p.get_to()
463
- if pto == self.xmpp.boundjid.bare:
464
- # NB: get_type() returns either a proper presence type or
465
- # a presence show if available. Weird, weird, weird slix.
466
- if (ptype := p.get_type()) not in _USEFUL_PRESENCES:
467
- return
468
- resources = self.xmpp.roster[self.xmpp.boundjid.bare][
469
- p.get_from()
470
- ].resources
471
- session.log.debug("Received a presence from %s", p.get_from())
472
- await session.on_presence(
473
- p.get_from().resource,
474
- ptype, # type: ignore
475
- p["status"],
476
- resources,
477
- merge_resources(resources),
478
- )
479
- return
480
-
481
- muc = session.bookmarks._mucs_by_bare_jid.get(pto.bare)
482
- if muc is None or p.get_from().resource not in muc.user_resources:
483
- return
484
-
485
- if pto.resource == muc.user_nick:
486
- # Ignore presence stanzas with the valid nick.
487
- # even if joined to the group, we might receive those from clients,
488
- # when setting a status message, or going away, etc.
489
- return
490
-
491
- # We can't use XMPPError here because from must be room@slidge/VALID-USER-NICK
492
-
493
- error_from = JID(muc.jid)
494
- error_from.resource = muc.user_nick
495
- error_stanza = p.error()
496
- error_stanza.set_to(p.get_from())
497
- error_stanza.set_from(error_from)
498
- error_stanza.enable("muc_join")
499
- error_stanza.enable("error")
500
- error_stanza["error"]["type"] = "cancel"
501
- error_stanza["error"]["by"] = muc.jid
502
- error_stanza["error"]["condition"] = "not-acceptable"
503
- error_stanza["error"][
504
- "text"
505
- ] = "Slidge does not let you change your nickname in groups."
506
- error_stanza.send()
507
-
508
- async def on_groupchat_join(self, p: Presence):
509
- if not self.xmpp.GROUPS:
510
- raise XMPPError(
511
- "feature-not-implemented",
512
- "This gateway does not implement multi-user chats.",
513
- )
514
- session = await self.__get_session(p)
515
- session.raise_if_not_logged()
516
- muc = await session.bookmarks.by_jid(p.get_to())
517
- await muc.join(p)
518
-
519
- async def on_message_displayed_synchronization_publish(self, msg: Message):
520
- session = await self.__get_session(msg, timeout=None)
521
-
522
- chat_jid = msg["pubsub_event"]["items"]["item"]["id"]
523
-
524
- if chat_jid == self.xmpp.boundjid.bare:
525
- return
526
-
527
- chat = await session.get_contact_or_group_or_participant(JID(chat_jid))
528
- if not isinstance(chat, LegacyMUC):
529
- session.log.debug("Ignoring non-groupchat MDS event")
530
- return
531
-
532
- stanza_id = msg["pubsub_event"]["items"]["item"]["displayed"]["stanza_id"]["id"]
533
- await session.on_displayed(chat, _xmpp_msg_id_to_legacy(session, stanza_id))
534
-
535
- async def on_avatar_metadata_publish(self, m: Message):
536
- if not config.SYNC_AVATAR:
537
- return
538
-
539
- session = await self.__get_session(m, timeout=None)
540
- info = m["pubsub_event"]["items"]["item"]["avatar_metadata"]["info"]
541
-
542
- await self.on_avatar_metadata_info(session, info)
543
-
544
- async def on_avatar_metadata_info(self, session: BaseSession, info: Info):
545
- session.log.debug("Avatar metadata info: %s", info)
546
- hash_ = info["id"]
547
-
548
- if session.avatar_hash == hash_:
549
- return
550
- session.avatar_hash = hash_
551
-
552
- if hash_:
553
- try:
554
- iq = await self.xmpp.plugin["xep_0084"].retrieve_avatar(
555
- session.user.jid, hash_, ifrom=self.xmpp.boundjid.bare
556
- )
557
- except IqError as e:
558
- session.log.warning("Could not fetch the user's avatar: %s", e)
559
- return
560
- bytes_ = iq["pubsub"]["items"]["item"]["avatar_data"]["value"]
561
- type_ = info["type"]
562
- height = info["height"]
563
- width = info["width"]
564
- else:
565
- bytes_ = type_ = height = width = hash_ = None
566
- try:
567
- await session.on_avatar(bytes_, hash_, type_, width, height)
568
- except NotImplementedError:
569
- pass
570
- except Exception as e:
571
- # If something goes wrong here, replying an error stanza will to the
572
- # avatar update will likely not show in most clients, so let's send
573
- # a normal message from the component to the user.
574
- session.send_gateway_message(
575
- f"Something went wrong trying to set your avatar: {e!r}"
576
- )
577
-
578
- async def on_user_moderation(self, iq: Iq):
579
- session = await self.__get_session(iq)
580
- session.raise_if_not_logged()
581
-
582
- muc = await session.bookmarks.by_jid(iq.get_to())
583
-
584
- apply_to = iq["apply_to"]
585
- xmpp_id = apply_to["id"]
586
- if not xmpp_id:
587
- raise XMPPError("bad-request", "Missing moderated message ID")
588
-
589
- moderate = apply_to["moderate"]
590
- if not moderate["retract"]:
591
- raise XMPPError(
592
- "feature-not-implemented",
593
- "Slidge only implements moderation/retraction",
594
- )
595
-
596
- legacy_id = _xmpp_msg_id_to_legacy(session, xmpp_id)
597
- await session.on_moderate(muc, legacy_id, moderate["reason"] or None)
598
- iq.reply(clear=True).send()
599
-
600
- async def on_user_set_affiliation(self, iq: Iq):
601
- session = await self.__get_session(iq)
602
- session.raise_if_not_logged()
603
-
604
- item = iq["mucadmin_query"]["item"]
605
- contact = await session.contacts.by_jid(JID(item["jid"]))
606
-
607
- muc = await session.bookmarks.by_jid(iq.get_to())
608
-
609
- await muc.on_set_affiliation(
610
- contact, item["affiliation"], item["reason"] or None, item["nick"] or None
611
- )
612
- iq.reply(clear=True).send()
613
-
614
- async def on_groupchat_direct_invite(self, msg: Message):
615
- session = await self.__get_session(msg)
616
- session.raise_if_not_logged()
617
-
618
- invite = msg["groupchat_invite"]
619
- jid = JID(invite["jid"])
620
-
621
- if jid.domain != self.xmpp.boundjid.bare:
622
- raise XMPPError(
623
- "bad-request",
624
- "Legacy contacts can only be invited to legacy groups, not standard XMPP MUCs.",
625
- )
626
-
627
- if invite["password"]:
628
- raise XMPPError(
629
- "bad-request", "Password-protected groups are not supported"
630
- )
631
-
632
- contact = await session.contacts.by_jid(msg.get_to())
633
- muc = await session.bookmarks.by_jid(jid)
634
-
635
- await session.on_invitation(contact, muc, invite["reason"] or None)
636
-
637
- async def on_muc_owner_query(self, iq: Iq):
638
- session = await self.__get_session(iq)
639
- session.raise_if_not_logged()
640
-
641
- muc = await session.bookmarks.by_jid(iq.get_to())
642
-
643
- reply = iq.reply()
644
-
645
- form = Form(title="Slidge room configuration")
646
- form["instructions"] = (
647
- "Complete this form to modify the configuration of your room."
648
- )
649
- form.add_field(
650
- var="FORM_TYPE",
651
- type="hidden",
652
- value="http://jabber.org/protocol/muc#roomconfig",
653
- )
654
- form.add_field(
655
- var="muc#roomconfig_roomname",
656
- label="Natural-Language Room Name",
657
- type="text-single",
658
- value=muc.name,
659
- )
660
- if muc.HAS_DESCRIPTION:
661
- form.add_field(
662
- var="muc#roomconfig_roomdesc",
663
- label="Short Description of Room",
664
- type="text-single",
665
- value=muc.description,
666
- )
667
-
668
- muc_owner = iq["mucowner_query"]
669
- muc_owner.append(form)
670
- reply.append(muc_owner)
671
- reply.send()
672
-
673
- async def on_muc_owner_set(self, iq: Iq):
674
- session = await self.__get_session(iq)
675
- session.raise_if_not_logged()
676
- muc = await session.bookmarks.by_jid(iq.get_to())
677
- query = iq["mucowner_query"]
678
-
679
- if form := query.get_plugin("form", check=True):
680
- values = form.get_values()
681
- await muc.on_set_config(
682
- name=values.get("muc#roomconfig_roomname"),
683
- description=(
684
- values.get("muc#roomconfig_roomdesc")
685
- if muc.HAS_DESCRIPTION
686
- else None
687
- ),
688
- )
689
- form["type"] = "result"
690
- clear = False
691
- elif destroy := query.get_plugin("destroy", check=True):
692
- reason = destroy["reason"] or None
693
- await muc.on_destroy_request(reason)
694
- user_participant = await muc.get_user_participant()
695
- user_participant._affiliation = "none"
696
- user_participant._role = "none"
697
- presence = user_participant._make_presence(ptype="unavailable", force=True)
698
- presence["muc"].enable("destroy")
699
- if reason is not None:
700
- presence["muc"]["destroy"]["reason"] = reason
701
- user_participant._send(presence)
702
- session.bookmarks.remove(muc)
703
- clear = True
704
- else:
705
- raise XMPPError("bad-request")
706
-
707
- iq.reply(clear=clear).send()
708
-
709
- async def on_groupchat_subject(self, msg: Message):
710
- session = await self.__get_session(msg)
711
- session.raise_if_not_logged()
712
- muc = await session.bookmarks.by_jid(msg.get_to())
713
- if not muc.HAS_SUBJECT:
714
- raise XMPPError(
715
- "bad-request",
716
- "There are no room subject in here. "
717
- "Use the room configuration to update its name or description",
718
- )
719
- await muc.on_set_subject(msg["subject"])
720
-
721
-
722
- def _xmpp_msg_id_to_legacy(session: "BaseSession", xmpp_id: str):
723
- sent = session.sent.inverse.get(xmpp_id)
724
- if sent:
725
- return sent
726
-
727
- multi = db.attachment_get_legacy_id_for_xmpp_id(xmpp_id)
728
- if multi:
729
- return multi
730
-
731
- try:
732
- return session.xmpp_to_legacy_msg_id(xmpp_id)
733
- except XMPPError:
734
- raise
735
- except Exception as e:
736
- log.debug("Couldn't convert xmpp msg ID to legacy ID.", exc_info=e)
737
- raise XMPPError(
738
- "internal-server-error", "Couldn't convert xmpp msg ID to legacy ID."
739
- )
740
-
741
-
742
- def _ignore(session: "BaseSession", msg: Message):
743
- if (i := msg.get_id()) not in session.ignore_messages:
744
- return False
745
- session.log.debug("Ignored sent carbon: %s", i)
746
- session.ignore_messages.remove(i)
747
- return True
748
-
749
-
750
- async def _xmpp_to_legacy_thread(
751
- session: "BaseSession", msg: Message, recipient: RecipientType
752
- ):
753
- xmpp_thread = msg["thread"]
754
- if not xmpp_thread:
755
- return
756
-
757
- if session.MESSAGE_IDS_ARE_THREAD_IDS:
758
- return session.threads.get(xmpp_thread)
759
-
760
- async with session.thread_creation_lock:
761
- legacy_thread = session.threads.get(xmpp_thread)
762
- if legacy_thread is None:
763
- legacy_thread = await recipient.create_thread(xmpp_thread)
764
- session.threads[xmpp_thread] = legacy_thread
765
- return legacy_thread
766
-
767
-
768
- async def _get_entity(session: "BaseSession", m: Message) -> RecipientType:
769
- session.raise_if_not_logged()
770
- if m.get_type() == "groupchat":
771
- muc = await session.bookmarks.by_jid(m.get_to())
772
- r = m.get_from().resource
773
- if r not in muc.user_resources:
774
- session.create_task(muc.kick_resource(r))
775
- raise XMPPError("not-acceptable", "You are not connected to this chat")
776
- return muc
777
- else:
778
- return await session.contacts.by_jid(m.get_to())
779
-
780
-
781
- def _exceptions_to_xmpp_errors(cb: HandlerType) -> HandlerType:
782
- async def wrapped(stanza: Union[Presence, Message]):
783
- try:
784
- await cb(stanza)
785
- except Ignore:
786
- pass
787
- except XMPPError:
788
- raise
789
- except NotImplementedError:
790
- log.debug("NotImplementedError raised in %s", cb)
791
- raise XMPPError(
792
- "feature-not-implemented", "Not implemented by the legacy module"
793
- )
794
- except Exception as e:
795
- log.error("Failed to handle incoming stanza: %s", stanza, exc_info=e)
796
- raise XMPPError("internal-server-error", str(e))
797
-
798
- return wrapped
799
-
800
-
801
- _USEFUL_PRESENCES = {"available", "unavailable", "away", "chat", "dnd", "xa"}
802
-
803
-
804
- log = logging.getLogger(__name__)