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
slidge/core/adhoc.py DELETED
@@ -1,492 +0,0 @@
1
- import asyncio
2
- import logging
3
- import tempfile
4
- from asyncio import iscoroutinefunction
5
- from dataclasses import dataclass
6
- from datetime import datetime
7
- from enum import Enum
8
- from functools import wraps
9
- from pathlib import Path
10
- from typing import TYPE_CHECKING, Any, Callable, Optional
11
-
12
- import qrcode
13
- from slixmpp import JID, Iq
14
- from slixmpp.exceptions import XMPPError
15
- from slixmpp.plugins.xep_0004 import Form, FormField
16
- from slixmpp.types import JidStr
17
-
18
- from ..util.db import GatewayUser, user_store
19
- from ..util.xep_0030.stanza.items import DiscoItems
20
- from . import config
21
-
22
- if TYPE_CHECKING:
23
- from .gateway import BaseGateway
24
- from .session import BaseSession
25
-
26
-
27
- class RegistrationType(int, Enum):
28
- SINGLE_STEP_FORM = 0
29
- QRCODE = 10
30
- TWO_FACTOR_CODE = 20
31
-
32
-
33
- @dataclass
34
- class RestrictedItem:
35
- bare_jid: str
36
- node: str
37
- name: str
38
-
39
- def __hash__(self):
40
- return hash(self.bare_jid + self.node + self.name)
41
-
42
-
43
- def restrict(func, condition):
44
- # fmt: off
45
- if iscoroutinefunction(func):
46
- @wraps(func)
47
- async def wrapped(iq: Iq, session: dict[str, Any]):
48
- if not condition(iq.get_from().bare):
49
- raise XMPPError("not-authorized")
50
- return await func(iq, session)
51
- else:
52
- @wraps(func)
53
- def wrapped(iq: Iq, session: dict[str, Any]):
54
- log.debug("WRAPPED: %s", locals())
55
- if not condition(iq.get_from().bare):
56
- raise XMPPError("not-authorized")
57
- return func(iq, session)
58
- # fmt: on
59
- return wrapped
60
-
61
-
62
- class TwoFactorNotRequired(Exception):
63
- pass
64
-
65
-
66
- class AdhocProvider:
67
- def __init__(self, xmpp: "BaseGateway"):
68
- self.xmpp = xmpp
69
-
70
- self._only_admin = set[RestrictedItem]()
71
- self._only_users = set[RestrictedItem]()
72
- self._only_nonusers = set[RestrictedItem]()
73
-
74
- xmpp.plugin["xep_0030"].set_node_handler(
75
- "get_items",
76
- jid=xmpp.boundjid,
77
- node=self.xmpp.plugin["xep_0050"].stanza.Command.namespace,
78
- handler=self.get_items,
79
- )
80
-
81
- self.add_commands()
82
-
83
- def add_commands(self):
84
- self.add_command(
85
- node="info",
86
- name="List registered users",
87
- handler=self._handle_info,
88
- only_admin=True,
89
- only_users=False,
90
- )
91
- self.add_command(
92
- node="delete_user",
93
- name="Delete a user",
94
- handler=self._handle_user_delete,
95
- only_admin=True,
96
- only_users=False,
97
- )
98
- self.add_command(
99
- node="search",
100
- name="Search for contacts",
101
- handler=self._handle_search,
102
- only_users=True,
103
- )
104
- self.add_command(
105
- node="jabber:iq:register",
106
- name="Register to the gateway",
107
- handler=self._handle_register,
108
- only_nonusers=True,
109
- )
110
- self.add_command(
111
- node="unregister",
112
- name="Unregister to the gateway",
113
- handler=self._handle_unregister,
114
- only_users=True,
115
- )
116
- self.add_command(
117
- node="sync-contacts",
118
- name="Sync XMPP roster",
119
- handler=self._handle_contact_sync,
120
- only_users=True,
121
- )
122
-
123
- async def get_items(self, jid: JID, node: str, iq: Iq):
124
- all_items = self.xmpp.plugin["xep_0030"].static.get_items(jid, node, None, None)
125
- log.debug("Static items: %r", all_items)
126
- if not all_items:
127
- return all_items
128
-
129
- ifrom = iq.get_from()
130
- admin = is_admin(ifrom)
131
- user = is_user(ifrom)
132
-
133
- filtered_items = DiscoItems()
134
- filtered_items["node"] = self.xmpp.plugin["xep_0050"].stanza.Command.namespace
135
- for item in all_items:
136
- restricted_item = RestrictedItem(
137
- bare_jid=jid.bare, node=item["node"], name=item["name"]
138
- )
139
- if restricted_item in self._only_admin and not admin:
140
- continue
141
- elif restricted_item in self._only_users and not user:
142
- continue
143
- elif restricted_item in self._only_nonusers and user:
144
- continue
145
-
146
- filtered_items.append(item)
147
-
148
- return filtered_items
149
-
150
- def add_command(
151
- self,
152
- node: str,
153
- name: str,
154
- handler: Callable,
155
- jid: Optional[JID] = None,
156
- only_admin=False,
157
- only_users=False,
158
- only_nonusers=False,
159
- ):
160
- if jid is None:
161
- jid = self.xmpp.boundjid
162
- elif not isinstance(jid, JID):
163
- jid = JID(jid)
164
- item = RestrictedItem(bare_jid=jid.bare, node=node, name=name)
165
- if only_admin:
166
- self._only_admin.add(item)
167
- if only_users:
168
- self._only_users.add(item)
169
- if only_nonusers:
170
- self._only_nonusers.add(item)
171
-
172
- if only_users:
173
- handler = restrict(handler, is_user)
174
- elif only_admin:
175
- handler = restrict(handler, is_admin)
176
- elif only_nonusers:
177
- handler = restrict(handler, is_not_user)
178
-
179
- self.xmpp.plugin["xep_0050"].add_command(
180
- jid=jid, node=node, name=name, handler=handler
181
- )
182
-
183
- def _handle_info(self, _iq: Iq, session: dict[str, Any]):
184
- """
185
- List registered users for admins
186
- """
187
- form = self.xmpp["xep_0004"].make_form("result", "Component info")
188
- form.add_reported("jid", label="JID", type="jid-single")
189
- form.add_reported("joined", label="Join date", type="text")
190
- for u in user_store.get_all():
191
- d = u.registration_date
192
- if d is None:
193
- joined = ""
194
- else:
195
- joined = d.isoformat(timespec="seconds")
196
- form.add_item({"jid": u.bare_jid, "joined": joined})
197
-
198
- session["payload"] = form
199
- session["has_next"] = False
200
-
201
- return session
202
-
203
- def _handle_user_delete(self, _iq: Iq, adhoc_session: dict[str, Any]):
204
- form = self.xmpp["xep_0004"].make_form(
205
- title="Delete user",
206
- instructions="Enter the bare JID(s) of the user(s) you want to delete",
207
- )
208
- form.add_field("user_jid", ftype="jid-single", label="User JID")
209
-
210
- adhoc_session["payload"] = form
211
- adhoc_session["has_next"] = True
212
- adhoc_session["next"] = self._handle_user_delete2
213
-
214
- return adhoc_session
215
-
216
- async def _handle_user_delete2(self, form, adhoc_session: dict[str, Any]):
217
- form_values = form.get_values()
218
- try:
219
- user_jid = JID(form_values.get("user_jid"))
220
- except ValueError:
221
- raise XMPPError("bad-request", text="This JID is invalid")
222
-
223
- user = user_store.get_by_jid(user_jid)
224
- if user is None:
225
- raise XMPPError("item-not-found", text=f"There is no user '{user_jid}'")
226
-
227
- log.debug("Admin requested unregister of %s", user_jid)
228
-
229
- await self.xmpp.session_cls.kill_by_jid(user_jid)
230
- user_store.remove_by_jid(user_jid)
231
-
232
- adhoc_session["notes"] = [("info", "Success!")]
233
- adhoc_session["has_next"] = False
234
-
235
- return adhoc_session
236
-
237
- async def _handle_search(self, iq: Iq, adhoc_session: dict[str, Any]):
238
- """
239
- Jabber search, but as an adhoc command (search form)
240
- """
241
- session: "BaseSession" = self.xmpp.get_session_from_stanza(iq) # type:ignore
242
-
243
- reply = await self.xmpp.search_get_form(None, None, ifrom=iq.get_from(), iq=iq)
244
- adhoc_session["payload"] = reply["search"]["form"]
245
- adhoc_session["next"] = self._handle_search2
246
- adhoc_session["has_next"] = True
247
- adhoc_session["session"] = session
248
-
249
- return adhoc_session
250
-
251
- async def _handle_search2(self, form, adhoc_session: dict[str, Any]):
252
- """
253
- Jabber search, but as an adhoc command (results)
254
- """
255
-
256
- search_results = await adhoc_session["session"].search(form.get_values())
257
-
258
- form = self.xmpp.plugin["xep_0004"].make_form(
259
- "result", "Contact search results"
260
- )
261
- if search_results is None:
262
- raise XMPPError("item-not-found", text="No contact was found")
263
-
264
- for field in search_results.fields:
265
- form.add_reported(field.var, label=field.label, type=field.type)
266
- for item in search_results.items:
267
- form.add_item(item)
268
-
269
- adhoc_session["next"] = None
270
- adhoc_session["has_next"] = False
271
- adhoc_session["payload"] = form
272
-
273
- return adhoc_session
274
-
275
- async def _handle_register(self, iq: Iq, adhoc_session: dict[str, Any]):
276
- reg_iq = await self.xmpp.make_registration_form(None, None, None, iq)
277
- form = reg_iq["register"]["form"]
278
- adhoc_session["payload"] = form
279
- adhoc_session["next"] = self._handle_register2
280
- adhoc_session["has_next"] = True
281
- adhoc_session["session"] = adhoc_session
282
-
283
- return adhoc_session
284
-
285
- async def _handle_register2(self, form: Form, adhoc_session: dict[str, Any]):
286
-
287
- form_values = form.get_values()
288
- two_fa_needed = True
289
- try:
290
- await self.xmpp.user_prevalidate(adhoc_session["from"], form_values)
291
- except ValueError as e:
292
- raise XMPPError("bad-request", str(e))
293
- except TwoFactorNotRequired:
294
- if self.xmpp.REGISTRATION_TYPE == RegistrationType.TWO_FACTOR_CODE:
295
- two_fa_needed = False
296
- else:
297
- raise
298
-
299
- adhoc_session["user"] = user = GatewayUser(
300
- bare_jid=adhoc_session["from"].bare,
301
- registration_form=form_values,
302
- registration_date=datetime.now(),
303
- )
304
- adhoc_session["registration_form"] = form_values
305
-
306
- if self.xmpp.REGISTRATION_TYPE == RegistrationType.SINGLE_STEP_FORM or (
307
- self.xmpp.REGISTRATION_TYPE == RegistrationType.TWO_FACTOR_CODE
308
- and not two_fa_needed
309
- ):
310
- adhoc_session["payload"] = None
311
- adhoc_session["next"] = None
312
- adhoc_session["notes"] = [("info", "Success!")]
313
- adhoc_session["has_next"] = False
314
- adhoc_session["completed"] = True
315
- user.commit()
316
- self.xmpp.event("user_register", Iq(sfrom=adhoc_session["from"]))
317
-
318
- elif self.xmpp.REGISTRATION_TYPE == RegistrationType.TWO_FACTOR_CODE:
319
- form = self.xmpp["xep_0004"].make_form(
320
- title=self.xmpp.REGISTRATION_2FA_TITLE,
321
- instructions=self.xmpp.REGISTRATION_2FA_INSTRUCTIONS,
322
- )
323
- form.add_field("code", ftype="text-single", label="Code", required=True)
324
- adhoc_session["payload"] = form
325
- adhoc_session["next"] = self._handle_two_factor_code
326
- adhoc_session["has_next"] = True
327
- adhoc_session["completed"] = False
328
-
329
- elif self.xmpp.REGISTRATION_TYPE == RegistrationType.QRCODE:
330
- self.xmpp.qr_pending_registrations[ # type:ignore
331
- user.bare_jid
332
- ] = self.xmpp.loop.create_future()
333
- qr_text = await self.xmpp.get_qr_text(user)
334
- qr = qrcode.make(qr_text)
335
- with tempfile.NamedTemporaryFile(suffix=".png") as f:
336
- qr.save(f.name)
337
- img_url = await self.xmpp.plugin["xep_0363"].upload_file(
338
- filename=Path(f.name), ifrom=config.UPLOAD_REQUESTER
339
- )
340
-
341
- msg = self.xmpp.make_message(mto=user.bare_jid)
342
- msg.set_from(self.xmpp.boundjid.bare)
343
- msg["oob"]["url"] = img_url
344
- msg["body"] = img_url
345
- msg.send()
346
-
347
- msg = self.xmpp.make_message(mto=user.bare_jid)
348
- msg.set_from(self.xmpp.boundjid.bare)
349
- msg["body"] = qr_text
350
- msg.send()
351
-
352
- form = self.xmpp["xep_0004"].make_form(
353
- title="Flash this",
354
- instructions="Flash this QR in the appropriate place",
355
- )
356
- img = FormField()
357
- img["media"]["height"] = "200"
358
- img["media"]["width"] = "200"
359
- img["media"]["alt"] = "The thing to flash"
360
- img["media"].add_uri(img_url, itype="image/png")
361
- form.append(img)
362
- form.add_field(ftype="fixed", value=qr_text, label="Content of the QR")
363
- form.add_field(ftype="fixed", value=img_url, label="QR image")
364
-
365
- adhoc_session["payload"] = form
366
- adhoc_session["next"] = self._handle_qr
367
- adhoc_session["has_next"] = True
368
- adhoc_session["completed"] = False
369
-
370
- return adhoc_session
371
-
372
- async def _handle_two_factor_code(
373
- self, form_code: Form, adhoc_session: dict[str, Any]
374
- ):
375
- log.debug("form %s", form_code)
376
- log.debug("session %s", adhoc_session)
377
- code: str = form_code.get_values().get("code")
378
- if code is None:
379
- raise XMPPError("bad-request", text="Please fill the code field")
380
-
381
- user = adhoc_session["user"]
382
- await self.xmpp.validate_two_factor_code(user, code)
383
- user.commit()
384
- self.xmpp.event("user_register", Iq(sfrom=adhoc_session["from"]))
385
-
386
- adhoc_session["notes"] = [("info", "Looks like we're all good")]
387
- adhoc_session["payload"] = None
388
- adhoc_session["next"] = None
389
- adhoc_session["has_next"] = False
390
- adhoc_session["completed"] = True
391
-
392
- return adhoc_session
393
-
394
- async def _handle_qr(self, _args, adhoc_session: dict[str, Any]):
395
- log.debug("handle QR: %s", _args)
396
- user = adhoc_session["user"]
397
- try:
398
- await asyncio.wait_for(
399
- self.xmpp.qr_pending_registrations[user.bare_jid], # type:ignore
400
- config.QR_TIMEOUT,
401
- )
402
- except asyncio.TimeoutError:
403
- raise XMPPError(
404
- "remote-server-timeout",
405
- "It does not seem that the QR code was correctly used.",
406
- )
407
- user.commit()
408
- self.xmpp.event("user_register", Iq(sfrom=adhoc_session["from"]))
409
-
410
- adhoc_session["notes"] = [("info", "Looks like we're all good")]
411
- adhoc_session["payload"] = None
412
- adhoc_session["next"] = None
413
- adhoc_session["has_next"] = False
414
- adhoc_session["completed"] = True
415
-
416
- return adhoc_session
417
-
418
- async def _handle_unregister(self, iq: Iq, adhoc_session: dict[str, Any]):
419
- await self.xmpp.plugin["xep_0077"].api["user_remove"](None, None, iq["from"])
420
- await self.xmpp.session_cls.kill_by_jid(iq.get_from())
421
- adhoc_session["notes"] = [("info", "Bye bye!")]
422
- adhoc_session["has_next"] = False
423
- adhoc_session["completed"] = True
424
-
425
- return adhoc_session
426
-
427
- async def _handle_contact_sync(self, iq: Iq, adhoc_session: dict[str, Any]):
428
- session: "BaseSession" = self.xmpp.get_session_from_stanza(iq) # type:ignore
429
-
430
- roster_iq = await self.xmpp["xep_0356"].get_roster(session.user.bare_jid)
431
-
432
- contacts = session.contacts.known_contacts()
433
-
434
- added = 0
435
- removed = 0
436
- updated = 0
437
- for item in roster_iq["roster"]:
438
- groups = set(item["groups"])
439
- if self.xmpp.ROSTER_GROUP in groups:
440
- contact = contacts.pop(item["jid"], None)
441
- if contact is None:
442
- if len(groups) == 1:
443
- await self.xmpp["xep_0356"].set_roster(
444
- session.user.jid, {item["jid"]: {"subscription": "remove"}}
445
- )
446
- removed += 1
447
- else:
448
- groups.remove(self.xmpp.ROSTER_GROUP)
449
- await self.xmpp["xep_0356"].set_roster(
450
- session.user.jid,
451
- {
452
- item["jid"]: {
453
- "subscription": item["subscription"],
454
- "name": item["name"],
455
- "groups": groups,
456
- }
457
- },
458
- )
459
- updated += 1
460
- else:
461
- if contact.name != item["name"]:
462
- log.debug("%s vs %s", contact.name, item["name"])
463
- await contact.add_to_roster()
464
- updated += 1
465
-
466
- # we popped before so this only acts on slidge contacts not in the xmpp roster
467
- for contact in contacts.values():
468
- added += 1
469
- await contact.add_to_roster()
470
-
471
- adhoc_session["notes"] = [
472
- ("info", f"{added} added, {removed} removed, {updated} updated")
473
- ]
474
- adhoc_session["has_next"] = False
475
- adhoc_session["completed"] = True
476
-
477
- return adhoc_session
478
-
479
-
480
- def is_admin(jid: JidStr):
481
- return JID(jid).bare in config.ADMINS
482
-
483
-
484
- def is_user(jid: JidStr):
485
- return user_store.get_by_jid(JID(jid))
486
-
487
-
488
- def is_not_user(jid: JidStr):
489
- return not is_user(jid)
490
-
491
-
492
- log = logging.getLogger(__name__)
@@ -1,197 +0,0 @@
1
- import asyncio
2
- import logging
3
- from typing import TYPE_CHECKING, Optional
4
-
5
- from slixmpp import Message
6
- from slixmpp.exceptions import XMPPError
7
-
8
- from ..util.db import GatewayUser
9
- from ..util.types import SessionType
10
- from . import config
11
- from .adhoc import RegistrationType, TwoFactorNotRequired
12
-
13
- if TYPE_CHECKING:
14
- from .gateway import BaseGateway
15
-
16
-
17
- class ChatCommandProvider:
18
- def __init__(self, xmpp: "BaseGateway"):
19
- self.xmpp = xmpp
20
-
21
- async def _chat_command_search(
22
- self, *args, msg: Message, session: Optional["SessionType"] = None
23
- ):
24
- if session is None:
25
- msg.reply("Register to the gateway first!")
26
- return
27
-
28
- search_form = {}
29
- diff = len(args) - len(self.xmpp.SEARCH_FIELDS)
30
-
31
- if diff > 0:
32
- session.send_gateway_message("Too many parameters!")
33
- return
34
-
35
- for field, arg in zip(self.xmpp.SEARCH_FIELDS, args):
36
- search_form[field.var] = arg
37
-
38
- if diff < 0:
39
- for field in self.xmpp.SEARCH_FIELDS[diff:]:
40
- if not field.required:
41
- continue
42
- search_form[field.var] = await session.input(
43
- (field.label or field.var) + "?"
44
- )
45
-
46
- results = await session.search(search_form)
47
- if results is None:
48
- session.send_gateway_message("No results!")
49
- return
50
-
51
- result_fields = results.fields
52
- for result in results.items:
53
- text = ""
54
- for f in result_fields:
55
- if f.type == "jid-single":
56
- text += f"xmpp:{result[f.var]}\n"
57
- else:
58
- text += f"{f.label}: {result[f.var]}\n"
59
- session.send_gateway_message(text)
60
-
61
- async def _chat_command_help(
62
- self, *_args, msg: Message, session: Optional["SessionType"]
63
- ):
64
- if session is None:
65
- msg.reply("Register to the gateway first!").send()
66
- else:
67
- t = "|".join(
68
- x
69
- for x in self.xmpp._chat_commands.keys()
70
- if x not in ("register", "help")
71
- )
72
- log.debug("In help: %s", t)
73
- msg.reply(f"Available commands: {t}").send()
74
-
75
- @staticmethod
76
- async def _chat_command_list_contacts(
77
- *_args, msg: Message, session: Optional["SessionType"]
78
- ):
79
- if session is None:
80
- msg.reply("Register to the gateway first!").send()
81
- else:
82
- contacts = sorted(
83
- session.contacts, key=lambda c: c.name.casefold() if c.name else ""
84
- )
85
- t = "\n".join(f"{c.name}: xmpp:{c.jid.bare}" for c in contacts)
86
- msg.reply(t).send()
87
-
88
- async def _chat_command_register(
89
- self, *args, msg: Message, session: Optional["SessionType"]
90
- ):
91
- if session is not None:
92
- msg.reply("You are already registered to this gateway").send()
93
- return
94
-
95
- jid = msg.get_from()
96
-
97
- if not self.xmpp.jid_validator.match(jid.bare): # type:ignore
98
- msg.reply("You are not allowed to register to this gateway").send()
99
- return
100
-
101
- msg.reply(self.xmpp.REGISTRATION_INSTRUCTIONS).send()
102
- form: dict[str, Optional[str]] = {}
103
- for field in self.xmpp.REGISTRATION_FIELDS:
104
- text = field.label or field.var
105
- if field.value != "":
106
- text += f" (default: '{field.value}')"
107
- if not field.required:
108
- text += " (optional, reply with '.' to skip)"
109
- if (options := field.options) is not None:
110
- for option in options:
111
- label = option["label"]
112
- value = option["value"]
113
- text += f"\n{label}: reply with '{value}'"
114
-
115
- while True:
116
- ans = await self.xmpp.input(jid, text + "?")
117
- if ans == "." and not field.required:
118
- form[field.var] = None
119
- break
120
- else:
121
- if (options := field.options) is not None:
122
- valid_choices = [x["value"] for x in options]
123
- if ans not in valid_choices:
124
- continue
125
- form[field.var] = ans
126
- break
127
-
128
- user = GatewayUser(bare_jid=jid.bare, registration_form=form)
129
-
130
- try:
131
- two_fa_needed = True
132
- try:
133
- await self.xmpp.user_prevalidate(jid, form)
134
- except TwoFactorNotRequired:
135
- if self.xmpp.REGISTRATION_TYPE == RegistrationType.TWO_FACTOR_CODE:
136
- two_fa_needed = False
137
- else:
138
- raise
139
-
140
- if self.xmpp.REGISTRATION_TYPE == RegistrationType.TWO_FACTOR_CODE:
141
- if two_fa_needed:
142
- code = await self.xmpp.input(
143
- jid,
144
- self.xmpp.REGISTRATION_2FA_TITLE
145
- + "\n"
146
- + self.xmpp.REGISTRATION_2FA_INSTRUCTIONS,
147
- )
148
- await self.xmpp.validate_two_factor_code(user, code)
149
-
150
- elif self.xmpp.REGISTRATION_TYPE == RegistrationType.QRCODE:
151
- fut = self.xmpp.loop.create_future()
152
- self.xmpp.qr_pending_registrations[user.bare_jid] = fut # type:ignore
153
- qr_url = await self.xmpp.get_qr_text(user)
154
- self.xmpp.send_message(
155
- mto=jid, mbody=qr_url, mfrom=self.xmpp.boundjid.bare
156
- )
157
- await self.xmpp.send_qr(qr_url, mto=jid)
158
- try:
159
- await asyncio.wait_for(fut, config.QR_TIMEOUT)
160
- except asyncio.TimeoutError:
161
- msg.reply(f"You did not flash the QR code in time!").send()
162
- return
163
-
164
- except (ValueError, XMPPError) as e:
165
- msg.reply(f"Something went wrong: {e}").send()
166
-
167
- else:
168
- user.commit()
169
- self.xmpp.event("user_register", msg)
170
- msg.reply(f"Success!").send()
171
-
172
- async def _chat_command_unregister(
173
- self, *args, msg: Message, session: Optional["SessionType"]
174
- ):
175
- ifrom = msg.get_from()
176
- await self.xmpp.plugin["xep_0077"].api["user_remove"](None, None, ifrom)
177
- await self.xmpp.session_cls.kill_by_jid(ifrom)
178
-
179
- @staticmethod
180
- async def _chat_command_list_groups(
181
- *_args, msg: Message, session: Optional["SessionType"]
182
- ):
183
- if session is None:
184
- msg.reply("Register to the gateway first!").send()
185
- else:
186
- groups = sorted(
187
- session.bookmarks,
188
- key=lambda m: m.DISCO_NAME.casefold() if m.DISCO_NAME else "",
189
- )
190
- if groups:
191
- t = "\n".join(f"{m.DISCO_NAME}: xmpp:{m.jid}?join" for m in groups)
192
- msg.reply(t).send()
193
- else:
194
- msg.reply("No groups!").send()
195
-
196
-
197
- log = logging.getLogger(__name__)