Models¶
The objects the library returns are frozen dataclasses: you can read them, compare them and
use them as dictionary keys, but not change them. Python names are in snake_case; the JSON
names of the API are in camelCase (from becomes from_user).
Every model keeps the JSON it was made from in raw, so a field that a newer server adds and
this version does not know is still there (message.raw.get("someNewField")). Each model has
a from_json() class method to build it from the API’s JSON; a missing required field raises
PayloadError. Dates are datetime objects in UTC.
Enums¶
The enums are str enums: a member equals its value (ChatKind.GROUP == "group"). A value
that this version does not know does not raise: it becomes a pseudo-member that still equals its
string.
- class ChatKind(*values)¶
The kind of a chat: private, group or channel.
A
strenum, sochat.kind == "group"andchat.kind == ChatKind.GROUPare the same test. A kind that a newer server adds does not raise: it becomes a member of its own that equals its string and matches none of the names below.- PRIVATE¶
"private", a chat between the bot and one person.
- GROUP¶
"group", a group chat.
- CHANNEL¶
"channel", a channel. A post there may speak in the channel’s name, and then it has no sender.
Examples
>>> ChatKind.GROUP == "group" True >>> ChatKind("supergroup") # a kind this version does not know <ChatKind.supergroup: 'supergroup'>
- class ChatMode(*values)¶
Whether a chat is readable by the server or end-to-end encrypted.
The server decides, and the mode says which body a message must carry. A private chat follows the bot: it is encrypted once the bot has published a key. A group or channel follows its history setting: readable when new members can see the history, encrypted otherwise.
Botfinds out the mode before it sends, and encrypts when it must.- PROTECTED¶
"protected", end-to-end encrypted. Messages travel asarmoredOpenPGP messages, which the server cannot read.
- OPEN¶
"open", readable. Messages travel astext, which the server reads and passes on.
- class BotStatus(*values)¶
Whether the bot is running or paused by its owner:
BotInfo.status.- ACTIVE¶
"active", the bot works normally.
- PAUSED¶
"paused", the owner paused the bot. While it is paused, every API call raisesBotPausedError, exceptGET /me,GET /webhook,DELETE /webhookandPOST /token/revoke.
- class SeatStatus(*values)¶
Where somebody stands in a chat: the
statusof aSeat.In a private chat the bot’s own seat is
member, orkickedwhile the person blocks it.- MEMBER¶
"member", an ordinary member.
- RESTRICTED¶
"restricted", a member who may do less than the others, untilSeat.restricted_untilwhen that is set.
- LEFT¶
"left", not in the chat, for example after leaving it.
- KICKED¶
"kicked", removed from the chat by somebody else. In a private chat, the person blocked the bot.
- class UpdateKind(*values)¶
What an
Updateis about, and so which of its fields is set.A kind that a newer server adds becomes a member of its own. Such an update has none of those fields set, and its JSON is still in
raw.- MESSAGE¶
"message", a new message for the bot, in a private chat or a group. It is in the update’smessage.
- EDITED_MESSAGE¶
"edited_message", somebody edited a message the bot had received. The new version is inmessage.
- DELETED_MESSAGES¶
"deleted_messages", messages were deleted for everyone. They are listed indeleted.
- CALLBACK_QUERY¶
"callback_query", somebody pressed a button with callback data under one of the bot’s messages. The press is incallback_query.
- MY_CHAT_MEMBER¶
"my_chat_member", the bot’s own seat in a chat changed: added, promoted, restricted, removed or left; in a private chat, blocked or unblocked. The change is inchat_member.
- class HandleKind(*values)¶
What a handle turned out to belong to: a person, a group or a channel.
A
strenum, sofound.kind == "user"andfound.kind == HandleKind.USERare the same test. A kind a newer server adds becomes a member of its own rather than raising.- USER¶
"user", somebody’s account - a bot’s included.
- GROUP¶
"group", a public group.
- CHANNEL¶
"channel", a public channel.
Updates¶
- class Update(
- *,
- raw=<factory>,
- update_id,
- kind,
- message=None,
- callback_query=None,
- deleted=None,
- chat_member=None,
One thing that happened, as the server delivers it.
kindsays what happened, and so which of the other fields is set:messagefor a new or an edited message,callback_queryfor a button press,deletedfor messages deleted for everyone,chat_memberfor a change of the bot’s own seat. The others areNone.Handlers get the event, not the update: a
Message, aCallbackQuery, aDeletedMessagesor aChatMemberUpdate. A handler registered withRouter.on_update, and any handler with anupdateparameter, gets theUpdateitself. Its events are data only: aMessageBaseor aCallbackQueryBase, without the methods. A kind that a newer server adds still parses: such an update has no event, and its JSON is inraw.- kind¶
What happened.
- Type:
- message¶
For a
messageupdate, the new message; for anedited_messageupdate, the message as it is after the edit.- Type:
MessageBase | None
- callback_query¶
For a
callback_queryupdate, the button press.- Type:
CallbackQueryBase | None
- deleted¶
For a
deleted_messagesupdate, the messages that were deleted.- Type:
DeletedMessages | None
- chat_member¶
For a
my_chat_memberupdate, the change of the bot’s seat.- Type:
ChatMemberUpdate | None
Example
>>> update = Update.from_json({ ... "updateId": 412, ... "kind": "deleted_messages", ... "chat": {"id": 7781, "kind": "private"}, ... "messageIds": [1834, 1835], ... }) >>> update.kind <UpdateKind.DELETED_MESSAGES: 'deleted_messages'> >>> update.deleted.message_ids, update.chat.id, update.message ((1834, 1835), 7781, None)
- property chat: ChatRef | None¶
The chat the update is about, whatever its kind.
Noneonly for a kind this version does not know.
- property from_user: User | None¶
Who caused the update, when it says.
The sender of a message, the person who pressed a button, or who changed the bot’s seat.
Nonefor deleted messages, for a channel post in the channel’s name, for a seat change that does not say who, and for a kind this version does not know.
- property event: Any¶
The event the update carries, whichever field holds it.
A
MessageBase, aCallbackQueryBase, aDeletedMessagesor aChatMemberUpdate, askindsays.Nonefor a kind this version does not know.
- classmethod from_json(data)¶
Builds an update, its event included, from the API’s JSON.
The bot does this for every update it fetches or is sent. The event is built as a plain
MessageBase,CallbackQueryBase,DeletedMessagesorChatMemberUpdate: attached to no bot, and not decrypted.- Parameters:
data (Any) – One update, as
GET /updateslists them or a webhook posts it.- Returns:
The update, with
datakept inraw. A missingkindis read as"unknown", and an update of a kind this version does not know has no event.- Raises:
PayloadError –
datais not an object; itsupdateIdis missing or not an integer; or the event itskindcalls for is missing or not valid (seeMessageBase.from_json,CallbackQueryBase.from_json,ChatRef.from_jsonandSeat.from_json).- Return type:
- class MessageBase(
- *,
- raw=<factory>,
- id,
- chat,
- from_user,
- date,
- reply_to=None,
- kind='user',
- text=None,
- media=(),
- signature=None,
- armored=None,
- plaintext=None,
- verification=None,
- _bot=None,
A message a person wrote, as the bot receives it: the data, without methods.
Handlers get it as a
Message(aSyncMessageon aSyncBot), which addsMessage.replyand the other methods. The bot’s own messages never arrive as updates: sends and edits return them asSentMessageobjects.In an end-to-end encrypted chat, the bot decrypts the message before any handler sees it.
textandmediathen come from inside it,armoredkeeps the original,plaintextthe exact decrypted body, andverificationsays what the signature check found. A message the bot cannot decrypt goes to the error handlers instead. In a readable chat the server reads the message itself:armored,plaintextandverificationareNone.- from_user¶
Who wrote it.
Nonefor a channel post in the channel’s name: the bot, like any reader, is not told who wrote it.- Type:
User | None
- kind¶
The kind of message, as the server says:
"user"for anything a person typed or attached.- Type:
- text¶
What the person wrote, or the caption of a picture.
Nonewhen there are no words, as for a sticker, a call notice or a payment request. In a readable chat the server hands it over trimmed, with runs of empty lines folded into one, and cut at 4000 characters. In an encrypted chat it is the decrypted text as the person wrote it; onlyCR LFline ends becomeLF.- Type:
str | None
- signature¶
For a channel post in the channel’s name, the name a signed post shows its readers.
Nonewhen the channel does not sign its posts, and for any other message.- Type:
str | None
- armored¶
In an encrypted chat, the OpenPGP message as it arrived, ASCII-armored.
Nonein a readable chat.- Type:
str | None
- plaintext¶
In an encrypted chat, the decrypted body exactly as the sender’s app wrote it, in the app’s internal format: a picture or a sticker is a marker followed by JSON. The bot reads
textandmediaout of it.Nonein a readable chat.- Type:
str | None
- verification¶
In an encrypted chat, what the check of the sender’s signature found.
Nonein a readable chat.- Type:
Verification | None
Example
>>> message = MessageBase.from_json({ ... "id": 1834, ... "chat": {"id": 7781, "kind": "private"}, ... "from": {"id": 55, "username": "anna"}, ... "date": 1767225600000, ... "text": "/weather rome", ... }) >>> message.chat_id, message.from_user.mention, message.is_command (7781, '@anna', True) >>> message.date.isoformat() '2026-01-01T00:00:00+00:00'
Note
In a readable chat, the server marks a message
armoredwhenever its text holds an OpenPGP armor line, as when somebody pastes a PGP block. If the bot cannot decrypt it, the handlers get it as words anyone in the chat can read:textholds the block, andarmoredisNone.- property is_command: bool¶
Truewhentextstarts with/.It looks at the first character only.
parse_commandtells a real command from a text such as"/ hi", and reads its name and arguments.
- classmethod from_json(data)¶
Builds a message from the API’s JSON, as an update carries it.
Nothing is decrypted here: from an encrypted chat,
textstaysNoneandarmoredholds the message. The bot decrypts messages before its handlers get them.- Parameters:
data (Any) – The
messageobject of an update.- Returns:
The message, with
datakept inraw. A missingkindis"user". Called onMessageorSyncMessage, it builds one of those, attached to no bot (seeMessage.bind).- Raises:
PayloadError –
datais not an object; itsidordateis missing or not an integer; itschatis missing or not valid; or itsfromor one of itsmediaitems is not valid.- Return type:
- class Message(
- *,
- raw=<factory>,
- id,
- chat,
- from_user,
- date,
- reply_to=None,
- kind='user',
- text=None,
- media=(),
- signature=None,
- armored=None,
- plaintext=None,
- verification=None,
- _bot=None,
Bases:
MessageBaseA received message whose methods use the
Botthat got it.- classmethod bind(message, bot)¶
Copies a plain
MessageBaseinto one that acts throughbot.The bot does this to every message before a handler sees it. You need it yourself only for a message you built or read back from somewhere, in a test for example.
- Parameters:
message (MessageBase) – The message as the API gave it.
bot (Bot) – The bot its methods will go through.
- Returns:
The same fields, as a
Messagebound tobot.- Return type:
- async reply(
- text,
- *,
- markup=None,
- quote=True,
- silent=False,
- encrypt=None,
- send_id=None,
Answers in the same chat, quoting this message unless
quote=False.A send like any other:
Bot.send_messagedoes the work, so the answer is sealed and signed when the chat is end-to-end encrypted, and the same errors come out of it.- Parameters:
text (str) – The answer, with the rules of
Bot.send_message.markup (InlineKeyboard | None) – Buttons under the answer. Never encrypted.
quote (bool) –
Falsewrites into the chat without quoting this message.silent (bool) –
Trueposts without a notification. It only matters in channels.encrypt (bool | None) –
Nonefollows the chat;Truerefuses to write in clear,Falserefuses to write sealed.send_id (str | None) – A UUID that makes this send safe to repeat. One is made for you.
- Returns:
The
SentMessageas the server stored it, which can be edited or deleted.- Raises:
RuntimeError – The message was not handed out by a
Bot, so it has nothing to answer through.- Return type:
- async answer(text, *, markup=None, silent=False, encrypt=None, send_id=None)¶
Writes into the same chat without quoting:
replywithquote=False.- Parameters:
text (str) – The message, with the rules of
Bot.send_message.markup (InlineKeyboard | None) – Buttons under it. Never encrypted.
silent (bool) –
Trueposts without a notification. It only matters in channels.encrypt (bool | None) –
Nonefollows the chat;Truerefuses to write in clear,Falserefuses to write sealed.send_id (str | None) – A UUID that makes this send safe to repeat. One is made for you.
- Returns:
The
SentMessageas the server stored it.- Raises:
RuntimeError – The message was not handed out by a
Bot.- Return type:
- async delete()¶
Deletes this message for everyone. Needs the delete messages right in a group.
- Returns:
Trueonce the message is gone.- Raises:
ForbiddenError – The bot may not delete other people’s messages here.
RuntimeError – The message was not handed out by a
Bot.
- Return type:
- async typing(typing=True)¶
Shows “typing…” in this chat for a few seconds.
- Parameters:
typing (bool) –
Falseclears the indicator early.- Returns:
Trueif the indicator was shown,Falseif one went out to this chat less than 3 seconds ago.- Raises:
RuntimeError – The message was not handed out by a
Bot.- Return type:
- class SyncMessage(
- *,
- raw=<factory>,
- id,
- chat,
- from_user,
- date,
- reply_to=None,
- kind='user',
- text=None,
- media=(),
- signature=None,
- armored=None,
- plaintext=None,
- verification=None,
- _bot=None,
Bases:
MessageBaseA received message whose methods use the
SyncBotthat got it.- classmethod bind(message, bot)¶
Copies a plain
MessageBaseinto one that acts throughbot.The bot does this to every message before a handler sees it. You need it yourself only for a message you built or read back from somewhere, in a test for example.
- Parameters:
message (MessageBase) – The message as the API gave it.
bot (SyncBot) – The bot its methods will go through.
- Returns:
The same fields, as a
SyncMessagebound tobot.- Return type:
- reply(
- text,
- *,
- markup=None,
- quote=True,
- silent=False,
- encrypt=None,
- send_id=None,
Answers in the same chat, quoting this message unless
quote=False.A send like any other:
SyncBot.send_messagedoes the work, so the answer is sealed and signed when the chat is end-to-end encrypted, and the same errors come out of it.- Parameters:
text (str) – The answer, with the rules of
SyncBot.send_message.markup (InlineKeyboard | None) – Buttons under the answer. Never encrypted.
quote (bool) –
Falsewrites into the chat without quoting this message.silent (bool) –
Trueposts without a notification. It only matters in channels.encrypt (bool | None) –
Nonefollows the chat;Truerefuses to write in clear,Falserefuses to write sealed.send_id (str | None) – A UUID that makes this send safe to repeat. One is made for you.
- Returns:
The
SyncSentMessageas the server stored it, which can be edited or deleted.- Raises:
RuntimeError – The message was not handed out by a
SyncBot, so it has nothing to answer through.- Return type:
- answer(text, *, markup=None, silent=False, encrypt=None, send_id=None)¶
Writes into the same chat without quoting:
replywithquote=False.- Parameters:
text (str) – The message, with the rules of
SyncBot.send_message.markup (InlineKeyboard | None) – Buttons under it. Never encrypted.
silent (bool) –
Trueposts without a notification. It only matters in channels.encrypt (bool | None) –
Nonefollows the chat;Truerefuses to write in clear,Falserefuses to write sealed.send_id (str | None) – A UUID that makes this send safe to repeat. One is made for you.
- Returns:
The
SyncSentMessageas the server stored it.- Raises:
RuntimeError – The message was not handed out by a
SyncBot.- Return type:
- delete()¶
Deletes this message for everyone. Needs the delete messages right in a group.
- Returns:
Trueonce the message is gone.- Raises:
ForbiddenError – The bot may not delete other people’s messages here.
RuntimeError – The message was not handed out by a
SyncBot.
- Return type:
- typing(typing=True)¶
Shows “typing…” in this chat for a few seconds.
- Parameters:
typing (bool) –
Falseclears the indicator early.- Returns:
Trueif the indicator was shown,Falseif one went out to this chat less than 3 seconds ago.- Raises:
RuntimeError – The message was not handed out by a
SyncBot.- Return type:
- class Media(
- *,
- raw=<factory>,
- id,
- kind,
- mime=None,
- url=None,
- width=None,
- height=None,
- duration_ms=None,
- name=None,
- size=None,
- encrypted=False,
A picture, clip or file attached to a message: one item of
message.media.In a readable chat the server lists only the pictures and videos that were sent in the clear. In an encrypted chat the list comes from inside the encrypted message, so it describes every kind of attachment, and the files on the server are normally encrypted too.
Bot.download_mediadownloads a file and decrypts it. That always works in a private chat. In an encrypted group the sender’s phone seals the file only to the members’ keys, so it works only when the bot is among them (its privacy mode is off); otherwise the download raisesNotARecipientError.- url¶
Where the file is,
/media/<id>on the API server.Bot.download_mediauses it.- Type:
str | None
- duration_ms¶
The length of a clip, in milliseconds, or
None. The API calls itduration.- Type:
int | None
- name¶
The file name, when the sender’s app gives one, or
None. Only in encrypted chats.- Type:
str | None
- size¶
The size in bytes, when the sender’s app gives it, or
None. Only in encrypted chats.- Type:
int | None
- encrypted¶
Truewhen the file on the server is encrypted with OpenPGP.Bot.download_mediathen decrypts it.- Type:
- classmethod from_json(data)¶
Builds an attachment from an item of the
mediaof a message in a readable chat.The server sends no
name,sizeorencryptedthere, so they keep their defaults. Attachments of encrypted chats are read from inside the decrypted message instead.- Parameters:
data (Any) – The media object (
id,kind,mime,url,width,height,duration).- Returns:
The attachment, with
datakept inraw. A missingkindis"file".- Raises:
PayloadError –
datais not an object, or itsidis missing.- Return type:
- class CallbackQueryBase(
- *,
- raw=<factory>,
- id,
- from_user,
- chat,
- message_id,
- data,
- expires_at=None,
- _bot=None,
A press on a button with callback data: the data, without methods.
Handlers get it as a
CallbackQuery(aSyncCallbackQueryon aSyncBot), which addsCallbackQuery.answerand the methods that change the message. The pressed button spins on the person’s phone until the bot answers, which it can do once, within 60 seconds of the press. A press that nothing answered gets an empty answer from the bot once the update is handled, so the button stops spinning (unlessauto_answer_callbacks=False).- expires_at¶
The deadline to answer, 60 seconds after the press, in UTC.
Nonewhen the server does not say.- Type:
datetime | None
Warning
The data of a button travels in clear, even in an encrypted chat: the server has to know which button was pressed. Put in it an ID that means something only to your bot (
order:8812), not personal data.- property expired: bool¶
Trueonceexpires_athas passed, by this computer’s clock.After that the server refuses an answer with
CallbackExpiredError.Falsewhenexpires_atisNone.
- classmethod from_json(data)¶
Builds a press from the
callbackobject of acallback_queryupdate.- Parameters:
data (Any) – The
callbackobject.- Returns:
The press, with
datakept inraw. When the object has nodatastring, thedatafield is"". Called onCallbackQueryorSyncCallbackQuery, it builds one of those, attached to no bot (seeCallbackQuery.bind).- Raises:
PayloadError –
datais not an object; itsidis missing or not a string; itsmessageIdis missing or not an integer; or itsfromorchatis missing or not valid.- Return type:
- class CallbackQuery(
- *,
- raw=<factory>,
- id,
- from_user,
- chat,
- message_id,
- data,
- expires_at=None,
- _bot=None,
Bases:
CallbackQueryBaseA button press, with
answer()and helpers to change the message.- classmethod bind(query, bot)¶
Copies a plain
CallbackQueryBaseinto one that acts throughbot.The bot does this to every press before a handler sees it.
- Parameters:
query (CallbackQueryBase) – The press as the API gave it.
bot (Bot) – The bot its methods will go through.
- Returns:
The same fields, as a
CallbackQuerybound tobot.- Return type:
- async answer(text=None, *, alert=False, url=None)¶
Stops the spinner.
textflashes a line,alert=Trueshows a box,urloffers a page.Once per press, within 60 seconds of it. The bot keeps no spinner turning: a press no handler answered gets an empty answer when the update is done.
- Parameters:
- Returns:
True. Answering twice raises instead.- Raises:
CallbackExpiredError – More than 60 seconds have passed since the press.
CallbackAnsweredError – This press was answered already.
RuntimeError – The press was not handed out by a
Bot.
- Return type:
Warning
Answers are not encrypted, even in an encrypted chat.
- async edit_message(text=None, *, markup=KEEP, encrypt=None)¶
Edits the message the pressed button sits under.
- Parameters:
text (str | None) – The new text, with the rules of
Bot.send_message.Nonekeeps it.markup (InlineKeyboard | None) – The buttons.
KEEPkeeps them,Noneremoves them, a keyboard replaces them. Never encrypted.encrypt (bool | None) –
Nonefollows the chat;Truerefuses to write in clear,Falserefuses to write sealed.
- Returns:
The edited message, as the server stored it.
- Raises:
ForbiddenError – The buttons are under somebody else’s message, so the bot may not edit it.
RuntimeError – The press was not handed out by a
Bot.
- Return type:
- async delete_message()¶
Deletes the message the pressed button sits under.
- Returns:
Trueonce the message is gone.- Raises:
ForbiddenError – The message is not the bot’s own and it has no right to delete it.
RuntimeError – The press was not handed out by a
Bot.
- Return type:
- async send(text, *, markup=None, encrypt=None)¶
Sends a new message into the chat the button was pressed in.
- Parameters:
text (str) – The message, with the rules of
Bot.send_message.markup (InlineKeyboard | None) – Buttons under it. Never encrypted.
encrypt (bool | None) –
Nonefollows the chat;Truerefuses to write in clear,Falserefuses to write sealed.
- Returns:
The
SentMessageas the server stored it.- Raises:
RuntimeError – The press was not handed out by a
Bot.- Return type:
- class SyncCallbackQuery(
- *,
- raw=<factory>,
- id,
- from_user,
- chat,
- message_id,
- data,
- expires_at=None,
- _bot=None,
Bases:
CallbackQueryBaseA button press, with
answer()and helpers to change the message (sync).- classmethod bind(query, bot)¶
Copies a plain
CallbackQueryBaseinto one that acts throughbot.The bot does this to every press before a handler sees it.
- Parameters:
query (CallbackQueryBase) – The press as the API gave it.
bot (SyncBot) – The bot its methods will go through.
- Returns:
The same fields, as a
SyncCallbackQuerybound tobot.- Return type:
- answer(text=None, *, alert=False, url=None)¶
Stops the spinner.
textflashes a line,alert=Trueshows a box,urloffers a page.Once per press, within 60 seconds of it. The bot keeps no spinner turning: a press no handler answered gets an empty answer when the update is done.
- Parameters:
- Returns:
True. Answering twice raises instead.- Raises:
CallbackExpiredError – More than 60 seconds have passed since the press.
CallbackAnsweredError – This press was answered already.
RuntimeError – The press was not handed out by a
SyncBot.
- Return type:
Warning
Answers are not encrypted, even in an encrypted chat.
- edit_message(text=None, *, markup=KEEP, encrypt=None)¶
Edits the message the pressed button sits under.
- Parameters:
text (str | None) – The new text, with the rules of
SyncBot.send_message.Nonekeeps it.markup (InlineKeyboard | None) – The buttons.
KEEPkeeps them,Noneremoves them, a keyboard replaces them. Never encrypted.encrypt (bool | None) –
Nonefollows the chat;Truerefuses to write in clear,Falserefuses to write sealed.
- Returns:
The edited message, as the server stored it.
- Raises:
ForbiddenError – The buttons are under somebody else’s message, so the bot may not edit it.
RuntimeError – The press was not handed out by a
SyncBot.
- Return type:
- delete_message()¶
Deletes the message the pressed button sits under.
- Returns:
Trueonce the message is gone.- Raises:
ForbiddenError – The message is not the bot’s own and it has no right to delete it.
RuntimeError – The press was not handed out by a
SyncBot.
- Return type:
- send(text, *, markup=None, encrypt=None)¶
Sends a new message into the chat the button was pressed in.
- Parameters:
text (str) – The message, with the rules of
SyncBot.send_message.markup (InlineKeyboard | None) – Buttons under it. Never encrypted.
encrypt (bool | None) –
Nonefollows the chat;Truerefuses to write in clear,Falserefuses to write sealed.
- Returns:
The
SyncSentMessageas the server stored it.- Raises:
RuntimeError – The press was not handed out by a
SyncBot.- Return type:
- class DeletedMessages(*, raw=<factory>, chat, message_ids)¶
Messages deleted for everyone in one chat: what a
deleted_messagesupdate carries.Handlers registered with
Router.on_deleted_messagesget it. In a group it lists the messages the bot had received. In a private chat it lists any message of the chat, the bot’s own included: also the ones the bot deleted itself, and a whole history the person cleared. It does not carry who deleted them. Itsrawis the JSON of the whole update.Example
Forget what the bot kept about them:
@bot.on_deleted_messages() async def forget(deleted): for message_id in deleted.message_ids: await db.forget(deleted.chat.id, message_id)
- class ChatMemberUpdate(*, raw=<factory>, chat, from_user, date, old, new)¶
A change of the bot’s own seat in a chat: what a
my_chat_memberupdate carries.Handlers registered with
Router.on_chat_memberget it. The bot was added, promoted, restricted, removed, or it left; in a private chat, the person blocked it (the seat becomeskicked) or unblocked it (memberagain).joined,left,blockedandunblockedname the usual cases; compareoldandnewfor the others, such as a promotion (change.new.is_admin). Itsrawis the JSON of the whole update.Example
Say hello in a new group, and log who blocks the bot:
@bot.on_chat_member() async def seat_changed(change): if change.joined and change.chat.is_group: await bot.send_message(change.chat, "Hello. Type /help to see what I do.") elif change.blocked: log.info("blocked in chat %s", change.chat.id)
- property joined: bool¶
Truewhen the bot is in the chat now and was not before.It was added to the chat, or unblocked in a private chat. In the chat means an
admin,memberorrestrictedseat (Seat.is_inside).
- property left: bool¶
Truewhen the bot was in the chat and is not any more.It was removed or it left, or the person blocked it in a private chat.
- property blocked: bool¶
Truewhen the person in a private chat blocked the bot.Until they unblock it, a send to them raises
BlockedByUserError.
Messages the bot sent¶
- class SentMessageBase(
- *,
- raw=<factory>,
- id,
- chat,
- date,
- reply_to=None,
- markup=None,
- text=None,
- armored=None,
- _bot=None,
A message of the bot’s own, as a send or an edit returns it: the data, without methods.
Bot.send_message,Bot.edit_messageand the methods that send, such asMessage.reply, return it as aSentMessage(aSyncSentMessageon aSyncBot), which addsSentMessage.editandSentMessage.delete. The fields are what the server stored.- markup¶
Its buttons, or
Nonewhen it has none.- Type:
InlineKeyboard | None
- text¶
Its text, in a readable chat.
Nonein an encrypted chat, where the server cannot read it.- Type:
str | None
- armored¶
In an encrypted chat, the OpenPGP message that was sent, sealed and signed, ASCII-armored.
Nonein a readable chat.- Type:
str | None
- classmethod from_json(data)¶
Builds the message from the answer of
POST /messagesorPATCH /messages/{id}.- Parameters:
data (Any) – The answer.
- Returns:
The message, with
datakept inraw. Called onSentMessageorSyncSentMessage, it builds one of those, attached to no bot (seeSentMessage.bind).- Raises:
PayloadError –
datais not an object; itsidordateis missing or not an integer; or itschatis missing or not valid.- Return type:
- class SentMessage(
- *,
- raw=<factory>,
- id,
- chat,
- date,
- reply_to=None,
- markup=None,
- text=None,
- armored=None,
- _bot=None,
Bases:
SentMessageBaseOne of the bot’s messages, with
edit()anddelete().- classmethod bind(message, bot)¶
Copies a plain
SentMessageBaseinto one that acts throughbot.The methods that send do this to what they return, so you rarely call it yourself.
- Parameters:
message (SentMessageBase) – The message as the API gave it back.
bot (Bot) – The bot its methods will go through.
- Returns:
The same fields, as a
SentMessagebound tobot.- Return type:
- async edit(text=None, *, markup=KEEP, encrypt=None)¶
New words, new buttons, or both:
Bot.edit_messageon this message.- Parameters:
text (str | None) – The new text, with the rules of
Bot.send_message.Nonekeeps it.markup (InlineKeyboard | None) – The buttons.
KEEP, the default, keeps them;Noneremoves them; a keyboard replaces them. Never encrypted.encrypt (bool | None) –
Nonefollows the chat;Truerefuses to write in clear,Falserefuses to write sealed.
- Returns:
The edited message, as the server stored it.
- Raises:
ValidationError – There is nothing to change: no
text, andmarkupleft atKEEP.RuntimeError – The message did not come from a
Bot.
- Return type:
- async delete()¶
Deletes this message for everyone. A bot may always delete its own.
- Returns:
Trueonce the message is gone.- Raises:
MessageNotFoundError – It was deleted already.
RuntimeError – The message did not come from a
Bot.
- Return type:
- class SyncSentMessage(
- *,
- raw=<factory>,
- id,
- chat,
- date,
- reply_to=None,
- markup=None,
- text=None,
- armored=None,
- _bot=None,
Bases:
SentMessageBaseOne of the bot’s messages, with
edit()anddelete()(sync).- classmethod bind(message, bot)¶
Copies a plain
SentMessageBaseinto one that acts throughbot.The methods that send do this to what they return, so you rarely call it yourself.
- Parameters:
message (SentMessageBase) – The message as the API gave it back.
bot (SyncBot) – The bot its methods will go through.
- Returns:
The same fields, as a
SyncSentMessagebound tobot.- Return type:
- edit(text=None, *, markup=KEEP, encrypt=None)¶
New words, new buttons, or both:
SyncBot.edit_messageon this message.- Parameters:
text (str | None) – The new text, with the rules of
SyncBot.send_message.Nonekeeps it.markup (InlineKeyboard | None) – The buttons.
KEEP, the default, keeps them;Noneremoves them; a keyboard replaces them. Never encrypted.encrypt (bool | None) –
Nonefollows the chat;Truerefuses to write in clear,Falserefuses to write sealed.
- Returns:
The edited message, as the server stored it.
- Raises:
ValidationError – There is nothing to change: no
text, andmarkupleft atKEEP.RuntimeError – The message did not come from a
SyncBot.
- Return type:
- delete()¶
Deletes this message for everyone. A bot may always delete its own.
- Returns:
Trueonce the message is gone.- Raises:
MessageNotFoundError – It was deleted already.
RuntimeError – The message did not come from a
SyncBot.
- Return type:
- KEEP¶
The default of
markupin every edit, meaning “leave the buttons as they are”.markup=Noneremoves the buttons, and a keyboard replaces them. You rarely writeKEEPyourself:Bot.edit_message,SentMessage.editandCallbackQuery.edit_messageuse it whenmarkupis left out. It is false in a boolean test, so check for it withmarkup is KEEP.
Chats and people¶
- class ChatRef(*, raw=<factory>, id, kind, title=None, username=None)¶
A chat as updates name it: its ID and kind, and the title of a group or channel.
Every
Botmethod that takes a chat takes aChatReftoo, somessage.chatcan be passed as it is.- username¶
The public handle of a group or channel, without
@.Nonein a private chat, and for a group or channel that has none.- Type:
str | None
- classmethod from_json(data)¶
Builds a chat from the API’s JSON (
id,kind,title,username).- Parameters:
data (Any) – The chat object, such as the
chatof a message.- Returns:
The chat, with
datakept inraw. A missingkindis read as"private".- Raises:
PayloadError –
datais not an object, or itsidis missing or not an integer.- Return type:
- class Chat(
- *,
- raw=<factory>,
- id,
- kind,
- title=None,
- username=None,
- mode,
- user=None,
- blocked=None,
- description=None,
- member_count=None,
- history_visible=None,
- slow_mode_seconds=None,
- me=None,
Bases:
ChatRefA chat the bot is in, with its mode: what
Bot.get_chatreturns (GET /chats/{id}).It has every field of
ChatRef, and more. A private chat fillsuserandblocked; a group or channel fills the others, including the bot’s own seat inme.- mode¶
Readable or end-to-end encrypted, and so which body a message must carry. If a server ever left it out,
protected: the safe guess, since a sealed message sent to a readable chat is refused without giving anything away.- Type:
- history_visible¶
In a group or channel,
Truewhen new members can read the history from before they joined. Such a chat is readable; any other is encrypted.- Type:
bool | None
- slow_mode_seconds¶
In a group, the slow mode delay in seconds,
0when it is off. Sending faster than that raisesSlowModeError.- Type:
int | None
- classmethod from_json(data)¶
Builds a chat from the answer of
GET /chats/{id}.- Parameters:
data (Any) – The chat object.
- Returns:
The chat, with
datakept inraw. A missingkindis read as"private", a missingmodeas"protected".- Raises:
PayloadError –
datais not an object, or it or itsuserhas no integerid.- Return type:
- class User(*, raw=<factory>, id, username=None, first_name=None, last_name=None, is_bot=False)¶
Somebody as a bot may know them: an ID, a handle and a name. Never a phone number.
Example
>>> anna = User.from_json({"id": 55, "username": "anna", "firstName": "Anna"}) >>> anna.full_name, anna.mention ('Anna', '@anna')
- property full_name: str¶
The first and last name, joined by a space.
Without a name, the handle with
@; without either,"user <id>", as in"user 55".
- classmethod from_json(data)¶
Builds a user from the API’s JSON (
id,username,firstName, …).- Parameters:
data (Any) – The user object, such as the
fromof a message.- Returns:
The user, with
datakept inraw.- Raises:
PayloadError –
datais not an object, or itsidis missing or not an integer.- Return type:
- class ChatPeer(
- *,
- raw=<factory>,
- id,
- username=None,
- first_name=None,
- last_name=None,
- is_bot=False,
- deleted=False,
- has_key=False,
Bases:
UserSomebody with a key:
Chat.user, the other side of a private chat, andResolved.user.It has every field of
User, and two more.isBotis sent for a lookup but not for the person in a private chat, whereis_botstaysFalse;deletedis the other way round, since a lookup never finds a closed account to begin with.- has_key¶
Truewhen the person has published an OpenPGP key. Without one, the bot cannot send them anything in an encrypted chat.- Type:
- classmethod from_json(data, where='chat.user')¶
Builds the person from a
userobject the server sent.- Parameters:
- Returns:
The person, with
datakept inraw.- Raises:
PayloadError –
datais not an object, or itsidis missing or not an integer.- Return type:
- class Seat(
- *,
- raw=<factory>,
- status,
- admin_rights=None,
- can=None,
- custom_title=None,
- rights=None,
- restricted_until=None,
A place in a chat: the bot’s own (
Chat.me), or somebody’s (Member).Which fields are set depends on
status. Anadminseat carriesadmin_rights,canand maybecustom_title; amemberorrestrictedseat carriesrightsand mayberestricted_until;leftandkickedcarry nothing else.- status¶
Where the seat stands: admin, member, restricted, left or kicked.
- Type:
- admin_rights¶
An administrator’s rights, as a bit mask.
canhas the same rights by name.- Type:
int | None
- can¶
An administrator’s rights, by name.
- Type:
AdminRights | None
- restricted_until¶
When a restriction ends.
Nonewhen it has no end, and for a seat that is not restricted.- Type:
datetime | None
- classmethod from_json(data)¶
Builds a seat from the API’s JSON (
status,adminRights,can, …).- Parameters:
data (Any) – The seat object, such as the
meof a chat or theoldandnewof amy_chat_memberupdate.- Returns:
The seat, with
datakept inraw. A missingstatusis read as"left".- Raises:
PayloadError –
datais not an object.- Return type:
- class Member(
- *,
- raw=<factory>,
- status,
- admin_rights=None,
- can=None,
- custom_title=None,
- rights=None,
- restricted_until=None,
- user,
Bases:
SeatSomebody’s seat in a group or channel, with who they are.
Bot.get_chat_memberreturns one, andChatMembers.memberslists them. It has every field ofSeat, and one more.- classmethod from_json(data)¶
Builds a member from the API’s JSON: a seat with a
userin it.- Parameters:
data (Any) – The member object.
- Returns:
The member, with
datakept inraw.- Raises:
PayloadError –
datais not an object, or itsuseris missing or has no integerid.- Return type:
- class AdminRights(
- *,
- raw=<factory>,
- delete_messages=False,
- ban_users=False,
- add_members=False,
- pin_messages=False,
- change_info=False,
- manage_admins=False,
- invite_links=False,
- post_messages=False,
- edit_messages=False,
- manage_comments=False,
What an administrator may do, one flag per right:
Seat.can.A right that this version does not know is still in
raw, under its JSON name.- classmethod from_json(data)¶
Builds the rights from the
canobject of an administrator’s seat.- Parameters:
data (Any) – The
canobject, with one boolean per right (deleteMessages,banUsers, …).- Returns:
The rights, with
datakept inraw. A right that is missing isFalse.- Raises:
PayloadError –
datais not an object.- Return type:
- class ChatMembers(*, raw=<factory>, members, member_count)¶
The members of a group or channel, as the bot may see them:
Bot.get_chat_members.The answer of
GET /chats/{id}/members. The bot sees what a member in its seat would see: where the member list is hidden, a bot that is not an admin sees the admins only.- member_count¶
How many members are in
members. It is the length of the list the bot may see, not the size of the chat:Chat.member_countcounts everyone.- Type:
- classmethod from_json(data)¶
Builds the list from the answer of
GET /chats/{id}/members.- Parameters:
data (Any) – The answer, with
membersandmemberCount.- Returns:
The members, with
datakept inraw. WithoutmemberCount,member_countis the length ofmembers.- Raises:
PayloadError –
datais not an object, or one of its members is not a valid member object (seeMember.from_json).- Return type:
- class ChatKeys(*, raw=<factory>, keys, member_count=None)¶
The keys a message into an encrypted chat must be sealed to:
Bot.get_chat_keys.The answer of
GET /chats/{id}/keys.Botfetches these keys by itself before it sends into an encrypted chat. The bot’s own key is never in the list: add it yourself when you seal by hand (BotKey.sealdoes). Bots in privacy mode are left out, as the app does.- member_count¶
In a group or channel, how many members it has. Always more than the number of keys: the bot itself is not in
keys, nor is a member without a key.Nonein a private chat.- Type:
int | None
- classmethod from_json(data)¶
Builds the list from the answer of
GET /chats/{id}/keys.- Parameters:
data (Any) – The answer, with
keysand, in a group or channel,memberCount.- Returns:
The keys, with
datakept inraw.- Raises:
PayloadError –
datais not an object, or one of its keys is not a valid key object (seeUserKey.from_json).- Return type:
- class UserKey(*, raw=<factory>, user_id, fingerprint, public_key)¶
Somebody’s OpenPGP public key, as the server hands it out.
Bot.get_user_keyreturns one, andChatKeys.keyslists them. The fingerprint is what the server says:PublicKey.from_user_keyreads the key and checks that it has that fingerprint before anything is sealed to it.Warning
The keys come from the server. A server that lies could hand out a key of its own in place of a person’s.
Bot(pin_keys=True)notices when a person’s key changes.- classmethod from_json(data)¶
Builds a key from the API’s JSON (
userId,fingerprint,publicKey).- Parameters:
data (Any) – The key object, such as the answer of
GET /users/{userId}/key.- Returns:
The key, with
datakept inraw.- Raises:
PayloadError –
datais not an object, itspublicKeyis missing, or itsuserIdis missing or not an integer.- Return type:
Handles¶
- class Resolved(*, raw=<factory>, kind, id, username=None, user=None, chat=None)¶
What a handle turned out to be: the answer of
Bot.resolve.idis filled in whichever it was, so a caller that only wants the number never has to look atkind. Exactly one ofuserandchatcarries the rest.- kind¶
Whether the handle belongs to a person, a group or a channel.
- Type:
- username¶
The handle it actually goes by, which is not always the one that was asked with: somebody who answers to several returns their main one.
- Type:
str | None
Example
>>> found = Resolved.from_json({"kind": "user", "id": 55, "username": "anna", ... "user": {"id": 55, "username": "anna"}}) >>> found.id, found.is_user, found.chat (55, True, None)
- classmethod from_json(data)¶
Builds the answer of
GET /resolve/{username}.- Parameters:
data (Any) – The result object.
- Returns:
What the handle was, with
datakept inraw. A missingkindis read as"user", which is what a bareuserobject means.- Raises:
PayloadError –
datais not an object, or itsidis missing or not an integer.- Return type:
- class ChatCard(
- *,
- raw=<factory>,
- id,
- kind,
- title=None,
- username=None,
- description=None,
- member_count=None,
- history_visible=None,
Bases:
ChatRefA public group or channel seen from outside:
Resolved.chat.What its short-link page shows and no more, since the bot is not in it: no
modeto pick a body with and no seat. Pass it straight toBot.get_chatonce an administrator has added the bot.- classmethod from_json(data)¶
Builds the card from the
chatofGET /resolve/{username}.- Parameters:
data (Any) – The
chatobject of the answer.- Returns:
The card, with
datakept inraw.- Raises:
PayloadError –
datais not an object, or itsidis missing or not an integer.- Return type:
The bot¶
- class BotInfo(
- *,
- raw=<factory>,
- id,
- username,
- name,
- about=None,
- description=None,
- mode=ChatMode.OPEN,
- fingerprint=None,
- privacy_mode=True,
- allow_groups=True,
- status=BotStatus.ACTIVE,
- webhook=<factory>,
- queued=0,
- limits=<factory>,
The bot as the server holds it, and the limits the server enforces (
GET /me).Bot.get_mereturns it, andBotkeeps the answer it got at start-up inBot.me. It works while the bot is paused, so it is the way to find out why nothing else does.- mode¶
The mode of the bot’s private chats:
protectedonce it has published a key,openbefore.- Type:
- privacy_mode¶
Truewhen, in groups, the bot gets only what is addressed to it: commands, mentions of its handle and replies to its messages.Falsewhen it gets every message of a readable group. The owner sets it in the app.- Type:
- webhook¶
The webhook in brief.
- Type:
Example
Read a limit instead of hard-coding it:
me = await bot.get_me() if len(report) > me.limits.text_max: report = report[: me.limits.text_max - 3] + "..."
- property paused: bool¶
Truewhen the owner paused the bot.While it is paused, most API calls raise
BotPausedError(seeBotStatus.PAUSED).
- classmethod from_json(data)¶
Builds the bot from the answer of
GET /me.- Parameters:
data (Any) – The answer.
- Returns:
The bot, with
datakept inraw. A missingmodeis read as"protected"whenfingerprintis set,"open"otherwise.- Raises:
PayloadError –
datais not an object, or itsidis missing or not an integer.- Return type:
- class Limits(
- *,
- raw=<factory>,
- text_max=4096,
- armored_max=131072,
- markup_bytes=8192,
- keyboard_rows=20,
- buttons_per_row=8,
- buttons_total=100,
- button_text_max=64,
- callback_data_bytes=64,
- url_max=1024,
- callback_answer_max=200,
- callback_window_ms=60000,
- poll_timeout_max_s=50,
- poll_limit_max=100,
- webhook_timeout_ms=10000,
- sends_per_second=30,
- sends_per_group_per_minute=20,
- sends_per_private_per_second=1,
- api_calls_per_minute=600,
- commands_per_scope=100,
- command_description_max=256,
The numbers the server you are on enforces:
BotInfo.limits.Read them rather than hard-coding them.
Botuses some by itself:text_maxto check a text before sending it, and the four rate limits to pace its ownRateLimiter.Each field is a JSON name of
limitsin Python style:text_maxistextMax,poll_timeout_max_sispollTimeoutMaxS. The defaults are the documented numbers. They apply only when a server leaves a name out, or sends something that is not a positive whole number. A limit this version has no field for is still inraw: read it withget.- text_max¶
The longest
text, in UTF-16 units, as JavaScript counts them: most emoji count 2. Default 4096.- Type:
- callback_data_bytes¶
The longest
callback_dataof a button, in bytes of UTF-8. Default 64.- Type:
- url_max¶
The longest URL of a link button, a callback answer or a webhook, in characters. Default 1024.
- Type:
- callback_window_ms¶
How long a button press can still be answered, in milliseconds. Default 60000.
- Type:
- webhook_timeout_ms¶
How long the server waits for the webhook to answer, in milliseconds. Default 10000.
- Type:
- get(name, default=None)¶
Reads any limit by its JSON name, including the ones this version has no field for.
It reads what the server sent, in
raw. A name the server left out givesdefault, even when a field has a documented default for it.- Parameters:
- Returns:
The server’s number, or
default.- Return type:
int | None
Example
>>> limits = Limits.from_json({"textMax": 4096, "botsPerChat": 20}) >>> limits.get("botsPerChat") 20 >>> limits.get("pollLimitMax") is None True >>> limits.poll_limit_max 100
- class WebhookInfo(
- *,
- raw=<factory>,
- url=None,
- has_secret=False,
- pending=0,
- failures=0,
- last_error=None,
- last_ok_at=None,
- next_attempt_at=None,
The webhook, if any, and how deliveries are going:
Bot.get_webhook.GET /webhookfills every field.POST /webhookandDELETE /webhook(Bot.set_webhookandBot.delete_webhook) answer withurl,has_secretandpendingonly, and the other fields keep their defaults.- next_attempt_at¶
While the server backs off after a failure, when it tries again; otherwise
None.- Type:
datetime | None
Example
See how the webhook is doing:
info = await bot.get_webhook() if info.failures: print(f"{info.failures} failures in a row, last: {info.last_error}")
- classmethod from_json(data)¶
Builds the webhook’s state from the answer of
GET /webhook.POST /webhookandDELETE /webhookanswer with the same object, with fewer fields.- Parameters:
data (Any) – The answer.
- Returns:
The webhook’s state, with
datakept inraw.- Raises:
PayloadError –
datais not an object.- Return type:
- class WebhookSummary(
- *,
- raw=<factory>,
- url=None,
- has_secret=False,
- failures=0,
- last_error=None,
The webhook in brief, as part of
BotInfo.Bot.get_webhookgives the whole picture, as aWebhookInfo.
- class PublishedKey(*, raw=<factory>, fingerprint, mode)¶
What the server answers when the bot publishes its key (
PUT /key).Bot.publish_keyandBot.rotate_keyreturn it. From then on the bot is protected: its private chats are end-to-end encrypted, and only its owner can turn that off, from the app.- classmethod from_json(data)¶
Builds the answer of
PUT /keyfrom its JSON.- Parameters:
data (Any) – The answer, with
fingerprintandmode.- Returns:
The published key, with
datakept inraw. A missingfingerprintis"", a missingmodeis"protected".- Raises:
PayloadError –
datais not an object.- Return type:
Commands¶
- class Command(*, raw=<factory>, command, description)¶
One entry of the menu the app shows when somebody types
/.Bot.get_commands, given a scope, returns a list of them. You can also make one to pass toBot.set_commands, which takes(name, description)pairs as well.- to_json()¶
Turns the command into the API’s JSON form.
- Returns:
A dict with
commandanddescription, asPUT /commandstakes it.- Return type:
Example
>>> Command(command="start", description="Say hello").to_json() {'command': 'start', 'description': 'Say hello'}
- classmethod from_json(data)¶
Builds a command from the API’s JSON (
command,description).- Parameters:
data (Any) – One command object, as
GET /commandslists them.- Returns:
The command, with
datakept inraw. A missing field is"".- Raises:
PayloadError –
datais not an object.- Return type:
- class CommandScope(*, raw=<factory>, scope, commands)¶
One command menu, and where the app shows it.
Bot.get_commands, without a scope, lists one per scope;Bot.set_commandsandBot.delete_commandsanswer with the one they changed. The app shows the narrowest scope that has commands: in a private chatprivate, thendefault; in a groupchat:<id>, thengroupAdmins(for admins), thengroups, thendefault.- classmethod from_json(data)¶
Builds a menu from the API’s JSON (
scope,commands).- Parameters:
data (Any) – One scope object, as
GET /commandslists them andPUT /commandsanswers.- Returns:
The menu, with
datakept inraw. A missingscopeis"default".- Raises:
PayloadError –
datais not an object, or one of its commands is not an object.- Return type: