Navigace
ELOWEN · Dokumentace

Poslední aktualizace: 20. října 2026

Procházet dokumentaci · telegram implementation

Tento článek zatím není přeložen. Je dostupný v angličtině.

Developer reference

telegram implementation

Problem audiences

The adapter classifies grammY's structured HTTP statuses, wrapped BotError causes, transport errno and typed core instance conditions at each logging producer. Authentication, blocked/missing chats, another poller, quota and unavailable integration transport stay local; malformed requests and programming failures remain reportable. Downloads preserve typed remote failures. Command, callback, message, polling, transport, transcription, image and delivery diagnostics keep their original text. tgSend returns a message id or throws the classified failure after its existing flood-wait retries and diagnostic. postFinalText wraps that failure in FinalTextDeliveryError with its native cause and stops before later chunks. Editable-message create and edit propagate their original failures to the shared engine; no null/false result masks an unknown write. The runTurn callback combines the original failure with core reporting metadata; no text classifier is used. These contracts require core 0.30.7, which supplies the type through its public elowen-plugin-shared/errors export.

Runtime ownership

lib/adapter.mjs owns grammY connection and polling, inbound admission, turn execution, media and outbound transport. It creates one TelegramCommands and one TelegramAskFlow for its lifetime, so replacing the bot during reconnect retains pending prompts and the short-token counter. Both owners read the adapter's current transport, control API and command catalog at call time.

lib/commands.mjs publishes the public command menu and handles slash commands, picker rendering, pagination and callbacks. It owns pending picker descriptors and uses the shared command core for controls and project/context selection. An empty catalog admits no local command. Operator checks stay before context-picker consumption and before the callback acknowledgement that would prevent showing a refusal alert.

lib/askFlow.mjs owns pending questions, option selection, custom-text capture and settlement. Final submission awaits ctx.answerQuestion before retiring the pending entry or editing the answered summary. A false result preserves the prompt for retry; a rejected host read propagates through the existing callback handler. The adapter's postAsk and resolveAsk hooks are consumed by the shared live-message reducer and delegate to this owner. Question callbacks allow the original sender or an operator; custom text is captured only from the original sender, in the existing inbound order. Core ask_resolved events retire prompts, including cancellation while delivery is pending: the question is registered before sending, removed immediately on resolution, and its late-arriving keyboard is cleared without re-registering it; the configured askTimeoutMs bounds only interactive command pickers. Keyboard rendering remains in lib/ask.mjs, and sends/edits use the adapter's existing transport and flood-wait handling.

Command picker callbacks and pagination must match their originating message. An older chooser cannot consume or replace a newer one; completed descriptors are consumed before callback acknowledgement.

askTimeoutMs controls only interactive picker menus, such as /model. With no override it uses six minutes. An expired menu closes locally; parked core questions have no additional adapter deadline.

Parked AskUserQuestion prompts remain answerable until the core accepts an answer or emits ask_resolved for timeout, cancellation or replacement. Channel adapters do not expire these questions locally. askTimeoutMs controls only the adapter's local interactive pickers or numbered menus; it does not change the core question deadline. Core resolution removes the pending entry and retires the prompt. Telegram accepts free text only from the original sender and permits option callbacks from that sender or an operator; WhatsApp text replies retain their original sender binding.

Native question rendering passes the selected service-message table to formatQuestionOptionLabel(option, messages, maxLabelLength) and formatQuestionPrompt(question, messages) from elowen-plugin-shared/ask. Recommendation labels and numbered reply instructions use English, Czech or Slovak without changing stored option labels or values. Non-recommended options have no suffix, and custom: false omits the custom-reply instruction. Rendering keeps the channel's markup and limits; there is no message-table fallback.

The entry module only registers the plugin. Import chunking from lib/format.mjs, question keyboards from lib/ask.mjs and identity helpers from lib/ids.mjs directly. chatTarget(value) in lib/ids.mjs trims a target and converts signed numeric strings to Bot API numbers while retaining channel usernames. Both TelegramAdapter.notify and the focused Telegram tools use it; notification destination selection and missing-target handling remain with the adapter.

Role-policy admission uses matchPolicy(ids, policies) from lib/ids.mjs. It returns the first matching policy or undefined, which makes accessFor refuse an unmapped sender. The existing wildcard branch matches * even without sender identifiers; named policies reuse sameId, also used by configuration deduplication, for exact numeric IDs and case-insensitive usernames with an optional @ prefix. senderIsAdmin derives operator access from that same first policy. This keeps message admission and picker authorization on one ordered scan; a later operator policy cannot override an earlier restricted match.

