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
@@ -1,515 +0,0 @@
1
- import asyncio
2
- import functools
3
- import logging
4
- import os
5
- import tempfile
6
- from datetime import datetime
7
- from pathlib import Path
8
- from typing import TYPE_CHECKING, Any, Optional, Union, cast
9
-
10
- import aiohttp
11
- import aiosignald.exc as sigexc
12
- import aiosignald.generated as sigapi
13
- from slixmpp.exceptions import XMPPError
14
-
15
- from slidge import *
16
- from slidge.core.muc.room import MucType
17
- from slidge.util.util import is_valid_phone_number
18
-
19
- if TYPE_CHECKING:
20
- from .contact import Contact, Roster
21
- from .gateway import Gateway
22
- from .group import Bookmarks, MUC, Participant
23
-
24
- from . import config
25
-
26
-
27
- def handle_unregistered_recipient(func):
28
- @functools.wraps(func)
29
- async def wrapped(*a, **kw):
30
- try:
31
- return await func(*a, **kw)
32
- except (
33
- sigexc.UnregisteredUserError,
34
- sigexc.IllegalArgumentException,
35
- sigexc.InternalError,
36
- sigexc.InvalidGroupError,
37
- ) as e:
38
- raise XMPPError(
39
- "item-not-found",
40
- text=e.message,
41
- )
42
-
43
- return wrapped
44
-
45
-
46
- class Session(
47
- BaseSession["Gateway", int, "Roster", "Contact", "Bookmarks", "MUC", "Participant"]
48
- ):
49
- """
50
- Represents a signal account
51
- """
52
-
53
- def __init__(self, user: GatewayUser):
54
- """
55
-
56
- :param user:
57
- """
58
- super().__init__(user)
59
- self.phone = self.user.registration_form["phone"]
60
- if self.phone is None:
61
- raise RuntimeError
62
- self.signal = self.xmpp.signal
63
- self.xmpp.sessions_by_phone[self.phone] = self
64
- self.user_uuid: asyncio.Future[str] = self.xmpp.loop.create_future()
65
- self.user_nick: asyncio.Future[str] = self.xmpp.loop.create_future()
66
- self.connected = self.xmpp.loop.create_future()
67
- self.sent_in_muc = dict[int, "MUC"]()
68
-
69
- @staticmethod
70
- def xmpp_msg_id_to_legacy_msg_id(i: str) -> int:
71
- try:
72
- return int(i)
73
- except ValueError:
74
- raise NotImplementedError
75
-
76
- @handle_unregistered_recipient
77
- async def paused(self, c: "Contact"):
78
- await (await self.signal).typing(
79
- account=self.phone, typing=False, address=c.signal_address
80
- )
81
-
82
- async def correct(self, text: str, legacy_msg_id: Any, c: "Contact"):
83
- return await self.send_text("Correction: " + text, c)
84
-
85
- async def search(self, form_values: dict[str, str]):
86
- phone = form_values.get("phone")
87
- if phone is None:
88
- raise XMPPError("bad-request", "Please enter a phone number")
89
-
90
- if not is_valid_phone_number(phone):
91
- raise XMPPError(
92
- "bad-request", "This does not look like a valid phone number"
93
- )
94
-
95
- try:
96
- address = await (await self.signal).resolve_address(
97
- account=self.phone,
98
- partial=sigapi.JsonAddressv1(number=phone),
99
- )
100
- except sigexc.UnregisteredUserError:
101
- return
102
-
103
- contact = await self.contacts.by_json_address(address)
104
- # the name will be updated once c.update_and_add(), triggered by by_json_address()
105
- # completes, but it's nicer to have a phone number instead of a UUID
106
- # in the meantime.
107
- contact.name = phone
108
-
109
- return SearchResult(
110
- fields=[FormField("phone"), FormField("jid", type="jid-single")],
111
- items=[{"phone": phone, "jid": contact.jid.bare}],
112
- )
113
-
114
- async def login(self):
115
- await (await self.signal).subscribe(account=self.phone)
116
- await self.connected
117
- sig = await self.signal
118
- # TODO: store the account UUID on registration so we don't have to do that
119
- try:
120
- # sometimes doesn't work with own phone number
121
- profile = await sig.get_profile(
122
- account=self.phone, address=sigapi.JsonAddressv1(number=self.phone)
123
- )
124
- except sigexc.ProfileUnavailableError:
125
- accounts = await sig.list_accounts()
126
- for a in accounts.accounts:
127
- if a.address.number == self.phone:
128
- profile = await sig.get_profile(
129
- account=self.phone, address=a.address
130
- )
131
- break
132
- else:
133
- raise RuntimeError("Could not find the signal address of your")
134
- nick: str = profile.name or profile.profile_name or "SlidgeUser"
135
- if nick is not None:
136
- nick = nick.replace("\u0000", " ")
137
- self.user_nick.set_result(nick)
138
- self.user_uuid.set_result(profile.address.uuid)
139
- self.bookmarks.set_username(nick)
140
- await self.add_contacts_to_roster()
141
- await self.add_groups()
142
- return f"Connected as {self.phone}"
143
-
144
- async def on_websocket_connection_state(
145
- self, state: sigapi.WebSocketConnectionStatev1
146
- ):
147
- if (
148
- state.state == "CONNECTED"
149
- and state.socket == "IDENTIFIED"
150
- and not self.connected.done()
151
- ):
152
- self.connected.set_result(True)
153
-
154
- async def logout(self):
155
- await (await self.signal).unsubscribe(account=self.phone)
156
-
157
- async def add_contacts_to_roster(self):
158
- """
159
- Populate a user's roster
160
- """
161
- profiles = await (await self.signal).list_contacts(account=self.phone)
162
- for profile in profiles.profiles:
163
- # contacts are added automatically if their profile could be resolved
164
- await self.contacts.by_json_address(profile.address)
165
-
166
- async def add_groups(self):
167
- groups = await (await self.signal).list_groups(account=self.phone)
168
- self.log.debug("GROUPS: %r", groups)
169
- for group in groups.groups:
170
- muc = await self.bookmarks.by_legacy_id(group.id)
171
- muc.type = MucType.GROUP
172
- muc.DISCO_NAME = group.title
173
- muc.subject = group.description
174
- muc.description = group.description
175
- muc.n_participants = len(group.members)
176
-
177
- async def on_signal_message(self, msg: sigapi.IncomingMessagev1):
178
- """
179
- User has received 'something' from signal
180
-
181
- :param msg:
182
- """
183
- if (sync_msg := msg.sync_message) is not None:
184
- if sync_msg.contacts is not None and msg.sync_message.contactsComplete:
185
- log.debug("Received a sync contact updates")
186
- await self.add_contacts_to_roster()
187
-
188
- if (sent := sync_msg.sent) is None:
189
- # Probably a 'message read' marker
190
- log.debug("No sent message in this sync message")
191
- return
192
- sent_msg = sent.message
193
- if sent_msg.group or sent_msg.groupV2:
194
- return
195
-
196
- contact = await self.contacts.by_json_address(sent.destination)
197
-
198
- await contact.send_attachments(
199
- sent_msg.attachments, sent_msg.timestamp, carbon=True
200
- )
201
-
202
- if (body := sent_msg.body) is not None:
203
- contact.send_text(
204
- body=body,
205
- when=datetime.fromtimestamp(sent_msg.timestamp / 1000),
206
- legacy_id=sent_msg.timestamp,
207
- carbon=True,
208
- )
209
- if (reaction := sent_msg.reaction) is not None:
210
- contact.react(
211
- reaction.targetSentTimestamp,
212
- () if reaction.remove else reaction.emoji,
213
- carbon=True,
214
- )
215
- if (delete := sent_msg.remoteDelete) is not None:
216
- contact.retract(delete.target_sent_timestamp, carbon=True)
217
-
218
- contact = await self.contacts.by_json_address(msg.source)
219
-
220
- if (data := msg.data_message) is not None:
221
- if data.group:
222
- return
223
-
224
- if data.groupV2:
225
- muc = await self.bookmarks.by_legacy_id(data.groupV2.id)
226
- entity = await muc.get_participant_by_contact(contact)
227
- else:
228
- entity = contact
229
-
230
- reply_self = False
231
- if (quote := data.quote) is None:
232
- reply_to_msg_id = None
233
- reply_to_fallback_text = None
234
- reply_to_author = None
235
- else:
236
- reply_to_msg_id = quote.id
237
- reply_to_fallback_text = quote.text
238
- reply_self = quote.author.uuid == msg.source.uuid
239
-
240
- if data.groupV2:
241
- reply_to_author = await muc.get_participant(muc.user_nick)
242
- else:
243
- reply_to_author = None
244
-
245
- kwargs = dict(
246
- reply_to_msg_id=reply_to_msg_id,
247
- reply_to_author=reply_to_author,
248
- reply_to_fallback_text=reply_to_fallback_text,
249
- reply_self=reply_self,
250
- when=datetime.fromtimestamp(msg.data_message.timestamp / 1000),
251
- )
252
-
253
- msg_id = data.timestamp
254
- text = data.body
255
- await entity.send_attachments(
256
- data.attachments, legacy_msg_id=None if text else msg_id, **kwargs
257
- )
258
- if text:
259
- entity.send_text(body=text, legacy_msg_id=msg_id, **kwargs)
260
- if (reaction := data.reaction) is not None:
261
- self.log.debug("Reaction: %s", reaction)
262
- if reaction.remove:
263
- entity.react(reaction.targetSentTimestamp)
264
- else:
265
- entity.react(reaction.targetSentTimestamp, reaction.emoji)
266
- if (delete := data.remoteDelete) is not None:
267
- entity.retract(delete.target_sent_timestamp)
268
-
269
- if (typing_message := msg.typing_message) is not None:
270
- if g := typing_message.group_id:
271
- muc = await self.bookmarks.by_legacy_id(g)
272
- entity = await muc.get_participant_by_contact(contact)
273
- else:
274
- entity = contact
275
-
276
- action = typing_message.action
277
- if action == "STARTED":
278
- entity.active()
279
- entity.composing()
280
- elif action == "STOPPED":
281
- entity.paused()
282
-
283
- if (receipt_message := msg.receipt_message) is not None:
284
- type_ = receipt_message.type
285
- if type_ == "DELIVERY":
286
- for t in msg.receipt_message.timestamps:
287
- entity = await self.__get_entity_by_sent_msg_id(contact, t)
288
- entity.received(t)
289
- elif type_ == "READ":
290
- # no need to mark all messages read, just the last one, see
291
- # "8.1. Optimizations" in XEP-0333
292
- t = max(msg.receipt_message.timestamps)
293
- entity = await self.__get_entity_by_sent_msg_id(contact, t)
294
- entity.displayed(t)
295
-
296
- async def __get_entity_by_sent_msg_id(self, contact: "Contact", t: int):
297
- self.log.debug("Looking for %s in %s", t, self.sent_in_muc)
298
- group = self.sent_in_muc.get(t)
299
- if group:
300
- return await group.get_participant_by_contact(contact)
301
- return contact
302
-
303
- @handle_unregistered_recipient
304
- async def send_text(
305
- self,
306
- text: str,
307
- chat: Union["Contact", "MUC"],
308
- *,
309
- reply_to_msg_id=None,
310
- reply_to_fallback_text=None,
311
- reply_to: Optional[Union["Contact", "Participant"]] = None,
312
- ) -> int:
313
- address, group = self._get_args_from_entity(chat)
314
- if reply_to_msg_id is None:
315
- quote = None
316
- elif reply_to is None:
317
- quote = None
318
- self.log.warning(
319
- "An XMPP client did not include reply to=, so we cannot make a quote here."
320
- )
321
- else:
322
- quote = sigapi.JsonQuotev1(
323
- id=reply_to_msg_id,
324
- author=sigapi.JsonAddressv1(uuid=await self.user_uuid)
325
- if reply_to is None
326
- else reply_to.signal_address,
327
- text=reply_to_fallback_text or "",
328
- )
329
- response = await (await self.signal).send(
330
- account=self.phone,
331
- recipientAddress=address,
332
- recipientGroupId=group,
333
- messageBody=text,
334
- quote=quote,
335
- )
336
- result = response.results[0]
337
- log.debug("Result: %s", result)
338
- if result.networkFailure or result.proof_required_failure:
339
- raise XMPPError(str(result))
340
- elif result.identityFailure:
341
- chat = cast("Contact", chat)
342
- s = await self.signal
343
- identities = (
344
- await s.get_identities(
345
- account=self.phone,
346
- address=chat.signal_address,
347
- )
348
- ).identities
349
- ans = await self.input(
350
- f"The identity of {chat.legacy_id} has changed. "
351
- f"Do you want to trust all their identities and resend the message?"
352
- )
353
- if ans.lower().startswith("y"):
354
- for i in identities:
355
- await (await self.signal).trust(
356
- account=self.phone,
357
- address=chat.signal_address,
358
- safety_number=i.safety_number,
359
- )
360
- await self.send_text(text, chat, reply_to_msg_id=reply_to_msg_id)
361
- else:
362
- raise XMPPError(str(result))
363
- legacy_msg_id = response.timestamp
364
- if group:
365
- self.sent_in_muc[legacy_msg_id] = cast("MUC", chat)
366
- return legacy_msg_id
367
-
368
- @handle_unregistered_recipient
369
- async def send_file(
370
- self,
371
- url: str,
372
- chat: "Contact",
373
- *,
374
- reply_to_msg_id=None,
375
- reply_to_fallback_text=None,
376
- reply_to: Optional[Union["Contact", "Participant"]] = None,
377
- ):
378
- s = await self.signal
379
- address, group = self._get_args_from_entity(chat)
380
- async with aiohttp.ClientSession() as client:
381
- async with client.get(url=url) as r:
382
- with tempfile.TemporaryDirectory(
383
- dir=config.SIGNALD_SOCKET.parent,
384
- ) as d:
385
- os.chmod(d, 0o777)
386
- with open(Path(d) / r.url.name, "wb") as f:
387
- f.write(await r.content.read())
388
- os.chmod(
389
- f.name, 0o666
390
- ) # temp file is 0600 https://stackoverflow.com/a/10541972/5902284
391
- signal_r = await s.send(
392
- account=self.phone,
393
- recipientAddress=address,
394
- recipientGroupId=group,
395
- attachments=[sigapi.JsonAttachmentv1(filename=f.name)],
396
- )
397
- return signal_r.timestamp
398
-
399
- async def active(self, c: "Contact"):
400
- pass
401
-
402
- async def inactive(self, c: "Contact"):
403
- pass
404
-
405
- @staticmethod
406
- def _get_args_from_entity(e):
407
- if e.is_group:
408
- address = None
409
- group = e.legacy_id
410
- else:
411
- address = e.signal_address
412
- group = None
413
- return address, group
414
-
415
- @handle_unregistered_recipient
416
- async def composing(self, c: "Contact"):
417
- self.log.debug("COMPOSING %s", c)
418
- address, group = self._get_args_from_entity(c)
419
- await (await self.signal).typing(
420
- account=self.phone,
421
- address=address,
422
- group=group,
423
- typing=True,
424
- )
425
-
426
- @handle_unregistered_recipient
427
- async def displayed(self, legacy_msg_id: int, entity: Union["Contact", "MUC"]):
428
- if entity.is_group:
429
- entity = cast("MUC", entity)
430
- address = entity.sent.get(legacy_msg_id)
431
- if address is None:
432
- self.log.debug(
433
- "Ignoring read mark %s in %s", legacy_msg_id, entity.sent
434
- )
435
- return
436
- else:
437
- entity = cast("Contact", entity)
438
- address = entity.signal_address
439
-
440
- await (await self.signal).mark_read(
441
- account=self.phone,
442
- to=address,
443
- timestamps=[legacy_msg_id],
444
- )
445
-
446
- @handle_unregistered_recipient
447
- async def react(
448
- self, legacy_msg_id: int, emojis: list[str], c: Union["Contact", "MUC"]
449
- ):
450
- address, group = self._get_args_from_entity(c)
451
- if group:
452
- return
453
- c = cast("Contact", c)
454
-
455
- remove = len(emojis) == 0
456
- if remove:
457
- try:
458
- emoji = c.user_reactions.pop(legacy_msg_id)
459
- except KeyError:
460
- self.send_gateway_message(
461
- f"Slidge failed to remove your reactions on message '{legacy_msg_id}'"
462
- )
463
- self.log.warning("Could not find the emoji to remove reaction")
464
- raise XMPPError(
465
- "undefined-condition",
466
- "Could not remove your reactions to this message",
467
- )
468
- else:
469
- emoji = emojis[-1]
470
-
471
- response = await (await self.signal).react(
472
- username=self.phone,
473
- recipientAddress=c.signal_address,
474
- recipientGroupId=group,
475
- reaction=sigapi.JsonReactionv1(
476
- emoji=emoji,
477
- remove=remove,
478
- targetAuthor=sigapi.JsonAddressv1(number=self.phone)
479
- if legacy_msg_id in self.sent
480
- else c.signal_address,
481
- targetSentTimestamp=legacy_msg_id,
482
- ),
483
- )
484
- result = response.results[0]
485
- if (
486
- result.networkFailure
487
- or result.identityFailure
488
- or result.proof_required_failure
489
- ):
490
- raise XMPPError(str(result))
491
- c.user_reactions[legacy_msg_id] = emoji
492
-
493
- @handle_unregistered_recipient
494
- async def retract(self, legacy_msg_id: int, c: "Contact"):
495
- address, group = self._get_args_from_entity(c)
496
- try:
497
- await (await self.signal).remote_delete(
498
- account=self.phone,
499
- address=address,
500
- group=group,
501
- timestamp=legacy_msg_id,
502
- )
503
- except sigexc.SignaldException as e:
504
- raise XMPPError(text=f"Something went wrong during remote delete: {e}")
505
-
506
- async def add_device(self, uri: str):
507
- try:
508
- await (await self.signal).add_device(account=self.phone, uri=uri)
509
- except sigexc.SignaldException as e:
510
- self.send_gateway_message(f"Problem: {e}")
511
- else:
512
- self.send_gateway_message("Linking OK")
513
-
514
-
515
- log = logging.getLogger(__name__)
@@ -1,13 +0,0 @@
1
- from slidge import *
2
-
3
- REGISTRATION_INSTRUCTIONS = (
4
- "Fill the form to register a new signal account. "
5
- "If you want to link slidge to an existing signal account, use 'link' (ad-hoc or chat command).\n"
6
- "You may need to complete a captcha first, cf https://signald.org/articles/captcha/#getting-a-token "
7
- )
8
-
9
- REGISTRATION_FIELDS = [
10
- FormField(var="phone", label="Phone number (ex: +123456789)", required=True),
11
- FormField(var="name", label="Your name (or nickname)", required=True),
12
- FormField(var="captcha", label="Captcha token"),
13
- ]
@@ -1,32 +0,0 @@
1
- from mimetypes import guess_extension
2
-
3
- import aiosignald.generated as sigapi
4
-
5
-
6
- class AttachmentSenderMixin:
7
- async def send_attachments(
8
- self, attachments: list[sigapi.JsonAttachmentv1], legacy_msg_id: int, **kwargs
9
- ):
10
- last_attachment_i = len(attachments) - 1
11
- for i, attachment in enumerate(attachments):
12
- filename = get_filename(attachment)
13
- with open(attachment.storedFilename, "rb") as f:
14
- await self.send_file( # type:ignore
15
- filename=filename,
16
- input_file=f,
17
- content_type=attachment.contentType,
18
- legacy_msg_id=legacy_msg_id if i == last_attachment_i else None,
19
- caption=attachment.caption,
20
- **kwargs,
21
- )
22
-
23
-
24
- def get_filename(attachment: sigapi.JsonAttachmentv1):
25
- if f := attachment.customFilename:
26
- return f
27
- else:
28
- filename = attachment.id or "unnamed"
29
- ext = guess_extension(attachment.contentType)
30
- if ext is not None:
31
- filename += ext
32
- return filename