slidge 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. slidge/__init__.py +61 -0
  2. slidge/__main__.py +192 -0
  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 +3 -0
  15. slidge/core/cache.py +183 -0
  16. slidge/core/config.py +209 -0
  17. slidge/core/gateway/__init__.py +3 -0
  18. slidge/core/gateway/base.py +892 -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 +757 -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 +525 -0
  41. slidge/core/session.py +752 -0
  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 +440 -0
  46. slidge/group/room.py +1095 -0
  47. slidge/migration.py +18 -0
  48. slidge/py.typed +0 -0
  49. slidge/slixfix/__init__.py +68 -0
  50. slidge/slixfix/link_preview/__init__.py +10 -0
  51. slidge/slixfix/link_preview/link_preview.py +17 -0
  52. slidge/slixfix/link_preview/stanza.py +99 -0
  53. slidge/slixfix/roster.py +60 -0
  54. slidge/slixfix/xep_0077/__init__.py +10 -0
  55. slidge/slixfix/xep_0077/register.py +289 -0
  56. slidge/slixfix/xep_0077/stanza.py +104 -0
  57. slidge/slixfix/xep_0100/__init__.py +5 -0
  58. slidge/slixfix/xep_0100/gateway.py +121 -0
  59. slidge/slixfix/xep_0100/stanza.py +9 -0
  60. slidge/slixfix/xep_0153/__init__.py +10 -0
  61. slidge/slixfix/xep_0153/stanza.py +25 -0
  62. slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
  63. slidge/slixfix/xep_0264/__init__.py +5 -0
  64. slidge/slixfix/xep_0264/stanza.py +36 -0
  65. slidge/slixfix/xep_0264/thumbnail.py +23 -0
  66. slidge/slixfix/xep_0292/__init__.py +5 -0
  67. slidge/slixfix/xep_0292/vcard4.py +100 -0
  68. slidge/slixfix/xep_0313/__init__.py +12 -0
  69. slidge/slixfix/xep_0313/mam.py +262 -0
  70. slidge/slixfix/xep_0313/stanza.py +359 -0
  71. slidge/slixfix/xep_0317/__init__.py +5 -0
  72. slidge/slixfix/xep_0317/hats.py +17 -0
  73. slidge/slixfix/xep_0317/stanza.py +28 -0
  74. slidge/slixfix/xep_0356_old/__init__.py +7 -0
  75. slidge/slixfix/xep_0356_old/privilege.py +167 -0
  76. slidge/slixfix/xep_0356_old/stanza.py +44 -0
  77. slidge/slixfix/xep_0424/__init__.py +9 -0
  78. slidge/slixfix/xep_0424/retraction.py +77 -0
  79. slidge/slixfix/xep_0424/stanza.py +28 -0
  80. slidge/slixfix/xep_0490/__init__.py +8 -0
  81. slidge/slixfix/xep_0490/mds.py +47 -0
  82. slidge/slixfix/xep_0490/stanza.py +17 -0
  83. slidge/util/__init__.py +15 -0
  84. slidge/util/archive_msg.py +61 -0
  85. slidge/util/conf.py +206 -0
  86. slidge/util/db.py +229 -0
  87. slidge/util/schema.sql +126 -0
  88. slidge/util/sql.py +508 -0
  89. slidge/util/test.py +295 -0
  90. slidge/util/types.py +180 -0
  91. slidge/util/util.py +295 -0
  92. slidge-0.1.0.dist-info/LICENSE +661 -0
  93. slidge-0.1.0.dist-info/METADATA +109 -0
  94. slidge-0.1.0.dist-info/RECORD +96 -0
  95. slidge-0.1.0.dist-info/WHEEL +4 -0
  96. slidge-0.1.0.dist-info/entry_points.txt +3 -0
