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/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")