Canonical polling and delivery ownership

The adapter receives the real PluginContext policy read/subscription. It subscribes before the async snapshot, fences revisions, pins each active episode and disposes on shutdown. connect({signal,onConnectionState}) installs the local lifetime; only an acknowledged initialized poll makes it application-ready. Reports use fresh local authority, actual attempt/schedule state and the non-secret numeric bot binding, never an Elowen account id. Await disconnect() for polling/request cleanup and readiness withdrawal.

ONE createRetryController(mode:'long-lived-transport') owns bootstrap and the whole polling lifetime with maxRetries:null: canonical deterministic exponential waits, 60000 ms cap, no jitter or hidden retry/reset budget. Continuous application readiness alone earns the canonical stability reset. Typed 401/409 stop polling; typed 429 retains retry_after as a server minimum. Public grammY error types, not diagnostic prose, classify SDK/network failures.

The supported single-request path is public Api.getMe(signal), deleteWebhook({},signal), getUpdates({offset,timeout:30,allowed_updates:[]},signal), Bot({botInfo}) and handleUpdate(update). No Bot.init/start/stop, private SDK method or auto-retry transformer introduces a second owner. Pending updates are not dropped; allowed_updates:[] is sent until the first acknowledged poll, then omitted. Processed offset and ordered middleware dispatch survive reconnect.

tgSend and tgEdit each own one finite canonical request episode. Only a typed provider-rejected 429 permits mutation replay; network/server/unknown outcomes do not. Send returns a confirmed message id, edit returns true, and failures retain the original typed cause through the existing local-audience wrapper. The shared live-message binding propagates create/edit failures rather than returning null or false; FinalTextDeliveryError preserves the cause and the shared owner decides whether higher delivery can proceed. No automatic repeated final-answer create is authorized by an unknown write. No adapter means no polling work; focused fake-provider/host tests are not physical Telegram or end-to-end account acceptance.

sendPhotos and sendDocuments await public grammY sendPhoto/sendDocument with native InputFile payloads. A rejected upload keeps its original telegram.delivery_failed diagnostic/classification and throws the SAME native error, stopping the batch before later files. No retry, fallback text, replacement upload or replay is introduced; successful uploads keep order, bytes, filenames and the first-upload-only caption with its existing 1024-character cap.

The adapter declares sharedFileMaxBytes as 25 MiB, the platform's shared-file ceiling: core applies it when copying a Project file into conversation storage, and resolveSharedFiles returns no bytes for a stored entry above it. A reply carries at most four shared files.

The binding's postImages/postFiles return those rejecting promises to the canonical shared finalizer. It awaits attachments even after a progress-only failure; an upload rejection fails the turn rather than marking it done or sending a successful first-answer fallback. Concurrent/repeated finalization shares the same promise without repeating an upload. notify also awaits sendPhotos, so its original rejection stops before notification text. Speech callbacks and incoming transcription retain their existing behavior.

Bot identity, downloads and chooser admission

TelegramAdapter initializes grammY before inbound handlers and uses botInfo for mention/reply identity. downloadFile gets metadata and uses the same configured Bot API root, including a path prefix, for file/bot/. The default is Telegram's public Bot API; absent paths or failed downloads throw. Image/document delivery and bounded core transcription retain transport limits and cleanup.

pendingPicker owns descriptor lookup, allowed kinds and TTL. Callback branches separately verify message identity/operator authority before editing or consuming. Refused old-message/unauthorized callbacks preserve a newer chooser; expired selection uses existing cleanup, while expired navigation does not consume state. Model/context/project/page consumers retain their behavior.

Entry imports StateStore directly from plugin-shared/stateStore. Registration awaits startup pruning. Model, reasoning and display callbacks await settings patches before confirming them; display rendering reads the committed cache after the write. A rejected patch propagates through the callback instead of confirming a change. Only authoritative registration prunes voice, fast and globalCleared before missing-token return, preserving other conversation fields. Failed atomic writes propagate without replacing the prior file; unchanged state is not rewritten. This is state-file cleanup, not a numbered core migration.

attachmentNote(name, type) from elowen-plugin-shared/voice returns the attachment or untranscribed-audio marker for caller-normalized inputs without downloads, storage, localization or inference. Minimal use is attachmentNote(clip.name, clip.type). Callers retain MIME normalization/defaults; no attachment means no call. Discord and Telegram use it after absent core transcription, Teams for non-image notes, and WhatsApp retains its different voice note. Ship these consumers with the containing core export; manifests remain the source for release requirements.