correspond.channels.telegram#
Telegram: a bot over the Bot API, with the standard library.
References
telegram: (the bot’s whole update stream, for listening),
telegram:<chat id> (a private chat, a group or a channel; telegram:@name for a
public channel), telegram:<chat id>/<topic id> (a forum topic).
Bots have no history API, and Telegram keeps undelivered updates for at most 24 hours, so
poll writes every update it receives to a local log under the data root
(telegram/) and read reads that log (a telegram:@name reference is first resolved
with getChat, over the network). getUpdates confirms updates for every chat
at once, which is why listening is account-wide: narrow to one chat when routing.
Senders are platform: Telegram authenticated the account, and correspond pulled the
update from the Bot API itself. The bot token never appears in an error or a plan.
Webhook delivery, for a server: Telegram.set_webhook() asks Telegram to POST
every update, one at a time, to an HTTPS address, carrying the configured
webhook_secret in the X-Telegram-Bot-Api-Secret-Token header.
mk_webhook_app() is the receiver, an ASGI app that stores each update carrying that
secret under the data root (telegram-webhook/), in the order it arrives; the secret is
the gate, and nothing without it is stored. serve_webhook() runs it (with uvicorn,
the server extra) on 127.0.0.1 behind your own HTTPS reverse proxy, and refuses
any other address unless webhook_allow_remote is set. With delivery = "webhook" in
the config, listen reads what the receiver stored instead of calling getUpdates:
Telegram serves one or the other, never both, so while a webhook is set getUpdates is
refused (409), and Telegram.delete_webhook() returns the bot to polling.
Telegram.verify() grades one delivery by the same header.
>>> Telegram().parse_ref("-4001/7").parent.encoded
'telegram:-4001'
Module Attributes
The header Telegram puts the webhook's secret token in. |
Functions
|
The webhook receiver as an ASGI app: Telegram POSTs updates, the app stores them in |
|
|
|
Run the receiver (with uvicorn) on |
|
|
Where the receiver keeps updates: the |
Classes
|
A Telegram bot: listen, read the local log, send, edit, react. |
- correspond.channels.telegram.SECRET_HEADER = 'x-telegram-bot-api-secret-token'#
The header Telegram puts the webhook’s secret token in.
- class correspond.channels.telegram.Telegram(*, http=<function urllib_http>, log=None, updates=None, run=<function run>, clock=<built-in function time_ns>)[source]#
Bases:
objectA Telegram bot: listen, read the local log, send, edit, react.
- audience(ref, *, draft=None)[source]#
Who reads a chat, from
getChat; a topic has its chat’s readers.A private chat:
named, the other account (by its id only, so a rename keeps the hash) as its reader.A chat with a public username:
public.A group or supergroup without one:
group; a channel without one:groupof its subscribers. Members are never listed.A linked chat (a channel’s discussion group, or the channel a group discusses) is read too: posts are copied into it. A public linked chat makes the audience public; one that cannot be read makes it unknown.
Joiners read the history unless
getChatsays the history is hidden from them. Forwarding widens every chat unless its content is protected. A failedgetChatraises, so the audience resolves to public, defaulted.- Return type:
- property capabilities: Capabilities#
A bot’s view of Telegram.
- delete_webhook(*, drop_pending_updates=False, dry_run=False)[source]#
Stop webhook delivery, so
getUpdatesworks again; pending updates are kept unlessdrop_pending_updates.- Return type:
- edit(ref, message_id, draft, *, dry_run=False)[source]#
editMessageTexton one of the bot’s messages.- Return type:
- property log: MutableMapping[str, Any]#
Every message this bot has received or sent, as JSON, keyed
<chat id>/<message id>.json.
- poll(ref, *, cursor=None, limit=None)[source]#
Updates since
cursor, every one logged before it is handed over.They come from
getUpdates(the cursor is an update offset), or, withdelivery = "webhook"in the config, from what the webhook receiver stored (_poll_received()).
- prepare(ref, draft)[source]#
The draft as it will post:
notifyusernames checked, the missing mentions added. Pure.- Return type:
- react(ref, message_id, reaction, *, dry_run=False)[source]#
setMessageReactionwith one emoji (a bot has one reaction per message).- Return type:
- send(ref, draft, *, dry_run=False)[source]#
sendMessage, the title (if any) prepended;draft.notifyusernames the text does not @mention yet are mentioned on a closingccline.- Return type:
- set_webhook(url, *, dry_run=False)[source]#
Have Telegram POST this bot’s updates to
url(HTTPS), with the configuredwebhook_secret.getUpdatesstops working untildelete_webhook(); setdelivery = "webhook"solistenreads what the receiver stores. The plan reads the webhook back withgetWebhookInfo. A dry run calls nothing and reads no secret.- Return type:
- property updates: MutableMapping[str, Any]#
What the webhook receiver stored, keyed by zero-padded update id (
listenreads it whendeliveryiswebhook).
- upload(ref, name, data, *, media_type='application/octet-stream', dry_run=False, native=None)[source]#
Send a file:
sendPhotofor an inline image (PHOTO_TYPES, 10 MB), elsesendDocument(50 MB).A photo Telegram refuses (too large in pixels, too narrow, not really an image) is sent again as a document, which takes any file; the plan says so (
photo_refused).native={"as_document": True}sends an image as a document: the file as it is, not recompressed.- Return type:
- correspond.channels.telegram.mk_webhook_app(*, secret, updates=None, max_body_bytes=1000000, clock=<built-in function time_ns>)[source]#
The webhook receiver as an ASGI app: Telegram POSTs updates, the app stores them in
updates.A request without
secretinX-Telegram-Bot-Api-Secret-Tokenis refused (401) and nothing is stored: the secret is the only gate, since behind a reverse proxy every connection comes from this machine. Each update is stored under<arrival time in ns>-<update id>.json, solistentakes them in the order they came, whatever their ids (Telegram restarts them at random after a week without updates). One receiver process per bot keeps that order:Telegram.set_webhook()asks Telegram for one connection at a time.updatesdefaults to files under the data root (telegram-webhook/), whichlistenreads whendeliveryiswebhook.
- correspond.channels.telegram.serve_main(argv=None)[source]#
correspond-telegram-webhook: run the webhook receiver (serve_webhook()).- Return type:
- correspond.channels.telegram.serve_webhook(*, host='127.0.0.1', port=8081, allow_remote=None, app=None, run=None)[source]#
Run the receiver (with uvicorn) on
host, this machine unlessallow_remote.allow_remotedefaults to thewebhook_allow_remotesetting. Put your HTTPS reverse proxy in front: Telegram reaches only public HTTPS addresses. Proxy headers are ignored, since the receiver needs no client address, and one worker keeps the order.- Return type:
- correspond.channels.telegram.webhook_app_from_env()[source]#
mk_webhook_app()configured from the settings, foruvicorn --factory.
- correspond.channels.telegram.webhook_store()[source]#
Where the receiver keeps updates: the
webhook_storesetting’smodule:attr, else files under the data root.The reference names a
MutableMapping, or a callable returning one, whose values are JSON-ready dicts (an update as Telegram sent it): a bytes store, such as a plains3dolone, needs a JSON codec around it. The receiver andlistenread the same setting, so they meet in the same store.- Return type: