slidge 0.1.0b2__py3-none-any.whl → 0.1.1__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (155) hide show
  1. slidge/__init__.py +55 -31
  2. slidge/__main__.py +118 -116
  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 +183 -0
  16. slidge/core/config.py +216 -0
  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 +789 -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 +282 -116
  41. slidge/core/session.py +595 -372
  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_0084 → slixfix/link_preview}/__init__.py +3 -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 +14 -2
  54. slidge/slixfix/xep_0077/stanza.py +104 -0
  55. slidge/{util → slixfix}/xep_0100/gateway.py +25 -15
  56. slidge/slixfix/xep_0100/stanza.py +9 -0
  57. slidge/slixfix/xep_0153/__init__.py +10 -0
  58. slidge/slixfix/xep_0153/stanza.py +25 -0
  59. slidge/slixfix/xep_0153/vcard_avatar.py +23 -0
  60. slidge/slixfix/xep_0264/__init__.py +5 -0
  61. slidge/slixfix/xep_0264/stanza.py +36 -0
  62. slidge/slixfix/xep_0264/thumbnail.py +23 -0
  63. slidge/slixfix/xep_0292/__init__.py +5 -0
  64. slidge/slixfix/xep_0292/vcard4.py +100 -0
  65. slidge/slixfix/xep_0313/__init__.py +12 -0
  66. slidge/slixfix/xep_0313/mam.py +262 -0
  67. slidge/slixfix/xep_0313/stanza.py +359 -0
  68. slidge/slixfix/xep_0317/__init__.py +5 -0
  69. slidge/slixfix/xep_0317/hats.py +17 -0
  70. slidge/slixfix/xep_0317/stanza.py +28 -0
  71. slidge/{util → slixfix}/xep_0356_old/privilege.py +9 -7
  72. slidge/slixfix/xep_0424/__init__.py +9 -0
  73. slidge/slixfix/xep_0424/retraction.py +77 -0
  74. slidge/slixfix/xep_0424/stanza.py +28 -0
  75. slidge/slixfix/xep_0490/__init__.py +8 -0
  76. slidge/slixfix/xep_0490/mds.py +47 -0
  77. slidge/slixfix/xep_0490/stanza.py +17 -0
  78. slidge/util/__init__.py +4 -6
  79. slidge/util/archive_msg.py +61 -0
  80. slidge/util/conf.py +206 -0
  81. slidge/util/db.py +57 -76
  82. slidge/util/schema.sql +126 -0
  83. slidge/util/sql.py +508 -0
  84. slidge/util/test.py +215 -25
  85. slidge/util/types.py +177 -4
  86. slidge/util/util.py +225 -59
  87. slidge-0.1.1.dist-info/METADATA +110 -0
  88. slidge-0.1.1.dist-info/RECORD +96 -0
  89. {slidge-0.1.0b2.dist-info → slidge-0.1.1.dist-info}/WHEEL +1 -1
  90. slidge/core/contact.py +0 -891
  91. slidge/core/gateway.py +0 -916
  92. slidge/plugins/discord/__init__.py +0 -90
  93. slidge/plugins/discord/client.py +0 -108
  94. slidge/plugins/discord/session.py +0 -162
  95. slidge/plugins/dummy.py +0 -203
  96. slidge/plugins/facebook.py +0 -493
  97. slidge/plugins/hackernews.py +0 -213
  98. slidge/plugins/mattermost/__init__.py +0 -1
  99. slidge/plugins/mattermost/api.py +0 -280
  100. slidge/plugins/mattermost/gateway.py +0 -365
  101. slidge/plugins/mattermost/websocket.py +0 -252
  102. slidge/plugins/signal/__init__.py +0 -3
  103. slidge/plugins/signal/contact.py +0 -106
  104. slidge/plugins/signal/gateway.py +0 -282
  105. slidge/plugins/signal/session.py +0 -448
  106. slidge/plugins/signal/txt.py +0 -53
  107. slidge/plugins/skype.py +0 -325
  108. slidge/plugins/steam.py +0 -310
  109. slidge/plugins/telegram/__init__.py +0 -5
  110. slidge/plugins/telegram/client.py +0 -228
  111. slidge/plugins/telegram/config.py +0 -12
  112. slidge/plugins/telegram/contact.py +0 -176
  113. slidge/plugins/telegram/gateway.py +0 -150
  114. slidge/plugins/telegram/session.py +0 -256
  115. slidge/util/xep_0030/__init__.py +0 -13
  116. slidge/util/xep_0030/disco.py +0 -811
  117. slidge/util/xep_0030/stanza/__init__.py +0 -7
  118. slidge/util/xep_0030/stanza/info.py +0 -270
  119. slidge/util/xep_0030/stanza/items.py +0 -147
  120. slidge/util/xep_0030/static.py +0 -467
  121. slidge/util/xep_0055/__init__.py +0 -5
  122. slidge/util/xep_0055/search.py +0 -75
  123. slidge/util/xep_0055/stanza.py +0 -10
  124. slidge/util/xep_0077/stanza.py +0 -71
  125. slidge/util/xep_0084/avatar.py +0 -137
  126. slidge/util/xep_0084/stanza.py +0 -104
  127. slidge/util/xep_0115/__init__.py +0 -12
  128. slidge/util/xep_0115/caps.py +0 -379
  129. slidge/util/xep_0115/stanza.py +0 -16
  130. slidge/util/xep_0115/static.py +0 -137
  131. slidge/util/xep_0292/__init__.py +0 -1
  132. slidge/util/xep_0292/stanza.py +0 -167
  133. slidge/util/xep_0292/vcard4.py +0 -75
  134. slidge/util/xep_0333/__init__.py +0 -10
  135. slidge/util/xep_0333/markers.py +0 -96
  136. slidge/util/xep_0333/stanza.py +0 -34
  137. slidge/util/xep_0356/__init__.py +0 -7
  138. slidge/util/xep_0356/permissions.py +0 -35
  139. slidge/util/xep_0356/privilege.py +0 -160
  140. slidge/util/xep_0356/stanza.py +0 -44
  141. slidge/util/xep_0363/__init__.py +0 -16
  142. slidge/util/xep_0363/http_upload.py +0 -215
  143. slidge/util/xep_0363/stanza.py +0 -46
  144. slidge/util/xep_0461/__init__.py +0 -6
  145. slidge/util/xep_0461/reply.py +0 -48
  146. slidge/util/xep_0461/stanza.py +0 -47
  147. slidge-0.1.0b2.dist-info/METADATA +0 -171
  148. slidge-0.1.0b2.dist-info/RECORD +0 -81
  149. /slidge/{plugins/__init__.py → py.typed} +0 -0
  150. /slidge/{util → slixfix}/xep_0077/__init__.py +0 -0
  151. /slidge/{util → slixfix}/xep_0100/__init__.py +0 -0
  152. /slidge/{util → slixfix}/xep_0356_old/__init__.py +0 -0
  153. /slidge/{util → slixfix}/xep_0356_old/stanza.py +0 -0
  154. {slidge-0.1.0b2.dist-info → slidge-0.1.1.dist-info}/LICENSE +0 -0
  155. {slidge-0.1.0b2.dist-info → slidge-0.1.1.dist-info}/entry_points.txt +0 -0
