aioshad 1.0.0__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.
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ ## 1.0.0
4
+
5
+ - Async account/client core based on the provided Shad protocol implementation.
6
+ - HTTP/2 transport with host failover, retry/backoff and local rate limiting.
7
+ - Persistent sessions with optional AES-GCM encrypted storage and file permissions.
8
+ - Message dispatcher with duplicate-update suppression and sync/async handler support.
9
+ - Message, chat and user models with reply/edit/delete helpers.
10
+ - File/photo upload helpers and voice-chat methods from the base protocol.
11
+ - Profile manager for first name, last name and bio.
12
+ - Presence time-name scheduler using account profile updates.
13
+ - Auto-responder and managed background scheduler.
14
+ - Extended filters and generic authenticated RPC access through `Client.invoke()`.
15
+ - Examples and unit tests.
aioshad-1.0.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) aioshad contributors
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.
@@ -0,0 +1,7 @@
1
+ include README.md
2
+ include LICENSE
3
+ include CHANGELOG.md
4
+ include requirements.txt
5
+ recursive-include aioshad *.py py.typed
6
+ recursive-include examples *.py
7
+ recursive-include tests *.py
aioshad-1.0.0/PKG-INFO ADDED
@@ -0,0 +1,184 @@
1
+ Metadata-Version: 2.4
2
+ Name: aioshad
3
+ Version: 1.0.0
4
+ Summary: Advanced asynchronous Python client/userbot library for a Shad account
5
+ Author: aioshad contributors
6
+ License: MIT
7
+ Keywords: shad,messenger,userbot,asyncio,python,account-client
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Framework :: AsyncIO
13
+ Classifier: Typing :: Typed
14
+ Requires-Python: >=3.11
15
+ Description-Content-Type: text/markdown
16
+ License-File: LICENSE
17
+ Requires-Dist: httpx>=0.27.0
18
+ Requires-Dist: h2>=4.0.0
19
+ Requires-Dist: cryptography>=42.0.0
20
+ Requires-Dist: Pillow>=10.0.0
21
+ Dynamic: license-file
22
+ Dynamic: requires-python
23
+
24
+ # aioshad 1.0.0
25
+
26
+ کتابخانهٔ asynchronous برای ساخت **custom client / self account** روی شاد. این پروژه یک Bot API جداگانه نیست؛ session به حسابی تعلق دارد که با آن login شده است.
27
+
28
+ > نکتهٔ compatibility: بخش transport و payloadهای شاد بر پایهٔ ساختار موجود در پروژهٔ `shadism` ارائه‌شده و APIهای مشاهده‌شدهٔ وب شاد ساخته شده‌اند. چون endpointها و payloadهای رسمی شاد عمومی و ثابت نیستند، قابلیت‌هایی که payload دقیق‌شان مستند نیستند به‌صورت `client.invoke()` در دسترس‌اند و حدس زده نشده‌اند.
29
+
30
+ ## نصب
31
+
32
+ ```bash
33
+ pip install -e .
34
+ ```
35
+
36
+ ## ورود اکانت
37
+
38
+ ```python
39
+ import asyncio
40
+ from aioshad import Client
41
+
42
+ app = Client("0937xxxxxxxx", session_directory="./sessions")
43
+
44
+ async def main():
45
+ await app.start()
46
+
47
+ asyncio.run(main())
48
+ ```
49
+
50
+ در اجرای اول کد OTP از شما درخواست می‌شود و session ذخیره می‌شود.
51
+
52
+ برای رمزنگاری session:
53
+
54
+ ```bash
55
+ export AIOSHAD_SESSION_KEY="یک-راز-32-بایتی-یا-عبارت-قوی"
56
+ ```
57
+
58
+ ## پیام و handler
59
+
60
+ ```python
61
+ from aioshad import Client, filters
62
+
63
+ app = Client("0937xxxxxxxx", session_directory="./sessions")
64
+
65
+ @app.on_message(filters.command("ping"))
66
+ async def ping(message):
67
+ await message.reply("pong")
68
+
69
+ @app.on_message(filters.private & filters.text)
70
+ async def private_text(message):
71
+ print(message.text)
72
+
73
+ asyncio.run(app.start())
74
+ ```
75
+
76
+ handlerهای sync و async هر دو پشتیبانی می‌شوند.
77
+
78
+ ## امکانات حساب
79
+
80
+ ### تایم کنار نام
81
+
82
+ این قابلیت واقعاً `updateProfile` را روی **همان حساب session** اجرا می‌کند:
83
+
84
+ ```python
85
+ from aioshad import Client, TimeName
86
+
87
+ app = Client("0937xxxxxxxx", session_directory="./sessions")
88
+
89
+ async def main():
90
+ await app.connect()
91
+ await app.presence.start_time_name(
92
+ TimeName(
93
+ format="⏰ {time}",
94
+ timezone="Asia/Tehran",
95
+ interval=60,
96
+ )
97
+ )
98
+ await app.start_in_background()
99
+ await app.run_until_disconnected()
100
+
101
+ asyncio.run(main())
102
+ ```
103
+
104
+ Placeholders: `{time}`, `{time_12}`, `{date}`, `{day}`, `{timestamp}`.
105
+
106
+ ### پروفایل
107
+
108
+ ```python
109
+ await app.profile.set_name("AioShad")
110
+ await app.profile.set_last_name("Client")
111
+ await app.profile.set_bio("Powered by aioshad")
112
+ ```
113
+
114
+ ### پاسخ خودکار حساب
115
+
116
+ ```python
117
+ app.autoreply.add("سلام", "سلام، در خدمتم.")
118
+ app.autoreply.set_default("پیامت دریافت شد.")
119
+ await app.autoreply.enable()
120
+ ```
121
+
122
+ ### زمان‌بندی داخلی
123
+
124
+ ```python
125
+ async def task():
126
+ await app.send_message("g0...", "پیام زمان‌بندی‌شده")
127
+
128
+ app.scheduler.every(60, task)
129
+ ```
130
+
131
+ حداقل فاصلهٔ scheduler پنج ثانیه است.
132
+
133
+ ## APIهای اصلی
134
+
135
+ - `send_message`, `edit_message`, `delete_message(s)`
136
+ - `reply`, `reply_photo`, `reply_file`
137
+ - `upload_file`, `send_photo`, `send_file`
138
+ - `get_me`, `get_user_info`
139
+ - `get_chats`, `get_chat_info`, `get_chat_info_by_username`
140
+ - `get_messages`, `get_chat_history`
141
+ - `get_chats_updates`, `get_messages_updates`
142
+ - login/session/device management
143
+ - voice chat methods موجود در پروتکل پایه
144
+ - `block_user` / `unblock_user`
145
+ - فیلترهای command/regex/private/group/channel/reply/edited/author/chat/media/text
146
+ - `&`, `|`, `~` برای ترکیب فیلترها
147
+ - host failover
148
+ - HTTP/2
149
+ - retry/backoff
150
+ - local rate limiting
151
+ - duplicate-update suppression
152
+ - encrypted session storage
153
+ - graceful shutdown
154
+
155
+ ## RPC خام
156
+
157
+ برای متدهای پروتکل که هنوز payload قطعی آن‌ها مشخص نیست:
158
+
159
+ ```python
160
+ result = await app.invoke(
161
+ "METHOD_NAME",
162
+ some_parameter="value",
163
+ )
164
+ ```
165
+
166
+ این قسمت عمداً generic است تا بدون جعل payload جدید، آدرس هر API واقعی را بتوانید به کتابخانه اضافه کنید.
167
+
168
+ ## معماری
169
+
170
+ ```text
171
+ Client
172
+ ├── Methods / RPC
173
+ ├── Transport (HTTP/2 + failover + rate limit)
174
+ ├── Session (optional AES-GCM storage)
175
+ ├── Dispatcher / Filters
176
+ ├── ProfileManager
177
+ ├── PresenceManager / TimeName
178
+ ├── AutoResponder
179
+ └── Scheduler
180
+ ```
181
+
182
+ ## وضعیت تست
183
+
184
+ قبل از release این پروژه تست‌های unit برای session encryption، filters، rate limiting و models دارد. تست زندهٔ شبکه به‌صورت پیش‌فرض اجرا نمی‌شود، چون نیازمند یک حساب شاد و endpointهای فعال است.
@@ -0,0 +1,161 @@
1
+ # aioshad 1.0.0
2
+
3
+ کتابخانهٔ asynchronous برای ساخت **custom client / self account** روی شاد. این پروژه یک Bot API جداگانه نیست؛ session به حسابی تعلق دارد که با آن login شده است.
4
+
5
+ > نکتهٔ compatibility: بخش transport و payloadهای شاد بر پایهٔ ساختار موجود در پروژهٔ `shadism` ارائه‌شده و APIهای مشاهده‌شدهٔ وب شاد ساخته شده‌اند. چون endpointها و payloadهای رسمی شاد عمومی و ثابت نیستند، قابلیت‌هایی که payload دقیق‌شان مستند نیستند به‌صورت `client.invoke()` در دسترس‌اند و حدس زده نشده‌اند.
6
+
7
+ ## نصب
8
+
9
+ ```bash
10
+ pip install -e .
11
+ ```
12
+
13
+ ## ورود اکانت
14
+
15
+ ```python
16
+ import asyncio
17
+ from aioshad import Client
18
+
19
+ app = Client("0937xxxxxxxx", session_directory="./sessions")
20
+
21
+ async def main():
22
+ await app.start()
23
+
24
+ asyncio.run(main())
25
+ ```
26
+
27
+ در اجرای اول کد OTP از شما درخواست می‌شود و session ذخیره می‌شود.
28
+
29
+ برای رمزنگاری session:
30
+
31
+ ```bash
32
+ export AIOSHAD_SESSION_KEY="یک-راز-32-بایتی-یا-عبارت-قوی"
33
+ ```
34
+
35
+ ## پیام و handler
36
+
37
+ ```python
38
+ from aioshad import Client, filters
39
+
40
+ app = Client("0937xxxxxxxx", session_directory="./sessions")
41
+
42
+ @app.on_message(filters.command("ping"))
43
+ async def ping(message):
44
+ await message.reply("pong")
45
+
46
+ @app.on_message(filters.private & filters.text)
47
+ async def private_text(message):
48
+ print(message.text)
49
+
50
+ asyncio.run(app.start())
51
+ ```
52
+
53
+ handlerهای sync و async هر دو پشتیبانی می‌شوند.
54
+
55
+ ## امکانات حساب
56
+
57
+ ### تایم کنار نام
58
+
59
+ این قابلیت واقعاً `updateProfile` را روی **همان حساب session** اجرا می‌کند:
60
+
61
+ ```python
62
+ from aioshad import Client, TimeName
63
+
64
+ app = Client("0937xxxxxxxx", session_directory="./sessions")
65
+
66
+ async def main():
67
+ await app.connect()
68
+ await app.presence.start_time_name(
69
+ TimeName(
70
+ format="⏰ {time}",
71
+ timezone="Asia/Tehran",
72
+ interval=60,
73
+ )
74
+ )
75
+ await app.start_in_background()
76
+ await app.run_until_disconnected()
77
+
78
+ asyncio.run(main())
79
+ ```
80
+
81
+ Placeholders: `{time}`, `{time_12}`, `{date}`, `{day}`, `{timestamp}`.
82
+
83
+ ### پروفایل
84
+
85
+ ```python
86
+ await app.profile.set_name("AioShad")
87
+ await app.profile.set_last_name("Client")
88
+ await app.profile.set_bio("Powered by aioshad")
89
+ ```
90
+
91
+ ### پاسخ خودکار حساب
92
+
93
+ ```python
94
+ app.autoreply.add("سلام", "سلام، در خدمتم.")
95
+ app.autoreply.set_default("پیامت دریافت شد.")
96
+ await app.autoreply.enable()
97
+ ```
98
+
99
+ ### زمان‌بندی داخلی
100
+
101
+ ```python
102
+ async def task():
103
+ await app.send_message("g0...", "پیام زمان‌بندی‌شده")
104
+
105
+ app.scheduler.every(60, task)
106
+ ```
107
+
108
+ حداقل فاصلهٔ scheduler پنج ثانیه است.
109
+
110
+ ## APIهای اصلی
111
+
112
+ - `send_message`, `edit_message`, `delete_message(s)`
113
+ - `reply`, `reply_photo`, `reply_file`
114
+ - `upload_file`, `send_photo`, `send_file`
115
+ - `get_me`, `get_user_info`
116
+ - `get_chats`, `get_chat_info`, `get_chat_info_by_username`
117
+ - `get_messages`, `get_chat_history`
118
+ - `get_chats_updates`, `get_messages_updates`
119
+ - login/session/device management
120
+ - voice chat methods موجود در پروتکل پایه
121
+ - `block_user` / `unblock_user`
122
+ - فیلترهای command/regex/private/group/channel/reply/edited/author/chat/media/text
123
+ - `&`, `|`, `~` برای ترکیب فیلترها
124
+ - host failover
125
+ - HTTP/2
126
+ - retry/backoff
127
+ - local rate limiting
128
+ - duplicate-update suppression
129
+ - encrypted session storage
130
+ - graceful shutdown
131
+
132
+ ## RPC خام
133
+
134
+ برای متدهای پروتکل که هنوز payload قطعی آن‌ها مشخص نیست:
135
+
136
+ ```python
137
+ result = await app.invoke(
138
+ "METHOD_NAME",
139
+ some_parameter="value",
140
+ )
141
+ ```
142
+
143
+ این قسمت عمداً generic است تا بدون جعل payload جدید، آدرس هر API واقعی را بتوانید به کتابخانه اضافه کنید.
144
+
145
+ ## معماری
146
+
147
+ ```text
148
+ Client
149
+ ├── Methods / RPC
150
+ ├── Transport (HTTP/2 + failover + rate limit)
151
+ ├── Session (optional AES-GCM storage)
152
+ ├── Dispatcher / Filters
153
+ ├── ProfileManager
154
+ ├── PresenceManager / TimeName
155
+ ├── AutoResponder
156
+ └── Scheduler
157
+ ```
158
+
159
+ ## وضعیت تست
160
+
161
+ قبل از release این پروژه تست‌های unit برای session encryption، filters، rate limiting و models دارد. تست زندهٔ شبکه به‌صورت پیش‌فرض اجرا نمی‌شود، چون نیازمند یک حساب شاد و endpointهای فعال است.
@@ -0,0 +1,25 @@
1
+ from aioshad.client import Client
2
+ from aioshad.config import ClientConfig
3
+ from aioshad.errors import (
4
+ AioShadError,
5
+ AuthenticationError,
6
+ InvalidSessionError,
7
+ RPCError,
8
+ RateLimitError,
9
+ UnsupportedMethodError,
10
+ )
11
+ from aioshad.features import AutoResponder, PresenceManager, ProfileManager, Scheduler, TimeName
12
+ from aioshad.types.chat import Chat
13
+ from aioshad.types.message import Message
14
+ from aioshad.types.user import User
15
+ from aioshad import filters
16
+
17
+ __all__ = [
18
+ "Client", "ClientConfig", "Message", "User", "Chat", "filters",
19
+ "TimeName", "ProfileManager", "PresenceManager", "AutoResponder", "Scheduler",
20
+ "AioShadError", "AuthenticationError", "InvalidSessionError", "RPCError",
21
+ "RateLimitError", "UnsupportedMethodError",
22
+ ]
23
+
24
+ __version__ = "1.0.0"
25
+ __author__ = "aioshad contributors"
@@ -0,0 +1,270 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+ import inspect
5
+ import logging
6
+ import signal
7
+ from pathlib import Path
8
+ from typing import Any, Callable, Dict, List, Optional, Tuple, Union
9
+
10
+ from aioshad.config import ClientConfig
11
+ from aioshad.dispatcher import Dispatcher, MessageHandler
12
+ from aioshad.features import AutoResponder, PresenceManager, ProfileManager, Scheduler
13
+ from aioshad.filters import Filter, ensure_filter
14
+ from aioshad.methods import Methods
15
+ from aioshad.network import Transport
16
+ from aioshad.session import Session, SessionStorage
17
+ from aioshad.types.chat import Chat
18
+ from aioshad.types.message import Message
19
+ from aioshad.types.user import User
20
+
21
+ logger = logging.getLogger("aioshad.client")
22
+
23
+
24
+ class Client:
25
+ """Async client for a Shad account (custom client/userbot).
26
+
27
+ The client operates with the authenticated account session; it is not a
28
+ separate bot identity. High-level account features live in ``profile``,
29
+ ``presence`` and ``autoreply``.
30
+ """
31
+
32
+ def __init__(
33
+ self,
34
+ phone_number: str,
35
+ session_directory: str = ".",
36
+ messenger_host: Optional[str] = None,
37
+ *,
38
+ config: Optional[ClientConfig] = None,
39
+ session_encryption_key: Optional[str] = None,
40
+ ) -> None:
41
+ self.phone_number = phone_number
42
+ self.config = config or ClientConfig(messenger_host=messenger_host)
43
+ self._storage = SessionStorage(
44
+ phone_number,
45
+ directory=session_directory,
46
+ encryption_key=session_encryption_key,
47
+ )
48
+ self.session: Session = self._storage.load(phone_number)
49
+ if messenger_host:
50
+ self.session.messenger_host = messenger_host
51
+ elif self.config.messenger_host:
52
+ self.session.messenger_host = self.config.messenger_host
53
+
54
+ self._pending_handlers: List[Tuple[MessageHandler, Optional[Filter]]] = []
55
+ self._transport: Optional[Transport] = None
56
+ self._methods: Optional[Methods] = None
57
+ self._dispatcher: Optional[Dispatcher] = None
58
+ self._started = False
59
+
60
+ self.profile = ProfileManager(self)
61
+ self.presence = PresenceManager(self)
62
+ self.autoreply = AutoResponder(self)
63
+ self.scheduler = Scheduler()
64
+
65
+ @property
66
+ def transport(self) -> Transport:
67
+ if self._transport is None:
68
+ raise RuntimeError("Client is not connected. Call await client.connect() first.")
69
+ return self._transport
70
+
71
+ @property
72
+ def methods(self) -> Methods:
73
+ if self._methods is None:
74
+ raise RuntimeError("Client is not connected. Call await client.connect() first.")
75
+ return self._methods
76
+
77
+ @property
78
+ def is_connected(self) -> bool:
79
+ return self._transport is not None and not self._transport.is_closed
80
+
81
+ def _bootstrap(self) -> None:
82
+ self._transport = Transport(self.session, self.config)
83
+ self._methods = Methods(self.session, self._transport, self._storage, client=self)
84
+ self._dispatcher = Dispatcher(
85
+ self,
86
+ poll_interval=self.config.poll_interval,
87
+ max_errors=self.config.max_consecutive_errors,
88
+ )
89
+ for handler, filter_obj in self._pending_handlers:
90
+ self._dispatcher.register_handler(handler, filter_obj=filter_obj)
91
+ self._pending_handlers.clear()
92
+
93
+ async def connect(self) -> "Client":
94
+ if self._transport is None:
95
+ self._bootstrap()
96
+ methods = self.methods
97
+ if not self.session.has_auth():
98
+ logger.info("No active session found; starting account login flow.")
99
+ await methods.login_flow(self.phone_number)
100
+ else:
101
+ try:
102
+ await methods.register_device()
103
+ except Exception as exc:
104
+ if "INVALID_AUTH" in str(exc):
105
+ logger.warning("Stored session is invalid; starting login flow.")
106
+ self._clear_auth()
107
+ await methods.login_flow(self.phone_number)
108
+ else:
109
+ logger.debug("Device registration check: %s", exc)
110
+ return self
111
+
112
+ def _clear_auth(self) -> None:
113
+ self.session.auth = ""
114
+ self.session.decode_auth = ""
115
+ self.session.private_key_pem = ""
116
+ self.session.key_hex = ""
117
+ self.session.iv_hex = ""
118
+ self.session.tmp_session = ""
119
+ self._storage.save(self.session)
120
+
121
+ async def start(self) -> None:
122
+ await self.connect()
123
+ if self._dispatcher is not None:
124
+ self._dispatcher.start()
125
+ self._started = True
126
+ await self._idle()
127
+
128
+ async def start_in_background(self) -> None:
129
+ await self.connect()
130
+ if self._dispatcher is not None:
131
+ self._dispatcher.start()
132
+ self._started = True
133
+
134
+ async def run_until_disconnected(self) -> None:
135
+ await self.start()
136
+
137
+ async def _idle(self) -> None:
138
+ while self._dispatcher is not None and self._dispatcher._running:
139
+ await asyncio.sleep(1.0)
140
+
141
+ async def stop(self) -> None:
142
+ await self.presence.close()
143
+ await self.scheduler.cancel_all()
144
+ if self._dispatcher:
145
+ await self._dispatcher.stop()
146
+ if self._transport:
147
+ await self._transport.close()
148
+ self._storage.save(self.session)
149
+ self._started = False
150
+
151
+ def on_message(
152
+ self,
153
+ filters_or_func: Optional[Union[Filter, Callable[..., Any], MessageHandler]] = None,
154
+ ) -> Any:
155
+ if inspect.iscoroutinefunction(filters_or_func) or (
156
+ callable(filters_or_func) and not isinstance(filters_or_func, Filter)
157
+ and not isinstance(filters_or_func, (str, bytes))
158
+ and len(inspect.signature(filters_or_func).parameters) == 1
159
+ ):
160
+ func = filters_or_func
161
+ self._register_message_handler(func, filter_obj=None)
162
+ return func
163
+
164
+ filter_obj: Optional[Filter] = None
165
+ if filters_or_func is not None:
166
+ filter_obj = ensure_filter(filters_or_func)
167
+
168
+ def decorator(func: MessageHandler) -> MessageHandler:
169
+ self._register_message_handler(func, filter_obj=filter_obj)
170
+ return func
171
+
172
+ return decorator
173
+
174
+ def _register_message_handler(self, func: MessageHandler, filter_obj: Optional[Filter] = None) -> None:
175
+ if self._dispatcher is not None:
176
+ self._dispatcher.register_handler(func, filter_obj=filter_obj)
177
+ else:
178
+ self._pending_handlers.append((func, filter_obj))
179
+
180
+ def remove_handler(self, handler: MessageHandler) -> int:
181
+ if self._dispatcher is None:
182
+ before = len(self._pending_handlers)
183
+ self._pending_handlers = [item for item in self._pending_handlers if item[0] is not handler]
184
+ return before - len(self._pending_handlers)
185
+ return self._dispatcher.unregister_handler(handler)
186
+
187
+ async def send_message(self, object_guid: str, text: str = "", reply_to_message_id: Optional[str] = None, file_inline: Optional[Dict[str, Any]] = None) -> Message:
188
+ return await self.methods.send_message(object_guid, text, reply_to_message_id, file_inline)
189
+
190
+ async def edit_message(self, object_guid: str, message_id: str, text: str) -> Message:
191
+ return await self.methods.edit_message(object_guid, message_id, text)
192
+
193
+ async def delete_messages(self, object_guid: str, message_ids: Union[str, int, List[Union[str, int]]], delete_type: str = "Global") -> Dict[str, Any]:
194
+ return await self.methods.delete_messages(object_guid, message_ids, delete_type)
195
+
196
+ async def delete_message(self, object_guid: str, message_id: Union[str, int], delete_type: str = "Global") -> Dict[str, Any]:
197
+ return await self.methods.delete_message(object_guid, message_id, delete_type)
198
+
199
+ async def upload_file(self, file: Union[str, bytes, Path], file_name: Optional[str] = None, mime: Optional[str] = None, chunk_size: Optional[int] = None) -> Dict[str, Any]:
200
+ return await self.methods.upload_file(file, file_name, mime, chunk_size or self.config.upload_chunk_size)
201
+
202
+ async def send_photo(self, object_guid: str, photo: Union[str, bytes, Path], caption: Optional[str] = None, reply_to_message_id: Optional[str] = None, file_name: Optional[str] = None) -> Message:
203
+ return await self.methods.send_photo(object_guid, photo, caption, reply_to_message_id, file_name)
204
+
205
+ async def send_file(self, object_guid: str, file: Union[str, bytes, Path], file_name: Optional[str] = None, mime: Optional[str] = None, caption: Optional[str] = None, reply_to_message_id: Optional[str] = None) -> Message:
206
+ return await self.methods.send_file(object_guid, file, file_name, mime, caption, reply_to_message_id)
207
+
208
+ async def get_user_info(self, user_guid: Optional[str] = None) -> User:
209
+ return await self.methods.get_user_info(user_guid)
210
+
211
+ async def get_me(self) -> User:
212
+ return await self.get_user_info()
213
+
214
+ async def update_profile(self, first_name: Optional[str] = None, last_name: Optional[str] = None, bio: Optional[str] = None) -> bool:
215
+ return await self.methods.update_profile(first_name, last_name, bio)
216
+
217
+ async def get_chats(self, start_id: Optional[str] = None) -> Dict[str, Any]:
218
+ return await self.methods.get_chats(start_id)
219
+
220
+ async def get_messages(self, object_guid: str, limit: int = 50, sort: str = "FromMax", max_id: Optional[str] = None, min_id: Optional[str] = None) -> Dict[str, Any]:
221
+ return await self.methods.get_messages(object_guid, limit, sort, max_id, min_id)
222
+
223
+ async def get_chats_updates(self, state: Optional[int] = None) -> Dict[str, Any]:
224
+ return await self.methods.get_chats_updates(state)
225
+
226
+ async def get_messages_updates(self, object_guid: str, state: Optional[int] = None) -> Dict[str, Any]:
227
+ return await self.methods.get_messages_updates(object_guid, state)
228
+
229
+ async def get_chat_history(self, object_guid: Optional[str] = None, limit: int = 50, max_id: Optional[str] = None, min_id: Optional[str] = None, sort: str = "FromMax", guid: Optional[str] = None, state: Optional[int] = None) -> List[Message]:
230
+ return await self.methods.get_chat_history(object_guid, limit, max_id, min_id, sort, guid, state)
231
+
232
+ async def register_device(self) -> Dict[str, Any]:
233
+ return await self.methods.register_device()
234
+
235
+ async def get_chat_info(self, object_guid: str) -> Chat:
236
+ return await self.methods.get_chat_info(object_guid)
237
+
238
+ async def get_chat_info_by_username(self, username: str) -> Chat:
239
+ return await self.methods.get_chat_info_by_username(username)
240
+
241
+ async def join_voice_chat(self, chat_guid: str, voice_chat_id: Optional[str] = None, sdp_offer_data: str = "") -> Dict[str, Any]:
242
+ return await self.methods.join_voice_chat(chat_guid, voice_chat_id, sdp_offer_data)
243
+
244
+ async def leave_voice_chat(self, chat_guid: str, voice_chat_id: Optional[str] = None) -> Dict[str, Any]:
245
+ return await self.methods.leave_voice_chat(chat_guid, voice_chat_id)
246
+
247
+ async def get_voice_chat_participants(self, chat_guid: str, voice_chat_id: Optional[str] = None) -> Dict[str, Any]:
248
+ return await self.methods.get_voice_chat_participants(chat_guid, voice_chat_id)
249
+
250
+ async def create_voice_chat(self, chat_guid: str) -> Dict[str, Any]:
251
+ return await self.methods.create_voice_chat(chat_guid)
252
+
253
+ async def discard_voice_chat(self, chat_guid: str, voice_chat_id: Optional[str] = None) -> Dict[str, Any]:
254
+ return await self.methods.discard_voice_chat(chat_guid, voice_chat_id)
255
+
256
+ async def set_voice_chat_state(self, chat_guid: str, voice_chat_id: str, activity: str = "Speaking", participant_object_guid: Optional[str] = None) -> Dict[str, Any]:
257
+ return await self.methods.set_voice_chat_state(chat_guid, voice_chat_id, activity, participant_object_guid)
258
+
259
+ async def invoke(self, method: str, **input_data: Any) -> Dict[str, Any]:
260
+ """Call an authenticated Shad RPC method not yet wrapped by a helper."""
261
+ return await self.transport.send_authenticated(method, input_data)
262
+
263
+ async def block_user(self, user_guid: str) -> Dict[str, Any]:
264
+ return await self.invoke("setBlockUser", action="Block", user_guid=user_guid)
265
+
266
+ async def unblock_user(self, user_guid: str) -> Dict[str, Any]:
267
+ return await self.invoke("setBlockUser", action="Unblock", user_guid=user_guid)
268
+
269
+ def set_messenger_host(self, host: str) -> None:
270
+ self.session.messenger_host = host