sleamdge 0.0.0.dev0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
sleamdge/session.py ADDED
@@ -0,0 +1,162 @@
1
+ import asyncio
2
+ import tempfile
3
+ from io import BytesIO
4
+ from typing import Optional, Union
5
+
6
+ import steam
7
+ from PIL import Image
8
+ from slidge import BaseSession, FormField, SearchResult, global_config
9
+ from slixmpp.exceptions import XMPPError
10
+
11
+ from .client import SteamClient
12
+ from .contact import Contact, Roster
13
+ from .group import MUC, Bookmarks
14
+ from .util import demojize, emojize
15
+
16
+ Recipient = Union[Contact, MUC]
17
+
18
+
19
+ class Session(BaseSession[int, Recipient]):
20
+ contacts: Roster
21
+ bookmarks: Bookmarks
22
+
23
+ def __init__(self, *a, **k):
24
+ super().__init__(*a, **k)
25
+ self.steam = SteamClient(self)
26
+ self.login_task: Optional[asyncio.Task] = None
27
+ self.emoticons = dict[str, steam.Emoticon]()
28
+
29
+ @staticmethod
30
+ def xmpp_to_legacy_msg_id(i: str) -> int:
31
+ return int(i)
32
+
33
+ async def __login_task(self):
34
+ refresh_token = self.user.legacy_module_data.get("refresh_token")
35
+ if refresh_token is None:
36
+ refresh_token = (
37
+ global_config.HOME_DIR / str(self.user_jid.bare)
38
+ ).read_text()
39
+ self.legacy_module_data_set({"refresh_token": refresh_token})
40
+ assert isinstance(refresh_token, str)
41
+
42
+ try:
43
+ async with self.steam:
44
+ await self.steam.login(refresh_token=refresh_token)
45
+ except Exception as e:
46
+ self.log.debug("logged out", exc_info=e)
47
+ self.send_gateway_status("Logged out", show="dnd")
48
+ self.send_gateway_message(
49
+ f"You have been logged out ('{e}').\n"
50
+ f"This may be because you have logged in to steam from another client.\n"
51
+ f"Use the 're-login' command when you want to log back in from here."
52
+ )
53
+ self.steam = SteamClient(self)
54
+ self.logged = False
55
+
56
+ async def login(self) -> None:
57
+ self.login_task = asyncio.create_task(self.__login_task())
58
+ await self.steam.wait_until_ready()
59
+ self.contacts.user_legacy_id = self.steam.user.id
60
+ self.emoticons = {e.name: e for e in self.steam.emoticons}
61
+
62
+ async def logout(self) -> None:
63
+ await self.steam.close()
64
+
65
+ async def on_composing(self, c: Recipient, thread=None) -> None:
66
+ if c.is_group:
67
+ return
68
+ assert isinstance(c, Contact)
69
+ user = await c.get_steam_channel()
70
+ await user.trigger_typing()
71
+
72
+ async def on_text(self, chat: Recipient, text: str, **_kwargs) -> int:
73
+ recipient = await chat.get_steam_channel()
74
+ mid = await self.steam.send(recipient, text)
75
+ return mid
76
+
77
+ async def on_file(self, chat: Recipient, url: str, **_kwargs) -> int:
78
+ recipient = await chat.get_steam_channel()
79
+ mid = await self.steam.send(recipient, url)
80
+ return mid
81
+
82
+ async def on_react(
83
+ self, chat: Recipient, legacy_msg_id: int, emojis: list[str], thread=None
84
+ ) -> None:
85
+ msg = await chat.fetch_message(legacy_msg_id)
86
+
87
+ reactions_xmpp = set(emojis)
88
+ reactions_steam = set()
89
+ for r in msg.reactions:
90
+ if emoticon := r.emoticon:
91
+ if r.user == self.steam.user:
92
+ reactions_steam.add(emojize(emoticon))
93
+
94
+ to_remove = reactions_steam - reactions_xmpp
95
+ to_add = reactions_xmpp - reactions_steam
96
+
97
+ self.log.debug("msg: %s", msg)
98
+
99
+ for emoji in to_add:
100
+ emoticon_name = demojize(emoji)
101
+ if emoticon_name is None:
102
+ raise XMPPError("bad-request", f"Forbidden emoji: {emoji}")
103
+ emoticon = self.emoticons[emoticon_name]
104
+ self.log.debug("add emoticon: %r", emoticon)
105
+ await self.steam.add_emoticon(msg, emoticon)
106
+
107
+ for emoji in to_remove:
108
+ emoticon_name = demojize(emoji)
109
+ emoticon = self.emoticons[emoticon_name]
110
+ self.log.debug("rm emoticon: %r", emoticon)
111
+ await self.steam.remove_emoticon(msg, emoticon)
112
+
113
+ async def on_correct(self, *args, **kwargs) -> None:
114
+ raise XMPPError("feature-not-implemented", "No correction in steam")
115
+
116
+ async def on_retract(
117
+ self, chat: Recipient, legacy_msg_id: int, thread=None
118
+ ) -> None:
119
+ raise XMPPError("feature-not-implemented", "No retraction in steam")
120
+
121
+ async def on_displayed(self, c: Recipient, legacy_msg_id: int, thread=None) -> None:
122
+ msg = await c.fetch_message(legacy_msg_id)
123
+ await msg.ack()
124
+
125
+ async def on_search(self, form_values: dict[str, str]):
126
+ user = await self.steam.fetch_user_named(form_values["user_name"])
127
+ if not user:
128
+ raise XMPPError("item-not-found")
129
+ return SearchResult(
130
+ fields=[FormField("username"), FormField("jid", type="jid-single")],
131
+ items=[
132
+ {
133
+ "username": form_values["user_name"],
134
+ "jid": (await self.contacts.by_steam_user(user)).jid.bare,
135
+ }
136
+ ],
137
+ )
138
+
139
+ async def on_avatar(
140
+ self, bytes_: Optional[bytes], hash_, type_, width, height
141
+ ) -> None:
142
+ if bytes_ is None:
143
+ # Steam forces you to have an avatar, so we cannot remove it AFAIK
144
+ return
145
+ if len(bytes_) > 10e6:
146
+ # steam does not want avatars larger than 1024 KB
147
+ bytes_ = await self.xmpp.loop.run_in_executor(None, resize_image, bytes_)
148
+ # workaround for bug in steam.py preventing use of BytesIO
149
+ # https://github.com/Gobot1234/steam.py/issues/566
150
+ with tempfile.NamedTemporaryFile() as tmp_file:
151
+ tmp_file.write(bytes_)
152
+ await self.steam.user.edit(avatar=steam.Media(tmp_file.name))
153
+
154
+
155
+ def resize_image(bytes_: bytes) -> bytes:
156
+ with BytesIO() as f:
157
+ img = Image.open(BytesIO(bytes_))
158
+ img.thumbnail((500, 500)) # should be < 1024 KB hopefully
159
+ img.save(f, format="JPEG")
160
+ f.flush()
161
+ f.seek(0)
162
+ return f.read()
sleamdge/types.py ADDED
@@ -0,0 +1,34 @@
1
+ from enum import IntEnum
2
+
3
+
4
+ class ParentType(IntEnum):
5
+ GROUP = 1
6
+ CLAN = 2
7
+
8
+
9
+ class Parent:
10
+ __slots__ = "type", "id"
11
+
12
+ def __init__(self, type_: ParentType, parent_id: int):
13
+ self.type = type_
14
+ self.id = parent_id
15
+
16
+
17
+ class ChannelId:
18
+ __slots__ = "parent", "channel_id"
19
+
20
+ def __init__(self, parent: Parent, channel_id: int):
21
+ self.parent = parent
22
+ self.channel_id = channel_id
23
+
24
+ def __repr__(self):
25
+ return f"{self.parent.type.value}-{self.parent.id}-{self.channel_id}"
26
+
27
+ def __hash__(self):
28
+ return hash(str(self))
29
+
30
+ @classmethod
31
+ def from_str(cls, string):
32
+ parent_type_int, parent_id, channel_id = (int(x) for x in string.split("-"))
33
+ parent = Parent(ParentType(parent_type_int), parent_id)
34
+ return cls(parent, channel_id)
sleamdge/util.py ADDED
@@ -0,0 +1,25 @@
1
+ import steam
2
+
3
+ EMOJIS = dict[str, str](
4
+ [
5
+ ("steamthumbsup", "👍"),
6
+ ("steamthumbsdown", "👎"),
7
+ ("steambored", "🥱"),
8
+ ("steamfacepalm", "🤦"),
9
+ ("steamhappy", "😄"),
10
+ ("steammocking", "😝"),
11
+ ("steamsalty", "🧂"),
12
+ ("steamsad", "😔"),
13
+ ("steamthis", "⬆"),
14
+ ]
15
+ )
16
+ EMOJIS_INVERSE = {v: k for k, v in EMOJIS.items()}
17
+ EMOJIS_SET = set(EMOJIS.values())
18
+
19
+
20
+ def emojize(emoticon: steam.Emoticon) -> str:
21
+ return EMOJIS.get(emoticon.name, "❔")
22
+
23
+
24
+ def demojize(emoji: str) -> str:
25
+ return EMOJIS_INVERSE[emoji]