Upgrade architecture + 5.0 Bot API (#469)

Upgrade architecture + 5.0 Bot API (#469)
* Moved `methods`, `types` and `client` to root package
* Removed update handler from routers to dispatcher
* Reworked events propagation mechanism to handlers
* Reworked inner middlewares logic (very small change)
* Updated to Bot API 5.0
* Initial migration from MkDocs to Sphinx + config for readthedocs
This commit is contained in:
Alex Root Junior 2021-01-26 21:20:52 +02:00 committed by GitHub
parent 566b7ff282
commit 4008a3114d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
608 changed files with 12537 additions and 6427 deletions

View file

@ -19,7 +19,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip poetry==1.0
python -m pip install --upgrade pip poetry==1.1.4
poetry install
mkdir -p reports

View file

@ -13,6 +13,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
max-parallel: 6
fail-fast: false
matrix:
os:
- ubuntu-latest
@ -21,6 +22,7 @@ jobs:
python-version:
- 3.7
- 3.8
- 3.9
steps:
- uses: actions/checkout@master
@ -32,7 +34,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip poetry==1.0
python -m pip install --upgrade pip poetry==1.1.4
poetry install
- name: Lint code

2
.gitignore vendored
View file

@ -1,10 +1,12 @@
.idea/
.vscode/
__pycache__/
*.py[cod]
env/
build/
_build/
dist/
site/
*.egg-info/

14
.readthedocs.yml Normal file
View file

@ -0,0 +1,14 @@
version: 2
sphinx:
configuration: docs2/conf.py
formats: all
python:
version: 3.8
install:
- method: pip
path: .
extra_requirements:
- docs

View file

@ -70,7 +70,7 @@ clean:
.PHONY: isort
isort:
$(py) isort -rc aiogram tests
$(py) isort aiogram tests
.PHONY: black
black:

View file

@ -1,6 +1,5 @@
from .api import methods, types
from .api.client import session
from .api.client.bot import Bot
from .client import session
from .client.bot import Bot
from .dispatcher import filters, handler
from .dispatcher.dispatcher import Dispatcher
from .dispatcher.middlewares.base import BaseMiddleware
@ -28,5 +27,5 @@ __all__ = (
"handler",
)
__version__ = "3.0.0a5"
__api_version__ = "4.9"
__version__ = "3.0.0a6"
__api_version__ = "5.0"

View file

@ -1,38 +0,0 @@
from dataclasses import dataclass
@dataclass
class TelegramAPIServer:
"""
Base config for API Endpoints
"""
base: str
file: str
def api_url(self, token: str, method: str) -> str:
"""
Generate URL for API methods
:param token: Bot token
:param method: API method name (case insensitive)
:return: URL
"""
return self.base.format(token=token, method=method)
def file_url(self, token: str, path: str) -> str:
"""
Generate URL for downloading files
:param token: Bot token
:param path: file path
:return: URL
"""
return self.file.format(token=token, path=path)
# Main API server
PRODUCTION = TelegramAPIServer(
base="https://api.telegram.org/bot{token}/{method}",
file="https://api.telegram.org/file/bot{token}/{path}",
)

View file

@ -1,44 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Optional
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class AnswerCallbackQuery(TelegramMethod[bool]):
"""
Use this method to send answers to callback queries sent from inline keyboards. The answer
will be displayed to the user as a notification at the top of the chat screen or as an alert.
On success, True is returned.
Alternatively, the user can be redirected to the specified Game URL. For this option to work,
you must first create a game for your bot via @Botfather and accept the terms. Otherwise, you
may use links like t.me/your_bot?start=XXXX that open your bot with a parameter.
Source: https://core.telegram.org/bots/api#answercallbackquery
"""
__returning__ = bool
callback_query_id: str
"""Unique identifier for the query to be answered"""
text: Optional[str] = None
"""Text of the notification. If not specified, nothing will be shown to the user, 0-200
characters"""
show_alert: Optional[bool] = None
"""If true, an alert will be shown by the client instead of a notification at the top of the
chat screen. Defaults to false."""
url: Optional[str] = None
"""URL that will be opened by the user's client. If you have created a Game and accepted the
conditions via @Botfather, specify the URL that opens your game note that this will only
work if the query comes from a callback_game button."""
cache_time: Optional[int] = None
"""The maximum amount of time in seconds that the result of the callback query may be cached
client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0."""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict()
return Request(method="answerCallbackQuery", data=data)

View file

@ -1,37 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Optional
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class AnswerPreCheckoutQuery(TelegramMethod[bool]):
"""
Once the user has confirmed their payment and shipping details, the Bot API sends the final
confirmation in the form of an Update with the field pre_checkout_query. Use this method to
respond to such pre-checkout queries. On success, True is returned. Note: The Bot API must
receive an answer within 10 seconds after the pre-checkout query was sent.
Source: https://core.telegram.org/bots/api#answerprecheckoutquery
"""
__returning__ = bool
pre_checkout_query_id: str
"""Unique identifier for the query to be answered"""
ok: bool
"""Specify True if everything is alright (goods are available, etc.) and the bot is ready to
proceed with the order. Use False if there are any problems."""
error_message: Optional[str] = None
"""Required if ok is False. Error message in human readable form that explains the reason for
failure to proceed with the checkout (e.g. "Sorry, somebody just bought the last of our
amazing black T-shirts while you were busy filling out your payment details. Please choose
a different color or garment!"). Telegram will display this message to the user."""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict()
return Request(method="answerPreCheckoutQuery", data=data)

View file

@ -1,40 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import UNSET, InlineKeyboardMarkup, Message
from .base import Request, TelegramMethod, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class EditMessageCaption(TelegramMethod[Union[Message, bool]]):
"""
Use this method to edit captions of messages. On success, if edited message is sent by the
bot, the edited Message is returned, otherwise True is returned.
Source: https://core.telegram.org/bots/api#editmessagecaption
"""
__returning__ = Union[Message, bool]
chat_id: Optional[Union[int, str]] = None
"""Required if inline_message_id is not specified. Unique identifier for the target chat or
username of the target channel (in the format @channelusername)"""
message_id: Optional[int] = None
"""Required if inline_message_id is not specified. Identifier of the message to edit"""
inline_message_id: Optional[str] = None
"""Required if chat_id and message_id are not specified. Identifier of the inline message"""
caption: Optional[str] = None
"""New caption of the message, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the message caption. See formatting options for more details."""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""A JSON-serialized object for an inline keyboard."""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict()
prepare_parse_mode(bot, data)
return Request(method="editMessageCaption", data=data)

View file

@ -1,41 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import InlineKeyboardMarkup, Message
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class EditMessageLiveLocation(TelegramMethod[Union[Message, bool]]):
"""
Use this method to edit live location messages. A location can be edited until its live_period
expires or editing is explicitly disabled by a call to stopMessageLiveLocation. On success, if
the edited message was sent by the bot, the edited Message is returned, otherwise True is
returned.
Source: https://core.telegram.org/bots/api#editmessagelivelocation
"""
__returning__ = Union[Message, bool]
latitude: float
"""Latitude of new location"""
longitude: float
"""Longitude of new location"""
chat_id: Optional[Union[int, str]] = None
"""Required if inline_message_id is not specified. Unique identifier for the target chat or
username of the target channel (in the format @channelusername)"""
message_id: Optional[int] = None
"""Required if inline_message_id is not specified. Identifier of the message to edit"""
inline_message_id: Optional[str] = None
"""Required if chat_id and message_id are not specified. Identifier of the inline message"""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""A JSON-serialized object for a new inline keyboard."""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict()
return Request(method="editMessageLiveLocation", data=data)

View file

@ -1,41 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import UNSET, InlineKeyboardMarkup, Message
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class EditMessageText(TelegramMethod[Union[Message, bool]]):
"""
Use this method to edit text and game messages. On success, if edited message is sent by the
bot, the edited Message is returned, otherwise True is returned.
Source: https://core.telegram.org/bots/api#editmessagetext
"""
__returning__ = Union[Message, bool]
text: str
"""New text of the message, 1-4096 characters after entities parsing"""
chat_id: Optional[Union[int, str]] = None
"""Required if inline_message_id is not specified. Unique identifier for the target chat or
username of the target channel (in the format @channelusername)"""
message_id: Optional[int] = None
"""Required if inline_message_id is not specified. Identifier of the message to edit"""
inline_message_id: Optional[str] = None
"""Required if chat_id and message_id are not specified. Identifier of the inline message"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the message text. See formatting options for more details."""
disable_web_page_preview: Optional[bool] = None
"""Disables link previews for links in this message"""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""A JSON-serialized object for an inline keyboard."""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict()
return Request(method="editMessageText", data=data)

View file

@ -1,34 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class ExportChatInviteLink(TelegramMethod[str]):
"""
Use this method to generate a new invite link for a chat; any previously generated link is
revoked. The bot must be an administrator in the chat for this to work and must have the
appropriate admin rights. Returns the new invite link as String on success.
Note: Each administrator in a chat generates their own invite links. Bots can't use invite
links generated by other administrators. If you want your bot to work with invite links, it
will need to generate its own link using exportChatInviteLink after this the link will
become available to the bot via the getChat method. If your bot needs to generate a new invite
link replacing its previous one, use exportChatInviteLink again.
Source: https://core.telegram.org/bots/api#exportchatinvitelink
"""
__returning__ = str
chat_id: Union[int, str]
"""Unique identifier for the target chat or username of the target channel (in the format
@channelusername)"""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict()
return Request(method="exportChatInviteLink", data=data)

View file

@ -1,33 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict
from ..types import File
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class GetFile(TelegramMethod[File]):
"""
Use this method to get basic info about a file and prepare it for downloading. For the moment,
bots can download files of up to 20MB in size. On success, a File object is returned. The file
can then be downloaded via the link https://api.telegram.org/file/bot<token>/<file_path>,
where <file_path> is taken from the response. It is guaranteed that the link will be valid for
at least 1 hour. When the link expires, a new one can be requested by calling getFile again.
Note: This function may not preserve the original file name and MIME type. You should save the
file's MIME type and name (if available) when the File object is received.
Source: https://core.telegram.org/bots/api#getfile
"""
__returning__ = File
file_id: str
"""File identifier to get info about"""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict()
return Request(method="getFile", data=data)

View file

@ -1,48 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, List, Optional
from ..types import Update
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class GetUpdates(TelegramMethod[List[Update]]):
"""
Use this method to receive incoming updates using long polling (wiki). An Array of Update
objects is returned.
Notes
1. This method will not work if an outgoing webhook is set up.
2. In order to avoid getting duplicate updates, recalculate offset after each server response.
Source: https://core.telegram.org/bots/api#getupdates
"""
__returning__ = List[Update]
offset: Optional[int] = None
"""Identifier of the first update to be returned. Must be greater by one than the highest
among the identifiers of previously received updates. By default, updates starting with the
earliest unconfirmed update are returned. An update is considered confirmed as soon as
getUpdates is called with an offset higher than its update_id. The negative offset can be
specified to retrieve updates starting from -offset update from the end of the updates
queue. All previous updates will forgotten."""
limit: Optional[int] = None
"""Limits the number of updates to be retrieved. Values between 1-100 are accepted. Defaults
to 100."""
timeout: Optional[int] = None
"""Timeout in seconds for long polling. Defaults to 0, i.e. usual short polling. Should be
positive, short polling should be used for testing purposes only."""
allowed_updates: Optional[List[str]] = None
"""A JSON-serialized list of the update types you want your bot to receive. For example,
specify ['message', 'edited_channel_post', 'callback_query'] to only receive updates of
these types. See Update for a complete list of available update types. Specify an empty
list to receive all updates regardless of type (default). If not specified, the previous
setting will be used."""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict()
return Request(method="getUpdates", data=data)

View file

@ -1,74 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import (
UNSET,
ForceReply,
InlineKeyboardMarkup,
InputFile,
Message,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
)
from .base import Request, TelegramMethod, prepare_file
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class SendAnimation(TelegramMethod[Message]):
"""
Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound). On
success, the sent Message is returned. Bots can currently send animation files of up to 50 MB
in size, this limit may be changed in the future.
Source: https://core.telegram.org/bots/api#sendanimation
"""
__returning__ = Message
chat_id: Union[int, str]
"""Unique identifier for the target chat or username of the target channel (in the format
@channelusername)"""
animation: Union[InputFile, str]
"""Animation to send. Pass a file_id as String to send an animation that exists on the
Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an
animation from the Internet, or upload a new animation using multipart/form-data."""
duration: Optional[int] = None
"""Duration of sent animation in seconds"""
width: Optional[int] = None
"""Animation width"""
height: Optional[int] = None
"""Animation height"""
thumb: Optional[Union[InputFile, str]] = None
"""Thumbnail of the file sent; can be ignored if thumbnail generation for the file is
supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded
using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new
file, so you can pass 'attach://<file_attach_name>' if the thumbnail was uploaded using
multipart/form-data under <file_attach_name>."""
caption: Optional[str] = None
"""Animation caption (may also be used when resending animation by file_id), 0-1024 characters
after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the animation caption. See formatting options for more
details."""
disable_notification: Optional[bool] = None
"""Sends the message silently. Users will receive a notification with no sound."""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
reply_markup: Optional[
Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
] = None
"""Additional interface options. A JSON-serialized object for an inline keyboard, custom reply
keyboard, instructions to remove reply keyboard or to force a reply from the user."""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict(exclude={"animation", "thumb"})
files: Dict[str, InputFile] = {}
prepare_file(data=data, files=files, name="animation", value=self.animation)
prepare_file(data=data, files=files, name="thumb", value=self.thumb)
return Request(method="sendAnimation", data=data, files=files)

View file

@ -1,74 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import (
UNSET,
ForceReply,
InlineKeyboardMarkup,
InputFile,
Message,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
)
from .base import Request, TelegramMethod, prepare_file
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class SendAudio(TelegramMethod[Message]):
"""
Use this method to send audio files, if you want Telegram clients to display them in the music
player. Your audio must be in the .MP3 or .M4A format. On success, the sent Message is
returned. Bots can currently send audio files of up to 50 MB in size, this limit may be
changed in the future.
For sending voice messages, use the sendVoice method instead.
Source: https://core.telegram.org/bots/api#sendaudio
"""
__returning__ = Message
chat_id: Union[int, str]
"""Unique identifier for the target chat or username of the target channel (in the format
@channelusername)"""
audio: Union[InputFile, str]
"""Audio file to send. Pass a file_id as String to send an audio file that exists on the
Telegram servers (recommended), pass an HTTP URL as a String for Telegram to get an audio
file from the Internet, or upload a new one using multipart/form-data."""
caption: Optional[str] = None
"""Audio caption, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the audio caption. See formatting options for more details."""
duration: Optional[int] = None
"""Duration of the audio in seconds"""
performer: Optional[str] = None
"""Performer"""
title: Optional[str] = None
"""Track name"""
thumb: Optional[Union[InputFile, str]] = None
"""Thumbnail of the file sent; can be ignored if thumbnail generation for the file is
supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded
using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new
file, so you can pass 'attach://<file_attach_name>' if the thumbnail was uploaded using
multipart/form-data under <file_attach_name>."""
disable_notification: Optional[bool] = None
"""Sends the message silently. Users will receive a notification with no sound."""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
reply_markup: Optional[
Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
] = None
"""Additional interface options. A JSON-serialized object for an inline keyboard, custom reply
keyboard, instructions to remove reply keyboard or to force a reply from the user."""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict(exclude={"audio", "thumb"})
files: Dict[str, InputFile] = {}
prepare_file(data=data, files=files, name="audio", value=self.audio)
prepare_file(data=data, files=files, name="thumb", value=self.thumb)
return Request(method="sendAudio", data=data, files=files)

View file

@ -1,40 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class SendChatAction(TelegramMethod[bool]):
"""
Use this method when you need to tell the user that something is happening on the bot's side.
The status is set for 5 seconds or less (when a message arrives from your bot, Telegram
clients clear its typing status). Returns True on success.
Example: The ImageBot needs some time to process a request and upload the image. Instead of
sending a text message along the lines of 'Retrieving image, please wait…', the bot may use
sendChatAction with action = upload_photo. The user will see a 'sending photo' status for the
bot.
We only recommend using this method when a response from the bot will take a noticeable amount
of time to arrive.
Source: https://core.telegram.org/bots/api#sendchataction
"""
__returning__ = bool
chat_id: Union[int, str]
"""Unique identifier for the target chat or username of the target channel (in the format
@channelusername)"""
action: str
"""Type of action to broadcast. Choose one, depending on what the user is about to receive:
typing for text messages, upload_photo for photos, record_video or upload_video for videos,
record_audio or upload_audio for audio files, upload_document for general files,
find_location for location data, record_video_note or upload_video_note for video notes."""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict()
return Request(method="sendChatAction", data=data)

View file

@ -1,67 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import (
UNSET,
ForceReply,
InlineKeyboardMarkup,
InputFile,
Message,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
)
from .base import Request, TelegramMethod, prepare_file
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class SendDocument(TelegramMethod[Message]):
"""
Use this method to send general files. On success, the sent Message is returned. Bots can
currently send files of any type of up to 50 MB in size, this limit may be changed in the
future.
Source: https://core.telegram.org/bots/api#senddocument
"""
__returning__ = Message
chat_id: Union[int, str]
"""Unique identifier for the target chat or username of the target channel (in the format
@channelusername)"""
document: Union[InputFile, str]
"""File to send. Pass a file_id as String to send a file that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get a file from the Internet,
or upload a new one using multipart/form-data."""
thumb: Optional[Union[InputFile, str]] = None
"""Thumbnail of the file sent; can be ignored if thumbnail generation for the file is
supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded
using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new
file, so you can pass 'attach://<file_attach_name>' if the thumbnail was uploaded using
multipart/form-data under <file_attach_name>."""
caption: Optional[str] = None
"""Document caption (may also be used when resending documents by file_id), 0-1024 characters
after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the document caption. See formatting options for more details."""
disable_notification: Optional[bool] = None
"""Sends the message silently. Users will receive a notification with no sound."""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
reply_markup: Optional[
Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
] = None
"""Additional interface options. A JSON-serialized object for an inline keyboard, custom reply
keyboard, instructions to remove reply keyboard or to force a reply from the user."""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict(exclude={"document", "thumb"})
files: Dict[str, InputFile] = {}
prepare_file(data=data, files=files, name="document", value=self.document)
prepare_file(data=data, files=files, name="thumb", value=self.thumb)
return Request(method="sendDocument", data=data, files=files)

View file

@ -1,77 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, List, Optional
from ..types import InlineKeyboardMarkup, LabeledPrice, Message
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class SendInvoice(TelegramMethod[Message]):
"""
Use this method to send invoices. On success, the sent Message is returned.
Source: https://core.telegram.org/bots/api#sendinvoice
"""
__returning__ = Message
chat_id: int
"""Unique identifier for the target private chat"""
title: str
"""Product name, 1-32 characters"""
description: str
"""Product description, 1-255 characters"""
payload: str
"""Bot-defined invoice payload, 1-128 bytes. This will not be displayed to the user, use for
your internal processes."""
provider_token: str
"""Payments provider token, obtained via Botfather"""
start_parameter: str
"""Unique deep-linking parameter that can be used to generate this invoice when used as a
start parameter"""
currency: str
"""Three-letter ISO 4217 currency code, see more on currencies"""
prices: List[LabeledPrice]
"""Price breakdown, a JSON-serialized list of components (e.g. product price, tax, discount,
delivery cost, delivery tax, bonus, etc.)"""
provider_data: Optional[str] = None
"""JSON-encoded data about the invoice, which will be shared with the payment provider. A
detailed description of required fields should be provided by the payment provider."""
photo_url: Optional[str] = None
"""URL of the product photo for the invoice. Can be a photo of the goods or a marketing image
for a service. People like it better when they see what they are paying for."""
photo_size: Optional[int] = None
"""Photo size"""
photo_width: Optional[int] = None
"""Photo width"""
photo_height: Optional[int] = None
"""Photo height"""
need_name: Optional[bool] = None
"""Pass True, if you require the user's full name to complete the order"""
need_phone_number: Optional[bool] = None
"""Pass True, if you require the user's phone number to complete the order"""
need_email: Optional[bool] = None
"""Pass True, if you require the user's email address to complete the order"""
need_shipping_address: Optional[bool] = None
"""Pass True, if you require the user's shipping address to complete the order"""
send_phone_number_to_provider: Optional[bool] = None
"""Pass True, if user's phone number should be sent to provider"""
send_email_to_provider: Optional[bool] = None
"""Pass True, if user's email address should be sent to provider"""
is_flexible: Optional[bool] = None
"""Pass True, if the final price depends on the shipping method"""
disable_notification: Optional[bool] = None
"""Sends the message silently. Users will receive a notification with no sound."""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""A JSON-serialized object for an inline keyboard. If empty, one 'Pay total price' button
will be shown. If not empty, the first button must be a Pay button."""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict()
return Request(method="sendInvoice", data=data)

View file

@ -1,39 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
from ..types import InputFile, InputMediaPhoto, InputMediaVideo, Message
from .base import Request, TelegramMethod, prepare_input_media, prepare_parse_mode
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class SendMediaGroup(TelegramMethod[List[Message]]):
"""
Use this method to send a group of photos or videos as an album. On success, an array of the
sent Messages is returned.
Source: https://core.telegram.org/bots/api#sendmediagroup
"""
__returning__ = List[Message]
chat_id: Union[int, str]
"""Unique identifier for the target chat or username of the target channel (in the format
@channelusername)"""
media: List[Union[InputMediaPhoto, InputMediaVideo]]
"""A JSON-serialized array describing photos and videos to be sent, must include 2-10 items"""
disable_notification: Optional[bool] = None
"""Sends the messages silently. Users will receive a notification with no sound."""
reply_to_message_id: Optional[int] = None
"""If the messages are a reply, ID of the original message"""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict()
prepare_parse_mode(bot, data["media"])
files: Dict[str, InputFile] = {}
prepare_input_media(data, files)
return Request(method="sendMediaGroup", data=data, files=files)

View file

@ -1,57 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import (
UNSET,
ForceReply,
InlineKeyboardMarkup,
InputFile,
Message,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
)
from .base import Request, TelegramMethod, prepare_file
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class SendPhoto(TelegramMethod[Message]):
"""
Use this method to send photos. On success, the sent Message is returned.
Source: https://core.telegram.org/bots/api#sendphoto
"""
__returning__ = Message
chat_id: Union[int, str]
"""Unique identifier for the target chat or username of the target channel (in the format
@channelusername)"""
photo: Union[InputFile, str]
"""Photo to send. Pass a file_id as String to send a photo that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get a photo from the Internet,
or upload a new photo using multipart/form-data."""
caption: Optional[str] = None
"""Photo caption (may also be used when resending photos by file_id), 0-1024 characters after
entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the photo caption. See formatting options for more details."""
disable_notification: Optional[bool] = None
"""Sends the message silently. Users will receive a notification with no sound."""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
reply_markup: Optional[
Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
] = None
"""Additional interface options. A JSON-serialized object for an inline keyboard, custom reply
keyboard, instructions to remove reply keyboard or to force a reply from the user."""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict(exclude={"photo"})
files: Dict[str, InputFile] = {}
prepare_file(data=data, files=files, name="photo", value=self.photo)
return Request(method="sendPhoto", data=data, files=files)

View file

@ -1,75 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import (
UNSET,
ForceReply,
InlineKeyboardMarkup,
InputFile,
Message,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
)
from .base import Request, TelegramMethod, prepare_file
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class SendVideo(TelegramMethod[Message]):
"""
Use this method to send video files, Telegram clients support mp4 videos (other formats may be
sent as Document). On success, the sent Message is returned. Bots can currently send video
files of up to 50 MB in size, this limit may be changed in the future.
Source: https://core.telegram.org/bots/api#sendvideo
"""
__returning__ = Message
chat_id: Union[int, str]
"""Unique identifier for the target chat or username of the target channel (in the format
@channelusername)"""
video: Union[InputFile, str]
"""Video to send. Pass a file_id as String to send a video that exists on the Telegram servers
(recommended), pass an HTTP URL as a String for Telegram to get a video from the Internet,
or upload a new video using multipart/form-data."""
duration: Optional[int] = None
"""Duration of sent video in seconds"""
width: Optional[int] = None
"""Video width"""
height: Optional[int] = None
"""Video height"""
thumb: Optional[Union[InputFile, str]] = None
"""Thumbnail of the file sent; can be ignored if thumbnail generation for the file is
supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded
using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new
file, so you can pass 'attach://<file_attach_name>' if the thumbnail was uploaded using
multipart/form-data under <file_attach_name>."""
caption: Optional[str] = None
"""Video caption (may also be used when resending videos by file_id), 0-1024 characters after
entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the video caption. See formatting options for more details."""
supports_streaming: Optional[bool] = None
"""Pass True, if the uploaded video is suitable for streaming"""
disable_notification: Optional[bool] = None
"""Sends the message silently. Users will receive a notification with no sound."""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
reply_markup: Optional[
Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
] = None
"""Additional interface options. A JSON-serialized object for an inline keyboard, custom reply
keyboard, instructions to remove reply keyboard or to force a reply from the user."""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict(exclude={"video", "thumb"})
files: Dict[str, InputFile] = {}
prepare_file(data=data, files=files, name="video", value=self.video)
prepare_file(data=data, files=files, name="thumb", value=self.thumb)
return Request(method="sendVideo", data=data, files=files)

View file

@ -1,63 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
from ..types import (
UNSET,
ForceReply,
InlineKeyboardMarkup,
InputFile,
Message,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
)
from .base import Request, TelegramMethod, prepare_file
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class SendVoice(TelegramMethod[Message]):
"""
Use this method to send audio files, if you want Telegram clients to display the file as a
playable voice message. For this to work, your audio must be in an .OGG file encoded with OPUS
(other formats may be sent as Audio or Document). On success, the sent Message is returned.
Bots can currently send voice messages of up to 50 MB in size, this limit may be changed in
the future.
Source: https://core.telegram.org/bots/api#sendvoice
"""
__returning__ = Message
chat_id: Union[int, str]
"""Unique identifier for the target chat or username of the target channel (in the format
@channelusername)"""
voice: Union[InputFile, str]
"""Audio file to send. Pass a file_id as String to send a file that exists on the Telegram
servers (recommended), pass an HTTP URL as a String for Telegram to get a file from the
Internet, or upload a new one using multipart/form-data."""
caption: Optional[str] = None
"""Voice message caption, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the voice message caption. See formatting options for more
details."""
duration: Optional[int] = None
"""Duration of the voice message in seconds"""
disable_notification: Optional[bool] = None
"""Sends the message silently. Users will receive a notification with no sound."""
reply_to_message_id: Optional[int] = None
"""If the message is a reply, ID of the original message"""
reply_markup: Optional[
Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply]
] = None
"""Additional interface options. A JSON-serialized object for an inline keyboard, custom reply
keyboard, instructions to remove reply keyboard or to force a reply from the user."""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict(exclude={"voice"})
files: Dict[str, InputFile] = {}
prepare_file(data=data, files=files, name="voice", value=self.voice)
return Request(method="sendVoice", data=data, files=files)

View file

@ -1,57 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, List, Optional
from ..types import InputFile
from .base import Request, TelegramMethod, prepare_file
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class SetWebhook(TelegramMethod[bool]):
"""
Use this method to specify a url and receive incoming updates via an outgoing webhook.
Whenever there is an update for the bot, we will send an HTTPS POST request to the specified
url, containing a JSON-serialized Update. In case of an unsuccessful request, we will give up
after a reasonable amount of attempts. Returns True on success.
If you'd like to make sure that the Webhook request comes from Telegram, we recommend using a
secret path in the URL, e.g. https://www.example.com/<token>. Since nobody else knows your
bot's token, you can be pretty sure it's us.
Notes
1. You will not be able to receive updates using getUpdates for as long as an outgoing webhook
is set up.
2. To use a self-signed certificate, you need to upload your public key certificate using
certificate parameter. Please upload as InputFile, sending a String will not work.
3. Ports currently supported for Webhooks: 443, 80, 88, 8443.
NEW! If you're having any trouble setting up webhooks, please check out this amazing guide to
Webhooks.
Source: https://core.telegram.org/bots/api#setwebhook
"""
__returning__ = bool
url: str
"""HTTPS url to send updates to. Use an empty string to remove webhook integration"""
certificate: Optional[InputFile] = None
"""Upload your public key certificate so that the root certificate in use can be checked. See
our self-signed guide for details."""
max_connections: Optional[int] = None
"""Maximum allowed number of simultaneous HTTPS connections to the webhook for update
delivery, 1-100. Defaults to 40. Use lower values to limit the load on your bot's server,
and higher values to increase your bot's throughput."""
allowed_updates: Optional[List[str]] = None
"""A JSON-serialized list of the update types you want your bot to receive. For example,
specify ['message', 'edited_channel_post', 'callback_query'] to only receive updates of
these types. See Update for a complete list of available update types. Specify an empty
list to receive all updates regardless of type (default). If not specified, the previous
setting will be used."""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict(exclude={"certificate"})
files: Dict[str, InputFile] = {}
prepare_file(data=data, files=files, name="certificate", value=self.certificate)
return Request(method="setWebhook", data=data, files=files)

View file

@ -1,31 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class UnbanChatMember(TelegramMethod[bool]):
"""
Use this method to unban a previously kicked user in a supergroup or channel. The user will
not return to the group or channel automatically, but will be able to join via link, etc. The
bot must be an administrator for this to work. Returns True on success.
Source: https://core.telegram.org/bots/api#unbanchatmember
"""
__returning__ = bool
chat_id: Union[int, str]
"""Unique identifier for the target group or username of the target supergroup or channel (in
the format @username)"""
user_id: int
"""Unique identifier of the target user"""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict()
return Request(method="unbanChatMember", data=data)

View file

@ -1,29 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, Union
from .base import Request, TelegramMethod
if TYPE_CHECKING: # pragma: no cover
from ..client.bot import Bot
class UnpinChatMessage(TelegramMethod[bool]):
"""
Use this method to unpin a message in a group, a supergroup, or a channel. The bot must be an
administrator in the chat for this to work and must have the 'can_pin_messages' admin right in
the supergroup or 'can_edit_messages' admin right in the channel. Returns True on success.
Source: https://core.telegram.org/bots/api#unpinchatmessage
"""
__returning__ = bool
chat_id: Union[int, str]
"""Unique identifier for the target chat or username of the target channel (in the format
@channelusername)"""
def build_request(self, bot: Bot) -> Request:
data: Dict[str, Any] = self.dict()
return Request(method="unpinChatMessage", data=data)

View file

@ -1,72 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import TelegramObject
if TYPE_CHECKING: # pragma: no cover
from .message import Message
from .user import User
from ..methods import AnswerCallbackQuery
class CallbackQuery(TelegramObject):
"""
This object represents an incoming callback query from a callback button in an inline
keyboard. If the button that originated the query was attached to a message sent by the bot,
the field message will be present. If the button was attached to a message sent via the bot
(in inline mode), the field inline_message_id will be present. Exactly one of the fields data
or game_short_name will be present.
NOTE: After the user presses a callback button, Telegram clients will display a progress bar
until you call answerCallbackQuery. It is, therefore, necessary to react by calling
answerCallbackQuery even if no notification to the user is needed (e.g., without specifying
any of the optional parameters).
Source: https://core.telegram.org/bots/api#callbackquery
"""
id: str
"""Unique identifier for this query"""
from_user: User = Field(..., alias="from")
"""Sender"""
chat_instance: str
"""Global identifier, uniquely corresponding to the chat to which the message with the
callback button was sent. Useful for high scores in games."""
message: Optional[Message] = None
"""Message with the callback button that originated the query. Note that message content and
message date will not be available if the message is too old"""
inline_message_id: Optional[str] = None
"""Identifier of the message sent via the bot in inline mode, that originated the query."""
data: Optional[str] = None
"""Data associated with the callback button. Be aware that a bad client can send arbitrary
data in this field."""
game_short_name: Optional[str] = None
"""Short name of a Game to be returned, serves as the unique identifier for the game"""
def answer(
self,
text: Optional[str] = None,
show_alert: Optional[bool] = None,
url: Optional[str] = None,
cache_time: Optional[int] = None,
) -> AnswerCallbackQuery:
"""
Answer to callback query
:param text:
:param show_alert:
:param url:
:param cache_time:
:return:
"""
from ..methods import AnswerCallbackQuery
return AnswerCallbackQuery(
callback_query_id=self.id,
text=text,
show_alert=show_alert,
url=url,
cache_time=cache_time,
)

View file

@ -1,53 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from .base import TelegramObject
if TYPE_CHECKING: # pragma: no cover
from .chat_permissions import ChatPermissions
from .chat_photo import ChatPhoto
from .message import Message
class Chat(TelegramObject):
"""
This object represents a chat.
Source: https://core.telegram.org/bots/api#chat
"""
id: int
"""Unique identifier for this chat. This number may be greater than 32 bits and some
programming languages may have difficulty/silent defects in interpreting it. But it is
smaller than 52 bits, so a signed 64 bit integer or double-precision float type are safe
for storing this identifier."""
type: str
"""Type of chat, can be either 'private', 'group', 'supergroup' or 'channel'"""
title: Optional[str] = None
"""Title, for supergroups, channels and group chats"""
username: Optional[str] = None
"""Username, for private chats, supergroups and channels if available"""
first_name: Optional[str] = None
"""First name of the other party in a private chat"""
last_name: Optional[str] = None
"""Last name of the other party in a private chat"""
photo: Optional[ChatPhoto] = None
"""Chat photo. Returned only in getChat."""
description: Optional[str] = None
"""Description, for groups, supergroups and channel chats. Returned only in getChat."""
invite_link: Optional[str] = None
"""Chat invite link, for groups, supergroups and channel chats. Each administrator in a chat
generates their own invite links, so the bot must first generate the link using
exportChatInviteLink. Returned only in getChat."""
pinned_message: Optional[Message] = None
"""Pinned message, for groups, supergroups and channels. Returned only in getChat."""
permissions: Optional[ChatPermissions] = None
"""Default chat member permissions, for groups and supergroups. Returned only in getChat."""
slow_mode_delay: Optional[int] = None
"""For supergroups, the minimum allowed delay between consecutive messages sent by each
unpriviledged user. Returned only in getChat."""
sticker_set_name: Optional[str] = None
"""For supergroups, name of group sticker set. Returned only in getChat."""
can_set_sticker_set: Optional[bool] = None
"""True, if the bot can change the group sticker set. Returned only in getChat."""

View file

@ -1,91 +0,0 @@
from __future__ import annotations
import datetime
from typing import TYPE_CHECKING, Optional, Union
from ...utils import helper
from .base import TelegramObject
if TYPE_CHECKING: # pragma: no cover
from .user import User
class ChatMember(TelegramObject):
"""
This object contains information about one member of a chat.
Source: https://core.telegram.org/bots/api#chatmember
"""
user: User
"""Information about the user"""
status: str
"""The member's status in the chat. Can be 'creator', 'administrator', 'member', 'restricted',
'left' or 'kicked'"""
custom_title: Optional[str] = None
"""Owner and administrators only. Custom title for this user"""
until_date: Optional[Union[datetime.datetime, datetime.timedelta, int]] = None
"""Restricted and kicked only. Date when restrictions will be lifted for this user; unix time"""
can_be_edited: Optional[bool] = None
"""Administrators only. True, if the bot is allowed to edit administrator privileges of that
user"""
can_post_messages: Optional[bool] = None
"""Administrators only. True, if the administrator can post in the channel; channels only"""
can_edit_messages: Optional[bool] = None
"""Administrators only. True, if the administrator can edit messages of other users and can
pin messages; channels only"""
can_delete_messages: Optional[bool] = None
"""Administrators only. True, if the administrator can delete messages of other users"""
can_restrict_members: Optional[bool] = None
"""Administrators only. True, if the administrator can restrict, ban or unban chat members"""
can_promote_members: Optional[bool] = None
"""Administrators only. True, if the administrator can add new administrators with a subset of
their own privileges or demote administrators that he has promoted, directly or indirectly
(promoted by administrators that were appointed by the user)"""
can_change_info: Optional[bool] = None
"""Administrators and restricted only. True, if the user is allowed to change the chat title,
photo and other settings"""
can_invite_users: Optional[bool] = None
"""Administrators and restricted only. True, if the user is allowed to invite new users to the
chat"""
can_pin_messages: Optional[bool] = None
"""Administrators and restricted only. True, if the user is allowed to pin messages; groups
and supergroups only"""
is_member: Optional[bool] = None
"""Restricted only. True, if the user is a member of the chat at the moment of the request"""
can_send_messages: Optional[bool] = None
"""Restricted only. True, if the user is allowed to send text messages, contacts, locations
and venues"""
can_send_media_messages: Optional[bool] = None
"""Restricted only. True, if the user is allowed to send audios, documents, photos, videos,
video notes and voice notes"""
can_send_polls: Optional[bool] = None
"""Restricted only. True, if the user is allowed to send polls"""
can_send_other_messages: Optional[bool] = None
"""Restricted only. True, if the user is allowed to send animations, games, stickers and use
inline bots"""
can_add_web_page_previews: Optional[bool] = None
"""Restricted only. True, if the user is allowed to add web page previews to their messages"""
@property
def is_chat_admin(self) -> bool:
return self.status in {ChatMemberStatus.CREATOR, ChatMemberStatus.ADMINISTRATOR}
@property
def is_chat_member(self) -> bool:
return self.status not in {ChatMemberStatus.LEFT, ChatMemberStatus.KICKED}
class ChatMemberStatus(helper.Helper):
"""
Chat member status
"""
mode = helper.HelperMode.lowercase
CREATOR = helper.Item() # creator
ADMINISTRATOR = helper.Item() # administrator
MEMBER = helper.Item() # member
RESTRICTED = helper.Item() # restricted
LEFT = helper.Item() # left
KICKED = helper.Item() # kicked

View file

@ -1,34 +0,0 @@
from __future__ import annotations
from typing import Optional
from .base import MutableTelegramObject
class ChatPermissions(MutableTelegramObject):
"""
Describes actions that a non-administrator user is allowed to take in a chat.
Source: https://core.telegram.org/bots/api#chatpermissions
"""
can_send_messages: Optional[bool] = None
"""True, if the user is allowed to send text messages, contacts, locations and venues"""
can_send_media_messages: Optional[bool] = None
"""True, if the user is allowed to send audios, documents, photos, videos, video notes and
voice notes, implies can_send_messages"""
can_send_polls: Optional[bool] = None
"""True, if the user is allowed to send polls, implies can_send_messages"""
can_send_other_messages: Optional[bool] = None
"""True, if the user is allowed to send animations, games, stickers and use inline bots,
implies can_send_media_messages"""
can_add_web_page_previews: Optional[bool] = None
"""True, if the user is allowed to add web page previews to their messages, implies
can_send_media_messages"""
can_change_info: Optional[bool] = None
"""True, if the user is allowed to change the chat title, photo and other settings. Ignored in
public supergroups"""
can_invite_users: Optional[bool] = None
"""True, if the user is allowed to invite new users to the chat"""
can_pin_messages: Optional[bool] = None
"""True, if the user is allowed to pin messages. Ignored in public supergroups"""

View file

@ -1,35 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import TelegramObject
if TYPE_CHECKING: # pragma: no cover
from .location import Location
from .user import User
class ChosenInlineResult(TelegramObject):
"""
Represents a result of an inline query that was chosen by the user and sent to their chat
partner.
Note: It is necessary to enable inline feedback via @Botfather in order to receive these
objects in updates.
Source: https://core.telegram.org/bots/api#choseninlineresult
"""
result_id: str
"""The unique identifier for the result that was chosen"""
from_user: User = Field(..., alias="from")
"""The user that chose the result"""
query: str
"""The query that was used to obtain the result"""
location: Optional[Location] = None
"""Sender location, only for bots that require user location"""
inline_message_id: Optional[str] = None
"""Identifier of the sent inline message. Available only if there is an inline keyboard
attached to the message. Will be also received in callback queries and can be used to edit
the message."""

View file

@ -1,5 +0,0 @@
from typing_extensions import Protocol
class Downloadable(Protocol):
file_id: str

View file

@ -1,22 +0,0 @@
from __future__ import annotations
from .base import TelegramObject
class EncryptedCredentials(TelegramObject):
"""
Contains data required for decrypting and authenticating EncryptedPassportElement. See the
Telegram Passport Documentation for a complete description of the data decryption and
authentication processes.
Source: https://core.telegram.org/bots/api#encryptedcredentials
"""
data: str
"""Base64-encoded encrypted JSON-serialized data with unique user's payload, data hashes and
secrets required for EncryptedPassportElement decryption and authentication"""
hash: str
"""Base64-encoded data hash for data authentication"""
secret: str
"""Base64-encoded secret, encrypted with the bot's public RSA key, required for data
decryption"""

View file

@ -1,54 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, List, Optional
from .base import TelegramObject
if TYPE_CHECKING: # pragma: no cover
from .passport_file import PassportFile
class EncryptedPassportElement(TelegramObject):
"""
Contains information about documents or other Telegram Passport elements shared with the bot
by the user.
Source: https://core.telegram.org/bots/api#encryptedpassportelement
"""
type: str
"""Element type. One of 'personal_details', 'passport', 'driver_license', 'identity_card',
'internal_passport', 'address', 'utility_bill', 'bank_statement', 'rental_agreement',
'passport_registration', 'temporary_registration', 'phone_number', 'email'."""
hash: str
"""Base64-encoded element hash for using in PassportElementErrorUnspecified"""
data: Optional[str] = None
"""Base64-encoded encrypted Telegram Passport element data provided by the user, available for
'personal_details', 'passport', 'driver_license', 'identity_card', 'internal_passport' and
'address' types. Can be decrypted and verified using the accompanying EncryptedCredentials."""
phone_number: Optional[str] = None
"""User's verified phone number, available only for 'phone_number' type"""
email: Optional[str] = None
"""User's verified email address, available only for 'email' type"""
files: Optional[List[PassportFile]] = None
"""Array of encrypted files with documents provided by the user, available for 'utility_bill',
'bank_statement', 'rental_agreement', 'passport_registration' and 'temporary_registration'
types. Files can be decrypted and verified using the accompanying EncryptedCredentials."""
front_side: Optional[PassportFile] = None
"""Encrypted file with the front side of the document, provided by the user. Available for
'passport', 'driver_license', 'identity_card' and 'internal_passport'. The file can be
decrypted and verified using the accompanying EncryptedCredentials."""
reverse_side: Optional[PassportFile] = None
"""Encrypted file with the reverse side of the document, provided by the user. Available for
'driver_license' and 'identity_card'. The file can be decrypted and verified using the
accompanying EncryptedCredentials."""
selfie: Optional[PassportFile] = None
"""Encrypted file with the selfie of the user holding a document, provided by the user;
available for 'passport', 'driver_license', 'identity_card' and 'internal_passport'. The
file can be decrypted and verified using the accompanying EncryptedCredentials."""
translation: Optional[List[PassportFile]] = None
"""Array of encrypted files with translated versions of documents provided by the user.
Available if requested for 'passport', 'driver_license', 'identity_card',
'internal_passport', 'utility_bill', 'bank_statement', 'rental_agreement',
'passport_registration' and 'temporary_registration' types. Files can be decrypted and
verified using the accompanying EncryptedCredentials."""

View file

@ -1,27 +0,0 @@
from __future__ import annotations
from typing import Optional
from .base import TelegramObject
class File(TelegramObject):
"""
This object represents a file ready to be downloaded. The file can be downloaded via the link
https://api.telegram.org/file/bot<token>/<file_path>. It is guaranteed that the link will be
valid for at least 1 hour. When the link expires, a new one can be requested by calling
getFile.
Maximum file size to download is 20 MB
Source: https://core.telegram.org/bots/api#file
"""
file_id: str
"""Identifier for this file, which can be used to download or reuse the file"""
file_unique_id: str
"""Unique identifier for this file, which is supposed to be the same over time and for
different bots. Can't be used to download or reuse the file."""
file_size: Optional[int] = None
"""File size, if known"""
file_path: Optional[str] = None
"""File path. Use https://api.telegram.org/file/bot<token>/<file_path> to get the file."""

View file

@ -1,35 +0,0 @@
from __future__ import annotations
from typing import Optional
from .base import MutableTelegramObject
class ForceReply(MutableTelegramObject):
"""
Upon receiving a message with this object, Telegram clients will display a reply interface to
the user (act as if the user has selected the bot's message and tapped 'Reply'). This can be
extremely useful if you want to create user-friendly step-by-step interfaces without having to
sacrifice privacy mode.
Example: A poll bot for groups runs in privacy mode (only receives commands, replies to its
messages and mentions). There could be two ways to create a new poll:
Explain the user how to send a command with parameters (e.g. /newpoll question answer1
answer2). May be appealing for hardcore users but lacks modern day polish.
Guide the user through a step-by-step process. 'Please send me your question', 'Cool, now
let's add the first answer option', 'Great. Keep adding answer options, then send /done when
you're ready'.
The last option is definitely more attractive. And if you use ForceReply in your bot's
questions, it will receive the user's answers even if it only receives replies, commands and
mentions without any extra work for the user.
Source: https://core.telegram.org/bots/api#forcereply
"""
force_reply: bool
"""Shows reply interface to the user, as if they manually selected the bot's message and
tapped 'Reply'"""
selective: Optional[bool] = None
"""Use this parameter if you want to force reply from specific users only. Targets: 1) users
that are @mentioned in the text of the Message object; 2) if the bot's message is a reply
(has reply_to_message_id), sender of the original message."""

View file

@ -1,40 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from .base import MutableTelegramObject
if TYPE_CHECKING: # pragma: no cover
from .callback_game import CallbackGame
from .login_url import LoginUrl
class InlineKeyboardButton(MutableTelegramObject):
"""
This object represents one button of an inline keyboard. You must use exactly one of the
optional fields.
Source: https://core.telegram.org/bots/api#inlinekeyboardbutton
"""
text: str
"""Label text on the button"""
url: Optional[str] = None
"""HTTP or tg:// url to be opened when button is pressed"""
login_url: Optional[LoginUrl] = None
"""An HTTP URL used to automatically authorize the user. Can be used as a replacement for the
Telegram Login Widget."""
callback_data: Optional[str] = None
"""Data to be sent in a callback query to the bot when button is pressed, 1-64 bytes"""
switch_inline_query: Optional[str] = None
"""If set, pressing the button will prompt the user to select one of their chats, open that
chat and insert the bot's username and the specified inline query in the input field. Can
be empty, in which case just the bot's username will be inserted."""
switch_inline_query_current_chat: Optional[str] = None
"""If set, pressing the button will insert the bot's username and the specified inline query
in the current chat's input field. Can be empty, in which case only the bot's username will
be inserted."""
callback_game: Optional[CallbackGame] = None
"""Description of the game that will be launched when the user presses the button."""
pay: Optional[bool] = None
"""Specify True, to send a Pay button."""

View file

@ -1,22 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, List
from .base import MutableTelegramObject
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_button import InlineKeyboardButton
class InlineKeyboardMarkup(MutableTelegramObject):
"""
This object represents an inline keyboard that appears right next to the message it belongs
to.
Note: This will only work in Telegram versions released after 9 April, 2016. Older clients
will display unsupported message.
Source: https://core.telegram.org/bots/api#inlinekeyboardmarkup
"""
inline_keyboard: List[List[InlineKeyboardButton]]
"""Array of button rows, each represented by an Array of InlineKeyboardButton objects"""

View file

@ -1,32 +0,0 @@
from __future__ import annotations
from .base import MutableTelegramObject
class InlineQueryResult(MutableTelegramObject):
"""
This object represents one result of an inline query. Telegram clients currently support
results of the following 20 types:
- InlineQueryResultCachedAudio
- InlineQueryResultCachedDocument
- InlineQueryResultCachedGif
- InlineQueryResultCachedMpeg4Gif
- InlineQueryResultCachedPhoto
- InlineQueryResultCachedSticker
- InlineQueryResultCachedVideo
- InlineQueryResultCachedVoice
- InlineQueryResultArticle
- InlineQueryResultAudio
- InlineQueryResultContact
- InlineQueryResultGame
- InlineQueryResultDocument
- InlineQueryResultGif
- InlineQueryResultLocation
- InlineQueryResultMpeg4Gif
- InlineQueryResultPhoto
- InlineQueryResultVenue
- InlineQueryResultVideo
- InlineQueryResultVoice
Source: https://core.telegram.org/bots/api#inlinequeryresult
"""

View file

@ -1,45 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import UNSET
from .inline_query_result import InlineQueryResult
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_markup import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
class InlineQueryResultAudio(InlineQueryResult):
"""
Represents a link to an MP3 audio file. By default, this audio file will be sent by the user.
Alternatively, you can use input_message_content to send a message with the specified content
instead of the audio.
Note: This will only work in Telegram versions released after 9 April, 2016. Older clients
will ignore them.
Source: https://core.telegram.org/bots/api#inlinequeryresultaudio
"""
type: str = Field("audio", const=True)
"""Type of the result, must be audio"""
id: str
"""Unique identifier for this result, 1-64 bytes"""
audio_url: str
"""A valid URL for the audio file"""
title: str
"""Title"""
caption: Optional[str] = None
"""Caption, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the audio caption. See formatting options for more details."""
performer: Optional[str] = None
"""Performer"""
audio_duration: Optional[int] = None
"""Audio duration in seconds"""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""Inline keyboard attached to the message"""
input_message_content: Optional[InputMessageContent] = None
"""Content of the message to be sent instead of the audio"""

View file

@ -1,39 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import UNSET
from .inline_query_result import InlineQueryResult
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_markup import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
class InlineQueryResultCachedAudio(InlineQueryResult):
"""
Represents a link to an MP3 audio file stored on the Telegram servers. By default, this audio
file will be sent by the user. Alternatively, you can use input_message_content to send a
message with the specified content instead of the audio.
Note: This will only work in Telegram versions released after 9 April, 2016. Older clients
will ignore them.
Source: https://core.telegram.org/bots/api#inlinequeryresultcachedaudio
"""
type: str = Field("audio", const=True)
"""Type of the result, must be audio"""
id: str
"""Unique identifier for this result, 1-64 bytes"""
audio_file_id: str
"""A valid file identifier for the audio file"""
caption: Optional[str] = None
"""Caption, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the audio caption. See formatting options for more details."""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""Inline keyboard attached to the message"""
input_message_content: Optional[InputMessageContent] = None
"""Content of the message to be sent instead of the audio"""

View file

@ -1,43 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import UNSET
from .inline_query_result import InlineQueryResult
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_markup import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
class InlineQueryResultCachedDocument(InlineQueryResult):
"""
Represents a link to a file stored on the Telegram servers. By default, this file will be sent
by the user with an optional caption. Alternatively, you can use input_message_content to send
a message with the specified content instead of the file.
Note: This will only work in Telegram versions released after 9 April, 2016. Older clients
will ignore them.
Source: https://core.telegram.org/bots/api#inlinequeryresultcacheddocument
"""
type: str = Field("document", const=True)
"""Type of the result, must be document"""
id: str
"""Unique identifier for this result, 1-64 bytes"""
title: str
"""Title for the result"""
document_file_id: str
"""A valid file identifier for the file"""
description: Optional[str] = None
"""Short description of the result"""
caption: Optional[str] = None
"""Caption of the document to be sent, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the document caption. See formatting options for more details."""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""Inline keyboard attached to the message"""
input_message_content: Optional[InputMessageContent] = None
"""Content of the message to be sent instead of the file"""

View file

@ -1,39 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import UNSET
from .inline_query_result import InlineQueryResult
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_markup import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
class InlineQueryResultCachedGif(InlineQueryResult):
"""
Represents a link to an animated GIF file stored on the Telegram servers. By default, this
animated GIF file will be sent by the user with an optional caption. Alternatively, you can
use input_message_content to send a message with specified content instead of the animation.
Source: https://core.telegram.org/bots/api#inlinequeryresultcachedgif
"""
type: str = Field("gif", const=True)
"""Type of the result, must be gif"""
id: str
"""Unique identifier for this result, 1-64 bytes"""
gif_file_id: str
"""A valid file identifier for the GIF file"""
title: Optional[str] = None
"""Title for the result"""
caption: Optional[str] = None
"""Caption of the GIF file to be sent, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the caption. See formatting options for more details."""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""Inline keyboard attached to the message"""
input_message_content: Optional[InputMessageContent] = None
"""Content of the message to be sent instead of the GIF animation"""

View file

@ -1,40 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import UNSET
from .inline_query_result import InlineQueryResult
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_markup import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
"""
Represents a link to a video animation (H.264/MPEG-4 AVC video without sound) stored on the
Telegram servers. By default, this animated MPEG-4 file will be sent by the user with an
optional caption. Alternatively, you can use input_message_content to send a message with the
specified content instead of the animation.
Source: https://core.telegram.org/bots/api#inlinequeryresultcachedmpeg4gif
"""
type: str = Field("mpeg4_gif", const=True)
"""Type of the result, must be mpeg4_gif"""
id: str
"""Unique identifier for this result, 1-64 bytes"""
mpeg4_file_id: str
"""A valid file identifier for the MP4 file"""
title: Optional[str] = None
"""Title for the result"""
caption: Optional[str] = None
"""Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the caption. See formatting options for more details."""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""Inline keyboard attached to the message"""
input_message_content: Optional[InputMessageContent] = None
"""Content of the message to be sent instead of the video animation"""

View file

@ -1,41 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import UNSET
from .inline_query_result import InlineQueryResult
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_markup import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
class InlineQueryResultCachedPhoto(InlineQueryResult):
"""
Represents a link to a photo stored on the Telegram servers. By default, this photo will be
sent by the user with an optional caption. Alternatively, you can use input_message_content to
send a message with the specified content instead of the photo.
Source: https://core.telegram.org/bots/api#inlinequeryresultcachedphoto
"""
type: str = Field("photo", const=True)
"""Type of the result, must be photo"""
id: str
"""Unique identifier for this result, 1-64 bytes"""
photo_file_id: str
"""A valid file identifier of the photo"""
title: Optional[str] = None
"""Title for the result"""
description: Optional[str] = None
"""Short description of the result"""
caption: Optional[str] = None
"""Caption of the photo to be sent, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the photo caption. See formatting options for more details."""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""Inline keyboard attached to the message"""
input_message_content: Optional[InputMessageContent] = None
"""Content of the message to be sent instead of the photo"""

View file

@ -1,41 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import UNSET
from .inline_query_result import InlineQueryResult
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_markup import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
class InlineQueryResultCachedVideo(InlineQueryResult):
"""
Represents a link to a video file stored on the Telegram servers. By default, this video file
will be sent by the user with an optional caption. Alternatively, you can use
input_message_content to send a message with the specified content instead of the video.
Source: https://core.telegram.org/bots/api#inlinequeryresultcachedvideo
"""
type: str = Field("video", const=True)
"""Type of the result, must be video"""
id: str
"""Unique identifier for this result, 1-64 bytes"""
video_file_id: str
"""A valid file identifier for the video file"""
title: str
"""Title for the result"""
description: Optional[str] = None
"""Short description of the result"""
caption: Optional[str] = None
"""Caption of the video to be sent, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the video caption. See formatting options for more details."""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""Inline keyboard attached to the message"""
input_message_content: Optional[InputMessageContent] = None
"""Content of the message to be sent instead of the video"""

View file

@ -1,42 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import UNSET
from .inline_query_result import InlineQueryResult
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_markup import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
class InlineQueryResultCachedVoice(InlineQueryResult):
"""
Represents a link to a voice message stored on the Telegram servers. By default, this voice
message will be sent by the user. Alternatively, you can use input_message_content to send a
message with the specified content instead of the voice message.
Note: This will only work in Telegram versions released after 9 April, 2016. Older clients
will ignore them.
Source: https://core.telegram.org/bots/api#inlinequeryresultcachedvoice
"""
type: str = Field("voice", const=True)
"""Type of the result, must be voice"""
id: str
"""Unique identifier for this result, 1-64 bytes"""
voice_file_id: str
"""A valid file identifier for the voice message"""
title: str
"""Voice message title"""
caption: Optional[str] = None
"""Caption, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the voice message caption. See formatting options for more
details."""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""Inline keyboard attached to the message"""
input_message_content: Optional[InputMessageContent] = None
"""Content of the message to be sent instead of the voice message"""

View file

@ -1,52 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import UNSET
from .inline_query_result import InlineQueryResult
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_markup import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
class InlineQueryResultDocument(InlineQueryResult):
"""
Represents a link to a file. By default, this file will be sent by the user with an optional
caption. Alternatively, you can use input_message_content to send a message with the specified
content instead of the file. Currently, only .PDF and .ZIP files can be sent using this
method.
Note: This will only work in Telegram versions released after 9 April, 2016. Older clients
will ignore them.
Source: https://core.telegram.org/bots/api#inlinequeryresultdocument
"""
type: str = Field("document", const=True)
"""Type of the result, must be document"""
id: str
"""Unique identifier for this result, 1-64 bytes"""
title: str
"""Title for the result"""
document_url: str
"""A valid URL for the file"""
mime_type: str
"""Mime type of the content of the file, either 'application/pdf' or 'application/zip'"""
caption: Optional[str] = None
"""Caption of the document to be sent, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the document caption. See formatting options for more details."""
description: Optional[str] = None
"""Short description of the result"""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""Inline keyboard attached to the message"""
input_message_content: Optional[InputMessageContent] = None
"""Content of the message to be sent instead of the file"""
thumb_url: Optional[str] = None
"""URL of the thumbnail (jpeg only) for the file"""
thumb_width: Optional[int] = None
"""Thumbnail width"""
thumb_height: Optional[int] = None
"""Thumbnail height"""

View file

@ -1,50 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import UNSET
from .inline_query_result import InlineQueryResult
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_markup import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
class InlineQueryResultGif(InlineQueryResult):
"""
Represents a link to an animated GIF file. By default, this animated GIF file will be sent by
the user with optional caption. Alternatively, you can use input_message_content to send a
message with the specified content instead of the animation.
Source: https://core.telegram.org/bots/api#inlinequeryresultgif
"""
type: str = Field("gif", const=True)
"""Type of the result, must be gif"""
id: str
"""Unique identifier for this result, 1-64 bytes"""
gif_url: str
"""A valid URL for the GIF file. File size must not exceed 1MB"""
thumb_url: str
"""URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result"""
gif_width: Optional[int] = None
"""Width of the GIF"""
gif_height: Optional[int] = None
"""Height of the GIF"""
gif_duration: Optional[int] = None
"""Duration of the GIF"""
thumb_mime_type: Optional[str] = None
"""MIME type of the thumbnail, must be one of 'image/jpeg', 'image/gif', or 'video/mp4'.
Defaults to 'image/jpeg'"""
title: Optional[str] = None
"""Title for the result"""
caption: Optional[str] = None
"""Caption of the GIF file to be sent, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the caption. See formatting options for more details."""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""Inline keyboard attached to the message"""
input_message_content: Optional[InputMessageContent] = None
"""Content of the message to be sent instead of the GIF animation"""

View file

@ -1,46 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .inline_query_result import InlineQueryResult
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_markup import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
class InlineQueryResultLocation(InlineQueryResult):
"""
Represents a location on a map. By default, the location will be sent by the user.
Alternatively, you can use input_message_content to send a message with the specified content
instead of the location.
Note: This will only work in Telegram versions released after 9 April, 2016. Older clients
will ignore them.
Source: https://core.telegram.org/bots/api#inlinequeryresultlocation
"""
type: str = Field("location", const=True)
"""Type of the result, must be location"""
id: str
"""Unique identifier for this result, 1-64 Bytes"""
latitude: float
"""Location latitude in degrees"""
longitude: float
"""Location longitude in degrees"""
title: str
"""Location title"""
live_period: Optional[int] = None
"""Period in seconds for which the location can be updated, should be between 60 and 86400."""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""Inline keyboard attached to the message"""
input_message_content: Optional[InputMessageContent] = None
"""Content of the message to be sent instead of the location"""
thumb_url: Optional[str] = None
"""Url of the thumbnail for the result"""
thumb_width: Optional[int] = None
"""Thumbnail width"""
thumb_height: Optional[int] = None
"""Thumbnail height"""

View file

@ -1,51 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import UNSET
from .inline_query_result import InlineQueryResult
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_markup import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
class InlineQueryResultMpeg4Gif(InlineQueryResult):
"""
Represents a link to a video animation (H.264/MPEG-4 AVC video without sound). By default,
this animated MPEG-4 file will be sent by the user with optional caption. Alternatively, you
can use input_message_content to send a message with the specified content instead of the
animation.
Source: https://core.telegram.org/bots/api#inlinequeryresultmpeg4gif
"""
type: str = Field("mpeg4_gif", const=True)
"""Type of the result, must be mpeg4_gif"""
id: str
"""Unique identifier for this result, 1-64 bytes"""
mpeg4_url: str
"""A valid URL for the MP4 file. File size must not exceed 1MB"""
thumb_url: str
"""URL of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result"""
mpeg4_width: Optional[int] = None
"""Video width"""
mpeg4_height: Optional[int] = None
"""Video height"""
mpeg4_duration: Optional[int] = None
"""Video duration"""
thumb_mime_type: Optional[str] = None
"""MIME type of the thumbnail, must be one of 'image/jpeg', 'image/gif', or 'video/mp4'.
Defaults to 'image/jpeg'"""
title: Optional[str] = None
"""Title for the result"""
caption: Optional[str] = None
"""Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the caption. See formatting options for more details."""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""Inline keyboard attached to the message"""
input_message_content: Optional[InputMessageContent] = None
"""Content of the message to be sent instead of the video animation"""

View file

@ -1,47 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import UNSET
from .inline_query_result import InlineQueryResult
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_markup import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
class InlineQueryResultPhoto(InlineQueryResult):
"""
Represents a link to a photo. By default, this photo will be sent by the user with optional
caption. Alternatively, you can use input_message_content to send a message with the specified
content instead of the photo.
Source: https://core.telegram.org/bots/api#inlinequeryresultphoto
"""
type: str = Field("photo", const=True)
"""Type of the result, must be photo"""
id: str
"""Unique identifier for this result, 1-64 bytes"""
photo_url: str
"""A valid URL of the photo. Photo must be in jpeg format. Photo size must not exceed 5MB"""
thumb_url: str
"""URL of the thumbnail for the photo"""
photo_width: Optional[int] = None
"""Width of the photo"""
photo_height: Optional[int] = None
"""Height of the photo"""
title: Optional[str] = None
"""Title for the result"""
description: Optional[str] = None
"""Short description of the result"""
caption: Optional[str] = None
"""Caption of the photo to be sent, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the photo caption. See formatting options for more details."""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""Inline keyboard attached to the message"""
input_message_content: Optional[InputMessageContent] = None
"""Content of the message to be sent instead of the photo"""

View file

@ -1,54 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import UNSET
from .inline_query_result import InlineQueryResult
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_markup import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
class InlineQueryResultVideo(InlineQueryResult):
"""
Represents a link to a page containing an embedded video player or a video file. By default,
this video file will be sent by the user with an optional caption. Alternatively, you can use
input_message_content to send a message with the specified content instead of the video.
If an InlineQueryResultVideo message contains an embedded video (e.g., YouTube), you must
replace its content using input_message_content.
Source: https://core.telegram.org/bots/api#inlinequeryresultvideo
"""
type: str = Field("video", const=True)
"""Type of the result, must be video"""
id: str
"""Unique identifier for this result, 1-64 bytes"""
video_url: str
"""A valid URL for the embedded video player or video file"""
mime_type: str
"""Mime type of the content of video url, 'text/html' or 'video/mp4'"""
thumb_url: str
"""URL of the thumbnail (jpeg only) for the video"""
title: str
"""Title for the result"""
caption: Optional[str] = None
"""Caption of the video to be sent, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the video caption. See formatting options for more details."""
video_width: Optional[int] = None
"""Video width"""
video_height: Optional[int] = None
"""Video height"""
video_duration: Optional[int] = None
"""Video duration in seconds"""
description: Optional[str] = None
"""Short description of the result"""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""Inline keyboard attached to the message"""
input_message_content: Optional[InputMessageContent] = None
"""Content of the message to be sent instead of the video. This field is required if
InlineQueryResultVideo is used to send an HTML-page as a result (e.g., a YouTube video)."""

View file

@ -1,45 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from pydantic import Field
from .base import UNSET
from .inline_query_result import InlineQueryResult
if TYPE_CHECKING: # pragma: no cover
from .inline_keyboard_markup import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
class InlineQueryResultVoice(InlineQueryResult):
"""
Represents a link to a voice recording in an .OGG container encoded with OPUS. By default,
this voice recording will be sent by the user. Alternatively, you can use
input_message_content to send a message with the specified content instead of the the voice
message.
Note: This will only work in Telegram versions released after 9 April, 2016. Older clients
will ignore them.
Source: https://core.telegram.org/bots/api#inlinequeryresultvoice
"""
type: str = Field("voice", const=True)
"""Type of the result, must be voice"""
id: str
"""Unique identifier for this result, 1-64 bytes"""
voice_url: str
"""A valid URL for the voice recording"""
title: str
"""Recording title"""
caption: Optional[str] = None
"""Caption, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the voice message caption. See formatting options for more
details."""
voice_duration: Optional[int] = None
"""Recording duration in seconds"""
reply_markup: Optional[InlineKeyboardMarkup] = None
"""Inline keyboard attached to the message"""
input_message_content: Optional[InputMessageContent] = None
"""Content of the message to be sent instead of the voice recording"""

View file

@ -1,20 +0,0 @@
from __future__ import annotations
from typing import Optional
from .input_message_content import InputMessageContent
class InputLocationMessageContent(InputMessageContent):
"""
Represents the content of a location message to be sent as the result of an inline query.
Source: https://core.telegram.org/bots/api#inputlocationmessagecontent
"""
latitude: float
"""Latitude of the location in degrees"""
longitude: float
"""Longitude of the location in degrees"""
live_period: Optional[int] = None
"""Period in seconds for which the location can be updated, should be between 60 and 86400."""

View file

@ -1,16 +0,0 @@
from __future__ import annotations
from .base import MutableTelegramObject
class InputMedia(MutableTelegramObject):
"""
This object represents the content of a media message to be sent. It should be one of
- InputMediaAnimation
- InputMediaDocument
- InputMediaAudio
- InputMediaPhoto
- InputMediaVideo
Source: https://core.telegram.org/bots/api#inputmedia
"""

View file

@ -1,45 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Union
from pydantic import Field
from .base import UNSET
from .input_media import InputMedia
if TYPE_CHECKING: # pragma: no cover
from .input_file import InputFile
class InputMediaAnimation(InputMedia):
"""
Represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent.
Source: https://core.telegram.org/bots/api#inputmediaanimation
"""
type: str = Field("animation", const=True)
"""Type of the result, must be animation"""
media: Union[str, InputFile]
"""File to send. Pass a file_id to send a file that exists on the Telegram servers
(recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass
'attach://<file_attach_name>' to upload a new one using multipart/form-data under
<file_attach_name> name."""
thumb: Optional[Union[InputFile, str]] = None
"""Thumbnail of the file sent; can be ignored if thumbnail generation for the file is
supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded
using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new
file, so you can pass 'attach://<file_attach_name>' if the thumbnail was uploaded using
multipart/form-data under <file_attach_name>."""
caption: Optional[str] = None
"""Caption of the animation to be sent, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the animation caption. See formatting options for more
details."""
width: Optional[int] = None
"""Animation width"""
height: Optional[int] = None
"""Animation height"""
duration: Optional[int] = None
"""Animation duration"""

View file

@ -1,44 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Union
from pydantic import Field
from .base import UNSET
from .input_media import InputMedia
if TYPE_CHECKING: # pragma: no cover
from .input_file import InputFile
class InputMediaAudio(InputMedia):
"""
Represents an audio file to be treated as music to be sent.
Source: https://core.telegram.org/bots/api#inputmediaaudio
"""
type: str = Field("audio", const=True)
"""Type of the result, must be audio"""
media: Union[str, InputFile]
"""File to send. Pass a file_id to send a file that exists on the Telegram servers
(recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass
'attach://<file_attach_name>' to upload a new one using multipart/form-data under
<file_attach_name> name."""
thumb: Optional[Union[InputFile, str]] = None
"""Thumbnail of the file sent; can be ignored if thumbnail generation for the file is
supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded
using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new
file, so you can pass 'attach://<file_attach_name>' if the thumbnail was uploaded using
multipart/form-data under <file_attach_name>."""
caption: Optional[str] = None
"""Caption of the audio to be sent, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the audio caption. See formatting options for more details."""
duration: Optional[int] = None
"""Duration of the audio in seconds"""
performer: Optional[str] = None
"""Performer of the audio"""
title: Optional[str] = None
"""Title of the audio"""

View file

@ -1,38 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Union
from pydantic import Field
from .base import UNSET
from .input_media import InputMedia
if TYPE_CHECKING: # pragma: no cover
from .input_file import InputFile
class InputMediaDocument(InputMedia):
"""
Represents a general file to be sent.
Source: https://core.telegram.org/bots/api#inputmediadocument
"""
type: str = Field("document", const=True)
"""Type of the result, must be document"""
media: Union[str, InputFile]
"""File to send. Pass a file_id to send a file that exists on the Telegram servers
(recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass
'attach://<file_attach_name>' to upload a new one using multipart/form-data under
<file_attach_name> name."""
thumb: Optional[Union[InputFile, str]] = None
"""Thumbnail of the file sent; can be ignored if thumbnail generation for the file is
supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded
using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new
file, so you can pass 'attach://<file_attach_name>' if the thumbnail was uploaded using
multipart/form-data under <file_attach_name>."""
caption: Optional[str] = None
"""Caption of the document to be sent, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the document caption. See formatting options for more details."""

View file

@ -1,31 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Union
from pydantic import Field
from .base import UNSET
from .input_media import InputMedia
if TYPE_CHECKING: # pragma: no cover
from .input_file import InputFile
class InputMediaPhoto(InputMedia):
"""
Represents a photo to be sent.
Source: https://core.telegram.org/bots/api#inputmediaphoto
"""
type: str = Field("photo", const=True)
"""Type of the result, must be photo"""
media: Union[str, InputFile]
"""File to send. Pass a file_id to send a file that exists on the Telegram servers
(recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass
'attach://<file_attach_name>' to upload a new one using multipart/form-data under
<file_attach_name> name."""
caption: Optional[str] = None
"""Caption of the photo to be sent, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the photo caption. See formatting options for more details."""

View file

@ -1,46 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Union
from pydantic import Field
from .base import UNSET
from .input_media import InputMedia
if TYPE_CHECKING: # pragma: no cover
from .input_file import InputFile
class InputMediaVideo(InputMedia):
"""
Represents a video to be sent.
Source: https://core.telegram.org/bots/api#inputmediavideo
"""
type: str = Field("video", const=True)
"""Type of the result, must be video"""
media: Union[str, InputFile]
"""File to send. Pass a file_id to send a file that exists on the Telegram servers
(recommended), pass an HTTP URL for Telegram to get a file from the Internet, or pass
'attach://<file_attach_name>' to upload a new one using multipart/form-data under
<file_attach_name> name."""
thumb: Optional[Union[InputFile, str]] = None
"""Thumbnail of the file sent; can be ignored if thumbnail generation for the file is
supported server-side. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail's width and height should not exceed 320. Ignored if the file is not uploaded
using multipart/form-data. Thumbnails can't be reused and can be only uploaded as a new
file, so you can pass 'attach://<file_attach_name>' if the thumbnail was uploaded using
multipart/form-data under <file_attach_name>."""
caption: Optional[str] = None
"""Caption of the video to be sent, 0-1024 characters after entities parsing"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the video caption. See formatting options for more details."""
width: Optional[int] = None
"""Video width"""
height: Optional[int] = None
"""Video height"""
duration: Optional[int] = None
"""Video duration"""
supports_streaming: Optional[bool] = None
"""Pass True, if the uploaded video is suitable for streaming"""

View file

@ -1,16 +0,0 @@
from __future__ import annotations
from .base import MutableTelegramObject
class InputMessageContent(MutableTelegramObject):
"""
This object represents the content of a message to be sent as a result of an inline query.
Telegram clients currently support the following 4 types:
- InputTextMessageContent
- InputLocationMessageContent
- InputVenueMessageContent
- InputContactMessageContent
Source: https://core.telegram.org/bots/api#inputmessagecontent
"""

View file

@ -1,21 +0,0 @@
from __future__ import annotations
from typing import Optional
from .base import UNSET
from .input_message_content import InputMessageContent
class InputTextMessageContent(InputMessageContent):
"""
Represents the content of a text message to be sent as the result of an inline query.
Source: https://core.telegram.org/bots/api#inputtextmessagecontent
"""
message_text: str
"""Text of the message to be sent, 1-4096 characters"""
parse_mode: Optional[str] = UNSET
"""Mode for parsing entities in the message text. See formatting options for more details."""
disable_web_page_preview: Optional[bool] = None
"""Disables link previews for links in the sent message"""

View file

@ -1,27 +0,0 @@
from __future__ import annotations
from typing import Optional
from .input_message_content import InputMessageContent
class InputVenueMessageContent(InputMessageContent):
"""
Represents the content of a venue message to be sent as the result of an inline query.
Source: https://core.telegram.org/bots/api#inputvenuemessagecontent
"""
latitude: float
"""Latitude of the venue in degrees"""
longitude: float
"""Longitude of the venue in degrees"""
title: str
"""Name of the venue"""
address: str
"""Address of the venue"""
foursquare_id: Optional[str] = None
"""Foursquare identifier of the venue, if known"""
foursquare_type: Optional[str] = None
"""Foursquare type of the venue, if known. (For example, 'arts_entertainment/default',
'arts_entertainment/aquarium' or 'food/icecream'.)"""

View file

@ -1,25 +0,0 @@
from __future__ import annotations
from .base import TelegramObject
class Invoice(TelegramObject):
"""
This object contains basic information about an invoice.
Source: https://core.telegram.org/bots/api#invoice
"""
title: str
"""Product name"""
description: str
"""Product description"""
start_parameter: str
"""Unique bot deep-linking parameter that can be used to generate this invoice"""
currency: str
"""Three-letter ISO 4217 currency code"""
total_amount: int
"""Total price in the smallest units of the currency (integer, not float/double). For example,
for a price of US$ 1.45 pass amount = 145. See the exp parameter in currencies.json, it
shows the number of digits past the decimal point for each currency (2 for the majority of
currencies)."""

View file

@ -1,35 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from .base import MutableTelegramObject
if TYPE_CHECKING: # pragma: no cover
from .keyboard_button_poll_type import KeyboardButtonPollType
class KeyboardButton(MutableTelegramObject):
"""
This object represents one button of the reply keyboard. For simple text buttons String can be
used instead of this object to specify text of the button. Optional fields request_contact,
request_location, and request_poll are mutually exclusive.
Note: request_contact and request_location options will only work in Telegram versions
released after 9 April, 2016. Older clients will display unsupported message.
Note: request_poll option will only work in Telegram versions released after 23 January, 2020.
Older clients will display unsupported message.
Source: https://core.telegram.org/bots/api#keyboardbutton
"""
text: str
"""Text of the button. If none of the optional fields are used, it will be sent as a message
when the button is pressed"""
request_contact: Optional[bool] = None
"""If True, the user's phone number will be sent as a contact when the button is pressed.
Available in private chats only"""
request_location: Optional[bool] = None
"""If True, the user's current location will be sent when the button is pressed. Available in
private chats only"""
request_poll: Optional[KeyboardButtonPollType] = None
"""If specified, the user will be asked to create a poll and send it to the bot when the
button is pressed. Available in private chats only"""

View file

@ -1,19 +0,0 @@
from __future__ import annotations
from .base import MutableTelegramObject
class LabeledPrice(MutableTelegramObject):
"""
This object represents a portion of the price for goods or services.
Source: https://core.telegram.org/bots/api#labeledprice
"""
label: str
"""Portion label"""
amount: int
"""Price of the product in the smallest units of the currency (integer, not float/double). For
example, for a price of US$ 1.45 pass amount = 145. See the exp parameter in
currencies.json, it shows the number of digits past the decimal point for each currency (2
for the majority of currencies)."""

View file

@ -1,16 +0,0 @@
from __future__ import annotations
from .base import TelegramObject
class Location(TelegramObject):
"""
This object represents a point on the map.
Source: https://core.telegram.org/bots/api#location
"""
longitude: float
"""Longitude as defined by sender"""
latitude: float
"""Latitude as defined by sender"""

View file

@ -1,33 +0,0 @@
from __future__ import annotations
from typing import Optional
from .base import TelegramObject
class LoginUrl(TelegramObject):
"""
This object represents a parameter of the inline keyboard button used to automatically
authorize a user. Serves as a great replacement for the Telegram Login Widget when the user is
coming from Telegram. All the user needs to do is tap/click a button and confirm that they
want to log in:
Telegram apps support these buttons as of version 5.7.
Sample bot: @discussbot
Source: https://core.telegram.org/bots/api#loginurl
"""
url: str
"""An HTTP URL to be opened with user authorization data added to the query string when the
button is pressed. If the user refuses to provide authorization data, the original URL
without information about the user will be opened. The data added is the same as described
in Receiving authorization data."""
forward_text: Optional[str] = None
"""New text of the button in forwarded messages."""
bot_username: Optional[str] = None
"""Username of a bot, which will be used for user authorization. See Setting up a bot for more
details. If not specified, the current bot's username will be assumed. The url's domain
must be the same as the domain linked with the bot. See Linking your domain to the bot for
more details."""
request_write_access: Optional[bool] = None
"""Pass True to request the permission for your bot to send messages to the user."""

View file

@ -1,35 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from .base import TelegramObject
if TYPE_CHECKING: # pragma: no cover
from .user import User
class MessageEntity(TelegramObject):
"""
This object represents one special entity in a text message. For example, hashtags, usernames,
URLs, etc.
Source: https://core.telegram.org/bots/api#messageentity
"""
type: str
"""Type of the entity. Can be 'mention' (@username), 'hashtag' (#hashtag), 'cashtag' ($USD),
'bot_command' (/start@jobs_bot), 'url' (https://telegram.org), 'email'
(do-not-reply@telegram.org), 'phone_number' (+1-212-555-0123), 'bold' (bold text), 'italic'
(italic text), 'underline' (underlined text), 'strikethrough' (strikethrough text), 'code'
(monowidth string), 'pre' (monowidth block), 'text_link' (for clickable text URLs),
'text_mention' (for users without usernames)"""
offset: int
"""Offset in UTF-16 code units to the start of the entity"""
length: int
"""Length of the entity in UTF-16 code units"""
url: Optional[str] = None
"""For 'text_link' only, url that will be opened after user taps on the text"""
user: Optional[User] = None
"""For 'text_mention' only, the mentioned user"""
language: Optional[str] = None
"""For 'pre' only, the programming language of the entity text"""

View file

@ -1,21 +0,0 @@
from __future__ import annotations
from .base import MutableTelegramObject
class PassportElementError(MutableTelegramObject):
"""
This object represents an error in the Telegram Passport element which was submitted that
should be resolved by the user. It should be one of:
- PassportElementErrorDataField
- PassportElementErrorFrontSide
- PassportElementErrorReverseSide
- PassportElementErrorSelfie
- PassportElementErrorFile
- PassportElementErrorFiles
- PassportElementErrorTranslationFile
- PassportElementErrorTranslationFiles
- PassportElementErrorUnspecified
Source: https://core.telegram.org/bots/api#passportelementerror
"""

View file

@ -1,33 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, List, Optional
from .base import MutableTelegramObject
if TYPE_CHECKING: # pragma: no cover
from .keyboard_button import KeyboardButton
class ReplyKeyboardMarkup(MutableTelegramObject):
"""
This object represents a custom keyboard with reply options (see Introduction to bots for
details and examples).
Source: https://core.telegram.org/bots/api#replykeyboardmarkup
"""
keyboard: List[List[KeyboardButton]]
"""Array of button rows, each represented by an Array of KeyboardButton objects"""
resize_keyboard: Optional[bool] = None
"""Requests clients to resize the keyboard vertically for optimal fit (e.g., make the keyboard
smaller if there are just two rows of buttons). Defaults to false, in which case the custom
keyboard is always of the same height as the app's standard keyboard."""
one_time_keyboard: Optional[bool] = None
"""Requests clients to hide the keyboard as soon as it's been used. The keyboard will still be
available, but clients will automatically display the usual letter-keyboard in the chat
the user can press a special button in the input field to see the custom keyboard again.
Defaults to false."""
selective: Optional[bool] = None
"""Use this parameter if you want to show the keyboard to specific users only. Targets: 1)
users that are @mentioned in the text of the Message object; 2) if the bot's message is a
reply (has reply_to_message_id), sender of the original message."""

View file

@ -1,25 +0,0 @@
from __future__ import annotations
from typing import Optional
from .base import MutableTelegramObject
class ReplyKeyboardRemove(MutableTelegramObject):
"""
Upon receiving a message with this object, Telegram clients will remove the current custom
keyboard and display the default letter-keyboard. By default, custom keyboards are displayed
until a new keyboard is sent by a bot. An exception is made for one-time keyboards that are
hidden immediately after the user presses a button (see ReplyKeyboardMarkup).
Source: https://core.telegram.org/bots/api#replykeyboardremove
"""
remove_keyboard: bool = True
"""Requests clients to remove the custom keyboard (user will not be able to summon this
keyboard; if you want to hide the keyboard from sight but keep it accessible, use
one_time_keyboard in ReplyKeyboardMarkup)"""
selective: Optional[bool] = None
"""Use this parameter if you want to remove the keyboard for specific users only. Targets: 1)
users that are @mentioned in the text of the Message object; 2) if the bot's message is a
reply (has reply_to_message_id), sender of the original message."""

View file

@ -1,22 +0,0 @@
from __future__ import annotations
from typing import Optional
from .base import TelegramObject
class ResponseParameters(TelegramObject):
"""
Contains information about why a request was unsuccessful.
Source: https://core.telegram.org/bots/api#responseparameters
"""
migrate_to_chat_id: Optional[int] = None
"""The group has been migrated to a supergroup with the specified identifier. This number may
be greater than 32 bits and some programming languages may have difficulty/silent defects
in interpreting it. But it is smaller than 52 bits, so a signed 64 bit integer or
double-precision float type are safe for storing this identifier."""
retry_after: Optional[int] = None
"""In case of exceeding flood control, the number of seconds left to wait before the request
can be repeated"""

View file

@ -1,57 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from .base import TelegramObject
if TYPE_CHECKING: # pragma: no cover
from .callback_query import CallbackQuery
from .chosen_inline_result import ChosenInlineResult
from .inline_query import InlineQuery
from .message import Message
from .poll import Poll
from .poll_answer import PollAnswer
from .pre_checkout_query import PreCheckoutQuery
from .shipping_query import ShippingQuery
class Update(TelegramObject):
"""
This object represents an incoming update.
At most one of the optional parameters can be present in any given update.
Source: https://core.telegram.org/bots/api#update
"""
update_id: int
"""The update's unique identifier. Update identifiers start from a certain positive number and
increase sequentially. This ID becomes especially handy if you're using Webhooks, since it
allows you to ignore repeated updates or to restore the correct update sequence, should
they get out of order. If there are no new updates for at least a week, then identifier of
the next update will be chosen randomly instead of sequentially."""
message: Optional[Message] = None
"""New incoming message of any kind — text, photo, sticker, etc."""
edited_message: Optional[Message] = None
"""New version of a message that is known to the bot and was edited"""
channel_post: Optional[Message] = None
"""New incoming channel post of any kind — text, photo, sticker, etc."""
edited_channel_post: Optional[Message] = None
"""New version of a channel post that is known to the bot and was edited"""
inline_query: Optional[InlineQuery] = None
"""New incoming inline query"""
chosen_inline_result: Optional[ChosenInlineResult] = None
"""The result of an inline query that was chosen by a user and sent to their chat partner.
Please see our documentation on the feedback collecting for details on how to enable these
updates for your bot."""
callback_query: Optional[CallbackQuery] = None
"""New incoming callback query"""
shipping_query: Optional[ShippingQuery] = None
"""New incoming shipping query. Only for invoices with flexible price"""
pre_checkout_query: Optional[PreCheckoutQuery] = None
"""New incoming pre-checkout query. Contains full information about checkout"""
poll: Optional[Poll] = None
"""New poll state. Bots receive only updates about stopped polls and polls, which are sent by
the bot"""
poll_answer: Optional[PollAnswer] = None
"""A user changed their answer in a non-anonymous poll. Bots receive new votes only in polls
that were sent by the bot itself."""

View file

@ -1,28 +0,0 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional
from .base import TelegramObject
if TYPE_CHECKING: # pragma: no cover
from .location import Location
class Venue(TelegramObject):
"""
This object represents a venue.
Source: https://core.telegram.org/bots/api#venue
"""
location: Location
"""Venue location"""
title: str
"""Name of the venue"""
address: str
"""Address of the venue"""
foursquare_id: Optional[str] = None
"""Foursquare identifier of the venue"""
foursquare_type: Optional[str] = None
"""Foursquare type of the venue. (For example, 'arts_entertainment/default',
'arts_entertainment/aquarium' or 'food/icecream'.)"""

File diff suppressed because it is too large Load diff

View file

@ -17,7 +17,7 @@ from typing import (
from aiohttp import BasicAuth, ClientSession, FormData, TCPConnector
from aiogram.api.methods import Request, TelegramMethod
from aiogram.methods import Request, TelegramMethod
from .base import BaseSession, UNSET
@ -56,7 +56,7 @@ def _retrieve_basic(basic: _ProxyBasic) -> Dict[str, Any]:
def _prepare_connector(chain_or_plain: _ProxyType) -> Tuple[Type["TCPConnector"], Dict[str, Any]]:
from aiohttp_socks import ProxyInfo, ProxyConnector, ChainProxyConnector # type: ignore
from aiohttp_socks import ChainProxyConnector, ProxyConnector, ProxyInfo # type: ignore
# since tuple is Iterable(compatible with _ProxyChain) object, we assume that
# user wants chained proxies if tuple is a pair of string(url) and BasicAuth

View file

@ -17,8 +17,8 @@ from typing import (
)
from aiogram.utils.exceptions import TelegramAPIError
from aiogram.utils.helper import Default
from ....utils.helper import Default
from ...methods import Response, TelegramMethod
from ...types import UNSET
from ..telegram import PRODUCTION, TelegramAPIServer
@ -32,35 +32,63 @@ _JsonDumps = Callable[..., str]
class BaseSession(abc.ABC):
default_timeout: ClassVar[float] = 60.0
api: Default[TelegramAPIServer] = Default(PRODUCTION)
"""Telegra Bot API URL patterns"""
json_loads: Default[_JsonLoads] = Default(json.loads)
"""JSON loader"""
json_dumps: Default[_JsonDumps] = Default(json.dumps)
"""JSON dumper"""
default_timeout: ClassVar[float] = 60.0
"""Default timeout"""
timeout: Default[float] = Default(fget=lambda self: float(self.__class__.default_timeout))
"""Session scope request timeout"""
@classmethod
def raise_for_status(cls, response: Response[T]) -> None:
"""
Check response status
:param response: Response instance
"""
if response.ok:
return
raise TelegramAPIError(response.description)
@abc.abstractmethod
async def close(self) -> None: # pragma: no cover
"""
Close client session
"""
pass
@abc.abstractmethod
async def make_request(
self, bot: Bot, method: TelegramMethod[T], timeout: Optional[int] = UNSET
) -> T: # pragma: no cover
"""
Make request to Telegram Bot API
:param bot: Bot instance
:param method: Method instance
:param timeout: Request timeout
:return:
:raise TelegramApiError:
"""
pass
@abc.abstractmethod
async def stream_content(
self, url: str, timeout: int, chunk_size: int
) -> AsyncGenerator[bytes, None]: # pragma: no cover
"""
Stream reader
"""
yield b""
def prepare_value(self, value: Any) -> Union[str, int, bool]:
"""
Prepare value before send
"""
if isinstance(value, str):
return value
if isinstance(value, (list, dict)):
@ -74,6 +102,9 @@ class BaseSession(abc.ABC):
return str(value)
def clean_json(self, value: Any) -> Any:
"""
Clean data before send
"""
if isinstance(value, list):
return [self.clean_json(v) for v in value if v is not None]
elif isinstance(value, dict):

View file

@ -0,0 +1,52 @@
from dataclasses import dataclass
@dataclass(frozen=True)
class TelegramAPIServer:
"""
Base config for API Endpoints
"""
base: str
file: str
is_local: bool = False
def api_url(self, token: str, method: str) -> str:
"""
Generate URL for API methods
:param token: Bot token
:param method: API method name (case insensitive)
:return: URL
"""
return self.base.format(token=token, method=method)
def file_url(self, token: str, path: str) -> str:
"""
Generate URL for downloading files
:param token: Bot token
:param path: file path
:return: URL
"""
return self.file.format(token=token, path=path)
@classmethod
def from_base(cls, base: str, is_local: bool = False) -> "TelegramAPIServer":
"""
Use this method to auto-generate TelegramAPIServer instance from base URL
:param base: Base URL
:param is_local: Mark this server is in `local mode <https://core.telegram.org/bots/api#using-a-local-bot-api-server>`_.
:return: instance of :class:`TelegramAPIServer`
"""
base = base.rstrip("/")
return cls(
base=f"{base}/bot{{token}}/{{method}}",
file=f"{base}/file/bot{{token}}/{{path}}",
is_local=is_local,
)
# Main API server
PRODUCTION = TelegramAPIServer.from_base("https://api.telegram.org")

View file

@ -7,11 +7,13 @@ from asyncio import CancelledError, Future, Lock
from typing import Any, AsyncGenerator, Dict, Optional, Union
from .. import loggers
from ..api.client.bot import Bot
from ..api.methods import TelegramMethod
from ..api.types import Update, User
from ..client.bot import Bot
from ..methods import TelegramMethod
from ..types import TelegramObject, Update, User
from ..utils.exceptions import TelegramAPIError
from .event.bases import NOT_HANDLED
from .event.bases import UNHANDLED, SkipHandler
from .event.telegram import TelegramEventObserver
from .middlewares.error import ErrorsMiddleware
from .middlewares.user_context import UserContextMiddleware
from .router import Router
@ -23,10 +25,15 @@ class Dispatcher(Router):
def __init__(self, **kwargs: Any) -> None:
super(Dispatcher, self).__init__(**kwargs)
self._running_lock = Lock()
# Default middleware is needed for contextual features
self.update = TelegramEventObserver(router=self, event_name="update")
self.observers["update"] = self.update
self.update.register(self._listen_update)
self.update.outer_middleware(UserContextMiddleware())
self.update.outer_middleware(ErrorsMiddleware(self))
self._running_lock = Lock()
@property
def parent_router(self) -> None:
@ -61,7 +68,7 @@ class Dispatcher(Router):
Bot.set_current(bot)
try:
response = await self.update.trigger(update, bot=bot, **kwargs)
handled = response is not NOT_HANDLED
handled = response is not UNHANDLED
return response
finally:
finish_time = loop.time()
@ -97,6 +104,74 @@ class Dispatcher(Router):
yield update
update_id = update.update_id + 1
async def _listen_update(self, update: Update, **kwargs: Any) -> Any:
"""
Main updates listener
Workflow:
- Detect content type and propagate to observers in current router
- If no one filter is pass - propagate update to child routers as Update
:param update:
:param kwargs:
:return:
"""
event: TelegramObject
if update.message:
update_type = "message"
event = update.message
elif update.edited_message:
update_type = "edited_message"
event = update.edited_message
elif update.channel_post:
update_type = "channel_post"
event = update.channel_post
elif update.edited_channel_post:
update_type = "edited_channel_post"
event = update.edited_channel_post
elif update.inline_query:
update_type = "inline_query"
event = update.inline_query
elif update.chosen_inline_result:
update_type = "chosen_inline_result"
event = update.chosen_inline_result
elif update.callback_query:
update_type = "callback_query"
event = update.callback_query
elif update.shipping_query:
update_type = "shipping_query"
event = update.shipping_query
elif update.pre_checkout_query:
update_type = "pre_checkout_query"
event = update.pre_checkout_query
elif update.poll:
update_type = "poll"
event = update.poll
elif update.poll_answer:
update_type = "poll_answer"
event = update.poll_answer
else:
warnings.warn(
"Detected unknown update type.\n"
"Seems like Telegram Bot API was updated and you have "
"installed not latest version of aiogram framework",
RuntimeWarning,
)
raise SkipHandler
kwargs.update(event_update=update)
for router in self.chain:
kwargs.update(event_router=router)
observer = router.observers[update_type]
response = await observer.trigger(event, update=update, **kwargs)
if response is not UNHANDLED:
break
else:
response = UNHANDLED
return response
@classmethod
async def _silent_call_request(cls, bot: Bot, result: TelegramMethod[Any]) -> None:
"""
@ -129,7 +204,7 @@ class Dispatcher(Router):
handled = False
try:
response = await self.feed_update(bot, update, **kwargs)
handled = handled is not NOT_HANDLED
handled = handled is not UNHANDLED
if call_answer and isinstance(response, TelegramMethod):
await self._silent_call_request(bot=bot, result=response)
return handled
@ -172,7 +247,7 @@ class Dispatcher(Router):
raise
async def feed_webhook_update(
self, bot: Bot, update: Union[Update, Dict[str, Any]], _timeout: int = 55, **kwargs: Any
self, bot: Bot, update: Union[Update, Dict[str, Any]], _timeout: float = 55, **kwargs: Any
) -> Optional[Dict[str, Any]]:
if not isinstance(update, Update): # Allow to use raw updates
update = Update(**update)
@ -255,7 +330,7 @@ class Dispatcher(Router):
await asyncio.gather(*coro_list)
finally:
for bot in bots: # Close sessions
await bot.close()
await bot.session.close()
loggers.dispatcher.info("Polling stopped")
await self.emit_shutdown(**workflow_data)

View file

@ -3,7 +3,7 @@ from __future__ import annotations
from typing import Any, Awaitable, Callable, Dict, NoReturn, Optional, Union
from unittest.mock import sentinel
from ...api.types import TelegramObject
from ...types import TelegramObject
from ..middlewares.base import BaseMiddleware
NextMiddlewareType = Callable[[TelegramObject, Dict[str, Any]], Awaitable[Any]]
@ -11,7 +11,7 @@ MiddlewareType = Union[
BaseMiddleware, Callable[[NextMiddlewareType, TelegramObject, Dict[str, Any]], Awaitable[Any]]
]
NOT_HANDLED = sentinel.NOT_HANDLED
UNHANDLED = sentinel.UNHANDLED
class SkipHandler(Exception):

View file

@ -8,6 +8,19 @@ from .handler import CallbackType, HandlerObject, HandlerType
class EventObserver:
"""
Simple events observer
Is used for managing events is not related with Telegram (For example startup/shutdown processes)
Handlers can be registered via decorator or method
.. code-block:: python
<observer>.register(my_handler)
.. code-block:: python
@<observer>()
async def my_handler(*args, **kwargs): ...
"""
def __init__(self) -> None:

View file

@ -24,12 +24,6 @@ class CallableMixin:
def __post_init__(self) -> None:
callback = inspect.unwrap(self.callback)
self.awaitable = inspect.isawaitable(callback) or inspect.iscoroutinefunction(callback)
if isinstance(callback, BaseFilter):
# Pydantic 1.5 has incorrect signature generator
# Issue: https://github.com/samuelcolvin/pydantic/issues/1419
# Fixes: https://github.com/samuelcolvin/pydantic/pull/1427
# TODO: Remove this temporary fix
callback = inspect.unwrap(callback.__call__)
self.spec = inspect.getfullargspec(callback)
def _prepare_kwargs(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:

View file

@ -6,9 +6,9 @@ from typing import TYPE_CHECKING, Any, Callable, Dict, Generator, List, Optional
from pydantic import ValidationError
from ...api.types import TelegramObject
from ...types import TelegramObject
from ..filters.base import BaseFilter
from .bases import NOT_HANDLED, MiddlewareType, NextMiddlewareType, SkipHandler
from .bases import UNHANDLED, MiddlewareType, NextMiddlewareType, SkipHandler
from .handler import CallbackType, FilterObject, FilterType, HandlerObject, HandlerType
if TYPE_CHECKING: # pragma: no cover
@ -18,6 +18,9 @@ if TYPE_CHECKING: # pragma: no cover
class TelegramEventObserver:
"""
Event observer for Telegram events
Here you can register handler with filters or bounded filters which can be used as keyword arguments instead of writing full references when you register new handlers.
This observer will stops event propagation when first handler is pass.
"""
def __init__(self, router: Router, event_name: str) -> None:
@ -57,6 +60,17 @@ class TelegramEventObserver:
yield filter_
registry.append(filter_)
def _resolve_inner_middlewares(self) -> List[MiddlewareType]:
"""
Get all inner middlewares in an tree
"""
middlewares = []
for router in self.router.chain_head:
observer = router.observers[self.event_name]
middlewares.extend(observer.middlewares)
return middlewares
def resolve_filters(self, full_config: Dict[str, Any]) -> List[BaseFilter]:
"""
Resolve keyword filters via filters factory
@ -126,12 +140,14 @@ class TelegramEventObserver:
if result:
kwargs.update(data)
try:
wrapped_inner = self._wrap_middleware(self.middlewares, handler.call)
wrapped_inner = self._wrap_middleware(
self._resolve_inner_middlewares(), handler.call
)
return await wrapped_inner(event, kwargs)
except SkipHandler:
continue
return NOT_HANDLED
return UNHANDLED
def __call__(
self, *args: FilterType, **bound_filters: BaseFilter
@ -147,16 +163,26 @@ class TelegramEventObserver:
return wrapper
def middleware(
self, middleware: Optional[MiddlewareType] = None,
self,
middleware: Optional[MiddlewareType] = None,
) -> Union[Callable[[MiddlewareType], MiddlewareType], MiddlewareType]:
"""
Decorator for registering inner middlewares
Usage:
>>> @<event>.middleware() # via decorator (variant 1)
>>> @<event>.middleware # via decorator (variant 2)
>>> async def my_middleware(handler, event, data): ...
>>> <event>.middleware(middleware) # via method
.. code-block:: python
@<event>.middleware() # via decorator (variant 1)
.. code-block:: python
@<event>.middleware # via decorator (variant 2)
.. code-block:: python
async def my_middleware(handler, event, data): ...
<event>.middleware(my_middleware) # via method
"""
def wrapper(m: MiddlewareType) -> MiddlewareType:
@ -168,16 +194,26 @@ class TelegramEventObserver:
return wrapper(middleware)
def outer_middleware(
self, middleware: Optional[MiddlewareType] = None,
self,
middleware: Optional[MiddlewareType] = None,
) -> Union[Callable[[MiddlewareType], MiddlewareType], MiddlewareType]:
"""
Decorator for registering outer middlewares
Usage:
>>> @<event>.outer_middleware() # via decorator (variant 1)
>>> @<event>.outer_middleware # via decorator (variant 2)
>>> async def my_middleware(handler, event, data): ...
>>> <event>.outer_middleware(my_middleware) # via method
.. code-block:: python
@<event>.outer_middleware() # via decorator (variant 1)
.. code-block:: python
@<event>.outer_middleware # via decorator (variant 2)
.. code-block:: python
async def my_middleware(handler, event, data): ...
<event>.outer_middleware(my_middleware) # via method
"""
def wrapper(m: MiddlewareType) -> MiddlewareType:

View file

@ -18,7 +18,6 @@ __all__ = (
)
BUILTIN_FILTERS: Dict[str, Tuple[Type[BaseFilter], ...]] = {
"update": (),
"message": (Text, Command, ContentTypesFilter),
"edited_message": (Text, Command, ContentTypesFilter),
"channel_post": (Text, ContentTypesFilter),

View file

@ -7,8 +7,8 @@ from typing import Any, Dict, Match, Optional, Pattern, Sequence, Union, cast
from pydantic import validator
from aiogram import Bot
from aiogram.api.types import Message
from aiogram.dispatcher.filters import BaseFilter
from aiogram.types import Message
CommandPatterType = Union[str, re.Pattern]

View file

@ -2,8 +2,9 @@ from typing import Any, Dict, Optional, Sequence, Union
from pydantic import validator
from ...api.types import Message
from ...api.types.message import ContentType
from aiogram.types.message import ContentType
from ...types import Message
from .base import BaseFilter

View file

@ -2,8 +2,8 @@ from typing import Any, Dict, Optional, Sequence, Union
from pydantic import root_validator
from aiogram.api.types import CallbackQuery, InlineQuery, Message, Poll
from aiogram.dispatcher.filters import BaseFilter
from aiogram.types import CallbackQuery, InlineQuery, Message, Poll
TextType = str

View file

@ -2,7 +2,7 @@ from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Any, Dict, Generic, TypeVar, cast
from aiogram import Bot
from aiogram.api.types import Update
from aiogram.types import Update
T = TypeVar("T")

View file

@ -1,8 +1,8 @@
from abc import ABC
from typing import Optional
from aiogram.api.types import CallbackQuery, Message, User
from aiogram.dispatcher.handler import BaseHandler
from aiogram.types import CallbackQuery, Message, User
class CallbackQueryHandler(BaseHandler[CallbackQuery], ABC):

View file

@ -1,7 +1,7 @@
from abc import ABC
from aiogram.api.types import ChosenInlineResult, User
from aiogram.dispatcher.handler import BaseHandler
from aiogram.types import ChosenInlineResult, User
class ChosenInlineResultHandler(BaseHandler[ChosenInlineResult], ABC):

View file

@ -1,7 +1,7 @@
from abc import ABC
from aiogram.api.types import InlineQuery, User
from aiogram.dispatcher.handler import BaseHandler
from aiogram.types import InlineQuery, User
class InlineQueryHandler(BaseHandler[InlineQuery], ABC):

View file

@ -1,9 +1,9 @@
from abc import ABC
from typing import Optional, cast
from aiogram.api.types import Chat, Message, User
from aiogram.dispatcher.filters import CommandObject
from aiogram.dispatcher.handler.base import BaseHandler, BaseHandlerMixin
from aiogram.types import Chat, Message, User
class MessageHandler(BaseHandler[Message], ABC):

View file

@ -1,8 +1,8 @@
from abc import ABC
from typing import List
from aiogram.api.types import Poll, PollOption
from aiogram.dispatcher.handler import BaseHandler
from aiogram.types import Poll, PollOption
class PollHandler(BaseHandler[Poll], ABC):

View file

@ -1,7 +1,7 @@
from abc import ABC
from aiogram.api.types import PreCheckoutQuery, User
from aiogram.dispatcher.handler import BaseHandler
from aiogram.types import PreCheckoutQuery, User
class PreCheckoutQueryHandler(BaseHandler[PreCheckoutQuery], ABC):

Some files were not shown because too many files have changed in this diff Show more