yee88 0.1.0__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.
- takopi/__init__.py +1 -0
- takopi/api.py +116 -0
- takopi/backends.py +25 -0
- takopi/backends_helpers.py +14 -0
- takopi/cli/__init__.py +228 -0
- takopi/cli/config.py +320 -0
- takopi/cli/doctor.py +173 -0
- takopi/cli/init.py +113 -0
- takopi/cli/onboarding_cmd.py +126 -0
- takopi/cli/plugins.py +196 -0
- takopi/cli/run.py +419 -0
- takopi/cli/topic.py +355 -0
- takopi/commands.py +134 -0
- takopi/config.py +142 -0
- takopi/config_migrations.py +124 -0
- takopi/config_watch.py +146 -0
- takopi/context.py +9 -0
- takopi/directives.py +146 -0
- takopi/engines.py +53 -0
- takopi/events.py +170 -0
- takopi/ids.py +17 -0
- takopi/lockfile.py +158 -0
- takopi/logging.py +283 -0
- takopi/markdown.py +298 -0
- takopi/model.py +77 -0
- takopi/plugins.py +312 -0
- takopi/presenter.py +25 -0
- takopi/progress.py +99 -0
- takopi/router.py +113 -0
- takopi/runner.py +712 -0
- takopi/runner_bridge.py +619 -0
- takopi/runners/__init__.py +1 -0
- takopi/runners/claude.py +483 -0
- takopi/runners/codex.py +656 -0
- takopi/runners/mock.py +221 -0
- takopi/runners/opencode.py +505 -0
- takopi/runners/pi.py +523 -0
- takopi/runners/run_options.py +39 -0
- takopi/runners/tool_actions.py +90 -0
- takopi/runtime_loader.py +207 -0
- takopi/scheduler.py +159 -0
- takopi/schemas/__init__.py +1 -0
- takopi/schemas/claude.py +238 -0
- takopi/schemas/codex.py +169 -0
- takopi/schemas/opencode.py +51 -0
- takopi/schemas/pi.py +117 -0
- takopi/settings.py +360 -0
- takopi/telegram/__init__.py +20 -0
- takopi/telegram/api_models.py +37 -0
- takopi/telegram/api_schemas.py +152 -0
- takopi/telegram/backend.py +163 -0
- takopi/telegram/bridge.py +425 -0
- takopi/telegram/chat_prefs.py +242 -0
- takopi/telegram/chat_sessions.py +112 -0
- takopi/telegram/client.py +409 -0
- takopi/telegram/client_api.py +539 -0
- takopi/telegram/commands/__init__.py +12 -0
- takopi/telegram/commands/agent.py +196 -0
- takopi/telegram/commands/cancel.py +116 -0
- takopi/telegram/commands/dispatch.py +111 -0
- takopi/telegram/commands/executor.py +449 -0
- takopi/telegram/commands/file_transfer.py +586 -0
- takopi/telegram/commands/handlers.py +45 -0
- takopi/telegram/commands/media.py +143 -0
- takopi/telegram/commands/menu.py +139 -0
- takopi/telegram/commands/model.py +215 -0
- takopi/telegram/commands/overrides.py +159 -0
- takopi/telegram/commands/parse.py +30 -0
- takopi/telegram/commands/plan.py +16 -0
- takopi/telegram/commands/reasoning.py +234 -0
- takopi/telegram/commands/reply.py +23 -0
- takopi/telegram/commands/topics.py +332 -0
- takopi/telegram/commands/trigger.py +143 -0
- takopi/telegram/context.py +140 -0
- takopi/telegram/engine_defaults.py +86 -0
- takopi/telegram/engine_overrides.py +105 -0
- takopi/telegram/files.py +178 -0
- takopi/telegram/loop.py +1822 -0
- takopi/telegram/onboarding.py +1088 -0
- takopi/telegram/outbox.py +177 -0
- takopi/telegram/parsing.py +239 -0
- takopi/telegram/render.py +198 -0
- takopi/telegram/state_store.py +88 -0
- takopi/telegram/topic_state.py +334 -0
- takopi/telegram/topics.py +256 -0
- takopi/telegram/trigger_mode.py +68 -0
- takopi/telegram/types.py +63 -0
- takopi/telegram/voice.py +110 -0
- takopi/transport.py +53 -0
- takopi/transport_runtime.py +323 -0
- takopi/transports.py +76 -0
- takopi/utils/__init__.py +1 -0
- takopi/utils/git.py +87 -0
- takopi/utils/json_state.py +21 -0
- takopi/utils/paths.py +47 -0
- takopi/utils/streams.py +44 -0
- takopi/utils/subprocess.py +86 -0
- takopi/worktrees.py +135 -0
- yee88-0.1.0.dist-info/METADATA +116 -0
- yee88-0.1.0.dist-info/RECORD +103 -0
- yee88-0.1.0.dist-info/WHEEL +4 -0
- yee88-0.1.0.dist-info/entry_points.txt +11 -0
- yee88-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Protocol, TypeVar
|
|
4
|
+
|
|
5
|
+
import httpx
|
|
6
|
+
import msgspec
|
|
7
|
+
|
|
8
|
+
from ..logging import get_logger
|
|
9
|
+
from .api_models import Chat, ChatMember, File, ForumTopic, Message, Update, User
|
|
10
|
+
|
|
11
|
+
logger = get_logger(__name__)
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RetryAfter(Exception):
|
|
17
|
+
def __init__(self, retry_after: float, description: str | None = None) -> None:
|
|
18
|
+
super().__init__(description or f"retry after {retry_after}")
|
|
19
|
+
self.retry_after = float(retry_after)
|
|
20
|
+
self.description = description
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class TelegramRetryAfter(RetryAfter):
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def retry_after_from_payload(payload: dict[str, Any]) -> float | None:
|
|
28
|
+
params = payload.get("parameters")
|
|
29
|
+
if isinstance(params, dict):
|
|
30
|
+
retry_after = params.get("retry_after")
|
|
31
|
+
if isinstance(retry_after, (int, float)):
|
|
32
|
+
return float(retry_after)
|
|
33
|
+
return None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BotClient(Protocol):
|
|
37
|
+
async def close(self) -> None: ...
|
|
38
|
+
|
|
39
|
+
async def get_updates(
|
|
40
|
+
self,
|
|
41
|
+
offset: int | None,
|
|
42
|
+
timeout_s: int = 50,
|
|
43
|
+
allowed_updates: list[str] | None = None,
|
|
44
|
+
) -> list[Update] | None: ...
|
|
45
|
+
|
|
46
|
+
async def get_file(self, file_id: str) -> File | None: ...
|
|
47
|
+
|
|
48
|
+
async def download_file(self, file_path: str) -> bytes | None: ...
|
|
49
|
+
|
|
50
|
+
async def send_message(
|
|
51
|
+
self,
|
|
52
|
+
chat_id: int,
|
|
53
|
+
text: str,
|
|
54
|
+
reply_to_message_id: int | None = None,
|
|
55
|
+
disable_notification: bool | None = False,
|
|
56
|
+
message_thread_id: int | None = None,
|
|
57
|
+
entities: list[dict] | None = None,
|
|
58
|
+
parse_mode: str | None = None,
|
|
59
|
+
reply_markup: dict[str, Any] | None = None,
|
|
60
|
+
*,
|
|
61
|
+
replace_message_id: int | None = None,
|
|
62
|
+
) -> Message | None: ...
|
|
63
|
+
|
|
64
|
+
async def send_document(
|
|
65
|
+
self,
|
|
66
|
+
chat_id: int,
|
|
67
|
+
filename: str,
|
|
68
|
+
content: bytes,
|
|
69
|
+
reply_to_message_id: int | None = None,
|
|
70
|
+
message_thread_id: int | None = None,
|
|
71
|
+
disable_notification: bool | None = False,
|
|
72
|
+
caption: str | None = None,
|
|
73
|
+
) -> Message | None: ...
|
|
74
|
+
|
|
75
|
+
async def edit_message_text(
|
|
76
|
+
self,
|
|
77
|
+
chat_id: int,
|
|
78
|
+
message_id: int,
|
|
79
|
+
text: str,
|
|
80
|
+
entities: list[dict] | None = None,
|
|
81
|
+
parse_mode: str | None = None,
|
|
82
|
+
reply_markup: dict[str, Any] | None = None,
|
|
83
|
+
*,
|
|
84
|
+
wait: bool = True,
|
|
85
|
+
) -> Message | None: ...
|
|
86
|
+
|
|
87
|
+
async def delete_message(
|
|
88
|
+
self,
|
|
89
|
+
chat_id: int,
|
|
90
|
+
message_id: int,
|
|
91
|
+
) -> bool: ...
|
|
92
|
+
|
|
93
|
+
async def set_my_commands(
|
|
94
|
+
self,
|
|
95
|
+
commands: list[dict[str, Any]],
|
|
96
|
+
*,
|
|
97
|
+
scope: dict[str, Any] | None = None,
|
|
98
|
+
language_code: str | None = None,
|
|
99
|
+
) -> bool: ...
|
|
100
|
+
|
|
101
|
+
async def get_me(self) -> User | None: ...
|
|
102
|
+
|
|
103
|
+
async def answer_callback_query(
|
|
104
|
+
self,
|
|
105
|
+
callback_query_id: str,
|
|
106
|
+
text: str | None = None,
|
|
107
|
+
show_alert: bool | None = None,
|
|
108
|
+
) -> bool: ...
|
|
109
|
+
|
|
110
|
+
async def get_chat(self, chat_id: int) -> Chat | None: ...
|
|
111
|
+
|
|
112
|
+
async def get_chat_member(
|
|
113
|
+
self, chat_id: int, user_id: int
|
|
114
|
+
) -> ChatMember | None: ...
|
|
115
|
+
|
|
116
|
+
async def create_forum_topic(
|
|
117
|
+
self,
|
|
118
|
+
chat_id: int,
|
|
119
|
+
name: str,
|
|
120
|
+
) -> ForumTopic | None: ...
|
|
121
|
+
|
|
122
|
+
async def edit_forum_topic(
|
|
123
|
+
self,
|
|
124
|
+
chat_id: int,
|
|
125
|
+
message_thread_id: int,
|
|
126
|
+
name: str,
|
|
127
|
+
) -> bool: ...
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class HttpBotClient:
|
|
131
|
+
def __init__(
|
|
132
|
+
self,
|
|
133
|
+
token: str,
|
|
134
|
+
*,
|
|
135
|
+
timeout_s: float = 120,
|
|
136
|
+
http_client: httpx.AsyncClient | None = None,
|
|
137
|
+
) -> None:
|
|
138
|
+
if not token:
|
|
139
|
+
raise ValueError("Telegram token is empty")
|
|
140
|
+
self._base = f"https://api.telegram.org/bot{token}"
|
|
141
|
+
self._file_base = f"https://api.telegram.org/file/bot{token}"
|
|
142
|
+
self._http_client = http_client or httpx.AsyncClient(timeout=timeout_s)
|
|
143
|
+
self._owns_http_client = http_client is None
|
|
144
|
+
|
|
145
|
+
async def close(self) -> None:
|
|
146
|
+
if self._owns_http_client:
|
|
147
|
+
await self._http_client.aclose()
|
|
148
|
+
|
|
149
|
+
def _parse_telegram_envelope(
|
|
150
|
+
self,
|
|
151
|
+
*,
|
|
152
|
+
method: str,
|
|
153
|
+
resp: httpx.Response,
|
|
154
|
+
payload: Any,
|
|
155
|
+
) -> Any | None:
|
|
156
|
+
if not isinstance(payload, dict):
|
|
157
|
+
logger.error(
|
|
158
|
+
"telegram.invalid_payload",
|
|
159
|
+
method=method,
|
|
160
|
+
url=str(resp.request.url),
|
|
161
|
+
payload=payload,
|
|
162
|
+
)
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
if not payload.get("ok"):
|
|
166
|
+
if payload.get("error_code") == 429:
|
|
167
|
+
retry_after = retry_after_from_payload(payload)
|
|
168
|
+
retry_after = 5.0 if retry_after is None else retry_after
|
|
169
|
+
logger.warning(
|
|
170
|
+
"telegram.rate_limited",
|
|
171
|
+
method=method,
|
|
172
|
+
url=str(resp.request.url),
|
|
173
|
+
retry_after=retry_after,
|
|
174
|
+
)
|
|
175
|
+
raise TelegramRetryAfter(retry_after)
|
|
176
|
+
logger.error(
|
|
177
|
+
"telegram.api_error",
|
|
178
|
+
method=method,
|
|
179
|
+
url=str(resp.request.url),
|
|
180
|
+
payload=payload,
|
|
181
|
+
)
|
|
182
|
+
return None
|
|
183
|
+
|
|
184
|
+
logger.debug("telegram.response", method=method, payload=payload)
|
|
185
|
+
return payload.get("result")
|
|
186
|
+
|
|
187
|
+
async def _request(
|
|
188
|
+
self,
|
|
189
|
+
method: str,
|
|
190
|
+
*,
|
|
191
|
+
json: dict[str, Any] | None = None,
|
|
192
|
+
data: dict[str, Any] | None = None,
|
|
193
|
+
files: dict[str, Any] | None = None,
|
|
194
|
+
) -> Any | None:
|
|
195
|
+
request_payload = json if json is not None else data
|
|
196
|
+
logger.debug("telegram.request", method=method, payload=request_payload)
|
|
197
|
+
try:
|
|
198
|
+
if json is not None:
|
|
199
|
+
resp = await self._http_client.post(f"{self._base}/{method}", json=json)
|
|
200
|
+
else:
|
|
201
|
+
resp = await self._http_client.post(
|
|
202
|
+
f"{self._base}/{method}", data=data, files=files
|
|
203
|
+
)
|
|
204
|
+
except httpx.HTTPError as exc:
|
|
205
|
+
url = getattr(exc.request, "url", None)
|
|
206
|
+
logger.error(
|
|
207
|
+
"telegram.network_error",
|
|
208
|
+
method=method,
|
|
209
|
+
url=str(url) if url is not None else None,
|
|
210
|
+
error=str(exc),
|
|
211
|
+
error_type=exc.__class__.__name__,
|
|
212
|
+
)
|
|
213
|
+
return None
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
resp.raise_for_status()
|
|
217
|
+
except httpx.HTTPStatusError as exc:
|
|
218
|
+
if resp.status_code == 429:
|
|
219
|
+
retry_after: float | None = None
|
|
220
|
+
try:
|
|
221
|
+
response_payload = resp.json()
|
|
222
|
+
except Exception: # noqa: BLE001
|
|
223
|
+
response_payload = None
|
|
224
|
+
if isinstance(response_payload, dict):
|
|
225
|
+
retry_after = retry_after_from_payload(response_payload)
|
|
226
|
+
retry_after = 5.0 if retry_after is None else retry_after
|
|
227
|
+
logger.warning(
|
|
228
|
+
"telegram.rate_limited",
|
|
229
|
+
method=method,
|
|
230
|
+
status=resp.status_code,
|
|
231
|
+
url=str(resp.request.url),
|
|
232
|
+
retry_after=retry_after,
|
|
233
|
+
)
|
|
234
|
+
raise TelegramRetryAfter(retry_after) from exc
|
|
235
|
+
body = resp.text
|
|
236
|
+
logger.error(
|
|
237
|
+
"telegram.http_error",
|
|
238
|
+
method=method,
|
|
239
|
+
status=resp.status_code,
|
|
240
|
+
url=str(resp.request.url),
|
|
241
|
+
error=str(exc),
|
|
242
|
+
body=body,
|
|
243
|
+
)
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
try:
|
|
247
|
+
response_payload = resp.json()
|
|
248
|
+
except Exception as exc: # noqa: BLE001
|
|
249
|
+
body = resp.text
|
|
250
|
+
logger.error(
|
|
251
|
+
"telegram.bad_response",
|
|
252
|
+
method=method,
|
|
253
|
+
status=resp.status_code,
|
|
254
|
+
url=str(resp.request.url),
|
|
255
|
+
error=str(exc),
|
|
256
|
+
error_type=exc.__class__.__name__,
|
|
257
|
+
body=body,
|
|
258
|
+
)
|
|
259
|
+
return None
|
|
260
|
+
|
|
261
|
+
return self._parse_telegram_envelope(
|
|
262
|
+
method=method,
|
|
263
|
+
resp=resp,
|
|
264
|
+
payload=response_payload,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
def _decode_result(
|
|
268
|
+
self,
|
|
269
|
+
*,
|
|
270
|
+
method: str,
|
|
271
|
+
payload: Any,
|
|
272
|
+
model: type[T],
|
|
273
|
+
) -> T | None:
|
|
274
|
+
if payload is None:
|
|
275
|
+
return None
|
|
276
|
+
try:
|
|
277
|
+
return msgspec.convert(payload, type=model)
|
|
278
|
+
except Exception as exc: # noqa: BLE001
|
|
279
|
+
logger.error(
|
|
280
|
+
"telegram.decode_error",
|
|
281
|
+
method=method,
|
|
282
|
+
error=str(exc),
|
|
283
|
+
error_type=exc.__class__.__name__,
|
|
284
|
+
)
|
|
285
|
+
return None
|
|
286
|
+
|
|
287
|
+
async def _post(self, method: str, json_data: dict[str, Any]) -> Any | None:
|
|
288
|
+
return await self._request(method, json=json_data)
|
|
289
|
+
|
|
290
|
+
async def _post_form(
|
|
291
|
+
self,
|
|
292
|
+
method: str,
|
|
293
|
+
data: dict[str, Any],
|
|
294
|
+
files: dict[str, Any],
|
|
295
|
+
) -> Any | None:
|
|
296
|
+
return await self._request(method, data=data, files=files)
|
|
297
|
+
|
|
298
|
+
async def get_updates(
|
|
299
|
+
self,
|
|
300
|
+
offset: int | None,
|
|
301
|
+
timeout_s: int = 50,
|
|
302
|
+
allowed_updates: list[str] | None = None,
|
|
303
|
+
) -> list[Update] | None:
|
|
304
|
+
params: dict[str, Any] = {"timeout": timeout_s}
|
|
305
|
+
if offset is not None:
|
|
306
|
+
params["offset"] = offset
|
|
307
|
+
if allowed_updates is not None:
|
|
308
|
+
params["allowed_updates"] = allowed_updates
|
|
309
|
+
result = await self._post("getUpdates", params)
|
|
310
|
+
if result is None or not isinstance(result, list):
|
|
311
|
+
return None
|
|
312
|
+
try:
|
|
313
|
+
return msgspec.convert(result, type=list[Update])
|
|
314
|
+
except Exception as exc: # noqa: BLE001
|
|
315
|
+
logger.error(
|
|
316
|
+
"telegram.decode_error",
|
|
317
|
+
method="getUpdates",
|
|
318
|
+
error=str(exc),
|
|
319
|
+
error_type=exc.__class__.__name__,
|
|
320
|
+
)
|
|
321
|
+
return None
|
|
322
|
+
|
|
323
|
+
async def get_file(self, file_id: str) -> File | None:
|
|
324
|
+
result = await self._post("getFile", {"file_id": file_id})
|
|
325
|
+
return self._decode_result(method="getFile", payload=result, model=File)
|
|
326
|
+
|
|
327
|
+
async def download_file(self, file_path: str) -> bytes | None:
|
|
328
|
+
url = f"{self._file_base}/{file_path}"
|
|
329
|
+
try:
|
|
330
|
+
resp = await self._http_client.get(url)
|
|
331
|
+
except httpx.HTTPError as exc:
|
|
332
|
+
request_url = getattr(exc.request, "url", None)
|
|
333
|
+
logger.error(
|
|
334
|
+
"telegram.file_network_error",
|
|
335
|
+
url=str(request_url) if request_url is not None else None,
|
|
336
|
+
error=str(exc),
|
|
337
|
+
error_type=exc.__class__.__name__,
|
|
338
|
+
)
|
|
339
|
+
return None
|
|
340
|
+
try:
|
|
341
|
+
resp.raise_for_status()
|
|
342
|
+
except httpx.HTTPStatusError as exc:
|
|
343
|
+
if resp.status_code == 429:
|
|
344
|
+
retry_after: float | None = None
|
|
345
|
+
try:
|
|
346
|
+
response_payload = resp.json()
|
|
347
|
+
except Exception: # noqa: BLE001
|
|
348
|
+
response_payload = None
|
|
349
|
+
if isinstance(response_payload, dict):
|
|
350
|
+
retry_after = retry_after_from_payload(response_payload)
|
|
351
|
+
retry_after = 5.0 if retry_after is None else retry_after
|
|
352
|
+
logger.warning(
|
|
353
|
+
"telegram.rate_limited",
|
|
354
|
+
method="download_file",
|
|
355
|
+
status=resp.status_code,
|
|
356
|
+
url=str(resp.request.url),
|
|
357
|
+
retry_after=retry_after,
|
|
358
|
+
)
|
|
359
|
+
raise TelegramRetryAfter(retry_after) from exc
|
|
360
|
+
|
|
361
|
+
logger.error(
|
|
362
|
+
"telegram.file_http_error",
|
|
363
|
+
status=resp.status_code,
|
|
364
|
+
url=str(resp.request.url),
|
|
365
|
+
error=str(exc),
|
|
366
|
+
body=resp.text,
|
|
367
|
+
)
|
|
368
|
+
return None
|
|
369
|
+
return resp.content
|
|
370
|
+
|
|
371
|
+
async def send_message(
|
|
372
|
+
self,
|
|
373
|
+
chat_id: int,
|
|
374
|
+
text: str,
|
|
375
|
+
reply_to_message_id: int | None = None,
|
|
376
|
+
disable_notification: bool | None = False,
|
|
377
|
+
message_thread_id: int | None = None,
|
|
378
|
+
entities: list[dict] | None = None,
|
|
379
|
+
parse_mode: str | None = None,
|
|
380
|
+
reply_markup: dict[str, Any] | None = None,
|
|
381
|
+
*,
|
|
382
|
+
replace_message_id: int | None = None,
|
|
383
|
+
) -> Message | None:
|
|
384
|
+
params: dict[str, Any] = {"chat_id": chat_id, "text": text}
|
|
385
|
+
if disable_notification is not None:
|
|
386
|
+
params["disable_notification"] = disable_notification
|
|
387
|
+
if reply_to_message_id is not None:
|
|
388
|
+
params["reply_to_message_id"] = reply_to_message_id
|
|
389
|
+
if message_thread_id is not None:
|
|
390
|
+
params["message_thread_id"] = message_thread_id
|
|
391
|
+
if entities is not None:
|
|
392
|
+
params["entities"] = entities
|
|
393
|
+
if parse_mode is not None:
|
|
394
|
+
params["parse_mode"] = parse_mode
|
|
395
|
+
params["link_preview_options"] = {"is_disabled": True}
|
|
396
|
+
if reply_markup is not None:
|
|
397
|
+
params["reply_markup"] = reply_markup
|
|
398
|
+
result = await self._post("sendMessage", params)
|
|
399
|
+
return self._decode_result(method="sendMessage", payload=result, model=Message)
|
|
400
|
+
|
|
401
|
+
async def send_document(
|
|
402
|
+
self,
|
|
403
|
+
chat_id: int,
|
|
404
|
+
filename: str,
|
|
405
|
+
content: bytes,
|
|
406
|
+
reply_to_message_id: int | None = None,
|
|
407
|
+
message_thread_id: int | None = None,
|
|
408
|
+
disable_notification: bool | None = False,
|
|
409
|
+
caption: str | None = None,
|
|
410
|
+
) -> Message | None:
|
|
411
|
+
params: dict[str, Any] = {"chat_id": chat_id}
|
|
412
|
+
if disable_notification is not None:
|
|
413
|
+
params["disable_notification"] = disable_notification
|
|
414
|
+
if reply_to_message_id is not None:
|
|
415
|
+
params["reply_to_message_id"] = reply_to_message_id
|
|
416
|
+
if message_thread_id is not None:
|
|
417
|
+
params["message_thread_id"] = message_thread_id
|
|
418
|
+
if caption is not None:
|
|
419
|
+
params["caption"] = caption
|
|
420
|
+
result = await self._post_form(
|
|
421
|
+
"sendDocument",
|
|
422
|
+
params,
|
|
423
|
+
files={"document": (filename, content)},
|
|
424
|
+
)
|
|
425
|
+
return self._decode_result(method="sendDocument", payload=result, model=Message)
|
|
426
|
+
|
|
427
|
+
async def edit_message_text(
|
|
428
|
+
self,
|
|
429
|
+
chat_id: int,
|
|
430
|
+
message_id: int,
|
|
431
|
+
text: str,
|
|
432
|
+
entities: list[dict] | None = None,
|
|
433
|
+
parse_mode: str | None = None,
|
|
434
|
+
reply_markup: dict[str, Any] | None = None,
|
|
435
|
+
*,
|
|
436
|
+
wait: bool = True,
|
|
437
|
+
) -> Message | None:
|
|
438
|
+
params: dict[str, Any] = {
|
|
439
|
+
"chat_id": chat_id,
|
|
440
|
+
"message_id": message_id,
|
|
441
|
+
"text": text,
|
|
442
|
+
}
|
|
443
|
+
if entities is not None:
|
|
444
|
+
params["entities"] = entities
|
|
445
|
+
if parse_mode is not None:
|
|
446
|
+
params["parse_mode"] = parse_mode
|
|
447
|
+
params["link_preview_options"] = {"is_disabled": True}
|
|
448
|
+
if reply_markup is not None:
|
|
449
|
+
params["reply_markup"] = reply_markup
|
|
450
|
+
result = await self._post("editMessageText", params)
|
|
451
|
+
return self._decode_result(
|
|
452
|
+
method="editMessageText",
|
|
453
|
+
payload=result,
|
|
454
|
+
model=Message,
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
async def delete_message(
|
|
458
|
+
self,
|
|
459
|
+
chat_id: int,
|
|
460
|
+
message_id: int,
|
|
461
|
+
) -> bool:
|
|
462
|
+
result = await self._post(
|
|
463
|
+
"deleteMessage",
|
|
464
|
+
{"chat_id": chat_id, "message_id": message_id},
|
|
465
|
+
)
|
|
466
|
+
return bool(result)
|
|
467
|
+
|
|
468
|
+
async def set_my_commands(
|
|
469
|
+
self,
|
|
470
|
+
commands: list[dict[str, Any]],
|
|
471
|
+
*,
|
|
472
|
+
scope: dict[str, Any] | None = None,
|
|
473
|
+
language_code: str | None = None,
|
|
474
|
+
) -> bool:
|
|
475
|
+
params: dict[str, Any] = {"commands": commands}
|
|
476
|
+
if scope is not None:
|
|
477
|
+
params["scope"] = scope
|
|
478
|
+
if language_code is not None:
|
|
479
|
+
params["language_code"] = language_code
|
|
480
|
+
result = await self._post("setMyCommands", params)
|
|
481
|
+
return bool(result)
|
|
482
|
+
|
|
483
|
+
async def get_me(self) -> User | None:
|
|
484
|
+
result = await self._post("getMe", {})
|
|
485
|
+
return self._decode_result(method="getMe", payload=result, model=User)
|
|
486
|
+
|
|
487
|
+
async def answer_callback_query(
|
|
488
|
+
self,
|
|
489
|
+
callback_query_id: str,
|
|
490
|
+
text: str | None = None,
|
|
491
|
+
show_alert: bool | None = None,
|
|
492
|
+
) -> bool:
|
|
493
|
+
params: dict[str, Any] = {"callback_query_id": callback_query_id}
|
|
494
|
+
if text is not None:
|
|
495
|
+
params["text"] = text
|
|
496
|
+
if show_alert is not None:
|
|
497
|
+
params["show_alert"] = show_alert
|
|
498
|
+
result = await self._post("answerCallbackQuery", params)
|
|
499
|
+
return bool(result)
|
|
500
|
+
|
|
501
|
+
async def get_chat(self, chat_id: int) -> Chat | None:
|
|
502
|
+
result = await self._post("getChat", {"chat_id": chat_id})
|
|
503
|
+
return self._decode_result(method="getChat", payload=result, model=Chat)
|
|
504
|
+
|
|
505
|
+
async def get_chat_member(self, chat_id: int, user_id: int) -> ChatMember | None:
|
|
506
|
+
result = await self._post(
|
|
507
|
+
"getChatMember", {"chat_id": chat_id, "user_id": user_id}
|
|
508
|
+
)
|
|
509
|
+
return self._decode_result(
|
|
510
|
+
method="getChatMember",
|
|
511
|
+
payload=result,
|
|
512
|
+
model=ChatMember,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
async def create_forum_topic(self, chat_id: int, name: str) -> ForumTopic | None:
|
|
516
|
+
result = await self._post(
|
|
517
|
+
"createForumTopic", {"chat_id": chat_id, "name": name}
|
|
518
|
+
)
|
|
519
|
+
return self._decode_result(
|
|
520
|
+
method="createForumTopic",
|
|
521
|
+
payload=result,
|
|
522
|
+
model=ForumTopic,
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
async def edit_forum_topic(
|
|
526
|
+
self,
|
|
527
|
+
chat_id: int,
|
|
528
|
+
message_thread_id: int,
|
|
529
|
+
name: str,
|
|
530
|
+
) -> bool:
|
|
531
|
+
result = await self._post(
|
|
532
|
+
"editForumTopic",
|
|
533
|
+
{
|
|
534
|
+
"chat_id": chat_id,
|
|
535
|
+
"message_thread_id": message_thread_id,
|
|
536
|
+
"name": name,
|
|
537
|
+
},
|
|
538
|
+
)
|
|
539
|
+
return bool(result)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from .cancel import handle_callback_cancel, handle_cancel
|
|
4
|
+
from .menu import build_bot_commands
|
|
5
|
+
from .parse import is_cancel_command
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"build_bot_commands",
|
|
9
|
+
"handle_callback_cancel",
|
|
10
|
+
"handle_cancel",
|
|
11
|
+
"is_cancel_command",
|
|
12
|
+
]
|