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,370 +0,0 @@
1
- import asyncio
2
- from asyncio import iscoroutine, run_coroutine_threadsafe
3
- from datetime import datetime
4
- from functools import wraps
5
- from io import BytesIO
6
- from mimetypes import guess_type
7
- from os import remove
8
- from os.path import basename
9
- from shelve import open
10
- from typing import Optional, Union
11
-
12
- from slixmpp.exceptions import XMPPError
13
-
14
- from slidge import (
15
- BaseSession,
16
- GatewayUser,
17
- LegacyBookmarks,
18
- LegacyMUC,
19
- LegacyParticipant,
20
- global_config,
21
- )
22
- from slidge.plugins.whatsapp.generated import go, whatsapp
23
-
24
- from . import config
25
- from .contact import Contact, Roster
26
- from .gateway import Gateway
27
-
28
- MESSAGE_PAIR_SUCCESS = (
29
- "Pairing successful! You might need to repeat this process in the future if the Linked Device is "
30
- "re-registered from your main device."
31
- )
32
-
33
- MESSAGE_LOGGED_OUT = "You have been logged out, please re-scan the QR code on your main device to log in."
34
-
35
-
36
- class Session(
37
- BaseSession[
38
- Gateway, str, Roster, Contact, LegacyBookmarks, LegacyMUC, LegacyParticipant
39
- ]
40
- ):
41
- def __init__(self, user: GatewayUser):
42
- super().__init__(user)
43
- self.user_shelf_path = (
44
- global_config.HOME_DIR / "whatsapp" / (self.user.bare_jid + ".shelf")
45
- )
46
- with open(str(self.user_shelf_path)) as shelf:
47
- try:
48
- device = whatsapp.LinkedDevice(ID=shelf["device_id"])
49
- except KeyError:
50
- device = whatsapp.LinkedDevice()
51
- self.whatsapp = self.xmpp.whatsapp.NewSession(device)
52
- self._handle_event = make_sync(self.handle_event, self.xmpp.loop)
53
- self.whatsapp.SetEventHandler(self._handle_event)
54
- self._connected: asyncio.Future[str] = self.xmpp.loop.create_future()
55
-
56
- def shutdown(self):
57
- for c in self.contacts:
58
- c.offline()
59
- self.xmpp.loop.create_task(self.disconnect())
60
-
61
- async def login(self):
62
- """
63
- Initiate login process and connect session to WhatsApp. Depending on existing state, login
64
- might either return having initiated the Linked Device registration process in the background,
65
- or will re-connect to a previously existing Linked Device session.
66
- """
67
- try:
68
- self.whatsapp.Login()
69
- except RuntimeError as err:
70
- raise XMPPError(text=str(err))
71
- return await self._connected
72
-
73
- async def logout(self):
74
- """
75
- Logout from the active WhatsApp session. This will also force a remote log-out, and thus
76
- require pairing on next login. For simply disconnecting the active session, look at the
77
- :meth:`.Session.disconnect` function.
78
- """
79
- try:
80
- self.whatsapp.Logout()
81
- except RuntimeError as err:
82
- raise XMPPError(text=str(err))
83
- remove(self.user_shelf_path)
84
-
85
- async def disconnect(self):
86
- """
87
- Disconnect the active WhatsApp session. This will not remove any local or remote state, and
88
- will thus allow previously authenticated sessions to re-authenticate without needing to pair.
89
- """
90
- try:
91
- self.whatsapp.Disconnect()
92
- except RuntimeError as err:
93
- raise XMPPError(text=str(err))
94
-
95
- async def handle_event(self, event, ptr):
96
- """
97
- Handle incoming event, as propagated by the WhatsApp adapter. Typically, events carry all
98
- state required for processing by the Gateway itself, and will do minimal processing themselves.
99
- """
100
- data = whatsapp.EventPayload(handle=ptr)
101
- if event == whatsapp.EventQRCode:
102
- self.send_gateway_status("QR Scan Needed", show="dnd")
103
- await self.send_qr(data.QRCode)
104
- elif event == whatsapp.EventPairSuccess:
105
- self.send_gateway_message(MESSAGE_PAIR_SUCCESS)
106
- with open(str(self.user_shelf_path)) as shelf:
107
- shelf["device_id"] = data.PairDeviceID
108
- try:
109
- self.whatsapp.FetchRoster(refresh=True)
110
- except RuntimeError as err:
111
- self.log.error("Failed refreshing roster on pair: %s", str(err))
112
- elif event == whatsapp.EventConnected:
113
- try:
114
- self.whatsapp.FetchRoster(refresh=config.ALWAYS_SYNC_ROSTER)
115
- except RuntimeError as err:
116
- self.log.error("Failed refreshing roster on connect: %s", str(err))
117
- try:
118
- self._connected.set_result("Connected")
119
- except asyncio.InvalidStateError:
120
- # FIXME: login should be made blocking, and/or a "socket disconnected" event
121
- # should be thrown, so we can properly reset the future
122
- self.log.debug(
123
- "We thought we were connected but apparently we weren't?"
124
- )
125
- elif event == whatsapp.EventLoggedOut:
126
- self._connected = self.xmpp.loop.create_future()
127
- self.send_gateway_message(MESSAGE_LOGGED_OUT)
128
- self.send_gateway_status("Logged out", show="away")
129
- await self.login()
130
- elif event == whatsapp.EventContactSync:
131
- contact = await self.contacts.by_legacy_id(data.Contact.JID)
132
- contact.name = data.Contact.Name
133
- if data.Contact.AvatarURL != "":
134
- contact.avatar = data.Contact.AvatarURL
135
- await contact.add_to_roster()
136
- elif event == whatsapp.EventPresence:
137
- contact = await self.contacts.by_legacy_id(data.Presence.JID)
138
- contact.update_presence(data.Presence.Away, data.Presence.LastSeen)
139
- elif event == whatsapp.EventChatState:
140
- contact = await self.contacts.by_legacy_id(data.ChatState.JID)
141
- if data.ChatState.Kind == whatsapp.ChatStateComposing:
142
- contact.composing()
143
- elif data.ChatState.Kind == whatsapp.ChatStatePaused:
144
- contact.paused()
145
- elif event == whatsapp.EventReceipt:
146
- await self.handle_receipt(data.Receipt)
147
- elif event == whatsapp.EventCall:
148
- contact = await self.contacts.by_legacy_id(data.Call.JID)
149
- if data.Call.State == whatsapp.CallMissed:
150
- text = "Missed call"
151
- text = text + f" from {contact.name} (xmpp:{contact.jid.bare})"
152
- if data.Call.Timestamp > 0:
153
- text = text + f" at {datetime.fromtimestamp(data.Call.Timestamp)}"
154
- self.send_gateway_message(text)
155
- elif event == whatsapp.EventMessage:
156
- await self.handle_message(data.Message)
157
-
158
- async def handle_receipt(self, receipt: whatsapp.Receipt):
159
- """
160
- Handle incoming delivered/read receipt, as propagated by the WhatsApp adapter.
161
- """
162
- contact = await self.contacts.by_legacy_id(receipt.JID)
163
- for message_id in receipt.MessageIDs:
164
- if receipt.Kind == whatsapp.ReceiptDelivered:
165
- contact.received(message_id)
166
- elif receipt.Kind == whatsapp.ReceiptRead:
167
- contact.displayed(legacy_msg_id=message_id, carbon=receipt.IsCarbon)
168
-
169
- async def handle_message(self, message: whatsapp.Message):
170
- """
171
- Handle incoming message, as propagated by the WhatsApp adapter. Messages can be one of many
172
- types, including plain-text messages, media messages, reactions, etc., and may also include
173
- other aspects such as references to other messages for the purposes of quoting or correction.
174
- """
175
- contact = await self.contacts.by_legacy_id(message.JID)
176
- message_reply_id = message.ReplyID if message.ReplyID != "" else None
177
- message_reply_body = message.ReplyBody if message.ReplyBody != "" else None
178
- message_timestamp = (
179
- datetime.fromtimestamp(message.Timestamp) if message.Timestamp > 0 else None
180
- )
181
- if message.Kind == whatsapp.MessagePlain:
182
- contact.send_text(
183
- body=message.Body,
184
- legacy_msg_id=message.ID,
185
- when=message_timestamp,
186
- reply_to_msg_id=message_reply_id,
187
- reply_to_fallback_text=message_reply_body,
188
- carbon=message.IsCarbon,
189
- )
190
- elif message.Kind == whatsapp.MessageAttachment:
191
- for ptr in message.Attachments:
192
- attachment = whatsapp.Attachment(handle=ptr)
193
- attachment_caption = (
194
- attachment.Caption if attachment.Caption != "" else None
195
- )
196
- await contact.send_file(
197
- filename=attachment.Filename,
198
- content_type=attachment.MIME,
199
- input_file=BytesIO(initial_bytes=bytes(attachment.Data)),
200
- legacy_msg_id=message.ID,
201
- reply_to_msg_id=message_reply_id,
202
- when=message_timestamp,
203
- caption=attachment_caption,
204
- carbon=message.IsCarbon,
205
- )
206
- elif message.Kind == whatsapp.MessageRevoke:
207
- contact.retract(legacy_msg_id=message.ID, carbon=message.IsCarbon)
208
- elif message.Kind == whatsapp.MessageReaction:
209
- contact.react(
210
- legacy_msg_id=message.ID, emojis=message.Body, carbon=message.IsCarbon
211
- )
212
-
213
- async def send_text(
214
- self,
215
- text: str,
216
- chat: Union[Contact, LegacyMUC],
217
- reply_to_msg_id: Optional[str] = None,
218
- reply_to_fallback_text: Optional[str] = None,
219
- **_,
220
- ):
221
- """
222
- Send outgoing plain-text message to given WhatsApp contact.
223
- """
224
- message_id = whatsapp.GenerateMessageID()
225
- message = whatsapp.Message(ID=message_id, JID=chat.legacy_id, Body=text)
226
- if reply_to_msg_id is not None:
227
- message.ReplyID = reply_to_msg_id
228
- if reply_to_fallback_text is not None:
229
- message.ReplyBody = strip_quote_prefix(reply_to_fallback_text)
230
- message.Body = message.Body.lstrip()
231
- try:
232
- self.whatsapp.SendMessage(message)
233
- except RuntimeError as err:
234
- raise XMPPError(text=str(err))
235
- return message_id
236
-
237
- async def send_file(
238
- self,
239
- url: str,
240
- chat: Union[Contact, LegacyMUC],
241
- reply_to_msg_id: Optional[str] = None,
242
- **_,
243
- ):
244
- """
245
- Send outgoing media message (i.e. audio, image, document) to given WhatsApp contact.
246
- """
247
- message_id = whatsapp.GenerateMessageID()
248
- message_attachment = whatsapp.Attachment(
249
- MIME=guess_type(url)[0], Filename=basename(url), URL=url
250
- )
251
- message = whatsapp.Message(
252
- Kind=whatsapp.MessageAttachment,
253
- ID=message_id,
254
- JID=chat.legacy_id,
255
- ReplyID=reply_to_msg_id if reply_to_msg_id is not None else "",
256
- Attachments=whatsapp.Slice_whatsapp_Attachment([message_attachment]),
257
- )
258
- try:
259
- self.whatsapp.SendMessage(message)
260
- except RuntimeError as err:
261
- raise XMPPError(text=str(err))
262
- return message_id
263
-
264
- async def active(self, c: Contact):
265
- """
266
- WhatsApp has no equivalent to the "active" chat state, so calls to this function are no-ops.
267
- """
268
- pass
269
-
270
- async def inactive(self, c: Contact):
271
- """
272
- WhatsApp has no equivalent to the "inactive" chat state, so calls to this function are no-ops.
273
- """
274
- pass
275
-
276
- async def composing(self, c: Contact):
277
- """
278
- Send "composing" chat state to given WhatsApp contact, signifying that a message is currently
279
- being composed.
280
- """
281
- state = whatsapp.ChatState(JID=c.legacy_id, Kind=whatsapp.ChatStateComposing)
282
- try:
283
- self.whatsapp.SendChatState(state)
284
- except RuntimeError as err:
285
- raise XMPPError(text=str(err))
286
-
287
- async def paused(self, c: Contact):
288
- """
289
- Send "paused" chat state to given WhatsApp contact, signifying that an (unsent) message is no
290
- longer being composed.
291
- """
292
- state = whatsapp.ChatState(JID=c.legacy_id, Kind=whatsapp.ChatStatePaused)
293
- try:
294
- self.whatsapp.SendChatState(state)
295
- except RuntimeError as err:
296
- raise XMPPError(text=str(err))
297
-
298
- async def displayed(self, legacy_msg_id: str, c: Contact):
299
- """
300
- Send "read" receipt, signifying that the WhatsApp message sent has been displayed on the XMPP
301
- client.
302
- """
303
- receipt = whatsapp.Receipt(
304
- MessageIDs=go.Slice_string([legacy_msg_id]), JID=c.legacy_id
305
- )
306
- try:
307
- self.whatsapp.SendReceipt(receipt)
308
- except RuntimeError as err:
309
- raise XMPPError(text=str(err))
310
-
311
- async def react(self, legacy_msg_id: str, emojis: list[str], c: Contact):
312
- """
313
- Send or remove emoji reaction to existing WhatsApp message.
314
- Slidge core makes sure that the emojis parameter is always empty or a
315
- *single* emoji.
316
- """
317
- message = whatsapp.Message(
318
- Kind=whatsapp.MessageReaction,
319
- ID=legacy_msg_id,
320
- JID=c.legacy_id,
321
- Body=emojis[0] if emojis else "",
322
- IsCarbon=legacy_msg_id in self.sent,
323
- )
324
- try:
325
- self.whatsapp.SendMessage(message)
326
- except RuntimeError as err:
327
- raise XMPPError(text=str(err))
328
-
329
- async def retract(self, legacy_msg_id: str, c: Contact):
330
- """
331
- Request deletion (aka retraction) for a given WhatsApp message.
332
- """
333
- message = whatsapp.Message(
334
- Kind=whatsapp.MessageRevoke, ID=legacy_msg_id, JID=c.legacy_id
335
- )
336
- try:
337
- self.whatsapp.SendMessage(message)
338
- except RuntimeError as err:
339
- raise XMPPError(text=str(err))
340
-
341
- async def correct(self, text: str, legacy_msg_id: str, c: Contact):
342
- self.send_gateway_message(
343
- "Warning: WhatsApp does not support message editing at this point in time."
344
- )
345
-
346
- async def search(self, form_values: dict[str, str]):
347
- self.send_gateway_message("Searching on WhatsApp has not been implemented yet.")
348
-
349
-
350
- def make_sync(func, loop):
351
- """
352
- Wrap async function in synchronous operation, running against the given loop in thread-safe mode.
353
- """
354
-
355
- @wraps(func)
356
- def wrapper(*args, **kwargs):
357
- result = func(*args, **kwargs)
358
- if iscoroutine(result):
359
- future = run_coroutine_threadsafe(result, loop)
360
- return future.result()
361
- return result
362
-
363
- return wrapper
364
-
365
-
366
- def strip_quote_prefix(text: str):
367
- """
368
- Return multi-line text without leading quote marks (i.e. the ">" character).
369
- """
370
- return "\n".join(x.lstrip(">").strip() for x in text.split("\n")).strip()
@@ -1,13 +0,0 @@
1
-
2
- # Slixmpp: The Slick XMPP Library
3
- # Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout
4
- # This file is part of Slixmpp.
5
- # See the file LICENSE for copying permission.
6
- from slixmpp.plugins.base import register_plugin
7
-
8
- from . import stanza
9
- from .disco import XEP_0030
10
- from .stanza import DiscoInfo, DiscoItems
11
- from .static import StaticDisco
12
-
13
- register_plugin(XEP_0030)