slidge/plugins/skype.py DELETED
@@ -1,325 +0,0 @@
1
- import asyncio
2
- import concurrent.futures
3
- import io
4
- import logging
5
- import pprint
6
- import threading
7
- from pathlib import Path
8
- from threading import Lock, Thread
9
- from typing import Any, Optional
10
-
11
- import aiohttp
12
- import skpy
13
- from slixmpp import JID
14
- from slixmpp.exceptions import XMPPError
15
-
16
- from slidge import *
17
-
18
-
19
- class Gateway(BaseGateway["Session"]):
20
- REGISTRATION_INSTRUCTIONS = "Enter skype credentials"
21
- REGISTRATION_FIELDS = [
22
- FormField(var="username", label="Username", required=True),
23
- FormField(var="password", label="Password", required=True, private=True),
24
- ]
25
-
26
- ROSTER_GROUP = "Skype"
27
-
28
- COMPONENT_NAME = "Skype (slidge)"
29
- COMPONENT_TYPE = "skype"
30
-
31
- COMPONENT_AVATAR = "https://logodownload.org/wp-content/uploads/2017/05/skype-logo-1-1-2048x2048.png"
32
-
33
- async def validate(
34
- self, user_jid: JID, registration_form: dict[str, Optional[str]]
35
- ):
36
- pass
37
-
38
- def __init__(self, args):
39
- super().__init__(args)
40
- self.executor = concurrent.futures.ThreadPoolExecutor()
41
-
42
- def shutdown(self):
43
- super().shutdown()
44
- log.debug("Shutting down thread pool")
45
- self.executor.shutdown(wait=False, cancel_futures=True)
46
- log.debug("Shutting down user threads")
47
- for user in user_store.get_all():
48
- session = self._session_cls.from_jid(user.jid)
49
- if thread := session.thread:
50
- thread.stop()
51
-
52
-
53
- class Roster(LegacyRoster):
54
- # ':' is forbidden in the username part of a JID
55
-
56
- @staticmethod
57
- def legacy_id_to_jid_username(legacy_id: str) -> str:
58
- if legacy_id.startswith("live:"):
59
- return legacy_id.replace("live:", "__live__")
60
- else:
61
- return legacy_id
62
-
63
- @staticmethod
64
- def jid_username_to_legacy_id(jid_username: str) -> str:
65
- if jid_username.startswith("__live__"):
66
- return jid_username.replace("__live__", "live:")
67
- else:
68
- return jid_username
69
-
70
-
71
- class Contact(LegacyContact):
72
- def update_presence(self, status: skpy.SkypeUtils.Status):
73
- if status == skpy.SkypeUtils.Status.Offline:
74
- self.offline()
75
- elif status == skpy.SkypeUtils.Status.Busy:
76
- self.busy()
77
- elif status == skpy.SkypeUtils.Status.Away:
78
- self.away("Away")
79
- elif status == skpy.SkypeUtils.Status.Idle:
80
- self.away("Idle")
81
- elif status == skpy.SkypeUtils.Status.Online:
82
- self.online()
83
- else:
84
- log.warning("Unknown contact status: %s", status)
85
-
86
-
87
- class ListenThread(Thread):
88
- def __init__(self, session: "Session", *a, **kw):
89
- super().__init__(*a, **kw, daemon=True)
90
- self.name = f"listen-{session.user.bare_jid}"
91
- self.session = session
92
- self._target = self.skype_blocking
93
- self.stop_event = threading.Event()
94
-
95
- def skype_blocking(self):
96
- session = self.session
97
- sk = session.sk
98
- loop = session.xmpp.loop
99
- while True:
100
- if self.stop_event.is_set():
101
- break
102
- for event in sk.getEvents():
103
- # no need to sleep since getEvents blocks for 30 seconds already
104
- asyncio.run_coroutine_threadsafe(session.on_skype_event(event), loop)
105
-
106
- def stop(self):
107
- self.stop_event.set()
108
-
109
-
110
- class Session(BaseSession[Contact, Roster, Gateway]):
111
- skype_token_path: Path
112
- sk: skpy.Skype
113
- thread: Optional[ListenThread]
114
- sent_by_user_to_ack: dict[int, asyncio.Future]
115
- unread_by_user: dict[int, skpy.SkypeMsg]
116
- send_lock: Lock
117
-
118
- def post_init(self):
119
- self.skype_token_path = self.xmpp.home_dir / self.user.bare_jid
120
- self.thread = None
121
- self.sent_by_user_to_ack = {}
122
- self.unread_by_user = {}
123
- self.send_lock = Lock()
124
-
125
- async def async_wrap(self, func, *args):
126
- return await self.xmpp.loop.run_in_executor(self.xmpp.executor, func, *args)
127
-
128
- async def login(self):
129
- f = self.user.registration_form
130
- self.sk = await self.async_wrap(
131
- skpy.Skype,
132
- f["username"],
133
- f["password"],
134
- str(self.skype_token_path),
135
- )
136
-
137
- self.sk.subscribePresence()
138
- for contact in self.sk.contacts:
139
- c = self.contacts.by_legacy_id(contact.id)
140
- first = contact.name.first
141
- last = contact.name.last
142
- if first is not None and last is not None:
143
- c.name = f"{first} {last}"
144
- elif first is not None:
145
- c.name = first
146
- elif last is not None:
147
- c.name = last
148
- if contact.avatar is not None:
149
- c.avatar = contact.avatar
150
- await c.add_to_roster()
151
- # TODO: Creating 1 thread per user is probably very not optimal.
152
- # We should contribute to skpy to make it aiohttp compatible…
153
- self.thread = thread = ListenThread(self)
154
- thread.start()
155
- return f"Connected as '{self.sk.userId}'"
156
-
157
- async def on_skype_event(self, event: skpy.SkypeEvent):
158
- log.debug("Skype event: %s", event)
159
- if isinstance(event, skpy.SkypeNewMessageEvent):
160
- while self.send_lock.locked():
161
- await asyncio.sleep(0.1)
162
- msg = event.msg
163
- chat = event.msg.chat
164
- if isinstance(chat, skpy.SkypeSingleChat):
165
- log.debug("this is a single chat with user: %s", chat.userIds[0])
166
- contact = self.contacts.by_legacy_id(chat.userIds[0])
167
- if msg.userId == self.sk.userId:
168
- try:
169
- fut = self.sent_by_user_to_ack.pop(msg.clientId)
170
- except KeyError:
171
- if log.isEnabledFor(logging.DEBUG):
172
- log.debug(
173
- "Slidge did not send this message: %s",
174
- pprint.pformat(vars(event)),
175
- )
176
- contact.carbon(msg.plain)
177
- else:
178
- fut.set_result(msg)
179
- else:
180
- if isinstance(msg, skpy.SkypeTextMsg):
181
- contact.send_text(msg.plain, legacy_msg_id=msg.clientId)
182
- self.unread_by_user[msg.clientId] = msg
183
- elif isinstance(msg, skpy.SkypeFileMsg):
184
- file = io.BytesIO(
185
- await self.async_wrap(lambda: msg.fileContent)
186
- ) # non-blocking download / lambda because fileContent = property
187
- await contact.send_file(filename=msg.file.name, input_file=file)
188
- elif isinstance(event, skpy.SkypeTypingEvent):
189
- contact = self.contacts.by_legacy_id(event.userId)
190
- if event.active:
191
- contact.composing()
192
- else:
193
- contact.paused()
194
- elif isinstance(event, skpy.SkypeEditMessageEvent):
195
- msg = event.msg
196
- chat = event.msg.chat
197
- if isinstance(chat, skpy.SkypeSingleChat):
198
- if (user_id := msg.userId) != self.sk.userId:
199
- if log.isEnabledFor(logging.DEBUG):
200
- log.debug("edit msg event: %s", pprint.pformat(vars(event)))
201
- contact = self.contacts.by_legacy_id(user_id)
202
- msg_id = msg.clientId
203
- log.debug("edited msg id: %s", msg_id)
204
- if text := msg.plain:
205
- contact.correct(msg_id, text)
206
- else:
207
- if msg_id:
208
- contact.retract(msg_id)
209
- else:
210
- contact.send_text(
211
- "/me tried to remove a message, but slidge got in trouble"
212
- )
213
- elif isinstance(event, skpy.SkypeChatUpdateEvent):
214
- if log.isEnabledFor(logging.DEBUG):
215
- log.debug("chat update: %s", pprint.pformat(vars(event)))
216
- elif isinstance(event, skpy.SkypePresenceEvent):
217
- if event.userId != self.sk.userId:
218
- self.contacts.by_legacy_id(event.userId).update_presence(event.status)
219
-
220
- # No 'contact has read' event :( https://github.com/Terrance/SkPy/issues/206
221
- await self.async_wrap(event.ack)
222
-
223
- async def send_text(self, t: str, c: LegacyContact, *, reply_to_msg_id=None):
224
- chat = self.sk.contacts[c.legacy_id].chat
225
- self.send_lock.acquire()
226
- msg = await self.async_wrap(chat.sendMsg, t)
227
- if log.isEnabledFor(logging.DEBUG):
228
- log.debug("Sent msg: %s", pprint.pformat(vars(msg)))
229
- future = asyncio.Future[skpy.SkypeMsg]()
230
- self.sent_by_user_to_ack[msg.clientId] = future
231
- self.send_lock.release()
232
- skype_msg = await future
233
- return skype_msg.clientId
234
-
235
- async def logout(self):
236
- if self.thread is not None:
237
- self.thread.stop()
238
- self.thread.join()
239
-
240
- async def send_file(self, u: str, c: LegacyContact, *, reply_to_msg_id=None):
241
- async with aiohttp.ClientSession() as session:
242
- async with session.get(u) as response:
243
- file_bytes = await response.read()
244
- fname = u.split("/")[-1]
245
- fname_lower = fname.lower()
246
- await self.async_wrap(
247
- self.sk.contacts[c.legacy_id].chat.sendFile,
248
- io.BytesIO(file_bytes),
249
- fname,
250
- any(fname_lower.endswith(x) for x in (".png", ".jpg", ".gif", ".jpeg")),
251
- )
252
-
253
- async def active(self, c: LegacyContact):
254
- pass
255
-
256
- async def inactive(self, c: LegacyContact):
257
- pass
258
-
259
- async def composing(self, c: LegacyContact):
260
- self.xmpp.executor.submit(self.sk.contacts[c.legacy_id].chat.setTyping, True)
261
-
262
- async def paused(self, c: LegacyContact):
263
- self.xmpp.executor.submit(self.sk.contacts[c.legacy_id].chat.setTyping, False)
264
-
265
- async def displayed(self, legacy_msg_id: int, c: LegacyContact):
266
- try:
267
- skype_msg = self.unread_by_user.pop(legacy_msg_id)
268
- except KeyError:
269
- log.debug(
270
- "We did not transmit: %s (%s)", legacy_msg_id, self.unread_by_user
271
- )
272
- else:
273
- log.debug("Calling read on %s", skype_msg)
274
- try:
275
- await self.async_wrap(skype_msg.read)
276
- except skpy.SkypeApiException as e:
277
- # FIXME: this raises HTTP 400 and does not mark the message as read
278
- # https://github.com/Terrance/SkPy/issues/207
279
- self.log.debug("Skype read marker failed: %r", e)
280
-
281
- async def correct(self, text: str, legacy_msg_id: Any, c: Contact):
282
- try:
283
- m = self.get_msg(legacy_msg_id, c)
284
- except RuntimeError:
285
- raise XMPPError("not-found")
286
- else:
287
- self.xmpp.executor.submit(m.edit, text)
288
-
289
- async def retract(self, legacy_msg_id: Any, c: Contact):
290
- try:
291
- m = self.get_msg(legacy_msg_id, c)
292
- except RuntimeError:
293
- raise XMPPError("not-found")
294
- else:
295
- log.debug("Deleting %s", m)
296
- self.xmpp.executor.submit(m.delete)
297
-
298
- async def search(self, form_values: dict[str, str]):
299
- pass
300
-
301
- def get_msg(self, legacy_msg_id: int, contact: Contact) -> skpy.SkypeTextMsg:
302
- for m in self.sk.contacts[contact.legacy_id].chat.getMsgs():
303
- log.debug("Message %r vs %r : %s", legacy_msg_id, m.clientId, m)
304
- if m.clientId == legacy_msg_id:
305
- return m
306
- else:
307
- raise RuntimeError("Could not find message ID")
308
-
309
-
310
- def handle_thread_exception(args):
311
- # TODO: establish what exceptions are OK (eg temporary network failures)
312
- # and which one should trigger killing the session and/or exiting slidge
313
- # and/or relogging in
314
- log.error("Exception in thread: %s", args)
315
- if (thread := getattr(args, "thread")) is not None:
316
- if isinstance(thread, ListenThread):
317
- session = thread.session
318
- log.warning("Attempting re-login for %s", session.user)
319
- thread.stop()
320
- session.re_login()
321
-
322
-
323
- threading.excepthook = handle_thread_exception
324
-
325
- log = logging.getLogger(__name__)
slidge/plugins/steam.py DELETED
@@ -1,310 +0,0 @@
1
- """
2
- Just a stab at steam chat.
3
-
4
- Unfortunately the library underneath uses gevent and there is probably some work to do
5
- to make it play nice with asyncio.
6
-
7
- Right now, listing friends + send them messages works BUT in a blocking way.
8
-
9
- Asyncsteampy https://github.com/somespecialone/asyncsteampy
10
- might be interesting as it uses python's asyncio BUT the
11
- login process seem a little too exotic for my taste.
12
- """
13
- import asyncio
14
- from collections import defaultdict
15
- from typing import Any
16
-
17
- import steam.enums
18
- from steam.client import SteamClient
19
- from steam.client.user import SteamUser
20
- from steam.core.msg import MsgProto
21
- from steam.enums.common import EPersonaState, EResult
22
- from steam.enums.emsg import EMsg
23
- from steam.protobufs.steammessages_friendmessages_pb2 import (
24
- k_EMessageReactionType_Emoticon,
25
- )
26
- from steam.steamid import SteamID
27
-
28
- from slidge import *
29
- from slidge.util import BiDict
30
-
31
-
32
- class Gateway(BaseGateway["Session"]):
33
- REGISTRATION_INSTRUCTIONS = "Enter steam credentials"
34
- REGISTRATION_FIELDS = [
35
- FormField(var="username", label="Steam username", required=True),
36
- FormField(var="password", label="Password", private=True, required=True),
37
- ]
38
-
39
- ROSTER_GROUP = "Steam"
40
-
41
- COMPONENT_NAME = "Steam (slidge)"
42
- COMPONENT_TYPE = "steam"
43
-
44
- COMPONENT_AVATAR = "https://logos-download.com/wp-content/uploads/2016/05/Steam_icon_logo_logotype.png"
45
-
46
-
47
- class Contact(LegacyContact["Session"]):
48
- MARKS = False
49
- CORRECTION = False
50
- RETRACTION = False
51
-
52
- def __init__(self, *a, **k):
53
- super().__init__(*a, **k)
54
- # keys = msg timestamp; vals = list of single character emoji
55
- self.user_reactions = defaultdict[int, set[str]](set)
56
- self.contact_reactions = defaultdict[int, set[str]](set)
57
-
58
- def update_status(self, persona_state: EPersonaState):
59
- if persona_state == EPersonaState.Offline:
60
- self.offline()
61
- elif persona_state == EPersonaState.Online:
62
- self.online()
63
- elif persona_state == EPersonaState.Busy:
64
- self.busy()
65
- elif persona_state == EPersonaState.Away:
66
- self.away()
67
-
68
- def update_reactions(self, timestamp: int):
69
- self.react(timestamp, self.contact_reactions[timestamp])
70
-
71
-
72
- class Roster(LegacyRoster[Contact, "Session"]):
73
- @staticmethod
74
- def jid_username_to_legacy_id(jid_username: str) -> int:
75
- return int(jid_username)
76
-
77
-
78
- class Session(BaseSession[Contact, Roster, Gateway]):
79
- steam: SteamClient
80
- job_futures: dict[str, asyncio.Future[Any]]
81
-
82
- def post_init(self):
83
- store_dir = self.xmpp.home_dir / self.user.bare_jid
84
- store_dir.mkdir(exist_ok=True)
85
-
86
- self.job_futures = {}
87
-
88
- self.steam = SteamClient()
89
- self.steam.set_credential_location(store_dir)
90
- self.steam.username = self.user.registration_form["username"]
91
-
92
- @staticmethod
93
- def xmpp_msg_id_to_legacy_msg_id(legacy_msg_id: Any) -> int:
94
- return int(legacy_msg_id)
95
-
96
- async def login(self):
97
- username = self.user.registration_form["username"]
98
- password = self.user.registration_form["password"]
99
-
100
- # self.steam.on(SteamClient.EVENT_CHAT_MESSAGE, self.on_steam_msg)
101
- self.steam.on(EMsg.ClientPersonaState, self.on_persona_state)
102
- self.steam.on("FriendMessagesClient.IncomingMessage#1", self.on_friend_message)
103
- self.steam.on("FriendMessagesClient.MessageReaction#1", self.on_friend_reaction)
104
- self.steam.on(EMsg.ServiceMethodResponse, self.on_service_method_response)
105
-
106
- login_result = self.steam.relogin()
107
-
108
- if login_result != EResult.OK:
109
- login_result = self.steam.login(username, password)
110
-
111
- if login_result == EResult.AccountLogonDenied: # 2FA
112
- code = await self.input("Enter the code you received by email")
113
- login_result = self.steam.login(
114
- self.user.registration_form["username"],
115
- self.user.registration_form["password"],
116
- auth_code=code,
117
- )
118
-
119
- self.log.debug("Login result: %s", login_result)
120
- if login_result == EResult.OK:
121
- self.log.debug("Login success")
122
- else:
123
- raise RuntimeError("Could not connect to steam")
124
-
125
- for f in self.steam.friends:
126
- f: SteamUser
127
- self.log.debug("Friend: %s - %s - %s", f, f.name, f.steam_id.id)
128
- c = self.contacts.by_legacy_id(f.steam_id.id)
129
- c.name = f.name
130
- c.avatar = f.get_avatar_url()
131
- await c.add_to_roster()
132
- c.update_status(f.state)
133
-
134
- asyncio.create_task(self.idle())
135
-
136
- async def idle(self):
137
- while True:
138
- self.steam.idle()
139
- await asyncio.sleep(0.1)
140
-
141
- def on_service_method_response(self, msg):
142
- self.log.debug("New service method response : %s", msg)
143
- try:
144
- fut = self.job_futures.pop(f"job_{msg.header.jobid_target}")
145
- except KeyError:
146
- self.log.debug(
147
- "Ignoring: %s vs %s", msg.header.jobid_target, self.job_futures
148
- )
149
- else:
150
- fut.set_result(msg.body)
151
-
152
- def on_friend_message(self, msg):
153
- self.log.debug("New friend message : %s", msg)
154
- if (type_ := msg.body.chat_entry_type) == steam.enums.EChatEntryType.Typing:
155
- user = self.steam.get_user(msg.body.steamid_friend)
156
- self.contacts.by_legacy_id(user.steam_id.id).composing()
157
- elif type_ == steam.enums.EChatEntryType.ChatMsg:
158
- user = self.steam.get_user(msg.body.steamid_friend)
159
- self.contacts.by_legacy_id(user.steam_id.id).send_text(
160
- msg.body.message, legacy_msg_id=msg.body.rtime32_server_timestamp
161
- )
162
-
163
- def on_friend_reaction(self, msg):
164
- self.log.debug("New friend reaction : %s", msg)
165
- body = msg.body
166
- timestamp = body.server_timestamp
167
- if body.reactor == self.steam.steam_id:
168
- if body.reaction_type == k_EMessageReactionType_Emoticon:
169
- contact = self.contacts.by_legacy_id(
170
- SteamID(msg.body.steamid_friend).id
171
- )
172
- emoji = emoji_translate.get(body.reaction)
173
- if emoji is None:
174
- return
175
- if body.is_add:
176
- contact.user_reactions[timestamp].add(emoji)
177
- else:
178
- try:
179
- contact.user_reactions[timestamp].remove(emoji)
180
- except KeyError:
181
- self.log.warning("User removed a reaction we didn't know about")
182
- contact.carbon_react(timestamp, contact.user_reactions[timestamp])
183
- else:
184
- if body.reaction_type == k_EMessageReactionType_Emoticon:
185
- contact = self.contacts.by_legacy_id(SteamID(msg.body.reactor).id)
186
- emoji = emoji_translate.get(body.reaction, "❓")
187
- if body.is_add:
188
- contact.contact_reactions[timestamp].add(emoji)
189
- else:
190
- try:
191
- contact.contact_reactions[timestamp].remove(emoji)
192
- except KeyError:
193
- self.log.warning(
194
- "Contact removed a reaction we didn't know about"
195
- )
196
- contact.update_reactions(timestamp)
197
-
198
- def on_persona_state(self, msg: MsgProto):
199
- persona_state = msg.body
200
- self.log.debug("New state event: %s", persona_state)
201
- for f in persona_state.friends:
202
- if f.friendid == self.steam.steam_id:
203
- self.log.debug("This is me %s", self.steam.steam_id)
204
- return
205
- self.contacts.by_legacy_id(SteamID(f.friendid).id).update_status(
206
- f.persona_state
207
- )
208
-
209
- async def logout(self):
210
- pass
211
-
212
- async def send_text(self, t: str, c: Contact, *, reply_to_msg_id=None):
213
- if not t:
214
- return
215
- job_id = self.steam.send_um(
216
- "FriendMessages.SendMessage#1",
217
- {
218
- "steamid": SteamID(c.legacy_id),
219
- "chat_entry_type": steam.enums.EChatEntryType.ChatMsg,
220
- "message": t,
221
- },
222
- )
223
- f = self.job_futures[job_id] = self.xmpp.loop.create_future()
224
- return (await f).server_timestamp
225
-
226
- async def send_file(self, u: str, c: Contact, *, reply_to_msg_id=None):
227
- return await self.send_text(u, c)
228
-
229
- async def active(self, c: Contact):
230
- pass
231
-
232
- async def inactive(self, c: Contact):
233
- pass
234
-
235
- async def composing(self, c: Contact):
236
- self.steam.send_um(
237
- "FriendMessages.SendMessage#1",
238
- {
239
- "steamid": SteamID(c.legacy_id),
240
- "chat_entry_type": steam.enums.EChatEntryType.Typing,
241
- },
242
- )
243
-
244
- async def paused(self, c: Contact):
245
- pass
246
-
247
- async def displayed(self, legacy_msg_id: Any, c: Contact):
248
- pass
249
-
250
- async def correct(self, text: str, legacy_msg_id: Any, c: Contact):
251
- pass
252
-
253
- async def search(self, form_values: dict[str, str]):
254
- pass
255
-
256
- async def react(self, legacy_msg_id: Any, emojis: list[str], c: Contact):
257
- old = c.user_reactions[legacy_msg_id]
258
- new = set[str]()
259
- for emoji in emojis:
260
- if emoji_translate.inverse.get(emoji) is None:
261
- self.send_gateway_message(
262
- f"On steam, you can only react with {' '.join(emoji_translate.values())}"
263
- )
264
- else:
265
- new.add(emoji)
266
-
267
- for emoji_char in old - new:
268
- self.steam.send_um(
269
- "FriendMessages.UpdateMessageReaction#1",
270
- {
271
- "steamid": SteamID(c.legacy_id).as_64,
272
- "server_timestamp": legacy_msg_id,
273
- "reaction_type": k_EMessageReactionType_Emoticon,
274
- "reaction": emoji_translate.inverse.get(emoji_char),
275
- "is_add": False,
276
- },
277
- )
278
-
279
- for emoji_char in new - old:
280
- self.steam.send_um(
281
- "FriendMessages.UpdateMessageReaction#1",
282
- {
283
- "steamid": SteamID(c.legacy_id).as_64,
284
- "server_timestamp": legacy_msg_id,
285
- "reaction_type": k_EMessageReactionType_Emoticon,
286
- "reaction": emoji_translate.inverse.get(emoji_char),
287
- "is_add": True,
288
- },
289
- )
290
-
291
- c.user_reactions[legacy_msg_id] = new
292
- c.carbon_react(legacy_msg_id, new)
293
-
294
- async def retract(self, legacy_msg_id: Any, c: Contact):
295
- pass
296
-
297
-
298
- emoji_translate = BiDict[str, str](
299
- [
300
- (":steamthumbsup:", "👍"),
301
- (":steamthumbsdown:", "👎"),
302
- (":steambored:", "🥱"),
303
- (":steamfacepalm:", "🤦"),
304
- (":steamhappy:", "😄"),
305
- (":steammocking:", "😝"),
306
- (":steamsalty:", "🧂"),
307
- (":steamsad:", "😔"),
308
- (":steamthis:", "⬆"),
309
- ]
310
- )
@@ -1,5 +0,0 @@
1
- from .client import TelegramClient
2
- from .config import get_parser
3
- from .contact import Contact, Roster
4
- from .gateway import Gateway
5
- from .session import Session