wintergram-userbot 1.2__tar.gz → 1.4__tar.gz

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.
Files changed (40) hide show
  1. {wintergram_userbot-1.2/wintergram_userbot.egg-info → wintergram_userbot-1.4}/PKG-INFO +3 -2
  2. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/README.md +2 -1
  3. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/pyproject.toml +1 -1
  4. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/__init__.py +1 -1
  5. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/api/functions.py +10 -0
  6. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/api/updates.py +31 -1
  7. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/client.py +47 -1
  8. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/network/mtproto.py +45 -3
  9. {wintergram_userbot-1.2 → wintergram_userbot-1.4/wintergram_userbot.egg-info}/PKG-INFO +3 -2
  10. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/MANIFEST.in +0 -0
  11. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/setup.cfg +0 -0
  12. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/api/__init__.py +0 -0
  13. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/crypto/__init__.py +0 -0
  14. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/crypto/aes.py +0 -0
  15. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/crypto/auth_key.py +0 -0
  16. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/crypto/dh.py +0 -0
  17. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/crypto/rsa.py +0 -0
  18. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/crypto/srp.py +0 -0
  19. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/errors.py +0 -0
  20. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/network/__init__.py +0 -0
  21. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/network/connection.py +0 -0
  22. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/network/transport.py +0 -0
  23. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/session/__init__.py +0 -0
  24. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/session/session.py +0 -0
  25. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/session/storage.py +0 -0
  26. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/tl/__init__.py +0 -0
  27. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/tl/schema_resolver.py +0 -0
  28. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/tl/schemas/__init__.py +0 -0
  29. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/tl/schemas/data/core.json +0 -0
  30. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/tl/schemas/data/latest.json +0 -0
  31. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/tl/schemas/data/v113.json +0 -0
  32. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/tl/schemas/data/v119.json +0 -0
  33. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/tl/schemas/data/v128.json +0 -0
  34. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/tl/schemas/data/v133.json +0 -0
  35. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/tl/serializer.py +0 -0
  36. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot/tl/types.py +0 -0
  37. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot.egg-info/SOURCES.txt +0 -0
  38. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot.egg-info/dependency_links.txt +0 -0
  39. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot.egg-info/requires.txt +0 -0
  40. {wintergram_userbot-1.2 → wintergram_userbot-1.4}/wintergram_userbot.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wintergram-userbot
3
- Version: 1.2
3
+ Version: 1.4
4
4
  Summary: WinterGram Userbot for custom MTProto server
5
5
  Author: WinterGram Team
6
6
  Requires-Python: >=3.8
@@ -122,7 +122,7 @@ history = await client.get_history(chat_id, limit=50)
122
122
  ### Свойства события
123
123
 
124
124
  - `event.id` — ID сообщения
125
- - `event.text` — текст сообщения (bytes или str)
125
+ - `event.text` — текст сообщения (всегда `str`)
126
126
  - `event.date` — время отправки
127
127
  - `event.chat_id` — ID чата
128
128
  - `event.sender_id` — ID отправителя
@@ -130,6 +130,7 @@ history = await client.get_history(chat_id, limit=50)
130
130
  - `event.is_private` — True если личное сообщение
131
131
  - `event.is_channel` — True если сообщение из канала
132
132
  - `event.from_channel` — True если отправлено каналом
133
+ - `event.message` — внутренний dict сообщения (текст тоже `str`)
133
134
 
134
135
  ## Требования
135
136
 
@@ -111,7 +111,7 @@ history = await client.get_history(chat_id, limit=50)
111
111
  ### Свойства события
112
112
 
113
113
  - `event.id` — ID сообщения
114
- - `event.text` — текст сообщения (bytes или str)
114
+ - `event.text` — текст сообщения (всегда `str`)
115
115
  - `event.date` — время отправки
116
116
  - `event.chat_id` — ID чата
117
117
  - `event.sender_id` — ID отправителя
@@ -119,6 +119,7 @@ history = await client.get_history(chat_id, limit=50)
119
119
  - `event.is_private` — True если личное сообщение
120
120
  - `event.is_channel` — True если сообщение из канала
121
121
  - `event.from_channel` — True если отправлено каналом
122
+ - `event.message` — внутренний dict сообщения (текст тоже `str`)
122
123
 
123
124
  ## Требования
124
125
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "wintergram-userbot"
7
- version = "1.2"
7
+ version = "1.4"
8
8
  description = "WinterGram Userbot for custom MTProto server"
9
9
  readme = "README.md"
10
10
  authors = [{name = "WinterGram Team"}]
@@ -4,7 +4,7 @@
4
4
  Поддерживаются слои (layers) от 167 до 228 включительно.