slidge/command/user.py ADDED
@@ -0,0 +1,250 @@
1
+ # Commands available to users
2
+ from typing import TYPE_CHECKING, Any, Optional, Union, cast
3
+
4
+ from slixmpp import JID # type:ignore[attr-defined]
5
+ from slixmpp.exceptions import XMPPError
6
+
7
+ from ..util.types import AnyBaseSession, LegacyGroupIdType
8
+ from .base import (
9
+ Command,
10
+ CommandAccess,
11
+ Confirmation,
12
+ Form,
13
+ FormField,
14
+ FormValues,
15
+ SearchResult,
16
+ TableResult,
17
+ )
18
+ from .categories import CONTACTS, GROUPS
19
+
20
+ if TYPE_CHECKING:
21
+ pass
22
+
23
+
24
+ class Search(Command):
25
+ NAME = "🔎 Search for contacts"
26
+ HELP = "Search for contacts via this gateway"
27
+ NODE = "search"
28
+ CHAT_COMMAND = "find"
29
+ ACCESS = CommandAccess.USER_LOGGED
30
+ CATEGORY = CONTACTS
31
+
32
+ async def run(
33
+ self, session: Optional[AnyBaseSession], _ifrom: JID, *args: str
34
+ ) -> Union[Form, SearchResult, None]:
35
+ if args:
36
+ assert session is not None
37
+ return await session.on_search(
38
+ {self.xmpp.SEARCH_FIELDS[0].var: " ".join(args)}
39
+ )
40
+ return Form(
41
+ title=self.xmpp.SEARCH_TITLE,
42
+ instructions=self.xmpp.SEARCH_INSTRUCTIONS,
43
+ fields=self.xmpp.SEARCH_FIELDS,
44
+ handler=self.search,
45
+ )
46
+
47
+ @staticmethod
48
+ async def search(
49
+ form_values: FormValues, session: Optional[AnyBaseSession], _ifrom: JID
50
+ ) -> SearchResult:
51
+ assert session is not None
52
+ results = await session.on_search(form_values) # type: ignore
53
+ if results is None:
54
+ raise XMPPError("item-not-found", "No contact was found")
55
+
56
+ return results
57
+
58
+
59
+ class SyncContacts(Command):
60
+ NAME = "🔄 Sync XMPP roster"
61
+ HELP = (
62
+ "Synchronize your XMPP roster with your legacy contacts. "
63
+ "Slidge will only add/remove/modify contacts in its dedicated roster group"
64
+ )
65
+ NODE = CHAT_COMMAND = "sync-contacts"
66
+ ACCESS = CommandAccess.USER_LOGGED
67
+ CATEGORY = CONTACTS
68
+
69
+ async def run(self, session: Optional[AnyBaseSession], _ifrom, *_) -> Confirmation:
70
+ return Confirmation(
71
+ prompt="Are you sure you want to sync your roster?",
72
+ success=None,
73
+ handler=self.sync,
74
+ )
75
+
76
+ async def sync(self, session: Optional[AnyBaseSession], _ifrom: JID) -> str:
77
+ if session is None:
78
+ raise RuntimeError
79
+ roster_iq = await self.xmpp["xep_0356"].get_roster(session.user.bare_jid)
80
+
81
+ contacts = session.contacts.known_contacts()
82
+
83
+ added = 0
84
+ removed = 0
85
+ updated = 0
86
+ for item in roster_iq["roster"]:
87
+ groups = set(item["groups"])
88
+ if self.xmpp.ROSTER_GROUP in groups:
89
+ contact = contacts.pop(item["jid"], None)
90
+ if contact is None:
91
+ if len(groups) == 1:
92
+ await self.xmpp["xep_0356"].set_roster(
93
+ session.user.jid, {item["jid"]: {"subscription": "remove"}}
94
+ )
95
+ removed += 1
96
+ else:
97
+ groups.remove(self.xmpp.ROSTER_GROUP)
98
+ await self.xmpp["xep_0356"].set_roster(
99
+ session.user.jid,
100
+ {
101
+ item["jid"]: {
102
+ "subscription": item["subscription"],
103
+ "name": item["name"],
104
+ "groups": groups,
105
+ }
106
+ },
107
+ )
108
+ updated += 1
109
+ else:
110
+ if contact.name != item["name"]:
111
+ await contact.add_to_roster(force=True)
112
+ updated += 1
113
+
114
+ # we popped before so this only acts on slidge contacts not in the xmpp roster
115
+ for contact in contacts.values():
116
+ added += 1
117
+ await contact.add_to_roster()
118
+
119
+ return f"{added} added, {removed} removed, {updated} updated"
120
+
121
+
122
+ class ListContacts(Command):
123
+ NAME = HELP = "👤 List your legacy contacts"
124
+ NODE = CHAT_COMMAND = "contacts"
125
+ ACCESS = CommandAccess.USER_LOGGED
126
+ CATEGORY = CONTACTS
127
+
128
+ async def run(
129
+ self, session: Optional[AnyBaseSession], _ifrom: JID, *_
130
+ ) -> TableResult:
131
+ assert session is not None
132
+ await session.contacts.fill()
133
+ contacts = sorted(
134
+ session.contacts, key=lambda c: c.name.casefold() if c.name else ""
135
+ )
136
+ return TableResult(
137
+ description="Your buddies",
138
+ fields=[FormField("name"), FormField("jid", type="jid-single")],
139
+ items=[{"name": c.name, "jid": c.jid.bare} for c in contacts],
140
+ )
141
+
142
+
143
+ class ListGroups(Command):
144
+ NAME = HELP = "👥 List your legacy groups"
145
+ NODE = CHAT_COMMAND = "groups"
146
+ ACCESS = CommandAccess.USER_LOGGED
147
+ CATEGORY = GROUPS
148
+
149
+ async def run(self, session, _ifrom, *_):
150
+ assert session is not None
151
+ await session.bookmarks.fill()
152
+ groups = sorted(session.bookmarks, key=lambda g: g.DISCO_NAME.casefold())
153
+ return TableResult(
154
+ description="Your groups",
155
+ fields=[FormField("name"), FormField("jid", type="jid-single")],
156
+ items=[{"name": g.name, "jid": g.jid.bare} for g in groups],
157
+ jids_are_mucs=True,
158
+ )
159
+
160
+
161
+ class Login(Command):
162
+ NAME = "🔐 Re-login to the legacy network"
163
+ HELP = "Login to the legacy service"
164
+ NODE = CHAT_COMMAND = "re-login"
165
+
166
+ ACCESS = CommandAccess.USER_NON_LOGGED
167
+
168
+ async def run(self, session: Optional[AnyBaseSession], _ifrom, *_):
169
+ assert session is not None
170
+ try:
171
+ msg = await session.login()
172
+ except Exception as e:
173
+ session.send_gateway_status(f"Re-login failed: {e}", show="dnd")
174
+ raise XMPPError(
175
+ "internal-server-error", etype="wait", text=f"Could not login: {e}"
176
+ )
177
+ session.logged = True
178
+ session.send_gateway_status(msg or "Re-connected", show="chat")
179
+ session.send_gateway_message(msg or "Re-connected")
180
+ return msg
181
+
182
+
183
+ class CreateGroup(Command):
184
+ NAME = "🆕 New legacy group"
185
+ HELP = "Create a group on the legacy service"
186
+ NODE = CHAT_COMMAND = "create-group"
187
+ CATEGORY = GROUPS
188
+
189
+ ACCESS = CommandAccess.USER_LOGGED
190
+
191
+ async def run(self, session: Optional[AnyBaseSession], _ifrom, *_):
192
+ assert session is not None
193
+ contacts = session.contacts.known_contacts(only_friends=True)
194
+ return Form(
195
+ title="Create a new group",
196
+ instructions="Pick contacts that should be part of this new group",
197
+ fields=[
198
+ FormField(var="group_name", label="Name of the group", required=True),
199
+ FormField(
200
+ var="contacts",
201
+ label="Contacts to add to the new group",
202
+ type="list-multi",
203
+ options=[
204
+ {"value": str(contact.jid), "label": contact.name}
205
+ for contact in sorted(contacts.values(), key=lambda c: c.name)
206
+ ],
207
+ required=False,
208
+ ),
209
+ ],
210
+ handler=self.finish,
211
+ )
212
+
213
+ @staticmethod
214
+ async def finish(form_values: FormValues, session: Optional[AnyBaseSession], *_):
215
+ assert session is not None
216
+ legacy_id: LegacyGroupIdType = await session.on_create_group( # type:ignore
217
+ cast(str, form_values["group_name"]),
218
+ [
219
+ await session.contacts.by_jid(JID(j))
220
+ for j in form_values.get("contacts", []) # type:ignore
221
+ ],
222
+ )
223
+ muc = await session.bookmarks.by_legacy_id(legacy_id)
224
+ return TableResult(
225
+ description=f"Your new group: xmpp:{muc.jid}?join",
226
+ fields=[FormField("name"), FormField("jid", type="jid-single")],
227
+ items=[{"name": muc.name, "jid": muc.jid}],
228
+ jids_are_mucs=True,
229
+ )
230
+
231
+
232
+ class Unregister(Command):
233
+ NAME = "❌ Unregister from the gateway"
234
+ HELP = "Unregister from the gateway"
235
+ NODE = CHAT_COMMAND = "unregister"
236
+ ACCESS = CommandAccess.USER
237
+
238
+ async def run(
239
+ self, session: Optional[AnyBaseSession], _ifrom: JID, *_: Any
240
+ ) -> Confirmation:
241
+ return Confirmation(
242
+ prompt=f"Are you sure you want to unregister from '{self.xmpp.boundjid}'?",
243
+ success=f"You are not registered to '{self.xmpp.boundjid}' anymore.",
244
+ handler=self.unregister,
245
+ )
246
+
247
+ async def unregister(self, session: Optional[AnyBaseSession], _ifrom: JID) -> str:
248
+ assert session is not None
249
+ await self.xmpp.unregister_user(session.user)
250
+ return "OK"
@@ -0,0 +1,8 @@
1
+ """
2
+ Everything related to 1 on 1 chats, and other legacy users' details.
3
+ """
4
+
5
+ from .contact import LegacyContact
6
+ from .roster import LegacyRoster
7
+
8
+ __all__ = ("LegacyContact", "LegacyRoster")