codex-autorunner 0.1.2__py3-none-any.whl → 1.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.
- codex_autorunner/__init__.py +12 -1
- codex_autorunner/__main__.py +4 -0
- codex_autorunner/agents/codex/harness.py +1 -1
- codex_autorunner/agents/opencode/client.py +68 -35
- codex_autorunner/agents/opencode/constants.py +3 -0
- codex_autorunner/agents/opencode/harness.py +6 -1
- codex_autorunner/agents/opencode/logging.py +21 -5
- codex_autorunner/agents/opencode/run_prompt.py +1 -0
- codex_autorunner/agents/opencode/runtime.py +176 -47
- codex_autorunner/agents/opencode/supervisor.py +36 -48
- codex_autorunner/agents/registry.py +155 -8
- codex_autorunner/api.py +25 -0
- codex_autorunner/bootstrap.py +22 -37
- codex_autorunner/cli.py +5 -1156
- codex_autorunner/codex_cli.py +20 -84
- codex_autorunner/core/__init__.py +4 -0
- codex_autorunner/core/about_car.py +49 -32
- codex_autorunner/core/adapter_utils.py +21 -0
- codex_autorunner/core/app_server_ids.py +59 -0
- codex_autorunner/core/app_server_logging.py +7 -3
- codex_autorunner/core/app_server_prompts.py +27 -260
- codex_autorunner/core/app_server_threads.py +26 -28
- codex_autorunner/core/app_server_utils.py +165 -0
- codex_autorunner/core/archive.py +349 -0
- codex_autorunner/core/codex_runner.py +12 -2
- codex_autorunner/core/config.py +587 -103
- codex_autorunner/core/docs.py +10 -2
- codex_autorunner/core/drafts.py +136 -0
- codex_autorunner/core/engine.py +1531 -866
- codex_autorunner/core/exceptions.py +4 -0
- codex_autorunner/core/flows/__init__.py +25 -0
- codex_autorunner/core/flows/controller.py +202 -0
- codex_autorunner/core/flows/definition.py +82 -0
- codex_autorunner/core/flows/models.py +88 -0
- codex_autorunner/core/flows/reasons.py +52 -0
- codex_autorunner/core/flows/reconciler.py +131 -0
- codex_autorunner/core/flows/runtime.py +382 -0
- codex_autorunner/core/flows/store.py +568 -0
- codex_autorunner/core/flows/transition.py +138 -0
- codex_autorunner/core/flows/ux_helpers.py +257 -0
- codex_autorunner/core/flows/worker_process.py +242 -0
- codex_autorunner/core/git_utils.py +62 -0
- codex_autorunner/core/hub.py +136 -16
- codex_autorunner/core/locks.py +4 -0
- codex_autorunner/core/notifications.py +14 -2
- codex_autorunner/core/ports/__init__.py +28 -0
- codex_autorunner/core/ports/agent_backend.py +150 -0
- codex_autorunner/core/ports/backend_orchestrator.py +41 -0
- codex_autorunner/core/ports/run_event.py +91 -0
- codex_autorunner/core/prompt.py +15 -7
- codex_autorunner/core/redaction.py +29 -0
- codex_autorunner/core/review_context.py +5 -8
- codex_autorunner/core/run_index.py +6 -0
- codex_autorunner/core/runner_process.py +5 -2
- codex_autorunner/core/state.py +0 -88
- codex_autorunner/core/state_roots.py +57 -0
- codex_autorunner/core/supervisor_protocol.py +15 -0
- codex_autorunner/core/supervisor_utils.py +67 -0
- codex_autorunner/core/text_delta_coalescer.py +54 -0
- codex_autorunner/core/ticket_linter_cli.py +201 -0
- codex_autorunner/core/ticket_manager_cli.py +432 -0
- codex_autorunner/core/update.py +24 -16
- codex_autorunner/core/update_paths.py +28 -0
- codex_autorunner/core/update_runner.py +2 -0
- codex_autorunner/core/usage.py +164 -12
- codex_autorunner/core/utils.py +120 -11
- codex_autorunner/discovery.py +2 -4
- codex_autorunner/flows/review/__init__.py +17 -0
- codex_autorunner/{core/review.py → flows/review/service.py} +15 -10
- codex_autorunner/flows/ticket_flow/__init__.py +3 -0
- codex_autorunner/flows/ticket_flow/definition.py +98 -0
- codex_autorunner/integrations/agents/__init__.py +17 -0
- codex_autorunner/integrations/agents/backend_orchestrator.py +284 -0
- codex_autorunner/integrations/agents/codex_adapter.py +90 -0
- codex_autorunner/integrations/agents/codex_backend.py +448 -0
- codex_autorunner/integrations/agents/opencode_adapter.py +108 -0
- codex_autorunner/integrations/agents/opencode_backend.py +598 -0
- codex_autorunner/integrations/agents/runner.py +91 -0
- codex_autorunner/integrations/agents/wiring.py +271 -0
- codex_autorunner/integrations/app_server/client.py +583 -152
- codex_autorunner/integrations/app_server/env.py +2 -107
- codex_autorunner/{core/app_server_events.py → integrations/app_server/event_buffer.py} +15 -8
- codex_autorunner/integrations/app_server/supervisor.py +59 -33
- codex_autorunner/integrations/telegram/adapter.py +204 -165
- codex_autorunner/integrations/telegram/api_schemas.py +120 -0
- codex_autorunner/integrations/telegram/config.py +221 -0
- codex_autorunner/integrations/telegram/constants.py +17 -2
- codex_autorunner/integrations/telegram/dispatch.py +17 -0
- codex_autorunner/integrations/telegram/doctor.py +47 -0
- codex_autorunner/integrations/telegram/handlers/callbacks.py +7 -4
- codex_autorunner/integrations/telegram/handlers/commands/__init__.py +2 -0
- codex_autorunner/integrations/telegram/handlers/commands/execution.py +53 -57
- codex_autorunner/integrations/telegram/handlers/commands/files.py +2 -6
- codex_autorunner/integrations/telegram/handlers/commands/flows.py +1364 -0
- codex_autorunner/integrations/telegram/handlers/commands/formatting.py +1 -1
- codex_autorunner/integrations/telegram/handlers/commands/github.py +41 -582
- codex_autorunner/integrations/telegram/handlers/commands/workspace.py +8 -8
- codex_autorunner/integrations/telegram/handlers/commands_runtime.py +137 -478
- codex_autorunner/integrations/telegram/handlers/commands_spec.py +17 -4
- codex_autorunner/integrations/telegram/handlers/messages.py +121 -9
- codex_autorunner/integrations/telegram/handlers/selections.py +61 -1
- codex_autorunner/integrations/telegram/helpers.py +111 -16
- codex_autorunner/integrations/telegram/outbox.py +208 -37
- codex_autorunner/integrations/telegram/progress_stream.py +3 -10
- codex_autorunner/integrations/telegram/service.py +221 -42
- codex_autorunner/integrations/telegram/state.py +100 -2
- codex_autorunner/integrations/telegram/ticket_flow_bridge.py +611 -0
- codex_autorunner/integrations/telegram/transport.py +39 -4
- codex_autorunner/integrations/telegram/trigger_mode.py +53 -0
- codex_autorunner/manifest.py +2 -0
- codex_autorunner/plugin_api.py +22 -0
- codex_autorunner/routes/__init__.py +37 -67
- codex_autorunner/routes/agents.py +2 -137
- codex_autorunner/routes/analytics.py +3 -0
- codex_autorunner/routes/app_server.py +2 -131
- codex_autorunner/routes/base.py +2 -624
- codex_autorunner/routes/file_chat.py +7 -0
- codex_autorunner/routes/flows.py +7 -0
- codex_autorunner/routes/messages.py +7 -0
- codex_autorunner/routes/repos.py +2 -196
- codex_autorunner/routes/review.py +2 -147
- codex_autorunner/routes/sessions.py +2 -175
- codex_autorunner/routes/settings.py +2 -168
- codex_autorunner/routes/shared.py +2 -275
- codex_autorunner/routes/system.py +4 -188
- codex_autorunner/routes/usage.py +3 -0
- codex_autorunner/routes/voice.py +2 -119
- codex_autorunner/routes/workspace.py +3 -0
- codex_autorunner/server.py +3 -2
- codex_autorunner/static/agentControls.js +41 -11
- codex_autorunner/static/agentEvents.js +248 -0
- codex_autorunner/static/app.js +35 -24
- codex_autorunner/static/archive.js +826 -0
- codex_autorunner/static/archiveApi.js +37 -0
- codex_autorunner/static/autoRefresh.js +36 -8
- codex_autorunner/static/bootstrap.js +1 -0
- codex_autorunner/static/bus.js +1 -0
- codex_autorunner/static/cache.js +1 -0
- codex_autorunner/static/constants.js +20 -4
- codex_autorunner/static/dashboard.js +344 -325
- codex_autorunner/static/diffRenderer.js +37 -0
- codex_autorunner/static/docChatCore.js +324 -0
- codex_autorunner/static/docChatStorage.js +65 -0
- codex_autorunner/static/docChatVoice.js +65 -0
- codex_autorunner/static/docEditor.js +133 -0
- codex_autorunner/static/env.js +1 -0
- codex_autorunner/static/eventSummarizer.js +166 -0
- codex_autorunner/static/fileChat.js +182 -0
- codex_autorunner/static/health.js +155 -0
- codex_autorunner/static/hub.js +126 -185
- codex_autorunner/static/index.html +839 -863
- codex_autorunner/static/liveUpdates.js +1 -0
- codex_autorunner/static/loader.js +1 -0
- codex_autorunner/static/messages.js +873 -0
- codex_autorunner/static/mobileCompact.js +2 -1
- codex_autorunner/static/preserve.js +17 -0
- codex_autorunner/static/settings.js +149 -217
- codex_autorunner/static/smartRefresh.js +52 -0
- codex_autorunner/static/styles.css +8850 -3876
- codex_autorunner/static/tabs.js +175 -11
- codex_autorunner/static/terminal.js +32 -0
- codex_autorunner/static/terminalManager.js +34 -59
- codex_autorunner/static/ticketChatActions.js +333 -0
- codex_autorunner/static/ticketChatEvents.js +16 -0
- codex_autorunner/static/ticketChatStorage.js +16 -0
- codex_autorunner/static/ticketChatStream.js +264 -0
- codex_autorunner/static/ticketEditor.js +844 -0
- codex_autorunner/static/ticketVoice.js +9 -0
- codex_autorunner/static/tickets.js +1988 -0
- codex_autorunner/static/utils.js +43 -3
- codex_autorunner/static/voice.js +1 -0
- codex_autorunner/static/workspace.js +765 -0
- codex_autorunner/static/workspaceApi.js +53 -0
- codex_autorunner/static/workspaceFileBrowser.js +504 -0
- codex_autorunner/surfaces/__init__.py +5 -0
- codex_autorunner/surfaces/cli/__init__.py +6 -0
- codex_autorunner/surfaces/cli/cli.py +1224 -0
- codex_autorunner/surfaces/cli/codex_cli.py +20 -0
- codex_autorunner/surfaces/telegram/__init__.py +3 -0
- codex_autorunner/surfaces/web/__init__.py +1 -0
- codex_autorunner/surfaces/web/app.py +2019 -0
- codex_autorunner/surfaces/web/hub_jobs.py +192 -0
- codex_autorunner/surfaces/web/middleware.py +587 -0
- codex_autorunner/surfaces/web/pty_session.py +370 -0
- codex_autorunner/surfaces/web/review.py +6 -0
- codex_autorunner/surfaces/web/routes/__init__.py +78 -0
- codex_autorunner/surfaces/web/routes/agents.py +138 -0
- codex_autorunner/surfaces/web/routes/analytics.py +277 -0
- codex_autorunner/surfaces/web/routes/app_server.py +132 -0
- codex_autorunner/surfaces/web/routes/archive.py +357 -0
- codex_autorunner/surfaces/web/routes/base.py +615 -0
- codex_autorunner/surfaces/web/routes/file_chat.py +836 -0
- codex_autorunner/surfaces/web/routes/flows.py +1164 -0
- codex_autorunner/surfaces/web/routes/messages.py +459 -0
- codex_autorunner/surfaces/web/routes/repos.py +197 -0
- codex_autorunner/surfaces/web/routes/review.py +148 -0
- codex_autorunner/surfaces/web/routes/sessions.py +176 -0
- codex_autorunner/surfaces/web/routes/settings.py +169 -0
- codex_autorunner/surfaces/web/routes/shared.py +280 -0
- codex_autorunner/surfaces/web/routes/system.py +196 -0
- codex_autorunner/surfaces/web/routes/usage.py +89 -0
- codex_autorunner/surfaces/web/routes/voice.py +120 -0
- codex_autorunner/surfaces/web/routes/workspace.py +271 -0
- codex_autorunner/surfaces/web/runner_manager.py +25 -0
- codex_autorunner/surfaces/web/schemas.py +417 -0
- codex_autorunner/surfaces/web/static_assets.py +490 -0
- codex_autorunner/surfaces/web/static_refresh.py +86 -0
- codex_autorunner/surfaces/web/terminal_sessions.py +78 -0
- codex_autorunner/tickets/__init__.py +27 -0
- codex_autorunner/tickets/agent_pool.py +399 -0
- codex_autorunner/tickets/files.py +89 -0
- codex_autorunner/tickets/frontmatter.py +55 -0
- codex_autorunner/tickets/lint.py +102 -0
- codex_autorunner/tickets/models.py +97 -0
- codex_autorunner/tickets/outbox.py +244 -0
- codex_autorunner/tickets/replies.py +179 -0
- codex_autorunner/tickets/runner.py +881 -0
- codex_autorunner/tickets/spec_ingest.py +77 -0
- codex_autorunner/web/__init__.py +5 -1
- codex_autorunner/web/app.py +2 -1771
- codex_autorunner/web/hub_jobs.py +2 -191
- codex_autorunner/web/middleware.py +2 -587
- codex_autorunner/web/pty_session.py +2 -369
- codex_autorunner/web/runner_manager.py +2 -24
- codex_autorunner/web/schemas.py +2 -396
- codex_autorunner/web/static_assets.py +4 -484
- codex_autorunner/web/static_refresh.py +2 -85
- codex_autorunner/web/terminal_sessions.py +2 -77
- codex_autorunner/workspace/__init__.py +40 -0
- codex_autorunner/workspace/paths.py +335 -0
- codex_autorunner-1.1.0.dist-info/METADATA +154 -0
- codex_autorunner-1.1.0.dist-info/RECORD +308 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/WHEEL +1 -1
- codex_autorunner/agents/execution/policy.py +0 -292
- codex_autorunner/agents/factory.py +0 -52
- codex_autorunner/agents/orchestrator.py +0 -358
- codex_autorunner/core/doc_chat.py +0 -1446
- codex_autorunner/core/snapshot.py +0 -580
- codex_autorunner/integrations/github/chatops.py +0 -268
- codex_autorunner/integrations/github/pr_flow.py +0 -1314
- codex_autorunner/routes/docs.py +0 -381
- codex_autorunner/routes/github.py +0 -327
- codex_autorunner/routes/runs.py +0 -250
- codex_autorunner/spec_ingest.py +0 -812
- codex_autorunner/static/docChatActions.js +0 -287
- codex_autorunner/static/docChatEvents.js +0 -300
- codex_autorunner/static/docChatRender.js +0 -205
- codex_autorunner/static/docChatStream.js +0 -361
- codex_autorunner/static/docs.js +0 -20
- codex_autorunner/static/docsClipboard.js +0 -69
- codex_autorunner/static/docsCrud.js +0 -257
- codex_autorunner/static/docsDocUpdates.js +0 -62
- codex_autorunner/static/docsDrafts.js +0 -16
- codex_autorunner/static/docsElements.js +0 -69
- codex_autorunner/static/docsInit.js +0 -285
- codex_autorunner/static/docsParse.js +0 -160
- codex_autorunner/static/docsSnapshot.js +0 -87
- codex_autorunner/static/docsSpecIngest.js +0 -263
- codex_autorunner/static/docsState.js +0 -127
- codex_autorunner/static/docsThreadRegistry.js +0 -44
- codex_autorunner/static/docsUi.js +0 -153
- codex_autorunner/static/docsVoice.js +0 -56
- codex_autorunner/static/github.js +0 -504
- codex_autorunner/static/logs.js +0 -678
- codex_autorunner/static/review.js +0 -157
- codex_autorunner/static/runs.js +0 -418
- codex_autorunner/static/snapshot.js +0 -124
- codex_autorunner/static/state.js +0 -94
- codex_autorunner/static/todoPreview.js +0 -27
- codex_autorunner/workspace.py +0 -16
- codex_autorunner-0.1.2.dist-info/METADATA +0 -249
- codex_autorunner-0.1.2.dist-info/RECORD +0 -222
- /codex_autorunner/{routes → surfaces/web/routes}/terminal_images.py +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/entry_points.txt +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/licenses/LICENSE +0 -0
- {codex_autorunner-0.1.2.dist-info → codex_autorunner-1.1.0.dist-info}/top_level.txt +0 -0
|
@@ -12,6 +12,16 @@ from ...core.circuit_breaker import CircuitBreaker
|
|
|
12
12
|
from ...core.exceptions import CodexError, PermanentError, TransientError
|
|
13
13
|
from ...core.logging_utils import log_event
|
|
14
14
|
from ...core.retry import retry_transient
|
|
15
|
+
from .api_schemas import (
|
|
16
|
+
TelegramAudioSchema,
|
|
17
|
+
TelegramDocumentSchema,
|
|
18
|
+
TelegramMessageEntitySchema,
|
|
19
|
+
TelegramPhotoSizeSchema,
|
|
20
|
+
TelegramVoiceSchema,
|
|
21
|
+
parse_callback_query_payload,
|
|
22
|
+
parse_message_payload,
|
|
23
|
+
parse_update_payload,
|
|
24
|
+
)
|
|
15
25
|
from .constants import TELEGRAM_CALLBACK_DATA_LIMIT, TELEGRAM_MAX_MESSAGE_LENGTH
|
|
16
26
|
from .retry import _extract_retry_after_seconds
|
|
17
27
|
|
|
@@ -120,6 +130,12 @@ class TelegramMessage:
|
|
|
120
130
|
voice: Optional[TelegramVoice] = None
|
|
121
131
|
media_group_id: Optional[str] = None
|
|
122
132
|
|
|
133
|
+
# Extra metadata used for trigger gating / UX (optional, depends on update payload).
|
|
134
|
+
chat_type: Optional[str] = None
|
|
135
|
+
reply_to_message_id: Optional[int] = None
|
|
136
|
+
reply_to_is_bot: bool = False
|
|
137
|
+
reply_to_username: Optional[str] = None
|
|
138
|
+
|
|
123
139
|
|
|
124
140
|
@dataclass(frozen=True)
|
|
125
141
|
class TelegramCallbackQuery:
|
|
@@ -227,12 +243,6 @@ class ReviewCommitCallback:
|
|
|
227
243
|
sha: str
|
|
228
244
|
|
|
229
245
|
|
|
230
|
-
@dataclass(frozen=True)
|
|
231
|
-
class PrFlowStartCallback:
|
|
232
|
-
slug: str
|
|
233
|
-
number: int
|
|
234
|
-
|
|
235
|
-
|
|
236
246
|
@dataclass(frozen=True)
|
|
237
247
|
class CancelCallback:
|
|
238
248
|
kind: str
|
|
@@ -249,6 +259,17 @@ class PageCallback:
|
|
|
249
259
|
page: int
|
|
250
260
|
|
|
251
261
|
|
|
262
|
+
@dataclass(frozen=True)
|
|
263
|
+
class FlowCallback:
|
|
264
|
+
action: str
|
|
265
|
+
run_id: Optional[str] = None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@dataclass(frozen=True)
|
|
269
|
+
class FlowRunCallback:
|
|
270
|
+
run_id: str
|
|
271
|
+
|
|
272
|
+
|
|
252
273
|
def parse_command(
|
|
253
274
|
text: Optional[str],
|
|
254
275
|
*,
|
|
@@ -323,111 +344,123 @@ def is_interrupt_alias(text: Optional[str]) -> bool:
|
|
|
323
344
|
|
|
324
345
|
|
|
325
346
|
def parse_update(update: dict[str, Any]) -> Optional[TelegramUpdate]:
|
|
326
|
-
|
|
327
|
-
|
|
347
|
+
try:
|
|
348
|
+
schema = parse_update_payload(update)
|
|
349
|
+
except Exception:
|
|
328
350
|
return None
|
|
329
|
-
message = _parse_message(update_id,
|
|
351
|
+
message = _parse_message(schema.update_id, schema.message, edited=False)
|
|
330
352
|
if message is None:
|
|
331
|
-
message = _parse_message(update_id,
|
|
332
|
-
callback = _parse_callback(update_id,
|
|
353
|
+
message = _parse_message(schema.update_id, schema.edited_message, edited=True)
|
|
354
|
+
callback = _parse_callback(schema.update_id, schema.callback_query)
|
|
333
355
|
if message is None and callback is None:
|
|
334
356
|
return None
|
|
335
|
-
return TelegramUpdate(
|
|
357
|
+
return TelegramUpdate(
|
|
358
|
+
update_id=schema.update_id, message=message, callback=callback
|
|
359
|
+
)
|
|
336
360
|
|
|
337
361
|
|
|
338
362
|
def _parse_message(
|
|
339
363
|
update_id: int, payload: Any, *, edited: bool = False
|
|
340
364
|
) -> Optional[TelegramMessage]:
|
|
341
|
-
|
|
365
|
+
schema = parse_message_payload(payload)
|
|
366
|
+
if schema is None:
|
|
342
367
|
return None
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
if not isinstance(message_id, int) or not isinstance(chat, dict):
|
|
346
|
-
return None
|
|
347
|
-
chat_id = chat.get("id")
|
|
368
|
+
|
|
369
|
+
chat_id = schema.chat.get("id") if isinstance(schema.chat, dict) else None
|
|
348
370
|
if not isinstance(chat_id, int):
|
|
349
371
|
return None
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
372
|
+
|
|
373
|
+
chat_type = schema.chat.get("type") if isinstance(schema.chat, dict) else None
|
|
374
|
+
if chat_type is not None and not isinstance(chat_type, str):
|
|
375
|
+
chat_type = None
|
|
376
|
+
|
|
377
|
+
reply_to_message_id: Optional[int] = None
|
|
378
|
+
reply_to_is_bot = False
|
|
379
|
+
reply_to_username: Optional[str] = None
|
|
380
|
+
if isinstance(schema.reply_to_message, dict):
|
|
381
|
+
rmid = schema.reply_to_message.get("message_id")
|
|
382
|
+
if isinstance(rmid, int):
|
|
383
|
+
reply_to_message_id = rmid
|
|
384
|
+
reply_from = schema.reply_to_message.get("from")
|
|
385
|
+
if isinstance(reply_from, dict):
|
|
386
|
+
is_bot = reply_from.get("is_bot")
|
|
387
|
+
if isinstance(is_bot, bool):
|
|
388
|
+
reply_to_is_bot = is_bot
|
|
389
|
+
username = reply_from.get("username")
|
|
390
|
+
if isinstance(username, str):
|
|
391
|
+
reply_to_username = username
|
|
392
|
+
|
|
393
|
+
from_user_id = (
|
|
394
|
+
schema.from_user.get("id") if isinstance(schema.from_user, dict) else None
|
|
395
|
+
)
|
|
355
396
|
if from_user_id is not None and not isinstance(from_user_id, int):
|
|
356
397
|
from_user_id = None
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
photos = _parse_photo_sizes(payload.get("photo"))
|
|
366
|
-
document = _parse_document(payload.get("document"))
|
|
367
|
-
audio = _parse_audio(payload.get("audio"))
|
|
368
|
-
voice = _parse_voice(payload.get("voice"))
|
|
369
|
-
media_group_id = payload.get("media_group_id")
|
|
370
|
-
if media_group_id is not None and not isinstance(media_group_id, str):
|
|
371
|
-
media_group_id = None
|
|
372
|
-
date = payload.get("date")
|
|
373
|
-
if date is not None and not isinstance(date, int):
|
|
374
|
-
date = None
|
|
375
|
-
is_topic_message = bool(payload.get("is_topic_message"))
|
|
398
|
+
|
|
399
|
+
entities = _parse_entities(schema.entities)
|
|
400
|
+
caption_entities = _parse_entities(schema.caption_entities)
|
|
401
|
+
photos = _parse_photo_sizes(schema.photo)
|
|
402
|
+
document = _parse_document(schema.document)
|
|
403
|
+
audio = _parse_audio(schema.audio)
|
|
404
|
+
voice = _parse_voice(schema.voice)
|
|
405
|
+
|
|
376
406
|
return TelegramMessage(
|
|
377
407
|
update_id=update_id,
|
|
378
|
-
message_id=message_id,
|
|
408
|
+
message_id=schema.message_id,
|
|
379
409
|
chat_id=chat_id,
|
|
380
|
-
thread_id=
|
|
410
|
+
thread_id=schema.message_thread_id,
|
|
381
411
|
from_user_id=from_user_id,
|
|
382
|
-
text=text,
|
|
383
|
-
date=date,
|
|
384
|
-
is_topic_message=is_topic_message,
|
|
412
|
+
text=schema.text,
|
|
413
|
+
date=schema.date,
|
|
414
|
+
is_topic_message=schema.is_topic_message,
|
|
385
415
|
is_edited=edited,
|
|
386
|
-
caption=caption,
|
|
416
|
+
caption=schema.caption,
|
|
387
417
|
entities=entities,
|
|
388
418
|
caption_entities=caption_entities,
|
|
389
419
|
photos=photos,
|
|
390
420
|
document=document,
|
|
391
421
|
audio=audio,
|
|
392
422
|
voice=voice,
|
|
393
|
-
media_group_id=media_group_id,
|
|
423
|
+
media_group_id=schema.media_group_id,
|
|
424
|
+
chat_type=chat_type,
|
|
425
|
+
reply_to_message_id=reply_to_message_id,
|
|
426
|
+
reply_to_is_bot=reply_to_is_bot,
|
|
427
|
+
reply_to_username=reply_to_username,
|
|
394
428
|
)
|
|
395
429
|
|
|
396
430
|
|
|
397
431
|
def _parse_callback(update_id: int, payload: Any) -> Optional[TelegramCallbackQuery]:
|
|
398
|
-
|
|
432
|
+
schema = parse_callback_query_payload(payload)
|
|
433
|
+
if schema is None:
|
|
399
434
|
return None
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
from_user_id = sender.get("id") if isinstance(sender, dict) else None
|
|
435
|
+
|
|
436
|
+
from_user_id = (
|
|
437
|
+
schema.from_user.get("id") if isinstance(schema.from_user, dict) else None
|
|
438
|
+
)
|
|
405
439
|
if from_user_id is not None and not isinstance(from_user_id, int):
|
|
406
440
|
from_user_id = None
|
|
407
|
-
|
|
408
|
-
if data is not None and not isinstance(data, str):
|
|
409
|
-
data = None
|
|
410
|
-
message = payload.get("message")
|
|
441
|
+
|
|
411
442
|
message_id = None
|
|
412
443
|
chat_id = None
|
|
413
444
|
thread_id = None
|
|
414
|
-
if isinstance(message, dict):
|
|
415
|
-
message_id = message.get("message_id")
|
|
416
|
-
chat = message.get("chat")
|
|
445
|
+
if isinstance(schema.message, dict):
|
|
446
|
+
message_id = schema.message.get("message_id")
|
|
447
|
+
chat = schema.message.get("chat")
|
|
417
448
|
if isinstance(chat, dict):
|
|
418
449
|
chat_id = chat.get("id")
|
|
419
|
-
thread_id = message.get("message_thread_id")
|
|
450
|
+
thread_id = schema.message.get("message_thread_id")
|
|
451
|
+
|
|
420
452
|
if message_id is not None and not isinstance(message_id, int):
|
|
421
453
|
message_id = None
|
|
422
454
|
if chat_id is not None and not isinstance(chat_id, int):
|
|
423
455
|
chat_id = None
|
|
424
456
|
if thread_id is not None and not isinstance(thread_id, int):
|
|
425
457
|
thread_id = None
|
|
458
|
+
|
|
426
459
|
return TelegramCallbackQuery(
|
|
427
460
|
update_id=update_id,
|
|
428
|
-
callback_id=
|
|
461
|
+
callback_id=schema.id,
|
|
429
462
|
from_user_id=from_user_id,
|
|
430
|
-
data=data,
|
|
463
|
+
data=schema.data,
|
|
431
464
|
message_id=message_id,
|
|
432
465
|
chat_id=chat_id,
|
|
433
466
|
thread_id=thread_id,
|
|
@@ -441,26 +474,17 @@ def _parse_photo_sizes(payload: Any) -> tuple[TelegramPhotoSize, ...]:
|
|
|
441
474
|
for item in payload:
|
|
442
475
|
if not isinstance(item, dict):
|
|
443
476
|
continue
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
file_unique_id = item.get("file_unique_id")
|
|
448
|
-
if file_unique_id is not None and not isinstance(file_unique_id, str):
|
|
449
|
-
file_unique_id = None
|
|
450
|
-
width = item.get("width")
|
|
451
|
-
height = item.get("height")
|
|
452
|
-
if not isinstance(width, int) or not isinstance(height, int):
|
|
477
|
+
try:
|
|
478
|
+
schema = TelegramPhotoSizeSchema.model_validate(item)
|
|
479
|
+
except Exception:
|
|
453
480
|
continue
|
|
454
|
-
file_size = item.get("file_size")
|
|
455
|
-
if file_size is not None and not isinstance(file_size, int):
|
|
456
|
-
file_size = None
|
|
457
481
|
sizes.append(
|
|
458
482
|
TelegramPhotoSize(
|
|
459
|
-
file_id=file_id,
|
|
460
|
-
file_unique_id=file_unique_id,
|
|
461
|
-
width=width,
|
|
462
|
-
height=height,
|
|
463
|
-
file_size=file_size,
|
|
483
|
+
file_id=schema.file_id,
|
|
484
|
+
file_unique_id=schema.file_unique_id,
|
|
485
|
+
width=schema.width,
|
|
486
|
+
height=schema.height,
|
|
487
|
+
file_size=schema.file_size,
|
|
464
488
|
)
|
|
465
489
|
)
|
|
466
490
|
return tuple(sizes)
|
|
@@ -469,85 +493,49 @@ def _parse_photo_sizes(payload: Any) -> tuple[TelegramPhotoSize, ...]:
|
|
|
469
493
|
def _parse_document(payload: Any) -> Optional[TelegramDocument]:
|
|
470
494
|
if not isinstance(payload, dict):
|
|
471
495
|
return None
|
|
472
|
-
|
|
473
|
-
|
|
496
|
+
try:
|
|
497
|
+
schema = TelegramDocumentSchema.model_validate(payload)
|
|
498
|
+
except Exception:
|
|
474
499
|
return None
|
|
475
|
-
file_unique_id = payload.get("file_unique_id")
|
|
476
|
-
if file_unique_id is not None and not isinstance(file_unique_id, str):
|
|
477
|
-
file_unique_id = None
|
|
478
|
-
file_name = payload.get("file_name")
|
|
479
|
-
if file_name is not None and not isinstance(file_name, str):
|
|
480
|
-
file_name = None
|
|
481
|
-
mime_type = payload.get("mime_type")
|
|
482
|
-
if mime_type is not None and not isinstance(mime_type, str):
|
|
483
|
-
mime_type = None
|
|
484
|
-
file_size = payload.get("file_size")
|
|
485
|
-
if file_size is not None and not isinstance(file_size, int):
|
|
486
|
-
file_size = None
|
|
487
500
|
return TelegramDocument(
|
|
488
|
-
file_id=file_id,
|
|
489
|
-
file_unique_id=file_unique_id,
|
|
490
|
-
file_name=file_name,
|
|
491
|
-
mime_type=mime_type,
|
|
492
|
-
file_size=file_size,
|
|
501
|
+
file_id=schema.file_id,
|
|
502
|
+
file_unique_id=schema.file_unique_id,
|
|
503
|
+
file_name=schema.file_name,
|
|
504
|
+
mime_type=schema.mime_type,
|
|
505
|
+
file_size=schema.file_size,
|
|
493
506
|
)
|
|
494
507
|
|
|
495
508
|
|
|
496
509
|
def _parse_audio(payload: Any) -> Optional[TelegramAudio]:
|
|
497
510
|
if not isinstance(payload, dict):
|
|
498
511
|
return None
|
|
499
|
-
|
|
500
|
-
|
|
512
|
+
try:
|
|
513
|
+
schema = TelegramAudioSchema.model_validate(payload)
|
|
514
|
+
except Exception:
|
|
501
515
|
return None
|
|
502
|
-
file_unique_id = payload.get("file_unique_id")
|
|
503
|
-
if file_unique_id is not None and not isinstance(file_unique_id, str):
|
|
504
|
-
file_unique_id = None
|
|
505
|
-
duration = payload.get("duration")
|
|
506
|
-
if duration is not None and not isinstance(duration, int):
|
|
507
|
-
duration = None
|
|
508
|
-
file_name = payload.get("file_name")
|
|
509
|
-
if file_name is not None and not isinstance(file_name, str):
|
|
510
|
-
file_name = None
|
|
511
|
-
mime_type = payload.get("mime_type")
|
|
512
|
-
if mime_type is not None and not isinstance(mime_type, str):
|
|
513
|
-
mime_type = None
|
|
514
|
-
file_size = payload.get("file_size")
|
|
515
|
-
if file_size is not None and not isinstance(file_size, int):
|
|
516
|
-
file_size = None
|
|
517
516
|
return TelegramAudio(
|
|
518
|
-
file_id=file_id,
|
|
519
|
-
file_unique_id=file_unique_id,
|
|
520
|
-
duration=duration,
|
|
521
|
-
file_name=file_name,
|
|
522
|
-
mime_type=mime_type,
|
|
523
|
-
file_size=file_size,
|
|
517
|
+
file_id=schema.file_id,
|
|
518
|
+
file_unique_id=schema.file_unique_id,
|
|
519
|
+
duration=schema.duration,
|
|
520
|
+
file_name=schema.file_name,
|
|
521
|
+
mime_type=schema.mime_type,
|
|
522
|
+
file_size=schema.file_size,
|
|
524
523
|
)
|
|
525
524
|
|
|
526
525
|
|
|
527
526
|
def _parse_voice(payload: Any) -> Optional[TelegramVoice]:
|
|
528
527
|
if not isinstance(payload, dict):
|
|
529
528
|
return None
|
|
530
|
-
|
|
531
|
-
|
|
529
|
+
try:
|
|
530
|
+
schema = TelegramVoiceSchema.model_validate(payload)
|
|
531
|
+
except Exception:
|
|
532
532
|
return None
|
|
533
|
-
file_unique_id = payload.get("file_unique_id")
|
|
534
|
-
if file_unique_id is not None and not isinstance(file_unique_id, str):
|
|
535
|
-
file_unique_id = None
|
|
536
|
-
duration = payload.get("duration")
|
|
537
|
-
if duration is not None and not isinstance(duration, int):
|
|
538
|
-
duration = None
|
|
539
|
-
mime_type = payload.get("mime_type")
|
|
540
|
-
if mime_type is not None and not isinstance(mime_type, str):
|
|
541
|
-
mime_type = None
|
|
542
|
-
file_size = payload.get("file_size")
|
|
543
|
-
if file_size is not None and not isinstance(file_size, int):
|
|
544
|
-
file_size = None
|
|
545
533
|
return TelegramVoice(
|
|
546
|
-
file_id=file_id,
|
|
547
|
-
file_unique_id=file_unique_id,
|
|
548
|
-
duration=duration,
|
|
549
|
-
mime_type=mime_type,
|
|
550
|
-
file_size=file_size,
|
|
534
|
+
file_id=schema.file_id,
|
|
535
|
+
file_unique_id=schema.file_unique_id,
|
|
536
|
+
duration=schema.duration,
|
|
537
|
+
mime_type=schema.mime_type,
|
|
538
|
+
file_size=schema.file_size,
|
|
551
539
|
)
|
|
552
540
|
|
|
553
541
|
|
|
@@ -558,14 +546,15 @@ def _parse_entities(payload: Any) -> tuple[TelegramMessageEntity, ...]:
|
|
|
558
546
|
for item in payload:
|
|
559
547
|
if not isinstance(item, dict):
|
|
560
548
|
continue
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
if not isinstance(kind, str):
|
|
565
|
-
continue
|
|
566
|
-
if not isinstance(offset, int) or not isinstance(length, int):
|
|
549
|
+
try:
|
|
550
|
+
schema = TelegramMessageEntitySchema.model_validate(item)
|
|
551
|
+
except Exception:
|
|
567
552
|
continue
|
|
568
|
-
entities.append(
|
|
553
|
+
entities.append(
|
|
554
|
+
TelegramMessageEntity(
|
|
555
|
+
type=schema.type, offset=schema.offset, length=schema.length
|
|
556
|
+
)
|
|
557
|
+
)
|
|
569
558
|
return tuple(entities)
|
|
570
559
|
|
|
571
560
|
|
|
@@ -757,14 +746,29 @@ def encode_page_callback(kind: str, page: int) -> str:
|
|
|
757
746
|
return data
|
|
758
747
|
|
|
759
748
|
|
|
760
|
-
def
|
|
761
|
-
data = f"
|
|
749
|
+
def encode_compact_callback(action: str) -> str:
|
|
750
|
+
data = f"compact:{action}"
|
|
751
|
+
_validate_callback_data(data)
|
|
752
|
+
return data
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
def encode_flow_callback(action: str, run_id: Optional[str] = None) -> str:
|
|
756
|
+
action = str(action or "").strip()
|
|
757
|
+
if not action:
|
|
758
|
+
raise ValueError("flow action required")
|
|
759
|
+
if run_id:
|
|
760
|
+
data = f"flow:{action}:{run_id}"
|
|
761
|
+
else:
|
|
762
|
+
data = f"flow:{action}"
|
|
762
763
|
_validate_callback_data(data)
|
|
763
764
|
return data
|
|
764
765
|
|
|
765
766
|
|
|
766
|
-
def
|
|
767
|
-
|
|
767
|
+
def encode_flow_run_callback(run_id: str) -> str:
|
|
768
|
+
run_id = str(run_id or "").strip()
|
|
769
|
+
if not run_id:
|
|
770
|
+
raise ValueError("flow run id required")
|
|
771
|
+
data = f"flow_run:{run_id}"
|
|
768
772
|
_validate_callback_data(data)
|
|
769
773
|
return data
|
|
770
774
|
|
|
@@ -786,9 +790,10 @@ def parse_callback_data(
|
|
|
786
790
|
UpdateCallback,
|
|
787
791
|
UpdateConfirmCallback,
|
|
788
792
|
ReviewCommitCallback,
|
|
789
|
-
PrFlowStartCallback,
|
|
790
793
|
CancelCallback,
|
|
791
794
|
CompactCallback,
|
|
795
|
+
FlowCallback,
|
|
796
|
+
FlowRunCallback,
|
|
792
797
|
PageCallback,
|
|
793
798
|
]
|
|
794
799
|
]:
|
|
@@ -868,16 +873,6 @@ def parse_callback_data(
|
|
|
868
873
|
if not sha:
|
|
869
874
|
return None
|
|
870
875
|
return ReviewCommitCallback(sha=sha)
|
|
871
|
-
if data.startswith("pr_flow_start:"):
|
|
872
|
-
_, _, rest = data.partition(":")
|
|
873
|
-
if not rest:
|
|
874
|
-
return None
|
|
875
|
-
if "#" not in rest:
|
|
876
|
-
return None
|
|
877
|
-
slug, _, number_str = rest.partition("#")
|
|
878
|
-
if not slug or not number_str or not number_str.isdigit():
|
|
879
|
-
return None
|
|
880
|
-
return PrFlowStartCallback(slug=slug, number=int(number_str))
|
|
881
876
|
if data.startswith("cancel:"):
|
|
882
877
|
_, _, kind = data.partition(":")
|
|
883
878
|
if not kind:
|
|
@@ -896,6 +891,19 @@ def parse_callback_data(
|
|
|
896
891
|
if not page.isdigit():
|
|
897
892
|
return None
|
|
898
893
|
return PageCallback(kind=kind, page=int(page))
|
|
894
|
+
if data.startswith("flow:"):
|
|
895
|
+
_, _, rest = data.partition(":")
|
|
896
|
+
action, sep, run_id = rest.partition(":")
|
|
897
|
+
if not action:
|
|
898
|
+
return None
|
|
899
|
+
if sep and not run_id:
|
|
900
|
+
return None
|
|
901
|
+
return FlowCallback(action=action, run_id=run_id or None)
|
|
902
|
+
if data.startswith("flow_run:"):
|
|
903
|
+
_, _, run_id = data.partition(":")
|
|
904
|
+
if not run_id:
|
|
905
|
+
return None
|
|
906
|
+
return FlowRunCallback(run_id=run_id)
|
|
899
907
|
return None
|
|
900
908
|
|
|
901
909
|
|
|
@@ -1101,6 +1109,24 @@ def build_bind_keyboard(
|
|
|
1101
1109
|
return build_inline_keyboard(rows)
|
|
1102
1110
|
|
|
1103
1111
|
|
|
1112
|
+
def build_flow_runs_keyboard(
|
|
1113
|
+
options: Sequence[tuple[str, str]],
|
|
1114
|
+
*,
|
|
1115
|
+
page_button: Optional[tuple[str, str]] = None,
|
|
1116
|
+
include_cancel: bool = False,
|
|
1117
|
+
) -> dict[str, Any]:
|
|
1118
|
+
rows = [
|
|
1119
|
+
[InlineButton(label, encode_flow_run_callback(run_id))]
|
|
1120
|
+
for run_id, label in options
|
|
1121
|
+
]
|
|
1122
|
+
if page_button:
|
|
1123
|
+
label, callback_data = page_button
|
|
1124
|
+
rows.append([InlineButton(label, callback_data)])
|
|
1125
|
+
if include_cancel:
|
|
1126
|
+
rows.append([InlineButton("Cancel", encode_cancel_callback("flow-runs"))])
|
|
1127
|
+
return build_inline_keyboard(rows)
|
|
1128
|
+
|
|
1129
|
+
|
|
1104
1130
|
def _validate_callback_data(data: str) -> None:
|
|
1105
1131
|
if len(data.encode("utf-8")) > TELEGRAM_CALLBACK_DATA_LIMIT:
|
|
1106
1132
|
raise ValueError("callback_data exceeds Telegram limit")
|
|
@@ -1447,6 +1473,7 @@ class TelegramBotClient:
|
|
|
1447
1473
|
message_id: int,
|
|
1448
1474
|
text: str,
|
|
1449
1475
|
*,
|
|
1476
|
+
message_thread_id: Optional[int] = None,
|
|
1450
1477
|
reply_markup: Optional[dict[str, Any]] = None,
|
|
1451
1478
|
parse_mode: Optional[str] = None,
|
|
1452
1479
|
disable_web_page_preview: bool = True,
|
|
@@ -1456,6 +1483,7 @@ class TelegramBotClient:
|
|
|
1456
1483
|
logging.INFO,
|
|
1457
1484
|
"telegram.edit_message",
|
|
1458
1485
|
chat_id=chat_id,
|
|
1486
|
+
thread_id=message_thread_id,
|
|
1459
1487
|
message_id=message_id,
|
|
1460
1488
|
text_len=len(text),
|
|
1461
1489
|
has_markup=reply_markup is not None,
|
|
@@ -1468,6 +1496,8 @@ class TelegramBotClient:
|
|
|
1468
1496
|
"text": text,
|
|
1469
1497
|
"disable_web_page_preview": disable_web_page_preview,
|
|
1470
1498
|
}
|
|
1499
|
+
if message_thread_id is not None:
|
|
1500
|
+
payload["message_thread_id"] = message_thread_id
|
|
1471
1501
|
if reply_markup is not None:
|
|
1472
1502
|
payload["reply_markup"] = reply_markup
|
|
1473
1503
|
if parse_mode is not None:
|
|
@@ -1479,12 +1509,15 @@ class TelegramBotClient:
|
|
|
1479
1509
|
self,
|
|
1480
1510
|
chat_id: Union[int, str],
|
|
1481
1511
|
message_id: int,
|
|
1512
|
+
*,
|
|
1513
|
+
message_thread_id: Optional[int] = None,
|
|
1482
1514
|
) -> bool:
|
|
1483
1515
|
log_event(
|
|
1484
1516
|
self._logger,
|
|
1485
1517
|
logging.INFO,
|
|
1486
1518
|
"telegram.delete_message",
|
|
1487
1519
|
chat_id=chat_id,
|
|
1520
|
+
thread_id=message_thread_id,
|
|
1488
1521
|
message_id=message_id,
|
|
1489
1522
|
)
|
|
1490
1523
|
payload: dict[str, Any] = {"chat_id": chat_id, "message_id": message_id}
|
|
@@ -1495,6 +1528,9 @@ class TelegramBotClient:
|
|
|
1495
1528
|
self,
|
|
1496
1529
|
callback_query_id: str,
|
|
1497
1530
|
*,
|
|
1531
|
+
chat_id: Optional[int] = None,
|
|
1532
|
+
thread_id: Optional[int] = None,
|
|
1533
|
+
message_id: Optional[int] = None,
|
|
1498
1534
|
text: Optional[str] = None,
|
|
1499
1535
|
show_alert: bool = False,
|
|
1500
1536
|
) -> dict[str, Any]:
|
|
@@ -1503,6 +1539,9 @@ class TelegramBotClient:
|
|
|
1503
1539
|
logging.INFO,
|
|
1504
1540
|
"telegram.answer_callback",
|
|
1505
1541
|
callback_query_id=callback_query_id,
|
|
1542
|
+
chat_id=chat_id,
|
|
1543
|
+
thread_id=thread_id,
|
|
1544
|
+
message_id=message_id,
|
|
1506
1545
|
text_len=len(text) if text else 0,
|
|
1507
1546
|
show_alert=show_alert,
|
|
1508
1547
|
)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"BaseTelegramSchema",
|
|
9
|
+
"TelegramPhotoSizeSchema",
|
|
10
|
+
"TelegramDocumentSchema",
|
|
11
|
+
"TelegramAudioSchema",
|
|
12
|
+
"TelegramVoiceSchema",
|
|
13
|
+
"TelegramMessageEntitySchema",
|
|
14
|
+
"TelegramMessageSchema",
|
|
15
|
+
"TelegramCallbackQuerySchema",
|
|
16
|
+
"TelegramUpdateSchema",
|
|
17
|
+
"parse_update_payload",
|
|
18
|
+
"parse_message_payload",
|
|
19
|
+
"parse_callback_query_payload",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class BaseTelegramSchema(BaseModel):
|
|
24
|
+
model_config = {"extra": "ignore", "validate_assignment": False}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TelegramPhotoSizeSchema(BaseTelegramSchema):
|
|
28
|
+
file_id: str
|
|
29
|
+
file_unique_id: Optional[str] = None
|
|
30
|
+
width: int
|
|
31
|
+
height: int
|
|
32
|
+
file_size: Optional[int] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class TelegramDocumentSchema(BaseTelegramSchema):
|
|
36
|
+
file_id: str
|
|
37
|
+
file_unique_id: Optional[str] = None
|
|
38
|
+
file_name: Optional[str] = None
|
|
39
|
+
mime_type: Optional[str] = None
|
|
40
|
+
file_size: Optional[int] = None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class TelegramAudioSchema(BaseTelegramSchema):
|
|
44
|
+
file_id: str
|
|
45
|
+
file_unique_id: Optional[str] = None
|
|
46
|
+
duration: Optional[int] = None
|
|
47
|
+
file_name: Optional[str] = None
|
|
48
|
+
mime_type: Optional[str] = None
|
|
49
|
+
file_size: Optional[int] = None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TelegramVoiceSchema(BaseTelegramSchema):
|
|
53
|
+
file_id: str
|
|
54
|
+
file_unique_id: Optional[str] = None
|
|
55
|
+
duration: Optional[int] = None
|
|
56
|
+
mime_type: Optional[str] = None
|
|
57
|
+
file_size: Optional[int] = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class TelegramMessageEntitySchema(BaseTelegramSchema):
|
|
61
|
+
type: str
|
|
62
|
+
offset: int
|
|
63
|
+
length: int
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class TelegramMessageSchema(BaseTelegramSchema):
|
|
67
|
+
message_id: int
|
|
68
|
+
chat: dict[str, Any] = Field(default_factory=dict)
|
|
69
|
+
from_user: Optional[dict[str, Any]] = Field(default=None, alias="from")
|
|
70
|
+
message_thread_id: Optional[int] = None
|
|
71
|
+
date: Optional[int] = None
|
|
72
|
+
text: Optional[str] = None
|
|
73
|
+
caption: Optional[str] = None
|
|
74
|
+
entities: Optional[list[dict[str, Any]]] = None
|
|
75
|
+
caption_entities: Optional[list[dict[str, Any]]] = None
|
|
76
|
+
photo: Optional[list[dict[str, Any]]] = None
|
|
77
|
+
document: Optional[dict[str, Any]] = None
|
|
78
|
+
audio: Optional[dict[str, Any]] = None
|
|
79
|
+
voice: Optional[dict[str, Any]] = None
|
|
80
|
+
media_group_id: Optional[str] = None
|
|
81
|
+
is_topic_message: bool = False
|
|
82
|
+
reply_to_message: Optional[dict[str, Any]] = None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TelegramCallbackQuerySchema(BaseTelegramSchema):
|
|
86
|
+
id: str
|
|
87
|
+
from_user: dict[str, Any] = Field(alias="from")
|
|
88
|
+
data: Optional[str] = None
|
|
89
|
+
message: Optional[dict[str, Any]] = None
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TelegramUpdateSchema(BaseTelegramSchema):
|
|
93
|
+
update_id: int
|
|
94
|
+
message: Optional[dict[str, Any]] = None
|
|
95
|
+
edited_message: Optional[dict[str, Any]] = Field(
|
|
96
|
+
default=None, alias="edited_message"
|
|
97
|
+
)
|
|
98
|
+
callback_query: Optional[dict[str, Any]] = Field(
|
|
99
|
+
default=None, alias="callback_query"
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def parse_update_payload(payload: dict[str, Any]) -> TelegramUpdateSchema:
|
|
104
|
+
return TelegramUpdateSchema.model_validate(payload)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def parse_message_payload(payload: dict[str, Any]) -> Optional[TelegramMessageSchema]:
|
|
108
|
+
try:
|
|
109
|
+
return TelegramMessageSchema.model_validate(payload)
|
|
110
|
+
except Exception:
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def parse_callback_query_payload(
|
|
115
|
+
payload: dict[str, Any],
|
|
116
|
+
) -> Optional[TelegramCallbackQuerySchema]:
|
|
117
|
+
try:
|
|
118
|
+
return TelegramCallbackQuerySchema.model_validate(payload)
|
|
119
|
+
except Exception:
|
|
120
|
+
return None
|