5
5
  """
6
6
 
7
- __version__ = "1.2"
7
+ __version__ = "1.4"
8
8
 
9
9
  from .client import WinterGramClient
10
10
  from .errors import (
@@ -183,6 +183,16 @@ class Functions:
183
183
  """users.getUsers([InputUser...])."""
184
184
  return await self.conn.rpc("users.getUsers", {"id": ids})
185
185
 
186
+ async def resolve_username(self, username: str):
187
+ """contacts.resolveUsername - ищет пользователя по @юзернейму.
188
+
189
+ Returns:
190
+ contacts.ResolvedPeer с полями peer, users, chats
191
+ """
192
+ return await self.conn.rpc("contacts.resolveUsername", {
193
+ "username": (username or "").strip().lstrip("@"),
194
+ })
195
+
186
196
  # ------------------------------------------------------------------ #
187
197
  # отправка / история
188
198
  # ------------------------------------------------------------------ #
@@ -27,6 +27,15 @@ def _as_dict(obj):
27
27
  return dict(obj)
28
28
 
29
29
 
30
+ def _as_text(value):
31
+ """TL-строки приходят как bytes - приводим к str."""
32
+ if value is None:
33
+ return ""
34
+ if isinstance(value, (bytes, bytearray)):
35
+ return bytes(value).decode("utf-8", "replace")
36
+ return str(value)
37
+
38
+
30
39
  class Event:
31
40
  """Базовое событие."""
32
41
 
@@ -37,6 +46,27 @@ class Event:
37
46
  def raw_dict(self):
38
47
  return _as_dict(self.raw)
39
48
 
49
+ @property
50
+ def message(self):
51
+ """Внутренний dict сообщения (updateNewMessage.message и т.п.).
52
+
53
+ Для updateShort* апдейтов, где сообщение лежит прямо в update,
54
+ возвращается сам update. Текст нормализован в str.
55
+ """
56
+ d = _as_dict(self.raw)
57
+ inner = d.get("message")
58
+ # у updateNew* поле message - вложенный dict, у updateShort* - строка
59
+ if isinstance(inner, (dict, TLObject)):
60
+ m = _as_dict(inner)
61
+ if isinstance(m.get("message"), (bytes, bytearray)):
62
+ m = dict(m)
63
+ m["message"] = _as_text(m["message"])
64
+ return m
65
+ if isinstance(inner, (bytes, bytearray)):
66
+ d = dict(d)
67
+ d["message"] = _as_text(inner)
68
+ return d
69
+
40
70
 
41
71
  class NewMessage(Event):
42
72
  """Новое сообщение.
@@ -48,7 +78,7 @@ class NewMessage(Event):
48
78
  sender_id=None, out=False, from_channel=False):
49
79
  super().__init__(raw)
50
80
  self.id = id
51
- self.text = text
81
+ self.text = _as_text(text)
52
82
  self.date = date
53
83
  self.chat_id = chat_id
54
84
  self.sender_id = sender_id
@@ -778,13 +778,59 @@ class WinterGramClient:
778
778
  return self.functions.input_peer_self()
779
779
  return await self.resolve_peer(peer)
780
780
 
781
+ async def resolve_username(self, username):
782
+ """Ищет пользователя по юзернейму (с @ или без) через contacts.resolveUsername.
783
+
784
+ Args:
785
+ username: "@nick" или "nick"
786
+
787
+ Returns:
788
+ InputPeer текущей эры
789
+
790
+ Raises:
791
+ WinterGramError: если юзернейм не найден
792
+ """
793
+ name = (username or "").strip().lstrip("@")
794
+ if not name:
795
+ raise WinterGramError("empty username")
796
+
797
+ _, result = await self.functions.resolve_username(name)
798
+
799
+ peer = result.get("peer") or {}
800
+ users = result.get("users") or []
801
+ chats = result.get("chats") or []
802
+
803
+ if "user_id" in peer:
804
+ target_id = peer.get("user_id")
805
+ user = next((u for u in users if u.get("id") == target_id), None)
806
+ if user is None and users:
807
+ user = users[0]
808
+ if user is None or user.get("access_hash") is None:
809
+ raise WinterGramError("no access_hash for @%s" % name)
810
+ self.storage.store_peer(user["id"], user["access_hash"], "user")
811
+ return self.functions.input_peer_user(user["id"], user["access_hash"])
812
+
813
+ if "chat_id" in peer:
814
+ return self.functions.input_peer_chat(peer["chat_id"])
815
+
816
+ if "channel_id" in peer:
817
+ target_id = peer.get("channel_id")
818
+ chat = next((c for c in chats if c.get("id") == target_id), None)
819
+ access_hash = (chat or {}).get("access_hash", 0)
820
+ self.storage.store_peer(target_id, access_hash, "channel")
821
+ return self.functions.input_peer_channel(target_id, access_hash)
822
+
823
+ raise WinterGramError("username @%s not found" % name)
824
+
781
825
  async def resolve_peer(self, peer):
782
826
  """Пиринг в InputPeer текущей эры (по возможности через кэш)."""
783
827
  if isinstance(peer, str):
784
828
  low = peer.lower()
785
829
  if low in ("me", "self"):
786
830
  return self.functions.input_peer_self()
787
- raise RPCError(0, "username resolution not supported; pass id or 'me'")
831
+ if low.startswith("@") or peer[:1].isalpha() or "_" in peer:
832
+ return await self.resolve_username(peer)
833
+ raise RPCError(0, "pass id, 'me' or @username")
788
834
  peer_id = int(peer)
