wintergram-userbot 1.1.2__tar.gz → 1.2__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.
- wintergram_userbot-1.2/MANIFEST.in +4 -0
- {wintergram_userbot-1.1.2/wintergram_userbot.egg-info → wintergram_userbot-1.2}/PKG-INFO +6 -23
- wintergram_userbot-1.2/pyproject.toml +26 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/__init__.py +1 -1
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/api/functions.py +402 -9
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/client.py +281 -3
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2/wintergram_userbot.egg-info}/PKG-INFO +6 -23
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot.egg-info/SOURCES.txt +0 -1
- wintergram_userbot-1.2/wintergram_userbot.egg-info/requires.txt +4 -0
- wintergram_userbot-1.1.2/LICENSE +0 -21
- wintergram_userbot-1.1.2/MANIFEST.in +0 -17
- wintergram_userbot-1.1.2/pyproject.toml +0 -43
- wintergram_userbot-1.1.2/wintergram_userbot.egg-info/requires.txt +0 -1
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/README.md +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/setup.cfg +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/api/__init__.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/api/updates.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/crypto/__init__.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/crypto/aes.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/crypto/auth_key.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/crypto/dh.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/crypto/rsa.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/crypto/srp.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/errors.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/network/__init__.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/network/connection.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/network/mtproto.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/network/transport.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/session/__init__.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/session/session.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/session/storage.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/__init__.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schema_resolver.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schemas/__init__.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schemas/data/core.json +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schemas/data/latest.json +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schemas/data/v113.json +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schemas/data/v119.json +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schemas/data/v128.json +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schemas/data/v133.json +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/serializer.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/types.py +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot.egg-info/dependency_links.txt +0 -0
- {wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot.egg-info/top_level.txt +0 -0
|
@@ -1,30 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wintergram-userbot
|
|
3
|
-
Version: 1.
|
|
4
|
-
Summary:
|
|
5
|
-
Author
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/femboychik/wintergram-userbot
|
|
8
|
-
Project-URL: Repository, https://github.com/femboychik/wintergram-userbot
|
|
9
|
-
Project-URL: Issues, https://github.com/femboychik/wintergram-userbot/issues
|
|
10
|
-
Keywords: telegram,mtproto,userbot,async,client
|
|
11
|
-
Classifier: Development Status :: 4 - Beta
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
-
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Operating System :: OS Independent
|
|
21
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
-
Classifier: Topic :: Communications :: Chat
|
|
3
|
+
Version: 1.2
|
|
4
|
+
Summary: WinterGram Userbot for custom MTProto server
|
|
5
|
+
Author: WinterGram Team
|
|
23
6
|
Requires-Python: >=3.8
|
|
24
7
|
Description-Content-Type: text/markdown
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
8
|
+
Requires-Dist: pycryptodome>=3.19.0
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
28
11
|
|
|
29
12
|
# wintergram-userbot
|
|
30
13
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=45", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "wintergram-userbot"
|
|
7
|
+
version = "1.2"
|
|
8
|
+
description = "WinterGram Userbot for custom MTProto server"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
authors = [{name = "WinterGram Team"}]
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
dependencies = [
|
|
13
|
+
"pycryptodome>=3.19.0",
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
[project.optional-dependencies]
|
|
17
|
+
dev = [
|
|
18
|
+
"pytest>=7.0.0",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
[tool.setuptools.packages.find]
|
|
22
|
+
where = ["."]
|
|
23
|
+
include = ["wintergram_userbot*"]
|
|
24
|
+
|
|
25
|
+
[tool.setuptools.package-data]
|
|
26
|
+
wintergram_userbot = ["tl/schemas/data/*.json"]
|
|
@@ -187,6 +187,32 @@ class Functions:
|
|
|
187
187
|
# отправка / история
|
|
188
188
|
# ------------------------------------------------------------------ #
|
|
189
189
|
|
|
190
|
+
# ------------------------------------------------------------------ #
|
|
191
|
+
# ответы на сообщения (разные эры схемы)
|
|
192
|
+
# ------------------------------------------------------------------ #
|
|
193
|
+
def reply_params(self, method: str, reply_to):
|
|
194
|
+
"""Параметры ответа на сообщение для метода - с учётом эры слоя.
|
|
195
|
+
|
|
196
|
+
Старые слои ждут int в reply_to_msg_id, новые - объект
|
|
197
|
+
inputReplyToMessage. Возвращает то, что нужно добавить в params.
|
|
198
|
+
"""
|
|
199
|
+
if not reply_to:
|
|
200
|
+
return {}
|
|
201
|
+
if not isinstance(reply_to, int):
|
|
202
|
+
return {"reply_to": reply_to}
|
|
203
|
+
|
|
204
|
+
out = {}
|
|
205
|
+
names = self._param_names(method)
|
|
206
|
+
if "reply_to_msg_id" in names:
|
|
207
|
+
out["reply_to_msg_id"] = reply_to
|
|
208
|
+
if "reply_to" in names:
|
|
209
|
+
reply_desc = self.schema.by_name("inputReplyToMessage")
|
|
210
|
+
if reply_desc is None:
|
|
211
|
+
out["reply_to_msg_id"] = reply_to
|
|
212
|
+
else:
|
|
213
|
+
out["reply_to"] = {"_id": reply_desc["id"], "reply_to_msg_id": reply_to}
|
|
214
|
+
return out
|
|
215
|
+
|
|
190
216
|
async def send_message(self, peer, text: str, random_id=None, reply_to=None, reply_markup=None):
|
|
191
217
|
"""messages.sendMessage(peer, text, random_id, reply_to, reply_markup)."""
|
|
192
218
|
params = {
|
|
@@ -196,15 +222,8 @@ class Functions:
|
|
|
196
222
|
int.from_bytes(os.urandom(8), "little", signed=True)
|
|
197
223
|
),
|
|
198
224
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if "reply_to_msg_id" in self._param_names("messages.sendMessage"):
|
|
202
|
-
if isinstance(reply_to, int):
|
|
203
|
-
params["reply_to_msg_id"] = reply_to
|
|
204
|
-
if "reply_to" in self._param_names("messages.sendMessage"):
|
|
205
|
-
if isinstance(reply_to, int):
|
|
206
|
-
reply_desc = self.schema.by_name("inputReplyToMessage")
|
|
207
|
-
params["reply_to"] = {"_id": reply_desc["id"], "reply_to_msg_id": reply_to}
|
|
225
|
+
# разные эры: reply_to_msg_id (int) или reply_to (InputReplyTo)
|
|
226
|
+
params.update(self.reply_params("messages.sendMessage", reply_to))
|
|
208
227
|
if reply_markup is not None:
|
|
209
228
|
params["reply_markup"] = reply_markup
|
|
210
229
|
return await self.conn.rpc("messages.sendMessage", params)
|
|
@@ -384,6 +403,240 @@ class Functions:
|
|
|
384
403
|
params = {"offline": offline}
|
|
385
404
|
return await self.conn.rpc("account.updateStatus", params)
|
|
386
405
|
|
|
406
|
+
# ------------------------------------------------------------------ #
|
|
407
|
+
# профиль: юзернеймы, аватар, приватность, сессии
|
|
408
|
+
# ------------------------------------------------------------------ #
|
|
409
|
+
|
|
410
|
+
_PRIVACY_KEYS = {
|
|
411
|
+
"status_timestamp": "inputPrivacyKeyStatusTimestamp",
|
|
412
|
+
"last_seen": "inputPrivacyKeyStatusTimestamp",
|
|
413
|
+
"phone_call": "inputPrivacyKeyPhoneCall",
|
|
414
|
+
"phone_p2p": "inputPrivacyKeyPhoneP2P",
|
|
415
|
+
"chat_invite": "inputPrivacyKeyChatInvite",
|
|
416
|
+
"phone_number": "inputPrivacyKeyPhoneNumber",
|
|
417
|
+
"profile_photo": "inputPrivacyKeyProfilePhoto",
|
|
418
|
+
"forwards": "inputPrivacyKeyForwards",
|
|
419
|
+
"about": "inputPrivacyKeyAbout",
|
|
420
|
+
"voice_messages": "inputPrivacyKeyVoiceMessages",
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
_PRIVACY_VALUES = {
|
|
424
|
+
"all": "inputPrivacyValueAllowAll",
|
|
425
|
+
"everyone": "inputPrivacyValueAllowAll",
|
|
426
|
+
"contacts": "inputPrivacyValueAllowContacts",
|
|
427
|
+
"close_friends": "inputPrivacyValueAllowCloseFriends",
|
|
428
|
+
"bots": "inputPrivacyValueAllowBots",
|
|
429
|
+
"premium": "inputPrivacyValueAllowPremium",
|
|
430
|
+
"nobody": "inputPrivacyValueAllowUsers",
|
|
431
|
+
"chats": "inputPrivacyValueAllowChatParticipants",
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
def _construct(self, ctor, **fields):
|
|
435
|
+
"""Собирает конструктор текущей эры по имени; fields — его поля."""
|
|
436
|
+
desc = self.schema.by_name(ctor)
|
|
437
|
+
if desc is None:
|
|
438
|
+
raise RPCError(0, "%s not available on layer %d" % (ctor, self.schema.layer))
|
|
439
|
+
obj = {"_id": desc["id"]}
|
|
440
|
+
obj.update(fields)
|
|
441
|
+
return obj
|
|
442
|
+
|
|
443
|
+
# -------------------------------------------------------------- #
|
|
444
|
+
# юзернеймы
|
|
445
|
+
# -------------------------------------------------------------- #
|
|
446
|
+
|
|
447
|
+
async def check_username(self, username: str):
|
|
448
|
+
"""account.checkUsername - свободен ли юзернейм (True/False)."""
|
|
449
|
+
return await self.conn.rpc("account.checkUsername", {"username": username})
|
|
450
|
+
|
|
451
|
+
async def update_username(self, username: str):
|
|
452
|
+
"""account.updateUsername - ставит основной юзернейм.
|
|
453
|
+
|
|
454
|
+
Пустая строка снимает текущий юзернейм.
|
|
455
|
+
"""
|
|
456
|
+
return await self.conn.rpc("account.updateUsername", {"username": username})
|
|
457
|
+
|
|
458
|
+
async def toggle_username(self, username: str, active=True):
|
|
459
|
+
"""account.toggleUsername - вкл/выкл дополнительный юзернейм."""
|
|
460
|
+
return await self.conn.rpc("account.toggleUsername", {
|
|
461
|
+
"username": username,
|
|
462
|
+
"active": bool(active),
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
async def reorder_usernames(self, order):
|
|
466
|
+
"""account.reorderUsernames - первый в списке становится основным."""
|
|
467
|
+
return await self.conn.rpc("account.reorderUsernames", {"order": list(order)})
|
|
468
|
+
|
|
469
|
+
# -------------------------------------------------------------- #
|
|
470
|
+
# аватар
|
|
471
|
+
# -------------------------------------------------------------- #
|
|
472
|
+
|
|
473
|
+
async def upload_file(self, data: bytes, file_name="file", part_size=524288):
|
|
474
|
+
"""Заливает байты по кускам через upload.saveFilePart.
|
|
475
|
+
|
|
476
|
+
Returns:
|
|
477
|
+
inputFile текущей эры - готов к передаче в photos.* / messages.sendMedia.
|
|
478
|
+
"""
|
|
479
|
+
if not isinstance(data, (bytes, bytearray)):
|
|
480
|
+
raise TypeError("upload_file expects bytes, got %r" % type(data).__name__)
|
|
481
|
+
chunks = [bytes(data[i:i + part_size]) for i in range(0, len(data), part_size)] or [b""]
|
|
482
|
+
file_id = int.from_bytes(os.urandom(8), "little", signed=True)
|
|
483
|
+
for idx, chunk in enumerate(chunks):
|
|
484
|
+
await self.conn.rpc("upload.saveFilePart", {
|
|
485
|
+
"file_id": file_id,
|
|
486
|
+
"file_part": idx,
|
|
487
|
+
"bytes": chunk,
|
|
488
|
+
})
|
|
489
|
+
return self._construct(
|
|
490
|
+
"inputFile", id=file_id, parts=len(chunks), name=file_name, md5_checksum="",
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
async def upload_profile_photo(self, data: bytes, file_name="avatar.jpg", fallback=None):
|
|
494
|
+
"""photos.uploadProfilePhoto - ставит фото основной аватаркой.
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
data: байты изображения
|
|
498
|
+
file_name: имя файла (для сервера)
|
|
499
|
+
fallback: True - запасная аватарка (если сервер/слой поддерживает)
|
|
500
|
+
"""
|
|
501
|
+
input_file = await self.upload_file(data, file_name=file_name)
|
|
502
|
+
params = {"file": input_file}
|
|
503
|
+
if fallback is not None:
|
|
504
|
+
params["fallback"] = bool(fallback)
|
|
505
|
+
return await self.conn.rpc("photos.uploadProfilePhoto", params)
|
|
506
|
+
|
|
507
|
+
async def delete_profile_photo(self, photo_id):
|
|
508
|
+
"""photos.deletePhotos - удаляет аватарки по id (списком или по одному)."""
|
|
509
|
+
ids = photo_id if isinstance(photo_id, (list, tuple)) else [photo_id]
|
|
510
|
+
payload = [self._construct("inputPhoto", id=int(i)) for i in ids]
|
|
511
|
+
return await self.conn.rpc("photos.deletePhotos", {"id": payload})
|
|
512
|
+
|
|
513
|
+
async def get_profile_photos(self, user_id=None, offset=0, max_id=0, limit=20):
|
|
514
|
+
"""photos.getUserPhotos - аватарки пользователя (свои, если user_id не задан)."""
|
|
515
|
+
return await self.conn.rpc("photos.getUserPhotos", {
|
|
516
|
+
"user_id": user_id if user_id is not None else self.input_user_self(),
|
|
517
|
+
"offset": int(offset),
|
|
518
|
+
"max_id": int(max_id),
|
|
519
|
+
"limit": int(limit),
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
# -------------------------------------------------------------- #
|
|
523
|
+
# приватность
|
|
524
|
+
# -------------------------------------------------------------- #
|
|
525
|
+
|
|
526
|
+
def privacy_key(self, key):
|
|
527
|
+
"""Имя ключа ('last_seen', 'phone_number', ...) -> InputPrivacyKey."""
|
|
528
|
+
if isinstance(key, dict):
|
|
529
|
+
return key
|
|
530
|
+
name = self._PRIVACY_KEYS.get(str(key).lower())
|
|
531
|
+
if name is None:
|
|
532
|
+
raise ValueError("unknown privacy key %r; known: %s"
|
|
533
|
+
% (key, ", ".join(sorted(self._PRIVACY_KEYS))))
|
|
534
|
+
return self._construct(name)
|
|
535
|
+
|
|
536
|
+
def privacy_rule(self, rule, chats=None):
|
|
537
|
+
"""Имя правила ('all', 'contacts', 'nobody', ...) -> InputPrivacyRule.
|
|
538
|
+
|
|
539
|
+
Для 'chats' передай chats=[InputChat,...].
|
|
540
|
+
"""
|
|
541
|
+
if isinstance(rule, dict):
|
|
542
|
+
return rule
|
|
543
|
+
name = self._PRIVACY_VALUES.get(str(rule).lower())
|
|
544
|
+
if name is None:
|
|
545
|
+
raise ValueError("unknown privacy rule %r; known: %s"
|
|
546
|
+
% (rule, ", ".join(sorted(self._PRIVACY_VALUES))))
|
|
547
|
+
if name == "inputPrivacyValueAllowChatParticipants":
|
|
548
|
+
return self._construct(name, chats=list(chats or []))
|
|
549
|
+
return self._construct(name)
|
|
550
|
+
|
|
551
|
+
async def get_privacy(self, key):
|
|
552
|
+
"""account.getPrivacy - текущие правила приватности."""
|
|
553
|
+
return await self.conn.rpc("account.getPrivacy", {"key": self.privacy_key(key)})
|
|
554
|
+
|
|
555
|
+
async def set_privacy(self, key, rules):
|
|
556
|
+
"""account.setPrivacy - правила приватности.
|
|
557
|
+
|
|
558
|
+
Args:
|
|
559
|
+
key: ключ ('last_seen', 'profile_photo', ...)
|
|
560
|
+
rules: список правил: ['nobody'], ['contacts'], ['chats'] и т.п.
|
|
561
|
+
Для конкретных чатов - пара ('chats', [InputChat, ...])
|
|
562
|
+
"""
|
|
563
|
+
built = []
|
|
564
|
+
for rule in rules:
|
|
565
|
+
if isinstance(rule, (list, tuple)):
|
|
566
|
+
if not rule:
|
|
567
|
+
raise ValueError("empty privacy rule")
|
|
568
|
+
built.append(self.privacy_rule(rule[0], chats=rule[1] if len(rule) > 1 else None))
|
|
569
|
+
else:
|
|
570
|
+
built.append(self.privacy_rule(rule))
|
|
571
|
+
return await self.conn.rpc("account.setPrivacy", {
|
|
572
|
+
"key": self.privacy_key(key),
|
|
573
|
+
"rules": built,
|
|
574
|
+
})
|
|
575
|
+
|
|
576
|
+
# -------------------------------------------------------------- #
|
|
577
|
+
# оформление профиля
|
|
578
|
+
# -------------------------------------------------------------- #
|
|
579
|
+
|
|
580
|
+
async def update_birthday(self, day, month, year=None):
|
|
581
|
+
"""account.updateBirthday - день рождения (layer >= latest only)."""
|
|
582
|
+
return await self.conn.rpc("account.updateBirthday", {
|
|
583
|
+
"birthday": self._construct("birthday", day=int(day), month=int(month), year=int(year or 0)),
|
|
584
|
+
})
|
|
585
|
+
|
|
586
|
+
async def update_profile_color(self, color=None, for_profile=True):
|
|
587
|
+
"""account.updateColor - цвет профиля/градиент аватарки."""
|
|
588
|
+
return await self.conn.rpc("account.updateColor", {
|
|
589
|
+
"for_profile": bool(for_profile),
|
|
590
|
+
"color": self._construct("peerColor", color=int(color) if color is not None else 0),
|
|
591
|
+
})
|
|
592
|
+
|
|
593
|
+
async def set_emoji_status(self, emoji=None):
|
|
594
|
+
"""account.updateEmojiStatus - эмодзи-статус.
|
|
595
|
+
|
|
596
|
+
emoji=None очищает статус. Установка конкретного эмодзи требует
|
|
597
|
+
конструктор emojiStatusEmoji, которого нет в собранных схемах.
|
|
598
|
+
"""
|
|
599
|
+
if emoji is None:
|
|
600
|
+
return await self.conn.rpc("account.updateEmojiStatus", {
|
|
601
|
+
"emoji_status": self._construct("emojiStatusEmpty"),
|
|
602
|
+
})
|
|
603
|
+
desc = self.schema.by_name("emojiStatusEmoji")
|
|
604
|
+
if desc is None:
|
|
605
|
+
raise RPCError(0, "emojiStatusEmoji not available on layer %d; "
|
|
606
|
+
"only clearing is possible" % self.schema.layer)
|
|
607
|
+
document_id = getattr(emoji, "document_id", None)
|
|
608
|
+
return await self.conn.rpc("account.updateEmojiStatus", {
|
|
609
|
+
"emoji_status": self._construct(
|
|
610
|
+
desc["name"], document_id=int(document_id or emoji), until=None,
|
|
611
|
+
),
|
|
612
|
+
})
|
|
613
|
+
|
|
614
|
+
async def get_recent_emoji_statuses(self, hash=0):
|
|
615
|
+
"""account.getRecentEmojiStatuses - недавние эмодзи-статусы."""
|
|
616
|
+
return await self.conn.rpc("account.getRecentEmojiStatuses", {"hash": int(hash)})
|
|
617
|
+
|
|
618
|
+
# -------------------------------------------------------------- #
|
|
619
|
+
# сессии и прочее
|
|
620
|
+
# -------------------------------------------------------------- #
|
|
621
|
+
|
|
622
|
+
async def get_authorizations(self):
|
|
623
|
+
"""account.getAuthorizations - список активных сессий аккаунта."""
|
|
624
|
+
return await self.conn.rpc("account.getAuthorizations", {})
|
|
625
|
+
|
|
626
|
+
async def reset_authorization(self, auth_hash):
|
|
627
|
+
"""account.resetAuthorization - завершает чужую сессию по hash."""
|
|
628
|
+
return await self.conn.rpc("account.resetAuthorization", {"hash": int(auth_hash)})
|
|
629
|
+
|
|
630
|
+
async def get_account_ttl(self):
|
|
631
|
+
"""account.getAccountTTL - настройка автоудаления аккаунта."""
|
|
632
|
+
return await self.conn.rpc("account.getAccountTTL", {})
|
|
633
|
+
|
|
634
|
+
async def set_account_ttl(self, days=0):
|
|
635
|
+
"""account.setAccountTTL - автоудаление через N дней (0 — выключить)."""
|
|
636
|
+
return await self.conn.rpc("account.setAccountTTL", {
|
|
637
|
+
"ttl": self._construct("accountDaysTTL", days=int(days)),
|
|
638
|
+
})
|
|
639
|
+
|
|
387
640
|
# ------------------------------------------------------------------ #
|
|
388
641
|
# управление чатами
|
|
389
642
|
# ------------------------------------------------------------------ #
|
|
@@ -548,6 +801,146 @@ class Functions:
|
|
|
548
801
|
|
|
549
802
|
return await self.conn.rpc("messages.setBotCallbackAnswer", params)
|
|
550
803
|
|
|
804
|
+
# ------------------------------------------------------------------ #
|
|
805
|
+
# подарки (star gifts)
|
|
806
|
+
# ------------------------------------------------------------------ #
|
|
807
|
+
|
|
808
|
+
async def get_star_gifts(self, hash=0):
|
|
809
|
+
"""payments.getStarGifts - получает список доступных подарков.
|
|
810
|
+
|
|
811
|
+
Args:
|
|
812
|
+
hash: хэш для проверки изменений (0 = получить все)
|
|
813
|
+
|
|
814
|
+
Returns:
|
|
815
|
+
Объект payments.StarGifts с полями: gifts, chats, users
|
|
816
|
+
"""
|
|
817
|
+
params = {"hash": int(hash)}
|
|
818
|
+
return await self.conn.rpc("payments.getStarGifts", params)
|
|
819
|
+
|
|
820
|
+
async def send_star_gift(self, peer, gift_id, message=None, hide_name=False):
|
|
821
|
+
"""Отправляет StarGift пользователю.
|
|
822
|
+
|
|
823
|
+
Args:
|
|
824
|
+
peer: InputPeer получателя подарка
|
|
825
|
+
gift_id: ID подарка (из списка payments.getStarGifts)
|
|
826
|
+
message: текст сообщения к подарку (опционально)
|
|
827
|
+
hide_name: скрыть имя отправителя
|
|
828
|
+
|
|
829
|
+
Returns:
|
|
830
|
+
Результат отправки подарка
|
|
831
|
+
"""
|
|
832
|
+
# Создаем InputInvoice для подарка
|
|
833
|
+
invoice_params = {
|
|
834
|
+
"_": "inputInvoiceStarGift",
|
|
835
|
+
"peer": peer,
|
|
836
|
+
"gift_id": int(gift_id),
|
|
837
|
+
"flags": 0
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
if hide_name:
|
|
841
|
+
invoice_params["flags"] |= 1 # flags.0
|
|
842
|
+
|
|
843
|
+
if message:
|
|
844
|
+
invoice_params["flags"] |= 2 # flags.1
|
|
845
|
+
# Создаем TextWithEntities
|
|
846
|
+
invoice_params["message"] = {
|
|
847
|
+
"_": "textWithEntities",
|
|
848
|
+
"text": message,
|
|
849
|
+
"entities": []
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
# Получаем форму оплаты
|
|
853
|
+
_, payment_form = await self.conn.rpc("payments.getPaymentForm", {
|
|
854
|
+
"invoice": invoice_params
|
|
855
|
+
})
|
|
856
|
+
|
|
857
|
+
form_id = payment_form.get("form_id")
|
|
858
|
+
if form_id is None:
|
|
859
|
+
raise RPCError(0, "payment form has no form_id")
|
|
860
|
+
|
|
861
|
+
# Отправляем подарок
|
|
862
|
+
return await self.conn.rpc("payments.sendStarsForm", {
|
|
863
|
+
"form_id": form_id,
|
|
864
|
+
"invoice": invoice_params
|
|
865
|
+
})
|
|
866
|
+
|
|
867
|
+
async def get_saved_star_gifts(self, peer, offset="", limit=100, exclude_unsaved=False):
|
|
868
|
+
"""payments.getSavedStarGifts - получает сохраненные подарки.
|
|
869
|
+
|
|
870
|
+
Args:
|
|
871
|
+
peer: InputPeer пользователя
|
|
872
|
+
offset: смещение для пагинации
|
|
873
|
+
limit: количество подарков
|
|
874
|
+
exclude_unsaved: исключить несохраненные
|
|
875
|
+
|
|
876
|
+
Returns:
|
|
877
|
+
Объект payments.SavedStarGifts
|
|
878
|
+
"""
|
|
879
|
+
params = {
|
|
880
|
+
"peer": peer,
|
|
881
|
+
"offset": str(offset),
|
|
882
|
+
"limit": int(limit),
|
|
883
|
+
"flags": 0
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
if exclude_unsaved:
|
|
887
|
+
params["flags"] |= 1 # flags.0
|
|
888
|
+
|
|
889
|
+
return await self.conn.rpc("payments.getSavedStarGifts", params)
|
|
890
|
+
|
|
891
|
+
async def convert_star_gift(self, msg_id):
|
|
892
|
+
"""payments.convertStarGift - конвертирует подарок в звезды.
|
|
893
|
+
|
|
894
|
+
Args:
|
|
895
|
+
msg_id: ID сообщения с подарком
|
|
896
|
+
|
|
897
|
+
Returns:
|
|
898
|
+
Результат конвертации
|
|
899
|
+
"""
|
|
900
|
+
# Создаем InputSavedStarGift
|
|
901
|
+
stargift = {
|
|
902
|
+
"_": "inputSavedStarGiftUser",
|
|
903
|
+
"msg_id": int(msg_id)
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
return await self.conn.rpc("payments.convertStarGift", {
|
|
907
|
+
"stargift": stargift
|
|
908
|
+
})
|
|
909
|
+
|
|
910
|
+
async def transfer_star_gift(self, msg_id, to_peer):
|
|
911
|
+
"""Переводит уникальный подарок другому пользователю.
|
|
912
|
+
|
|
913
|
+
Args:
|
|
914
|
+
msg_id: ID сообщения с подарком
|
|
915
|
+
to_peer: InputPeer получателя
|
|
916
|
+
|
|
917
|
+
Returns:
|
|
918
|
+
Результат перевода
|
|
919
|
+
"""
|
|
920
|
+
# Создаем InputSavedStarGift
|
|
921
|
+
stargift = {
|
|
922
|
+
"_": "inputSavedStarGiftUser",
|
|
923
|
+
"msg_id": int(msg_id)
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
return await self.conn.rpc("payments.transferStarGift", {
|
|
927
|
+
"stargift": stargift,
|
|
928
|
+
"to_id": to_peer
|
|
929
|
+
})
|
|
930
|
+
|
|
931
|
+
async def check_can_send_gift(self, gift_id):
|
|
932
|
+
"""payments.checkCanSendGift - проверяет возможность отправки подарка.
|
|
933
|
+
|
|
934
|
+
Args:
|
|
935
|
+
gift_id: ID подарка
|
|
936
|
+
|
|
937
|
+
Returns:
|
|
938
|
+
payments.CheckCanSendGiftResult (Ok или Fail с причиной)
|
|
939
|
+
"""
|
|
940
|
+
return await self.conn.rpc("payments.checkCanSendGift", {
|
|
941
|
+
"gift_id": int(gift_id)
|
|
942
|
+
})
|
|
943
|
+
|
|
551
944
|
# ------------------------------------------------------------------ #
|
|
552
945
|
def _param_names(self, method_name):
|
|
553
946
|
desc = self.schema.by_name(method_name)
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import inspect
|
|
5
5
|
import logging
|
|
6
|
+
import os
|
|
6
7
|
|
|
7
8
|
from .errors import (
|
|
8
9
|
WinterGramError,
|
|
@@ -277,13 +278,14 @@ class WinterGramClient:
|
|
|
277
278
|
raise RPCError(0, "peer %r could not be resolved" % (peer,))
|
|
278
279
|
return await self.functions.send_message(peer_obj, text)
|
|
279
280
|
|
|
280
|
-
async def send_photo(self, peer, photo_path_or_url, caption=""):
|
|
281
|
+
async def send_photo(self, peer, photo_path_or_url, caption="", reply_to=0):
|
|
281
282
|
"""Отправляет фото по URL или локальному пути с подписью.
|
|
282
283
|
|
|
283
284
|
Args:
|
|
284
285
|
peer: ID чата, username или "me"
|
|
285
286
|
photo_path_or_url: URL фото или путь к локальному файлу
|
|
286
287
|
caption: подпись к фото (опционально)
|
|
288
|
+
reply_to: ID сообщения, на которое отвечаем (опционально)
|
|
287
289
|
"""
|
|
288
290
|
import os
|
|
289
291
|
import tempfile
|
|
@@ -341,7 +343,7 @@ class WinterGramClient:
|
|
|
341
343
|
})
|
|
342
344
|
|
|
343
345
|
# Отправляем медиа-сообщение
|
|
344
|
-
|
|
346
|
+
media_params = {
|
|
345
347
|
"peer": peer_obj,
|
|
346
348
|
"media": {
|
|
347
349
|
"_": "inputMediaUploadedPhoto",
|
|
@@ -355,7 +357,19 @@ class WinterGramClient:
|
|
|
355
357
|
},
|
|
356
358
|
"message": caption,
|
|
357
359
|
"random_id": random.randint(1, 2**63 - 1)
|
|
358
|
-
}
|
|
360
|
+
}
|
|
361
|
+
media_params.update(self.functions.reply_params("messages.sendMedia", reply_to))
|
|
362
|
+
|
|
363
|
+
try:
|
|
364
|
+
result = await self.conn.rpc("messages.sendMedia", media_params)
|
|
365
|
+
except Exception as e:
|
|
366
|
+
if not reply_to:
|
|
367
|
+
raise
|
|
368
|
+
# сервер не принял ответ на сообщение — шлём фото без него
|
|
369
|
+
__log__.warning("sendMedia with reply_to failed (%r), retrying without", e)
|
|
370
|
+
media_params.pop("reply_to", None)
|
|
371
|
+
media_params.pop("reply_to_msg_id", None)
|
|
372
|
+
result = await self.conn.rpc("messages.sendMedia", media_params)
|
|
359
373
|
|
|
360
374
|
return result
|
|
361
375
|
|
|
@@ -566,6 +580,170 @@ class WinterGramClient:
|
|
|
566
580
|
"""
|
|
567
581
|
return await self.functions.update_status(offline)
|
|
568
582
|
|
|
583
|
+
# ------------------------------------------------------------------ #
|
|
584
|
+
# профиль: юзернейм, аватар, приватность, сессии
|
|
585
|
+
# ------------------------------------------------------------------ #
|
|
586
|
+
|
|
587
|
+
async def check_username(self, username):
|
|
588
|
+
"""Проверяет, свободен ли юзернейм.
|
|
589
|
+
|
|
590
|
+
Args:
|
|
591
|
+
username: имя без @
|
|
592
|
+
|
|
593
|
+
Returns:
|
|
594
|
+
True, если можно занять
|
|
595
|
+
"""
|
|
596
|
+
return await self.functions.check_username(username)
|
|
597
|
+
|
|
598
|
+
async def set_username(self, username):
|
|
599
|
+
"""Меняет основной юзернейм аккаунта.
|
|
600
|
+
|
|
601
|
+
Args:
|
|
602
|
+
username: новый юзернейм без @
|
|
603
|
+
|
|
604
|
+
Raises:
|
|
605
|
+
RPCError: если юзернейм занят или сервер его отклонил
|
|
606
|
+
"""
|
|
607
|
+
return await self.functions.update_username((username or "").lstrip("@"))
|
|
608
|
+
|
|
609
|
+
async def remove_username(self):
|
|
610
|
+
"""Снимает основной юзернейм (то же, что set_username(""))."""
|
|
611
|
+
return await self.functions.update_username("")
|
|
612
|
+
|
|
613
|
+
async def toggle_username(self, username, active=True):
|
|
614
|
+
"""Включает/выключает дополнительный юзернейм."""
|
|
615
|
+
return await self.functions.toggle_username((username or "").lstrip("@"), active)
|
|
616
|
+
|
|
617
|
+
async def reorder_usernames(self, order):
|
|
618
|
+
"""Задаёт порядок юзернеймов (первый становится основным)."""
|
|
619
|
+
return await self.functions.reorder_usernames([u.lstrip("@") for u in order])
|
|
620
|
+
|
|
621
|
+
async def set_avatar(self, photo, fallback=None):
|
|
622
|
+
"""Загружает новую аватарку.
|
|
623
|
+
|
|
624
|
+
Args:
|
|
625
|
+
photo: путь к файлу, http(s)-ссылка или сами байты
|
|
626
|
+
fallback: True - поставить как запасную аватарку
|
|
627
|
+
|
|
628
|
+
Returns:
|
|
629
|
+
photos.Photo от сервера
|
|
630
|
+
"""
|
|
631
|
+
data = await self._load_media(photo)
|
|
632
|
+
if isinstance(photo, str) and not photo.startswith(("http://", "https://")):
|
|
633
|
+
name = os.path.basename(photo) or "avatar.jpg"
|
|
634
|
+
else:
|
|
635
|
+
name = "avatar.jpg"
|
|
636
|
+
return await self.functions.upload_profile_photo(data, file_name=name, fallback=fallback)
|
|
637
|
+
|
|
638
|
+
async def remove_avatar(self, photo_id=None):
|
|
639
|
+
"""Удаляет аватарки.
|
|
640
|
+
|
|
641
|
+
Args:
|
|
642
|
+
photo_id: id фото; если не задан - удаляется самая свежая своя
|
|
643
|
+
"""
|
|
644
|
+
if photo_id is None:
|
|
645
|
+
_, res = await self.functions.get_profile_photos(limit=1)
|
|
646
|
+
photos = res.get("photos") or []
|
|
647
|
+
if not photos:
|
|
648
|
+
raise WinterGramError("no profile photos to remove")
|
|
649
|
+
photo_id = photos[0].get("id")
|
|
650
|
+
return await self.functions.delete_profile_photo(photo_id)
|
|
651
|
+
|
|
652
|
+
async def get_avatars(self, peer=None, limit=20):
|
|
653
|
+
"""Возвращает список аватарок (своих, если peer не задан)."""
|
|
654
|
+
user = await self._resolve_input_user(peer) if peer is not None else None
|
|
655
|
+
_, result = await self.functions.get_profile_photos(user_id=user, limit=limit)
|
|
656
|
+
return result.get("photos", [])
|
|
657
|
+
|
|
658
|
+
async def get_profile(self, peer=None):
|
|
659
|
+
"""Полная информация о профиле (users.getFullUser)."""
|
|
660
|
+
if peer is None:
|
|
661
|
+
return await self.functions.get_full_user(self.functions.input_user_self())
|
|
662
|
+
return await self.functions.get_full_user(await self._resolve_input_user(peer))
|
|
663
|
+
|
|
664
|
+
async def set_privacy(self, key, rules):
|
|
665
|
+
"""Меняет правила приватности.
|
|
666
|
+
|
|
667
|
+
Args:
|
|
668
|
+
key: last_seen | phone_number | profile_photo | chat_invite | forwards | about
|
|
669
|
+
rules: ['all'] | ['contacts'] | ['close_friends'] | ['nobody'] |
|
|
670
|
+
['bots'] | ['premium'] | [('chats', chats)]
|
|
671
|
+
|
|
672
|
+
Examples:
|
|
673
|
+
await client.set_privacy("last_seen", ["nobody"])
|
|
674
|
+
await client.set_privacy("profile_photo", ["contacts"])
|
|
675
|
+
"""
|
|
676
|
+
return await self.functions.set_privacy(key, rules)
|
|
677
|
+
|
|
678
|
+
async def get_privacy(self, key):
|
|
679
|
+
"""Текущие правила приватности по ключу."""
|
|
680
|
+
return await self.functions.get_privacy(key)
|
|
681
|
+
|
|
682
|
+
async def set_birthday(self, day, month, year=None):
|
|
683
|
+
"""Задаёт день рождения в профиле."""
|
|
684
|
+
return await self.functions.update_birthday(day, month, year)
|
|
685
|
+
|
|
686
|
+
async def set_profile_color(self, color=None, for_profile=True):
|
|
687
|
+
"""Задаёт цвет профиля (None - сбросить)."""
|
|
688
|
+
return await self.functions.update_profile_color(color, for_profile)
|
|
689
|
+
|
|
690
|
+
async def set_emoji_status(self, emoji=None):
|
|
691
|
+
"""Ставит эмодзи-статус (None - очистить)."""
|
|
692
|
+
return await self.functions.set_emoji_status(emoji)
|
|
693
|
+
|
|
694
|
+
async def get_sessions(self):
|
|
695
|
+
"""Список активных сессий аккаунта (account.getAuthorizations)."""
|
|
696
|
+
_, result = await self.functions.get_authorizations()
|
|
697
|
+
return result.get("authorizations", [])
|
|
698
|
+
|
|
699
|
+
async def terminate_session(self, auth_hash):
|
|
700
|
+
"""Завершает чужую сессию по её hash."""
|
|
701
|
+
return await self.functions.reset_authorization(auth_hash)
|
|
702
|
+
|
|
703
|
+
async def get_account_ttl(self):
|
|
704
|
+
"""Текущая настройка автоудаления аккаунта."""
|
|
705
|
+
return await self.functions.get_account_ttl()
|
|
706
|
+
|
|
707
|
+
async def set_account_ttl(self, days=0):
|
|
708
|
+
"""Автоудаление аккаунта через N дней (0 — выключить)."""
|
|
709
|
+
return await self.functions.set_account_ttl(days)
|
|
710
|
+
|
|
711
|
+
async def _load_media(self, source):
|
|
712
|
+
"""Приводит путь / http-ссылку / bytes к байтам файла."""
|
|
713
|
+
if isinstance(source, (bytes, bytearray)):
|
|
714
|
+
return bytes(source)
|
|
715
|
+
import urllib.request
|
|
716
|
+
|
|
717
|
+
if isinstance(source, str) and source.startswith(("http://", "https://")):
|
|
718
|
+
def _download():
|
|
719
|
+
with urllib.request.urlopen(source, timeout=30) as resp:
|
|
720
|
+
return resp.read()
|
|
721
|
+
return await asyncio.to_thread(_download)
|
|
722
|
+
|
|
723
|
+
if isinstance(source, str) and os.path.isfile(source):
|
|
724
|
+
def _read():
|
|
725
|
+
with open(source, "rb") as f:
|
|
726
|
+
return f.read()
|
|
727
|
+
return await asyncio.to_thread(_read)
|
|
728
|
+
|
|
729
|
+
raise WinterGramError(
|
|
730
|
+
"expected bytes, file path or http(s) URL, got %r" % type(source).__name__
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
async def _resolve_input_user(self, peer):
|
|
734
|
+
"""InputPeer (или 'me'/None -> self) -> InputUser для users.*/photos.*."""
|
|
735
|
+
peer_obj = await self._resolve_peer_helper(peer)
|
|
736
|
+
if peer_obj is None or "user_id" not in peer_obj:
|
|
737
|
+
raise WinterGramError("peer %r is not a user" % (peer,))
|
|
738
|
+
_, res = await self.functions.get_users([
|
|
739
|
+
self.functions.input_user(peer_obj["user_id"], peer_obj.get("access_hash"))
|
|
740
|
+
])
|
|
741
|
+
users = res if isinstance(res, list) else (res.get("users") if isinstance(res, dict) else None)
|
|
742
|
+
if not users:
|
|
743
|
+
raise WinterGramError("cannot resolve user for peer %r" % (peer,))
|
|
744
|
+
user = users[0]
|
|
745
|
+
return self.functions.input_user(user.get("id"), user.get("access_hash"))
|
|
746
|
+
|
|
569
747
|
async def join_chat(self, peer):
|
|
570
748
|
"""Присоединяется к каналу/супергруппе.
|
|
571
749
|
|
|
@@ -628,6 +806,106 @@ class WinterGramClient:
|
|
|
628
806
|
_, obj = await self.conn.rpc(method, params)
|
|
629
807
|
return obj
|
|
630
808
|
|
|
809
|
+
# ------------------------------------------------------------------ #
|
|
810
|
+
# подарки (star gifts и NFT)
|
|
811
|
+
# ------------------------------------------------------------------ #
|
|
812
|
+
|
|
813
|
+
async def get_available_gifts(self):
|
|
814
|
+
"""Получает список всех доступных подарков для отправки.
|
|
815
|
+
|
|
816
|
+
Returns:
|
|
817
|
+
Список подарков с информацией о цене, иконке и т.д.
|
|
818
|
+
"""
|
|
819
|
+
_, result = await self.functions.get_star_gifts()
|
|
820
|
+
return result.get("gifts", [])
|
|
821
|
+
|
|
822
|
+
async def send_gift(self, peer, gift_id, message=None, hide_name=False):
|
|
823
|
+
"""Отправляет подарок (мишка, NFT и т.д.) пользователю.
|
|
824
|
+
|
|
825
|
+
Args:
|
|
826
|
+
peer: ID пользователя, username или "me"
|
|
827
|
+
gift_id: ID подарка из списка get_available_gifts()
|
|
828
|
+
message: текст сообщения к подарку (опционально)
|
|
829
|
+
hide_name: True - скрыть имя отправителя, False - показать
|
|
830
|
+
|
|
831
|
+
Returns:
|
|
832
|
+
Результат отправки подарка
|
|
833
|
+
|
|
834
|
+
Example:
|
|
835
|
+
# Получаем список подарков
|
|
836
|
+
gifts = await client.get_available_gifts()
|
|
837
|
+
# Отправляем первый подарок с сообщением
|
|
838
|
+
await client.send_gift(user_id, gifts[0]["id"], "С Днём Рождения!")
|
|
839
|
+
"""
|
|
840
|
+
peer_obj = await self._resolve_peer_helper(peer)
|
|
841
|
+
|
|
842
|
+
# Проверяем, можно ли отправить этот подарок
|
|
843
|
+
try:
|
|
844
|
+
_, check_result = await self.functions.check_can_send_gift(gift_id)
|
|
845
|
+
result_type = check_result.get("_", "")
|
|
846
|
+
if "Fail" in result_type:
|
|
847
|
+
reason = check_result.get("reason", {})
|
|
848
|
+
reason_text = reason.get("text", "Unknown reason") if isinstance(reason, dict) else str(reason)
|
|
849
|
+
raise RPCError(0, f"Cannot send gift: {reason_text}")
|
|
850
|
+
except RPCError as e:
|
|
851
|
+
if "METHOD_NOT_FOUND" not in str(e):
|
|
852
|
+
raise
|
|
853
|
+
# Метод checkCanSendGift не поддерживается, продолжаем
|
|
854
|
+
|
|
855
|
+
# Отправляем подарок
|
|
856
|
+
return await self.functions.send_star_gift(peer_obj, gift_id, message, hide_name)
|
|
857
|
+
|
|
858
|
+
async def get_my_gifts(self, exclude_unsaved=False, limit=100):
|
|
859
|
+
"""Получает подарки, полученные текущим пользователем.
|
|
860
|
+
|
|
861
|
+
Args:
|
|
862
|
+
exclude_unsaved: True - только сохраненные подарки
|
|
863
|
+
limit: максимальное количество подарков
|
|
864
|
+
|
|
865
|
+
Returns:
|
|
866
|
+
Список полученных подарков
|
|
867
|
+
"""
|
|
868
|
+
peer_obj = self.functions.input_peer_self()
|
|
869
|
+
_, result = await self.functions.get_saved_star_gifts(
|
|
870
|
+
peer_obj,
|
|
871
|
+
offset="",
|
|
872
|
+
limit=limit,
|
|
873
|
+
exclude_unsaved=exclude_unsaved
|
|
874
|
+
)
|
|
875
|
+
return result.get("gifts", [])
|
|
876
|
+
|
|
877
|
+
async def convert_gift_to_stars(self, msg_id):
|
|
878
|
+
"""Конвертирует полученный подарок в звезды (Stars).
|
|
879
|
+
|
|
880
|
+
Args:
|
|
881
|
+
msg_id: ID сообщения с подарком
|
|
882
|
+
|
|
883
|
+
Returns:
|
|
884
|
+
Результат конвертации
|
|
885
|
+
|
|
886
|
+
Note:
|
|
887
|
+
Конвертированный подарок исчезнет из коллекции!
|
|
888
|
+
"""
|
|
889
|
+
return await self.functions.convert_star_gift(msg_id)
|
|
890
|
+
|
|
891
|
+
async def transfer_gift(self, msg_id, to_peer):
|
|
892
|
+
"""Переводит уникальный подарок (NFT) другому пользователю.
|
|
893
|
+
|
|
894
|
+
Args:
|
|
895
|
+
msg_id: ID сообщения с подарком
|
|
896
|
+
to_peer: ID получателя, username или "me"
|
|
897
|
+
|
|
898
|
+
Returns:
|
|
899
|
+
Результат перевода
|
|
900
|
+
|
|
901
|
+
Note:
|
|
902
|
+
Работает только для уникальных подарков (NFT), которые можно передавать
|
|
903
|
+
"""
|
|
904
|
+
to_peer_obj = await self._resolve_peer_helper(to_peer)
|
|
905
|
+
return await self.functions.transfer_star_gift(msg_id, to_peer_obj)
|
|
906
|
+
|
|
907
|
+
# ------------------------------------------------------------------ #
|
|
908
|
+
|
|
631
909
|
async def __aenter__(self):
|
|
632
910
|
return await self.connect()
|
|
633
911
|
|
|
@@ -1,30 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: wintergram-userbot
|
|
3
|
-
Version: 1.
|
|
4
|
-
Summary:
|
|
5
|
-
Author
|
|
6
|
-
License: MIT
|
|
7
|
-
Project-URL: Homepage, https://github.com/femboychik/wintergram-userbot
|
|
8
|
-
Project-URL: Repository, https://github.com/femboychik/wintergram-userbot
|
|
9
|
-
Project-URL: Issues, https://github.com/femboychik/wintergram-userbot/issues
|
|
10
|
-
Keywords: telegram,mtproto,userbot,async,client
|
|
11
|
-
Classifier: Development Status :: 4 - Beta
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
-
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Operating System :: OS Independent
|
|
21
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
-
Classifier: Topic :: Communications :: Chat
|
|
3
|
+
Version: 1.2
|
|
4
|
+
Summary: WinterGram Userbot for custom MTProto server
|
|
5
|
+
Author: WinterGram Team
|
|
23
6
|
Requires-Python: >=3.8
|
|
24
7
|
Description-Content-Type: text/markdown
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
8
|
+
Requires-Dist: pycryptodome>=3.19.0
|
|
9
|
+
Provides-Extra: dev
|
|
10
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
28
11
|
|
|
29
12
|
# wintergram-userbot
|
|
30
13
|
|
wintergram_userbot-1.1.2/LICENSE
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 femboychik
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
include README.md
|
|
2
|
-
include LICENSE
|
|
3
|
-
include pyproject.toml
|
|
4
|
-
recursive-include wintergram_userbot *.py
|
|
5
|
-
recursive-include wintergram_userbot/tl/schemas/data *.json
|
|
6
|
-
exclude userbot_demo.py
|
|
7
|
-
exclude check_session.py
|
|
8
|
-
exclude wintergram_session.db
|
|
9
|
-
exclude промпт.txt
|
|
10
|
-
recursive-exclude tests *
|
|
11
|
-
recursive-exclude tools *
|
|
12
|
-
recursive-exclude __pycache__ *
|
|
13
|
-
recursive-exclude .pytest_cache *
|
|
14
|
-
recursive-exclude .claude *
|
|
15
|
-
global-exclude *.pyc
|
|
16
|
-
global-exclude *.pyo
|
|
17
|
-
global-exclude *~
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
-
build-backend = "setuptools.build_meta"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "wintergram-userbot"
|
|
7
|
-
version = "1.1.2"
|
|
8
|
-
description = "Полнофункциональный асинхронный MTProto-клиент для приватных Telegram-серверов (layers 167-228) с поддержкой отправки медиа"
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.8"
|
|
11
|
-
license = {text = "MIT"}
|
|
12
|
-
authors = [
|
|
13
|
-
{name = "femboychik", email = "your.email@example.com"}
|
|
14
|
-
]
|
|
15
|
-
dependencies = [
|
|
16
|
-
"aiohttp>=3.8.0"
|
|
17
|
-
]
|
|
18
|
-
keywords = ["telegram", "mtproto", "userbot", "async", "client"]
|
|
19
|
-
classifiers = [
|
|
20
|
-
"Development Status :: 4 - Beta",
|
|
21
|
-
"Intended Audience :: Developers",
|
|
22
|
-
"License :: OSI Approved :: MIT License",
|
|
23
|
-
"Programming Language :: Python :: 3",
|
|
24
|
-
"Programming Language :: Python :: 3.8",
|
|
25
|
-
"Programming Language :: Python :: 3.9",
|
|
26
|
-
"Programming Language :: Python :: 3.10",
|
|
27
|
-
"Programming Language :: Python :: 3.11",
|
|
28
|
-
"Programming Language :: Python :: 3.12",
|
|
29
|
-
"Operating System :: OS Independent",
|
|
30
|
-
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
31
|
-
"Topic :: Communications :: Chat",
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
[project.urls]
|
|
35
|
-
Homepage = "https://github.com/femboychik/wintergram-userbot"
|
|
36
|
-
Repository = "https://github.com/femboychik/wintergram-userbot"
|
|
37
|
-
Issues = "https://github.com/femboychik/wintergram-userbot/issues"
|
|
38
|
-
|
|
39
|
-
[tool.setuptools]
|
|
40
|
-
packages = ["wintergram_userbot"]
|
|
41
|
-
|
|
42
|
-
[tool.setuptools.package-data]
|
|
43
|
-
wintergram_userbot = ["tl/schemas/data/*.json"]
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
aiohttp>=3.8.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/network/connection.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schema_resolver.py
RENAMED
|
File without changes
|
{wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schemas/__init__.py
RENAMED
|
File without changes
|
{wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schemas/data/core.json
RENAMED
|
File without changes
|
{wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schemas/data/latest.json
RENAMED
|
File without changes
|
{wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schemas/data/v113.json
RENAMED
|
File without changes
|
{wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schemas/data/v119.json
RENAMED
|
File without changes
|
{wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schemas/data/v128.json
RENAMED
|
File without changes
|
{wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot/tl/schemas/data/v133.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{wintergram_userbot-1.1.2 → wintergram_userbot-1.2}/wintergram_userbot.egg-info/top_level.txt
RENAMED
|
File without changes
|