789
835
  cached = self.storage.get_peer(abs(peer_id)) if self.storage._db else None
790
836
  access_hash = cached[0] if cached else 0
@@ -67,6 +67,21 @@ ID_PONG = 0x347773C5
67
67
  ID_GZIP_PACKED = 0x3072CFA1
68
68
  ID_PING = 0x7ABE77EC
69
69
 
70
+ # little-endian сигнатура gzip_packed в начале тела ответа
71
+ GZIP_MAGIC_LE = struct.pack("<I", ID_GZIP_PACKED)
72
+
73
+
74
+ def inflate(data: bytes) -> bytes:
75
+ """Распаковывает gzip/zlib/deflate - сервер встречается со всеми тремя."""
76
+ if not data:
77
+ raise ValueError("empty packed payload")
78
+ for wbits in (16 + zlib.MAX_WBITS, zlib.MAX_WBITS, -zlib.MAX_WBITS):
79
+ try:
80
+ return zlib.decompress(data, wbits)
81
+ except zlib.error:
82
+ continue
83
+ raise ValueError("cannot inflate %d bytes" % len(data))
84
+
70
85
  # якоря эр (по убыванию) для определения layer
71
86
  ERA_ORDER = (228, 202, 198, 181, 167)
72
87
 
@@ -309,7 +324,33 @@ class MTProtoConnection:
309
324
  desc = self._schema.by_id(ident)
310
325
  if desc is None:
311
326
  raise TLParseError("unknown constructor 0x%08x" % ident)
312
- return decode_constructor(r, desc, self._schema)
327
+ return self.unpack_object(decode_constructor(r, desc, self._schema))
328
+
329
+ @staticmethod
330
+ def _is_gzip(obj) -> bool:
331
+ if not isinstance(obj, dict):
332
+ return False
333
+ if obj.get("_id") == ID_GZIP_PACKED:
334
+ return True
335
+ return str(obj.get("_", "")).lower() == "gzip_packed"
336
+
337
+ def unpack_object(self, obj, depth=0):
338
+ """Распаковывает gzip_packed и декодирует то, что внутри.
339
+
340
+ Сервер заворачивает крупные ответы (messages.getDialogs и др.)
341
+ в gzip_packed: он приходит полем result у rpc_result, поэтому
342
+ проверка по сигнатуре тела ответа его не ловит.
343
+ """
344
+ while depth < 4 and self._is_gzip(obj):
345
+ packed = obj.get("packed_data") or obj.get("bytes") or b""
346
+ raw = inflate(packed)
347
+ rr = TLReader(raw)
348
+ desc = self._schema.by_id(rr.read_uint())
349
+ if desc is None:
350
+ raise TLParseError("unknown constructor inside gzip_packed")
351
+ obj = decode_constructor(rr, desc, self._schema)
352
+ depth += 1
353
+ return obj
313
354
 
314
355
  def _handle_rpc_result(self, req_msg_id: int, r: TLReader):
315
356
  raw = bytes(r.data[r.pos:])
@@ -323,10 +364,10 @@ class MTProtoConnection:
323
364
  self._pending.pop(req_msg_id, None)
324
365
  return
325
366
  body = raw
326
- if body[:4] == b"\x30\xcf\xa1\x07": # gzip_packed little-endian
367
+ if body[:4] == GZIP_MAGIC_LE:
327
368
  pack_r = TLReader(body)
328
369
  pack_r.read_uint()
329
- body = zlib.decompress(pack_r.read_bytes())
370
+ body = inflate(pack_r.read_bytes())
330
371
  rr = TLReader(body)
331
372
  ident = rr.read_uint()
332
373
  if ident == ID_RPC_ERROR:
@@ -346,6 +387,7 @@ class MTProtoConnection:
346
387
  return
347
388
  try:
348
389
  obj = decode_constructor(rr, desc, self._schema)
390
+ obj = self.unpack_object(obj)
349
391
  except TLParseError as e:
350
392
  fut.set_exception(
351
393
  RPCError(0, "parse failed for 0x%08x: %s (raw=%s)" % (ident, e, body.hex()))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wintergram-userbot
3
- Version: 1.2
3
+ Version: 1.4
4
4
  Summary: WinterGram Userbot for custom MTProto server
5
5
  Author: WinterGram Team
6
6
  Requires-Python: >=3.8
@@ -122,7 +122,7 @@ history = await client.get_history(chat_id, limit=50)
122
122
  ### Свойства события
123
123
 
124
124
  - `event.id` — ID сообщения
125
- - `event.text` — текст сообщения (bytes или str)
125
+ - `event.text` — текст сообщения (всегда `str`)
126
126
  - `event.date` — время отправки
127
127
  - `event.chat_id` — ID чата
128
128
  - `event.sender_id` — ID отправителя
@@ -130,6 +130,7 @@ history = await client.get_history(chat_id, limit=50)
130
130
  - `event.is_private` — True если личное сообщение
131
131
  - `event.is_channel` — True если сообщение из канала
132
132
  - `event.from_channel` — True если отправлено каналом
133
+ - `event.message` — внутренний dict сообщения (текст тоже `str`)
133
134
 
134
135
  ## Требования
135
136