diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index c4430ef6..d643b3f0 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1 +1,2 @@
open_collective: aiogram
+patreon: aiogram
diff --git a/Makefile b/Makefile
index f21ec8ae..da6493d9 100644
--- a/Makefile
+++ b/Makefile
@@ -43,7 +43,7 @@ test:
tox
summary:
- cloc aiogram/ tests/ examples/ setup.py
+ cloc aiogram/ tests/ examples/ docs/ setup.py
docs: docs/source/*
cd docs && $(MAKE) html
diff --git a/README.md b/README.md
index dfef918f..fca118a0 100644
--- a/README.md
+++ b/README.md
@@ -6,22 +6,82 @@
[](https://pypi.python.org/pypi/aiogram)
[](https://pypi.python.org/pypi/aiogram)
[](https://pypi.python.org/pypi/aiogram)
-[](https://core.telegram.org/bots/api)
+[](https://core.telegram.org/bots/api)
[](http://docs.aiogram.dev/en/latest/?badge=latest)
[](https://github.com/aiogram/aiogram/issues)
[](https://opensource.org/licenses/MIT)
**aiogram** is a pretty simple and fully asynchronous framework for [Telegram Bot API](https://core.telegram.org/bots/api) written in Python 3.7 with [asyncio](https://docs.python.org/3/library/asyncio.html) and [aiohttp](https://github.com/aio-libs/aiohttp). It helps you to make your bots faster and simpler.
-You can [read the docs here](http://docs.aiogram.dev/en/latest/).
+
+## Examples
+
+ 📚 Click to see some basic examples
+
+
+**Few steps before getting started...**
+- First, you should obtain token for your bot from [BotFather](https://t.me/BotFather).
+- Install latest stable version of aiogram, simply running `pip install aiogram`
+
+### Simple [`getMe`](https://core.telegram.org/bots/api#getme) request
+
+```python
+import asyncio
+from aiogram import Bot
+
+BOT_TOKEN = ""
+
+async def main():
+ bot = Bot(token=BOT_TOKEN)
+
+ try:
+ me = await bot.get_me()
+ print(f"🤖 Hello, I'm {me.first_name}.\nHave a nice Day!")
+ finally:
+ await bot.close()
+
+asyncio.run(main())
+```
+
+### Poll BotAPI for updates and process updates
+
+```python
+import asyncio
+from aiogram import Bot, Dispatcher, types
+
+BOT_TOKEN = ""
+
+async def start_handler(event: types.Message):
+ await event.answer(
+ f"Hello, {event.from_user.get_mention(as_html=True)} 👋!",
+ parse_mode=types.ParseMode.HTML,
+ )
+
+async def main():
+ bot = Bot(token=BOT_TOKEN)
+ try:
+ disp = Dispatcher(bot=bot)
+ disp.register_message_handler(start_handler, commands={"start", "restart"})
+ await disp.start_polling()
+ finally:
+ await bot.close()
+
+asyncio.run(main())
+```
+
+### Moar!
+
+You can find more examples in [`examples/`](https://github.com/aiogram/aiogram/tree/dev-2.x/examples) directory
+
+
## Official aiogram resources:
- News: [@aiogram_live](https://t.me/aiogram_live)
- Community: [@aiogram](https://t.me/aiogram)
- Russian community: [@aiogram_ru](https://t.me/aiogram_ru)
- - Pip: [aiogram](https://pypi.python.org/pypi/aiogram)
- - Docs: [aiogram site](https://docs.aiogram.dev/)
+ - PyPI: [aiogram](https://pypi.python.org/pypi/aiogram)
+ - Documentation: [aiogram site](https://docs.aiogram.dev/en/latest/)
- Source: [Github repo](https://github.com/aiogram/aiogram)
- Issues/Bug tracker: [Github issues tracker](https://github.com/aiogram/aiogram/issues)
- Test bot: [@aiogram_bot](https://t.me/aiogram_bot)
diff --git a/README.rst b/README.rst
index 1cf2765d..6df651a2 100644
--- a/README.rst
+++ b/README.rst
@@ -21,7 +21,7 @@ AIOGramBot
:target: https://pypi.python.org/pypi/aiogram
:alt: Supported python versions
-.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-4.9-blue.svg?style=flat-square&logo=telegram
+.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.3-blue.svg?style=flat-square&logo=telegram
:target: https://core.telegram.org/bots/api
:alt: Telegram Bot API
diff --git a/aiogram/__init__.py b/aiogram/__init__.py
index bebafcec..ceca7f58 100644
--- a/aiogram/__init__.py
+++ b/aiogram/__init__.py
@@ -25,7 +25,7 @@ else:
if 'DISABLE_UVLOOP' not in os.environ:
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
-__all__ = [
+__all__ = (
'Bot',
'Dispatcher',
'__api_version__',
@@ -40,8 +40,8 @@ __all__ = [
'md',
'middlewares',
'types',
- 'utils'
-]
+ 'utils',
+)
-__version__ = '2.9.2'
-__api_version__ = '4.9'
+__version__ = '2.14.3'
+__api_version__ = '5.3'
diff --git a/aiogram/bot/__init__.py b/aiogram/bot/__init__.py
index 252c465b..19c051bf 100644
--- a/aiogram/bot/__init__.py
+++ b/aiogram/bot/__init__.py
@@ -2,8 +2,8 @@ from . import api
from .base import BaseBot
from .bot import Bot
-__all__ = [
+__all__ = (
'BaseBot',
'Bot',
- 'api'
-]
+ 'api',
+)
diff --git a/aiogram/bot/api.py b/aiogram/bot/api.py
index 1d0c4f7b..1bf00d47 100644
--- a/aiogram/bot/api.py
+++ b/aiogram/bot/api.py
@@ -1,20 +1,57 @@
import logging
import os
+from dataclasses import dataclass
from http import HTTPStatus
import aiohttp
from .. import types
-from ..utils import exceptions
-from ..utils import json
+from ..utils import exceptions, json
from ..utils.helper import Helper, HelperMode, Item
# Main aiogram logger
log = logging.getLogger('aiogram')
-# API Url's
-API_URL = "https://api.telegram.org/bot{token}/{method}"
-FILE_URL = "https://api.telegram.org/file/bot{token}/{path}"
+
+@dataclass(frozen=True)
+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)
+
+ @classmethod
+ def from_base(cls, base: str) -> 'TelegramAPIServer':
+ base = base.rstrip("/")
+ return cls(
+ base=f"{base}/bot{{token}}/{{method}}",
+ file=f"{base}/file/bot{{token}}/{{path}}",
+ )
+
+
+TELEGRAM_PRODUCTION = TelegramAPIServer.from_base("https://api.telegram.org")
def check_token(token: str) -> bool:
@@ -80,7 +117,7 @@ def check_result(method_name: str, content_type: str, status_code: int, body: st
exceptions.NotFound.detect(description)
elif status_code == HTTPStatus.CONFLICT:
exceptions.ConflictError.detect(description)
- elif status_code in [HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN]:
+ elif status_code in (HTTPStatus.UNAUTHORIZED, HTTPStatus.FORBIDDEN):
exceptions.Unauthorized.detect(description)
elif status_code == HTTPStatus.REQUEST_ENTITY_TOO_LARGE:
raise exceptions.NetworkError('File too large for uploading. '
@@ -92,11 +129,10 @@ def check_result(method_name: str, content_type: str, status_code: int, body: st
raise exceptions.TelegramAPIError(f"{description} [{status_code}]")
-async def make_request(session, token, method, data=None, files=None, **kwargs):
- # log.debug(f"Make request: '{method}' with data: {data} and files {files}")
+async def make_request(session, server, token, method, data=None, files=None, **kwargs):
log.debug('Make request: "%s" with data: "%r" and files "%r"', method, data, files)
- url = Methods.api_url(token=token, method=method)
+ url = server.api_url(token=token, method=method)
req = compose_data(data, files)
try:
@@ -153,7 +189,7 @@ class Methods(Helper):
"""
Helper for Telegram API Methods listed on https://core.telegram.org/bots/api
- List is updated to Bot API 4.9
+ List is updated to Bot API 5.3
"""
mode = HelperMode.lowerCamelCase
@@ -165,8 +201,11 @@ class Methods(Helper):
# Available methods
GET_ME = Item() # getMe
+ LOG_OUT = Item() # logOut
+ CLOSE = Item() # close
SEND_MESSAGE = Item() # sendMessage
FORWARD_MESSAGE = Item() # forwardMessage
+ COPY_MESSAGE = Item() # copyMessage
SEND_PHOTO = Item() # sendPhoto
SEND_AUDIO = Item() # sendAudio
SEND_DOCUMENT = Item() # sendDocument
@@ -186,27 +225,34 @@ class Methods(Helper):
GET_USER_PROFILE_PHOTOS = Item() # getUserProfilePhotos
GET_FILE = Item() # getFile
KICK_CHAT_MEMBER = Item() # kickChatMember
+ BAN_CHAT_MEMBER = Item() # banChatMember
UNBAN_CHAT_MEMBER = Item() # unbanChatMember
RESTRICT_CHAT_MEMBER = Item() # restrictChatMember
PROMOTE_CHAT_MEMBER = Item() # promoteChatMember
SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE = Item() # setChatAdministratorCustomTitle
SET_CHAT_PERMISSIONS = Item() # setChatPermissions
EXPORT_CHAT_INVITE_LINK = Item() # exportChatInviteLink
+ CREATE_CHAT_INVITE_LINK = Item() # createChatInviteLink
+ EDIT_CHAT_INVITE_LINK = Item() # editChatInviteLink
+ REVOKE_CHAT_INVITE_LINK = Item() # revokeChatInviteLink
SET_CHAT_PHOTO = Item() # setChatPhoto
DELETE_CHAT_PHOTO = Item() # deleteChatPhoto
SET_CHAT_TITLE = Item() # setChatTitle
SET_CHAT_DESCRIPTION = Item() # setChatDescription
PIN_CHAT_MESSAGE = Item() # pinChatMessage
UNPIN_CHAT_MESSAGE = Item() # unpinChatMessage
+ UNPIN_ALL_CHAT_MESSAGES = Item() # unpinAllChatMessages
LEAVE_CHAT = Item() # leaveChat
GET_CHAT = Item() # getChat
GET_CHAT_ADMINISTRATORS = Item() # getChatAdministrators
- GET_CHAT_MEMBERS_COUNT = Item() # getChatMembersCount
+ GET_CHAT_MEMBER_COUNT = Item() # getChatMemberCount
+ GET_CHAT_MEMBERS_COUNT = Item() # getChatMembersCount (renamed to getChatMemberCount)
GET_CHAT_MEMBER = Item() # getChatMember
SET_CHAT_STICKER_SET = Item() # setChatStickerSet
DELETE_CHAT_STICKER_SET = Item() # deleteChatStickerSet
ANSWER_CALLBACK_QUERY = Item() # answerCallbackQuery
SET_MY_COMMANDS = Item() # setMyCommands
+ DELETE_MY_COMMANDS = Item() # deleteMyCommands
GET_MY_COMMANDS = Item() # getMyCommands
# Updating messages
@@ -242,25 +288,3 @@ class Methods(Helper):
SEND_GAME = Item() # sendGame
SET_GAME_SCORE = Item() # setGameScore
GET_GAME_HIGH_SCORES = Item() # getGameHighScores
-
- @staticmethod
- def api_url(token, method):
- """
- Generate API URL with included token and method name
-
- :param token:
- :param method:
- :return:
- """
- return API_URL.format(token=token, method=method)
-
- @staticmethod
- def file_url(token, path):
- """
- Generate File URL with included token and file path
-
- :param token:
- :param path:
- :return:
- """
- return FILE_URL.format(token=token, path=path)
diff --git a/aiogram/bot/base.py b/aiogram/bot/base.py
index 86347e88..07e44c1c 100644
--- a/aiogram/bot/base.py
+++ b/aiogram/bot/base.py
@@ -12,9 +12,11 @@ import certifi
from aiohttp.helpers import sentinel
from . import api
+from .api import TelegramAPIServer, TELEGRAM_PRODUCTION
from ..types import ParseMode, base
from ..utils import json
from ..utils.auth_widget import check_integrity
+from ..utils.deprecated import deprecated
class BaseBot:
@@ -33,7 +35,8 @@ class BaseBot:
proxy_auth: Optional[aiohttp.BasicAuth] = None,
validate_token: Optional[base.Boolean] = True,
parse_mode: typing.Optional[base.String] = None,
- timeout: typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]] = None
+ timeout: typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]] = None,
+ server: TelegramAPIServer = TELEGRAM_PRODUCTION
):
"""
Instructions how to get Bot token is found here: https://core.telegram.org/bots#3-how-do-i-create-a-bot
@@ -54,31 +57,29 @@ class BaseBot:
:type parse_mode: :obj:`str`
:param timeout: Request timeout
:type timeout: :obj:`typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]]`
+ :param server: Telegram Bot API Server endpoint.
+ :type server: :obj:`TelegramAPIServer`
:raise: when token is invalid throw an :obj:`aiogram.utils.exceptions.ValidationError`
"""
+ self._main_loop = loop
+
# Authentication
if validate_token:
api.check_token(token)
self._token = None
self.__token = token
self.id = int(token.split(sep=':')[0])
+ self.server = server
self.proxy = proxy
self.proxy_auth = proxy_auth
- # Asyncio loop instance
- if loop is None:
- loop = asyncio.get_event_loop()
- self.loop = loop
-
# aiohttp main session
ssl_context = ssl.create_default_context(cafile=certifi.where())
self._session: Optional[aiohttp.ClientSession] = None
self._connector_class: Type[aiohttp.TCPConnector] = aiohttp.TCPConnector
- self._connector_init = dict(
- limit=connections_limit, ssl=ssl_context, loop=self.loop
- )
+ self._connector_init = dict(limit=connections_limit, ssl=ssl_context)
if isinstance(proxy, str) and (proxy.startswith('socks5://') or proxy.startswith('socks4://')):
from aiohttp_socks import SocksConnector
@@ -106,11 +107,15 @@ class BaseBot:
def get_new_session(self) -> aiohttp.ClientSession:
return aiohttp.ClientSession(
- connector=self._connector_class(**self._connector_init),
- loop=self.loop,
+ connector=self._connector_class(**self._connector_init, loop=self._main_loop),
+ loop=self._main_loop,
json_serialize=json.dumps
)
+ @property
+ def loop(self) -> Optional[asyncio.AbstractEventLoop]:
+ return self._main_loop
+
@property
def session(self) -> Optional[aiohttp.ClientSession]:
if self._session is None or self._session.closed:
@@ -174,6 +179,8 @@ class BaseBot:
finally:
self._ctx_token.reset(token)
+ @deprecated("This method's behavior will be changed in aiogram v3.0. "
+ "More info: https://core.telegram.org/bots/api#close", stacklevel=3)
async def close(self):
"""
Close all client sessions
@@ -198,7 +205,7 @@ class BaseBot:
:rtype: Union[List, Dict]
:raise: :obj:`aiogram.exceptions.TelegramApiError`
"""
- return await api.make_request(self.session, self.__token, method, data, files,
+ return await api.make_request(self.session, self.server, self.__token, method, data, files,
proxy=self.proxy, proxy_auth=self.proxy_auth, timeout=self.timeout, **kwargs)
async def download_file(self, file_path: base.String,
@@ -238,7 +245,7 @@ class BaseBot:
return dest
def get_file_url(self, file_path):
- return api.Methods.file_url(token=self.__token, path=file_path)
+ return self.server.file_url(token=self.__token, path=file_path)
async def send_file(self, file_type, method, file, payload) -> Union[Dict, base.Boolean]:
"""
diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py
index f427fa8c..435def3e 100644
--- a/aiogram/bot/bot.py
+++ b/aiogram/bot/bot.py
@@ -7,6 +7,8 @@ import warnings
from .base import BaseBot, api
from .. import types
from ..types import base
+from ..utils.deprecated import deprecated
+from ..utils.exceptions import ValidationError
from ..utils.mixins import DataMixin, ContextInstanceMixin
from ..utils.payload import generate_payload, prepare_arg, prepare_attachment, prepare_file
@@ -64,9 +66,9 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
# === Getting updates ===
# https://core.telegram.org/bots/api#getting-updates
- async def get_updates(self, offset: typing.Union[base.Integer, None] = None,
- limit: typing.Union[base.Integer, None] = None,
- timeout: typing.Union[base.Integer, None] = None,
+ async def get_updates(self, offset: typing.Optional[base.Integer] = None,
+ limit: typing.Optional[base.Integer] = None,
+ timeout: typing.Optional[base.Integer] = None,
allowed_updates:
typing.Union[typing.List[base.String], None] = None) -> typing.List[types.Update]:
"""
@@ -79,11 +81,11 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
Source: https://core.telegram.org/bots/api#getupdates
:param offset: Identifier of the first update to be returned
- :type offset: :obj:`typing.Union[base.Integer, None]`
+ :type offset: :obj:`typing.Optional[base.Integer]`
:param limit: Limits the number of updates to be retrieved
- :type limit: :obj:`typing.Union[base.Integer, None]`
+ :type limit: :obj:`typing.Optional[base.Integer]`
:param timeout: Timeout in seconds for long polling
- :type timeout: :obj:`typing.Union[base.Integer, None]`
+ :type timeout: :obj:`typing.Optional[base.Integer]`
:param allowed_updates: List the types of updates you want your bot to receive
:type allowed_updates: :obj:`typing.Union[typing.List[base.String], None]`
:return: An Array of Update objects is returned
@@ -95,27 +97,61 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.GET_UPDATES, payload)
return [types.Update(**update) for update in result]
- async def set_webhook(self, url: base.String,
- certificate: typing.Union[base.InputFile, None] = None,
- max_connections: typing.Union[base.Integer, None] = None,
- allowed_updates: typing.Union[typing.List[base.String], None] = None) -> base.Boolean:
+ async def set_webhook(self,
+ url: base.String,
+ certificate: typing.Optional[base.InputFile] = None,
+ ip_address: typing.Optional[base.String] = None,
+ max_connections: typing.Optional[base.Integer] = None,
+ allowed_updates: typing.Optional[typing.List[base.String]] = None,
+ drop_pending_updates: typing.Optional[base.Boolean] = None,
+ ) -> base.Boolean:
"""
- 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.
+ 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/`.
+ Since nobody else knows your bot's token, you can be pretty sure it's us.
Source: https://core.telegram.org/bots/api#setwebhook
- :param url: HTTPS url to send updates to. Use an empty string to remove webhook integration
+ :param url: HTTPS url to send updates to. Use an empty string to remove
+ webhook integration
:type url: :obj:`base.String`
- :param certificate: Upload your public key certificate so that the root certificate in use can be checked
- :type certificate: :obj:`typing.Union[base.InputFile, None]`
- :param max_connections: Maximum allowed number of simultaneous HTTPS connections to the webhook
- for update delivery, 1-100.
- :type max_connections: :obj:`typing.Union[base.Integer, None]`
- :param allowed_updates: List the types of updates you want your bot to receive
- :type allowed_updates: :obj:`typing.Union[typing.List[base.String], None]`
+
+ :param certificate: Upload your public key certificate so that the root
+ certificate in use can be checked. See our self-signed guide for details:
+ https://core.telegram.org/bots/self-signed
+ :type certificate: :obj:`typing.Optional[base.InputFile]`
+
+ :param ip_address: The fixed IP address which will be used to send webhook
+ requests instead of the IP address resolved through DNS
+ :type ip_address: :obj:`typing.Optional[base.String]`
+
+ :param max_connections: 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.
+ :type max_connections: :obj:`typing.Optional[base.Integer]`
+
+ :param allowed_updates: A 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.
+ Please note that this parameter doesn't affect updates created before the
+ call to the setWebhook, so unwanted updates may be received for a short
+ period of time.
+ :type allowed_updates: :obj:`typing.Optional[typing.List[base.String]]`
+
+ :param drop_pending_updates: Pass True to drop all pending updates
+ :type drop_pending_updates: :obj:`typing.Optional[base.Boolean]`
+
:return: Returns true
:rtype: :obj:`base.Boolean`
"""
@@ -125,23 +161,26 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
files = {}
prepare_file(payload, files, 'certificate', certificate)
- result = await self.request(api.Methods.SET_WEBHOOK, payload, files)
- return result
+ return await self.request(api.Methods.SET_WEBHOOK, payload, files)
- async def delete_webhook(self) -> base.Boolean:
+ async def delete_webhook(self,
+ drop_pending_updates: typing.Optional[base.Boolean] = None,
+ ) -> base.Boolean:
"""
- Use this method to remove webhook integration if you decide to switch back to getUpdates.
- Returns True on success. Requires no parameters.
+ Use this method to remove webhook integration if you decide to switch back
+ to getUpdates. Returns True on success.
Source: https://core.telegram.org/bots/api#deletewebhook
+ :param drop_pending_updates: Pass True to drop all pending updates
+ :type drop_pending_updates: :obj:`typing.Optional[base.Boolean]`
+
:return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.DELETE_WEBHOOK, payload)
- return result
+ return await self.request(api.Methods.DELETE_WEBHOOK, payload)
async def get_webhook_info(self) -> types.WebhookInfo:
"""
@@ -176,15 +215,55 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.GET_ME, payload)
return types.User(**result)
- async def send_message(self, chat_id: typing.Union[base.Integer, base.String], text: base.String,
- parse_mode: typing.Union[base.String, None] = None,
- disable_web_page_preview: typing.Union[base.Boolean, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_to_message_id: typing.Union[base.Integer, None] = None,
+ async def log_out(self) -> base.Boolean:
+ """
+ Use this method to log out from the cloud Bot API server before launching
+ the bot locally. You **must** log out the bot before running it locally,
+ otherwise there is no guarantee that the bot will receive updates.
+ After a successful call, you will not be able to log in again using the
+ same token for 10 minutes. Returns True on success. Requires no parameters.
+
+ Source: https://core.telegram.org/bots/api#logout
+
+ :return: Returns True on success
+ :rtype: :obj:`base.Boolean`
+ """
+ payload = generate_payload(**locals())
+
+ return await self.request(api.Methods.LOG_OUT, payload)
+
+ @deprecated("This method will be renamed to `close` in aiogram v3.0")
+ async def close_bot(self) -> base.Boolean:
+ """
+ Use this method to close the bot instance before moving it from one local
+ server to another. You need to delete the webhook before calling this method
+ to ensure that the bot isn't launched again after server restart. The method
+ will return error 429 in the first 10 minutes after the bot is launched.
+ Returns True on success. Requires no parameters.
+
+ Source: https://core.telegram.org/bots/api#close
+
+ :return: Returns True on success
+ :rtype: :obj:`base.Boolean`
+ """
+ payload = generate_payload(**locals())
+
+ return await self.request(api.Methods.CLOSE, payload)
+
+ async def send_message(self,
+ chat_id: typing.Union[base.Integer, base.String],
+ text: base.String,
+ parse_mode: typing.Optional[base.String] = None,
+ entities: typing.Optional[typing.List[types.MessageEntity]] = None,
+ disable_web_page_preview: typing.Optional[base.Boolean] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove,
- types.ForceReply, None] = None) -> types.Message:
+ types.ForceReply, None] = None,
+ ) -> types.Message:
"""
Use this method to send text messages.
@@ -192,28 +271,44 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Unique identifier for the target chat or username of the target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param text: Text of the message to be sent
:type text: :obj:`base.String`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type entities: :obj:`typing.Optional[typing.List[types.MessageEntity]]`
+
:param disable_web_page_preview: Disables link previews for links in this message
- :type disable_web_page_preview: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_web_page_preview: :obj:`typing.Optional[base.Boolean]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
reply_markup = prepare_arg(reply_markup)
+ entities = prepare_arg(entities)
payload = generate_payload(**locals())
- if self.parse_mode:
+ if self.parse_mode and entities is None:
payload.setdefault('parse_mode', self.parse_mode)
result = await self.request(api.Methods.SEND_MESSAGE, payload)
@@ -221,7 +316,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
async def forward_message(self, chat_id: typing.Union[base.Integer, base.String],
from_chat_id: typing.Union[base.Integer, base.String], message_id: base.Integer,
- disable_notification: typing.Union[base.Boolean, None] = None) -> types.Message:
+ disable_notification: typing.Optional[base.Boolean] = None) -> types.Message:
"""
Use this method to forward messages of any kind.
@@ -232,7 +327,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param from_chat_id: Unique identifier for the chat where the original message was sent
:type from_chat_id: :obj:`typing.Union[base.Integer, base.String]`
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
:param message_id: Message identifier in the chat specified in from_chat_id
:type message_id: :obj:`base.Integer`
:return: On success, the sent Message is returned
@@ -243,16 +338,98 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.FORWARD_MESSAGE, payload)
return types.Message(**result)
- async def send_photo(self, chat_id: typing.Union[base.Integer, base.String],
+ async def copy_message(self,
+ chat_id: typing.Union[base.Integer, base.String],
+ from_chat_id: typing.Union[base.Integer, base.String],
+ message_id: base.Integer,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[types.MessageEntity]] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup,
+ types.ReplyKeyboardRemove,
+ types.ForceReply, None] = None,
+ ) -> types.MessageId:
+ """
+ Use this method to copy messages of any kind. The method is analogous to the
+ method forwardMessages, but the copied message doesn't have a link to the
+ original message. Returns the MessageId of the sent message on success.
+
+ Source: https://core.telegram.org/bots/api#copymessage
+
+ :param chat_id: Unique identifier for the target chat or username of the
+ target channel (in the format @channelusername)
+ :type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
+ :param from_chat_id: Unique identifier for the chat where the original
+ message was sent (or channel username in the format @channelusername)
+ :type from_chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
+ :param message_id: Message identifier in the chat specified in from_chat_id
+ :type message_id: :obj:`base.Integer`
+
+ :param caption: New caption for media, 0-1024 characters after entities
+ parsing. If not specified, the original caption is kept
+ :type caption: :obj:`typing.Optional[base.String]`
+
+ :param parse_mode: Mode for parsing entities in the new caption. See
+ formatting options for more details:
+ https://core.telegram.org/bots/api#formatting-options
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in the new
+ caption, which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[types.MessageEntity]]`
+
+ :param disable_notification: Sends the message silently. Users will receive
+ a notification with no sound
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_to_message_id: If the message is a reply, ID of the original
+ message
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_markup: 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.
+ :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply,
+ None]`
+
+ :return: On success, the sent Message is returned
+ :rtype: :obj:`types.Message`
+ """
+ reply_markup = prepare_arg(reply_markup)
+ caption_entities = prepare_arg(caption_entities)
+ payload = generate_payload(**locals())
+
+ if self.parse_mode and caption_entities is None:
+ payload.setdefault('parse_mode', self.parse_mode)
+
+ result = await self.request(api.Methods.COPY_MESSAGE, payload)
+ return types.MessageId(**result)
+
+ async def send_photo(self,
+ chat_id: typing.Union[base.Integer, base.String],
photo: typing.Union[base.InputFile, base.String],
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_to_message_id: typing.Union[base.Integer, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[types.MessageEntity]] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove,
- types.ForceReply, None] = None) -> types.Message:
+ types.ForceReply, None] = None,
+ ) -> types.Message:
"""
Use this method to send photos.
@@ -260,27 +437,43 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Unique identifier for the target chat or username of the target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param photo: Photo to send
:type photo: :obj:`typing.Union[base.InputFile, base.String]`
+
:param caption: Photo caption (may also be used when resending photos by file_id), 0-1024 characters
- :type caption: :obj:`typing.Union[base.String, None]`
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[types.MessageEntity]]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
reply_markup = prepare_arg(reply_markup)
+ caption_entities = prepare_arg(caption_entities)
payload = generate_payload(**locals(), exclude=['photo'])
- if self.parse_mode:
+ if self.parse_mode and caption_entities is None:
payload.setdefault('parse_mode', self.parse_mode)
files = {}
@@ -289,20 +482,24 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.SEND_PHOTO, payload, files)
return types.Message(**result)
- async def send_audio(self, chat_id: typing.Union[base.Integer, base.String],
+ async def send_audio(self,
+ chat_id: typing.Union[base.Integer, base.String],
audio: typing.Union[base.InputFile, base.String],
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- duration: typing.Union[base.Integer, None] = None,
- performer: typing.Union[base.String, None] = None,
- title: typing.Union[base.String, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[types.MessageEntity]] = None,
+ duration: typing.Optional[base.Integer] = None,
+ performer: typing.Optional[base.String] = None,
+ title: typing.Optional[base.String] = None,
thumb: typing.Union[base.InputFile, base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_to_message_id: typing.Union[base.Integer, None] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove,
- types.ForceReply, None] = None) -> types.Message:
+ types.ForceReply, None] = None,
+ ) -> types.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 format.
@@ -313,35 +510,55 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Unique identifier for the target chat or username of the target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param audio: Audio file to send
:type audio: :obj:`typing.Union[base.InputFile, base.String]`
+
:param caption: Audio caption, 0-1024 characters
- :type caption: :obj:`typing.Union[base.String, None]`
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[types.MessageEntity]]`
+
:param duration: Duration of the audio in seconds
- :type duration: :obj:`typing.Union[base.Integer, None]`
+ :type duration: :obj:`typing.Optional[base.Integer]`
+
:param performer: Performer
- :type performer: :obj:`typing.Union[base.String, None]`
+ :type performer: :obj:`typing.Optional[base.String]`
+
:param title: Track name
- :type title: :obj:`typing.Union[base.String, None]`
+ :type title: :obj:`typing.Optional[base.String]`
+
:param thumb: Thumbnail of the file sent
:type thumb: :obj:`typing.Union[base.InputFile, base.String, None]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
reply_markup = prepare_arg(reply_markup)
+ caption_entities = prepare_arg(caption_entities)
payload = generate_payload(**locals(), exclude=['audio', 'thumb'])
- if self.parse_mode:
+ if self.parse_mode and caption_entities is None:
payload.setdefault('parse_mode', self.parse_mode)
files = {}
@@ -351,49 +568,82 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.SEND_AUDIO, payload, files)
return types.Message(**result)
- async def send_document(self, chat_id: typing.Union[base.Integer, base.String],
+ async def send_document(self,
+ chat_id: typing.Union[base.Integer, base.String],
document: typing.Union[base.InputFile, base.String],
thumb: typing.Union[base.InputFile, base.String, None] = None,
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_to_message_id: typing.Union[base.Integer, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[types.MessageEntity]] = None,
+ disable_content_type_detection: typing.Optional[base.Boolean] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove,
- types.ForceReply, None] = None) -> types.Message:
+ types.ForceReply,
+ None] = None,
+ ) -> types.Message:
"""
- Use this method to send general files.
-
- Bots can currently send files of any type of up to 50 MB in size, this limit may be changed in the future.
+ 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
- :param chat_id: Unique identifier for the target chat or username of the target channel
+ :param chat_id: Unique identifier for the target chat or username of the
+ target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param document: File to send
:type document: :obj:`typing.Union[base.InputFile, base.String]`
+
:param thumb: Thumbnail of the file sent
:type thumb: :obj:`typing.Union[base.InputFile, base.String, None]`
- :param caption: Document caption (may also be used when resending documents by file_id), 0-1024 characters
- :type caption: :obj:`typing.Union[base.String, None]`
- :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
- fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
- :param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
- :param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
- :param reply_markup: 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
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup,
- types.ReplyKeyboardRemove, types.ForceReply], None]`
+
+ :param caption: Document caption (may also be used when resending documents
+ by file_id), 0-1024 characters
+ :type caption: :obj:`typing.Optional[base.String]`
+
+ :param disable_content_type_detection: Disables automatic server-side content
+ type detection for files uploaded using multipart/form-data
+ :type disable_content_type_detection: :obj:`typing.Optional[base.Boolean]`
+
+ :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show
+ bold, italic, fixed-width text or inline URLs in your bot's message.
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[types.MessageEntity]]`
+
+ :param disable_notification: Sends the message silently. Users will receive a
+ notification with no sound
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_to_message_id: If the message is a reply, ID of the original
+ message
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_markup: 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
+ :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply],
+ None]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
reply_markup = prepare_arg(reply_markup)
+ caption_entities = prepare_arg(caption_entities)
payload = generate_payload(**locals(), exclude=['document'])
- if self.parse_mode:
+ if self.parse_mode and caption_entities is None:
payload.setdefault('parse_mode', self.parse_mode)
files = {}
@@ -405,15 +655,17 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
async def send_video(self, chat_id: typing.Union[base.Integer, base.String],
video: typing.Union[base.InputFile, base.String],
- duration: typing.Union[base.Integer, None] = None,
- width: typing.Union[base.Integer, None] = None,
- height: typing.Union[base.Integer, None] = None,
+ duration: typing.Optional[base.Integer] = None,
+ width: typing.Optional[base.Integer] = None,
+ height: typing.Optional[base.Integer] = None,
thumb: typing.Union[base.InputFile, base.String, None] = None,
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- supports_streaming: typing.Union[base.Boolean, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_to_message_id: typing.Union[base.Integer, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[types.MessageEntity]] = None,
+ supports_streaming: typing.Optional[base.Boolean] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove,
@@ -426,37 +678,58 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Unique identifier for the target chat or username of the target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param video: Video to send
:type video: :obj:`typing.Union[base.InputFile, base.String]`
+
:param duration: Duration of sent video in seconds
- :type duration: :obj:`typing.Union[base.Integer, None]`
+ :type duration: :obj:`typing.Optional[base.Integer]`
+
:param width: Video width
- :type width: :obj:`typing.Union[base.Integer, None]`
+ :type width: :obj:`typing.Optional[base.Integer]`
+
:param height: Video height
- :type height: :obj:`typing.Union[base.Integer, None]`
+ :type height: :obj:`typing.Optional[base.Integer]`
+
:param thumb: Thumbnail of the file sent
:type thumb: :obj:`typing.Union[base.InputFile, base.String, None]`
+
:param caption: Video caption (may also be used when resending videos by file_id), 0-1024 characters
- :type caption: :obj:`typing.Union[base.String, None]`
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[types.MessageEntity]]`
+
:param supports_streaming: Pass True, if the uploaded video is suitable for streaming
- :type supports_streaming: :obj:`typing.Union[base.Boolean, None]`
+ :type supports_streaming: :obj:`typing.Optional[base.Boolean]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
reply_markup = prepare_arg(reply_markup)
+ caption_entities = prepare_arg(caption_entities)
payload = generate_payload(**locals(), exclude=['video', 'thumb'])
- if self.parse_mode:
+ if self.parse_mode and caption_entities is None:
payload.setdefault('parse_mode', self.parse_mode)
files = {}
@@ -469,18 +742,20 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
async def send_animation(self,
chat_id: typing.Union[base.Integer, base.String],
animation: typing.Union[base.InputFile, base.String],
- duration: typing.Union[base.Integer, None] = None,
- width: typing.Union[base.Integer, None] = None,
- height: typing.Union[base.Integer, None] = None,
+ duration: typing.Optional[base.Integer] = None,
+ width: typing.Optional[base.Integer] = None,
+ height: typing.Optional[base.Integer] = None,
thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None,
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_to_message_id: typing.Union[base.Integer, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[types.MessageEntity]] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove,
- types.ForceReply], None] = None
+ types.ForceReply], None] = None,
) -> types.Message:
"""
Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound).
@@ -493,38 +768,58 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Unique identifier for the target chat or username of the target channel
(in the format @channelusername)
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param animation: 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
:type animation: :obj:`typing.Union[base.InputFile, base.String]`
+
:param duration: Duration of sent animation in seconds
- :type duration: :obj:`typing.Union[base.Integer, None]`
+ :type duration: :obj:`typing.Optional[base.Integer]`
+
:param width: Animation width
- :type width: :obj:`typing.Union[base.Integer, None]`
+ :type width: :obj:`typing.Optional[base.Integer]`
+
:param height: Animation height
- :type height: :obj:`typing.Union[base.Integer, None]`
+ :type height: :obj:`typing.Optional[base.Integer]`
+
:param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail‘s width and height should not exceed 320.
:type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]`
+
:param caption: Animation caption (may also be used when resending animation by file_id), 0-1024 characters
- :type caption: :obj:`typing.Union[base.String, None]`
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in the media caption
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[types.MessageEntity]]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove, types.ForceReply], None]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
reply_markup = prepare_arg(reply_markup)
+ caption_entities = prepare_arg(caption_entities)
payload = generate_payload(**locals(), exclude=["animation", "thumb"])
- if self.parse_mode:
+ if self.parse_mode and caption_entities is None:
payload.setdefault('parse_mode', self.parse_mode)
files = {}
@@ -534,17 +829,21 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.SEND_ANIMATION, payload, files)
return types.Message(**result)
- async def send_voice(self, chat_id: typing.Union[base.Integer, base.String],
+ async def send_voice(self,
+ chat_id: typing.Union[base.Integer, base.String],
voice: typing.Union[base.InputFile, base.String],
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- duration: typing.Union[base.Integer, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_to_message_id: typing.Union[base.Integer, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[types.MessageEntity]] = None,
+ duration: typing.Optional[base.Integer] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove,
- types.ForceReply, None] = None) -> types.Message:
+ types.ForceReply, None] = None,
+ ) -> types.Message:
"""
Use this method to send audio files, if you want Telegram clients to display the file
as a playable voice message.
@@ -556,29 +855,46 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Unique identifier for the target chat or username of the target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param voice: Audio file to send
:type voice: :obj:`typing.Union[base.InputFile, base.String]`
+
:param caption: Voice message caption, 0-1024 characters
- :type caption: :obj:`typing.Union[base.String, None]`
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[types.MessageEntity]]`
+
:param duration: Duration of the voice message in seconds
- :type duration: :obj:`typing.Union[base.Integer, None]`
+ :type duration: :obj:`typing.Optional[base.Integer]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
reply_markup = prepare_arg(reply_markup)
+ caption_entities = prepare_arg(caption_entities)
payload = generate_payload(**locals(), exclude=['voice'])
- if self.parse_mode:
+ if self.parse_mode and caption_entities is None:
payload.setdefault('parse_mode', self.parse_mode)
files = {}
@@ -589,11 +905,12 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
async def send_video_note(self, chat_id: typing.Union[base.Integer, base.String],
video_note: typing.Union[base.InputFile, base.String],
- duration: typing.Union[base.Integer, None] = None,
- length: typing.Union[base.Integer, None] = None,
+ duration: typing.Optional[base.Integer] = None,
+ length: typing.Optional[base.Integer] = None,
thumb: typing.Union[base.InputFile, base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_to_message_id: typing.Union[base.Integer, None] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove,
@@ -606,22 +923,34 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Unique identifier for the target chat or username of the target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param video_note: Video note to send
:type video_note: :obj:`typing.Union[base.InputFile, base.String]`
+
:param duration: Duration of sent video in seconds
- :type duration: :obj:`typing.Union[base.Integer, None]`
+ :type duration: :obj:`typing.Optional[base.Integer]`
+
:param length: Video width and height
- :type length: :obj:`typing.Union[base.Integer, None]`
+ :type length: :obj:`typing.Optional[base.Integer]`
+
:param thumb: Thumbnail of the file sent
:type thumb: :obj:`typing.Union[base.InputFile, base.String, None]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -634,24 +963,41 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.SEND_VIDEO_NOTE, payload, files)
return types.Message(**result)
- async def send_media_group(self, chat_id: typing.Union[base.Integer, base.String],
+ async def send_media_group(self,
+ chat_id: typing.Union[base.Integer, base.String],
media: typing.Union[types.MediaGroup, typing.List],
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_to_message_id: typing.Union[base.Integer,
- None] = None) -> typing.List[types.Message]:
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
+ ) -> typing.List[types.Message]:
"""
- Use this method to send a group of photos or videos as an album.
+ Use this method to send a group of photos, videos, documents or audios as
+ an album. Documents and audio files can be only group in an album with
+ messages of the same type. On success, an array of Messages that were sent
+ is returned.
Source: https://core.telegram.org/bots/api#sendmediagroup
- :param chat_id: Unique identifier for the target chat or username of the target channel
+ :param chat_id: Unique identifier for the target chat or username of the
+ target channel (in the format @channelusername)
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
- :param media: A JSON-serialized array describing photos and videos to be sent
+
+ :param media: A JSON-serialized array describing messages to be sent, must
+ include 2-10 items
:type media: :obj:`typing.Union[types.MediaGroup, typing.List]`
- :param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
- :param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
+
+ :param disable_notification: Sends messages silently. Users will receive a
+ notification with no sound.
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_to_message_id: If the messages are a reply, ID of the original
+ message
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:return: On success, an array of the sent Messages is returned
:rtype: typing.List[types.Message]
"""
@@ -659,6 +1005,10 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
if isinstance(media, list):
media = types.MediaGroup(media)
+ # check MediaGroup quantity
+ if 2 > len(media.media) > 10:
+ raise ValidationError("Media group must include 2-10 items")
+
files = dict(media.get_files())
media = prepare_arg(media)
@@ -669,9 +1019,13 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
async def send_location(self, chat_id: typing.Union[base.Integer, base.String],
latitude: base.Float, longitude: base.Float,
- live_period: typing.Union[base.Integer, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_to_message_id: typing.Union[base.Integer, None] = None,
+ horizontal_accuracy: typing.Optional[base.Float] = None,
+ live_period: typing.Optional[base.Integer] = None,
+ heading: typing.Optional[base.Integer] = None,
+ proximity_alert_radius: typing.Optional[base.Integer] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove,
@@ -683,20 +1037,44 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Unique identifier for the target chat or username of the target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param latitude: Latitude of the location
:type latitude: :obj:`base.Float`
+
:param longitude: Longitude of the location
:type longitude: :obj:`base.Float`
+
+ :param horizontal_accuracy: The radius of uncertainty for the location,
+ measured in meters; 0-1500
+ :type horizontal_accuracy: :obj:`typing.Optional[base.Float]`
+
:param live_period: Period in seconds for which the location will be updated
- :type live_period: :obj:`typing.Union[base.Integer, None]`
+ :type live_period: :obj:`typing.Optional[base.Integer]`
+
+ :param heading: For live locations, a direction in which the user is moving,
+ in degrees. Must be between 1 and 360 if specified.
+ :type heading: :obj:`typing.Optional[base.Integer]`
+
+ :param proximity_alert_radius: For live locations, a maximum distance for
+ proximity alerts about approaching another chat member, in meters. Must
+ be between 1 and 100000 if specified.
+ :type proximity_alert_radius: :obj:`typing.Optional[base.Integer]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -706,12 +1084,17 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.SEND_LOCATION, payload)
return types.Message(**result)
- async def edit_message_live_location(self, latitude: base.Float, longitude: base.Float,
+ async def edit_message_live_location(self,
+ latitude: base.Float,
+ longitude: base.Float,
chat_id: typing.Union[base.Integer, base.String, None] = None,
- message_id: typing.Union[base.Integer, None] = None,
- inline_message_id: typing.Union[base.String, None] = None,
- reply_markup: typing.Union[types.InlineKeyboardMarkup,
- None] = None) -> types.Message or base.Boolean:
+ message_id: typing.Optional[base.Integer] = None,
+ inline_message_id: typing.Optional[base.String] = None,
+ horizontal_accuracy: typing.Optional[base.Float] = None,
+ heading: typing.Optional[base.Integer] = None,
+ proximity_alert_radius: typing.Optional[base.Integer] = None,
+ reply_markup: typing.Optional[types.InlineKeyboardMarkup] = None,
+ ) -> types.Message or base.Boolean:
"""
Use this method to edit live location messages sent by the bot or via the bot (for inline bots).
A location can be edited until its live_period expires or editing is explicitly disabled by a call
@@ -721,16 +1104,35 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Required if inline_message_id is not specified
:type chat_id: :obj:`typing.Union[base.Integer, base.String, None]`
+
:param message_id: Required if inline_message_id is not specified. Identifier of the sent message
- :type message_id: :obj:`typing.Union[base.Integer, None]`
+ :type message_id: :obj:`typing.Optional[base.Integer]`
+
:param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message
- :type inline_message_id: :obj:`typing.Union[base.String, None]`
+ :type inline_message_id: :obj:`typing.Optional[base.String]`
+
:param latitude: Latitude of new location
:type latitude: :obj:`base.Float`
+
:param longitude: Longitude of new location
:type longitude: :obj:`base.Float`
+
+ :param horizontal_accuracy: The radius of uncertainty for the location,
+ measured in meters; 0-1500
+ :type horizontal_accuracy: :obj:`typing.Optional[base.Float]`
+
+ :param heading: Direction in which the user is moving, in degrees. Must be
+ between 1 and 360 if specified.
+ :type heading: :obj:`typing.Optional[base.Integer]`
+
+ :param proximity_alert_radius: For live locations, a maximum distance for
+ proximity alerts about approaching another chat member, in meters. Must
+ be between 1 and 100000 if specified.
+ :type proximity_alert_radius: :obj:`typing.Optional[base.Integer]`
+
:param reply_markup: A JSON-serialized object for a new inline keyboard
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
+ :type reply_markup: :obj:`typing.Optional[types.InlineKeyboardMarkup]`
+
:return: On success, if the edited message was sent by the bot, the edited Message is returned,
otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
@@ -745,8 +1147,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
async def stop_message_live_location(self,
chat_id: typing.Union[base.Integer, base.String, None] = None,
- message_id: typing.Union[base.Integer, None] = None,
- inline_message_id: typing.Union[base.String, None] = None,
+ message_id: typing.Optional[base.Integer] = None,
+ inline_message_id: typing.Optional[base.String] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
None] = None) -> types.Message or base.Boolean:
"""
@@ -758,11 +1160,11 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Required if inline_message_id is not specified
:type chat_id: :obj:`typing.Union[base.Integer, base.String, None]`
:param message_id: Required if inline_message_id is not specified. Identifier of the sent message
- :type message_id: :obj:`typing.Union[base.Integer, None]`
+ :type message_id: :obj:`typing.Optional[base.Integer]`
:param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message
- :type inline_message_id: :obj:`typing.Union[base.String, None]`
+ :type inline_message_id: :obj:`typing.Optional[base.String]`
:param reply_markup: A JSON-serialized object for a new inline keyboard
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
+ :type reply_markup: :obj:`typing.Optional[types.InlineKeyboardMarkup]`
:return: On success, if the message was sent by the bot, the sent Message is returned,
otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
@@ -775,44 +1177,77 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
return result
return types.Message(**result)
- async def send_venue(self, chat_id: typing.Union[base.Integer, base.String],
- latitude: base.Float, longitude: base.Float,
- title: base.String, address: base.String,
- foursquare_id: typing.Union[base.String, None] = None,
- foursquare_type: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_to_message_id: typing.Union[base.Integer, None] = None,
+ async def send_venue(self,
+ chat_id: typing.Union[base.Integer, base.String],
+ latitude: base.Float,
+ longitude: base.Float,
+ title: base.String,
+ address: base.String,
+ foursquare_id: typing.Optional[base.String] = None,
+ foursquare_type: typing.Optional[base.String] = None,
+ google_place_id: typing.Optional[base.String] = None,
+ google_place_type: typing.Optional[base.String] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove,
- types.ForceReply, None] = None) -> types.Message:
+ types.ForceReply, None] = None,
+ ) -> types.Message:
"""
Use this method to send information about a venue.
Source: https://core.telegram.org/bots/api#sendvenue
- :param chat_id: Unique identifier for the target chat or username of the target channel
+ :param chat_id: Unique identifier for the target chat or username of the
+ target channel (in the format @channelusername)
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param latitude: Latitude of the venue
:type latitude: :obj:`base.Float`
+
:param longitude: Longitude of the venue
:type longitude: :obj:`base.Float`
+
:param title: Name of the venue
:type title: :obj:`base.String`
+
:param address: Address of the venue
:type address: :obj:`base.String`
+
:param foursquare_id: Foursquare identifier of the venue
- :type foursquare_id: :obj:`typing.Union[base.String, None]`
+ :type foursquare_id: :obj:`typing.Optional[base.String]`
+
:param foursquare_type: Foursquare type of the venue, if known
- :type foursquare_type: :obj:`typing.Union[base.String, None]`
- :param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
- :param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
- :param reply_markup: 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
+ :type foursquare_type: :obj:`typing.Optional[base.String]`
+
+ :param google_place_id: Google Places identifier of the venue
+ :type google_place_id: :obj:`typing.Optional[base.String]`
+
+ :param google_place_type: Google Places type of the venue. See supported
+ types: https://developers.google.com/places/web-service/supported_types
+ :type google_place_type: :obj:`typing.Optional[base.String]`
+
+ :param disable_notification: Sends the message silently. Users will receive
+ a notification with no sound
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_to_message_id: If the message is a reply, ID of the original
+ message
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+ types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply,
+ None]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -824,10 +1259,11 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
async def send_contact(self, chat_id: typing.Union[base.Integer, base.String],
phone_number: base.String, first_name: base.String,
- last_name: typing.Union[base.String, None] = None,
- vcard: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_to_message_id: typing.Union[base.Integer, None] = None,
+ last_name: typing.Optional[base.String] = None,
+ vcard: typing.Optional[base.String] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove,
@@ -839,22 +1275,34 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Unique identifier for the target chat or username of the target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param phone_number: Contact's phone number
:type phone_number: :obj:`base.String`
+
:param first_name: Contact's first name
:type first_name: :obj:`base.String`
+
:param last_name: Contact's last name
- :type last_name: :obj:`typing.Union[base.String, None]`
+ :type last_name: :obj:`typing.Optional[base.String]`
+
:param vcard: vcard
- :type vcard: :obj:`typing.Union[base.String, None]`
+ :type vcard: :obj:`typing.Optional[base.String]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -864,7 +1312,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.SEND_CONTACT, payload)
return types.Message(**result)
- async def send_poll(self, chat_id: typing.Union[base.Integer, base.String],
+ async def send_poll(self,
+ chat_id: typing.Union[base.Integer, base.String],
question: base.String,
options: typing.List[base.String],
is_anonymous: typing.Optional[base.Boolean] = None,
@@ -873,97 +1322,155 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
correct_option_id: typing.Optional[base.Integer] = None,
explanation: typing.Optional[base.String] = None,
explanation_parse_mode: typing.Optional[base.String] = None,
- open_period: typing.Union[base.Integer, None] = None,
+ explanation_entities: typing.Optional[typing.List[types.MessageEntity]] = None,
+ open_period: typing.Optional[base.Integer] = None,
close_date: typing.Union[
- base.Integer, datetime.datetime, datetime.timedelta, None] = None,
+ base.Integer,
+ datetime.datetime,
+ datetime.timedelta,
+ None] = None,
is_closed: typing.Optional[base.Boolean] = None,
disable_notification: typing.Optional[base.Boolean] = None,
reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove,
- types.ForceReply, None] = None) -> types.Message:
+ types.ForceReply, None] = None,
+ ) -> types.Message:
"""
- Use this method to send a native poll. A native poll can't be sent to a private chat.
- On success, the sent Message is returned.
+ Use this method to send a native poll. On success, the sent Message is
+ returned.
Source: https://core.telegram.org/bots/api#sendpoll
- :param chat_id: Unique identifier for the target chat
- or username of the target channel (in the format @channelusername).
- A native poll can't be sent to a private chat.
+ :param chat_id: Unique identifier for the target chat or username of the
+ target channel (in the format @channelusername)
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
- :param question: Poll question, 1-255 characters
+
+ :param question: Poll question, 1-300 characters
:type question: :obj:`base.String`
- :param options: List of answer options, 2-10 strings 1-100 characters each
+
+ :param options: A list of answer options, 2-10 strings 1-100 characters each
:type options: :obj:`typing.List[base.String]`
+
:param is_anonymous: True, if the poll needs to be anonymous, defaults to True
:type is_anonymous: :obj:`typing.Optional[base.Boolean]`
+
:param type: Poll type, “quiz” or “regular”, defaults to “regular”
:type type: :obj:`typing.Optional[base.String]`
- :param allows_multiple_answers: True, if the poll allows multiple answers, ignored for polls in quiz mode, defaults to False
+
+ :param allows_multiple_answers: True, if the poll allows multiple answers,
+ ignored for polls in quiz mode, defaults to False
:type allows_multiple_answers: :obj:`typing.Optional[base.Boolean]`
- :param correct_option_id: 0-based identifier of the correct answer option, required for polls in quiz mode
+
+ :param correct_option_id: 0-based identifier of the correct answer option,
+ required for polls in quiz mode
:type correct_option_id: :obj:`typing.Optional[base.Integer]`
- :param explanation: Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-200 characters with at most 2 line feeds after entities parsing
+
+ :param explanation: Text that is shown when a user chooses an incorrect
+ answer or taps on the lamp icon in a quiz-style poll, 0-200 characters
+ with at most 2 line feeds after entities parsing
:type explanation: :obj:`typing.Optional[base.String]`
- :param explanation_parse_mode: Mode for parsing entities in the explanation. See formatting options for more details.
+
+ :param explanation_parse_mode: Mode for parsing entities in the explanation.
+ See formatting options for more details.
:type explanation_parse_mode: :obj:`typing.Optional[base.String]`
- :param open_period: Amount of time in seconds the poll will be active after creation, 5-600. Can't be used together with close_date.
- :type open_period: :obj:`typing.Union[base.Integer, None]`
- :param close_date: Point in time (Unix timestamp) when the poll will be automatically closed. Must be at least 5 and no more than 600 seconds in the future. Can't be used together with open_period.
- :type close_date: :obj:`typing.Union[base.Integer, datetime.datetime, datetime.timedelta, None]`
+
+ :param explanation_entities: List of special entities that appear in message
+ text, which can be specified instead of parse_mode
+ :type explanation_entities: :obj:`typing.Optional[typing.List[types.MessageEntity]]`
+
+ :param open_period: Amount of time in seconds the poll will be active after
+ creation, 5-600. Can't be used together with close_date.
+ :type open_period: :obj:`typing.Optional[base.Integer]`
+
+ :param close_date: Point in time (Unix timestamp) when the poll will be
+ automatically closed. Must be at least 5 and no more than 600 seconds in
+ the future. Can't be used together with open_period.
+ :type close_date: :obj:`typing.Union[base.Integer, datetime.datetime,
+ datetime.timedelta, None]`
+
:param is_closed: Pass True, if the poll needs to be immediately closed
:type is_closed: :obj:`typing.Optional[base.Boolean]`
- :param disable_notification: Sends the message silently. Users will receive a notification with no sound.
+
+ :param disable_notification: Sends the message silently. Users will receive
+ a notification with no sound.
:type disable_notification: :obj:`typing.Optional[Boolean]`
- :param reply_to_message_id: If the message is a reply, ID of the original message
+
+ :param reply_to_message_id: If the message is a reply, ID of the original
+ message
:type reply_to_message_id: :obj:`typing.Optional[Integer]`
- :param reply_markup: 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
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+ types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply,
+ None]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
options = prepare_arg(options)
+ explanation_entities = prepare_arg(explanation_entities)
open_period = prepare_arg(open_period)
close_date = prepare_arg(close_date)
payload = generate_payload(**locals())
- if self.parse_mode:
+ if self.parse_mode and explanation_entities is None:
payload.setdefault('explanation_parse_mode', self.parse_mode)
result = await self.request(api.Methods.SEND_POLL, payload)
return types.Message(**result)
- async def send_dice(self, chat_id: typing.Union[base.Integer, base.String],
- disable_notification: typing.Union[base.Boolean, None] = None,
- emoji: typing.Union[base.String, None] = None,
- reply_to_message_id: typing.Union[base.Integer, None] = None,
+ async def send_dice(self,
+ chat_id: typing.Union[base.Integer, base.String],
+ disable_notification: typing.Optional[base.Boolean] = None,
+ emoji: typing.Optional[base.String] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove,
- types.ForceReply, None] = None) -> types.Message:
+ types.ForceReply, None] = None,
+ ) -> types.Message:
"""
- Use this method to send a dice, which will have a random value from 1 to 6.
+ Use this method to send an animated emoji that will display a random value.
On success, the sent Message is returned.
- (Yes, we're aware of the “proper” singular of die.
- But it's awkward, and we decided to help it change. One dice at a time!)
Source: https://core.telegram.org/bots/api#senddice
- :param chat_id: Unique identifier for the target chat or username of the target channel
+ :param chat_id: Unique identifier for the target chat or username of the
+ target channel (in the format @channelusername)
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
- :param emoji: Emoji on which the dice throw animation is based. Currently, must be one of “🎲” or “🎯”. Defauts to “🎲”
- :type emoji: :obj:`typing.Union[base.String, None]`
- :param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+
+ :param emoji: Emoji on which the dice throw animation is based. Currently,
+ must be one of “🎲”, “🎯”, “🏀”, “⚽”, or “🎰”. Dice can have values 1-6
+ for “🎲” and “🎯”, values 1-5 for “🏀” and “⚽”, and values 1-64 for “🎰”.
+ Defaults to “🎲”
+ :type emoji: :obj:`typing.Optional[base.String]`
+
+ :param disable_notification: Sends the message silently. Users will receive
+ a notification with no sound
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
- :param reply_markup: 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
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -977,29 +1484,45 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
async def send_chat_action(self, chat_id: typing.Union[base.Integer, base.String],
action: base.String) -> base.Boolean:
"""
- 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).
+ 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.
- We only recommend using this method when a response from the bot will take
- a noticeable amount of time to arrive.
+ 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
- :param chat_id: Unique identifier for the target chat or username of the target channel
+ :param chat_id: Unique identifier for the target chat or
+ username of the target channel (in the format
+ @channelusername)
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
- :param action: Type of action to broadcast
+
+ :param action: 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_voice` or `upload_voice`
+ for voice notes, `upload_document` for general files,
+ `find_location` for location data, `record_video_note` or
+ `upload_video_note` for video notes.
:type action: :obj:`base.String`
+
:return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.SEND_CHAT_ACTION, payload)
- return result
+ return await self.request(api.Methods.SEND_CHAT_ACTION, payload)
- async def get_user_profile_photos(self, user_id: base.Integer, offset: typing.Union[base.Integer, None] = None,
- limit: typing.Union[base.Integer, None] = None) -> types.UserProfilePhotos:
+ async def get_user_profile_photos(self, user_id: base.Integer, offset: typing.Optional[base.Integer] = None,
+ limit: typing.Optional[base.Integer] = None) -> types.UserProfilePhotos:
"""
Use this method to get a list of profile pictures for a user. Returns a UserProfilePhotos object.
@@ -1008,9 +1531,9 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param user_id: Unique identifier of the target user
:type user_id: :obj:`base.Integer`
:param offset: Sequential number of the first photo to be returned. By default, all photos are returned
- :type offset: :obj:`typing.Union[base.Integer, None]`
+ :type offset: :obj:`typing.Optional[base.Integer]`
:param limit: Limits the number of photos to be retrieved. Values between 1—100 are accepted. Defaults to 100
- :type limit: :obj:`typing.Union[base.Integer, None]`
+ :type limit: :obj:`typing.Optional[base.Integer]`
:return: Returns a UserProfilePhotos object
:rtype: :obj:`types.UserProfilePhotos`
"""
@@ -1039,58 +1562,99 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.GET_FILE, payload)
return types.File(**result)
- async def kick_chat_member(self, chat_id: typing.Union[base.Integer, base.String], user_id: base.Integer,
- until_date: typing.Union[
- base.Integer, datetime.datetime, datetime.timedelta, None] = None) -> base.Boolean:
+ async def ban_chat_member(self,
+ chat_id: typing.Union[base.Integer, base.String],
+ user_id: base.Integer,
+ until_date: typing.Union[base.Integer, datetime.datetime,
+ datetime.timedelta, None] = None,
+ revoke_messages: typing.Optional[base.Boolean] = None,
+ ) -> base.Boolean:
"""
- Use this method to kick a user from a group, a supergroup or a channel.
- In the case of supergroups and channels, the user will not be able to return to the group
- on their own using invite links, etc., unless unbanned first.
+ Use this method to ban a user in a group, a supergroup or a
+ channel. In the case of supergroups and channels, the user will
+ not be able to return to the chat on their own using invite
+ links, etc., unless unbanned first. The bot must be an
+ administrator in the chat for this to work and must have the
+ appropriate admin rights. Returns True on success.
- The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
+ Source: https://core.telegram.org/bots/api#banchatmember
- Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ setting
- is off in the target group.
- Otherwise members may only be removed by the group's creator or by the member that added them.
-
- Source: https://core.telegram.org/bots/api#kickchatmember
-
- :param chat_id: Unique identifier for the target group or username of the target supergroup or channel
+ :param chat_id: Unique identifier for the target group or
+ username of the target supergroup or channel (in the format
+ @channelusername)
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param user_id: Unique identifier of the target user
:type user_id: :obj:`base.Integer`
- :param until_date: Date when the user will be unbanned, unix time
- :type until_date: :obj:`typing.Union[base.Integer, None]`
+
+ :param until_date: Date when the user will be unbanned, unix
+ time. If user is banned for more than 366 days or less than
+ 30 seconds from the current time they are considered to be
+ banned forever. Applied for supergroups and channels only.
+ :type until_date: :obj:`typing.Union[base.Integer,
+ datetime.datetime, datetime.timedelta, None]`
+
+ :param revoke_messages: Pass True to delete all messages from
+ the chat for the user that is being removed. If False, the user
+ will be able to see messages in the group that were sent before
+ the user was removed. Always True for supergroups and channels.
+ :type revoke_messages: :obj:`typing.Optional[base.Boolean]`
+
:return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
until_date = prepare_arg(until_date)
payload = generate_payload(**locals())
- result = await self.request(api.Methods.KICK_CHAT_MEMBER, payload)
- return result
+ return await self.request(api.Methods.BAN_CHAT_MEMBER, payload)
- async def unban_chat_member(self, chat_id: typing.Union[base.Integer, base.String],
- user_id: base.Integer) -> base.Boolean:
+ async def kick_chat_member(self,
+ chat_id: typing.Union[base.Integer, base.String],
+ user_id: base.Integer,
+ until_date: typing.Union[base.Integer, datetime.datetime,
+ datetime.timedelta, None] = None,
+ revoke_messages: typing.Optional[base.Boolean] = None,
+ ) -> base.Boolean:
+ """Renamed to ban_chat_member."""
+ return await self.ban_chat_member(
+ chat_id=chat_id,
+ user_id=user_id,
+ until_date=until_date,
+ revoke_messages=revoke_messages,
+ )
+
+ async def unban_chat_member(self,
+ chat_id: typing.Union[base.Integer, base.String],
+ user_id: base.Integer,
+ only_if_banned: typing.Optional[base.Boolean] = None,
+ ) -> base.Boolean:
"""
- 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.
+ 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. By default, this method guarantees that after the call the user is not
+ a member of the chat, but will be able to join it. So if the user is a member
+ of the chat they will also be removed from the chat. If you don't want this,
+ use the parameter only_if_banned. Returns True on success.
Source: https://core.telegram.org/bots/api#unbanchatmember
- :param chat_id: Unique identifier for the target group or username of the target supergroup or channel
+ :param chat_id: Unique identifier for the target group or username of the
+ target supergroup or channel (in the format @username)
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param user_id: Unique identifier of the target user
:type user_id: :obj:`base.Integer`
+
+ :param only_if_banned: Do nothing if the user is not banned
+ :type only_if_banned: :obj:`typing.Optional[base.Boolean]`
+
:return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.UNBAN_CHAT_MEMBER, payload)
- return result
+ return await self.request(api.Methods.UNBAN_CHAT_MEMBER, payload)
async def restrict_chat_member(self, chat_id: typing.Union[base.Integer, base.String],
user_id: base.Integer,
@@ -1098,10 +1662,10 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
# permissions argument need to be required after removing other `can_*` arguments
until_date: typing.Union[
base.Integer, datetime.datetime, datetime.timedelta, None] = None,
- can_send_messages: typing.Union[base.Boolean, None] = None,
- can_send_media_messages: typing.Union[base.Boolean, None] = None,
- can_send_other_messages: typing.Union[base.Boolean, None] = None,
- can_add_web_page_previews: typing.Union[base.Boolean, None] = None) -> base.Boolean:
+ can_send_messages: typing.Optional[base.Boolean] = None,
+ can_send_media_messages: typing.Optional[base.Boolean] = None,
+ can_send_other_messages: typing.Optional[base.Boolean] = None,
+ can_add_web_page_previews: typing.Optional[base.Boolean] = None) -> base.Boolean:
"""
Use this method to restrict a user in a supergroup.
The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights.
@@ -1116,18 +1680,18 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param permissions: New user permissions
:type permissions: :obj:`ChatPermissions`
:param until_date: Date when restrictions will be lifted for the user, unix time
- :type until_date: :obj:`typing.Union[base.Integer, None]`
+ :type until_date: :obj:`typing.Optional[base.Integer]`
:param can_send_messages: Pass True, if the user can send text messages, contacts, locations and venues
- :type can_send_messages: :obj:`typing.Union[base.Boolean, None]`
+ :type can_send_messages: :obj:`typing.Optional[base.Boolean]`
:param can_send_media_messages: Pass True, if the user can send audios, documents, photos, videos,
video notes and voice notes, implies can_send_messages
- :type can_send_media_messages: :obj:`typing.Union[base.Boolean, None]`
+ :type can_send_media_messages: :obj:`typing.Optional[base.Boolean]`
:param can_send_other_messages: Pass True, if the user can send animations, games, stickers and
use inline bots, implies can_send_media_messages
- :type can_send_other_messages: :obj:`typing.Union[base.Boolean, None]`
+ :type can_send_other_messages: :obj:`typing.Optional[base.Boolean]`
:param can_add_web_page_previews: Pass True, if the user may add web page previews to their messages,
implies can_send_media_messages
- :type can_add_web_page_previews: :obj:`typing.Union[base.Boolean, None]`
+ :type can_add_web_page_previews: :obj:`typing.Optional[base.Boolean]`
:return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
@@ -1135,29 +1699,33 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
permissions = prepare_arg(permissions)
payload = generate_payload(**locals())
- for permission in ['can_send_messages',
+ for permission in ('can_send_messages',
'can_send_media_messages',
'can_send_other_messages',
- 'can_add_web_page_previews']:
+ 'can_add_web_page_previews'):
if permission in payload:
warnings.warn(f"The method `restrict_chat_member` now takes the new user permissions "
f"in a single argument of the type ChatPermissions instead of "
f"passing regular argument {payload[permission]}",
DeprecationWarning, stacklevel=2)
- result = await self.request(api.Methods.RESTRICT_CHAT_MEMBER, payload)
- return result
+ return await self.request(api.Methods.RESTRICT_CHAT_MEMBER, payload)
- async def promote_chat_member(self, chat_id: typing.Union[base.Integer, base.String],
+ async def promote_chat_member(self,
+ chat_id: typing.Union[base.Integer, base.String],
user_id: base.Integer,
- can_change_info: typing.Union[base.Boolean, None] = None,
- can_post_messages: typing.Union[base.Boolean, None] = None,
- can_edit_messages: typing.Union[base.Boolean, None] = None,
- can_delete_messages: typing.Union[base.Boolean, None] = None,
- can_invite_users: typing.Union[base.Boolean, None] = None,
- can_restrict_members: typing.Union[base.Boolean, None] = None,
- can_pin_messages: typing.Union[base.Boolean, None] = None,
- can_promote_members: typing.Union[base.Boolean, None] = None) -> base.Boolean:
+ is_anonymous: typing.Optional[base.Boolean] = None,
+ can_manage_chat: typing.Optional[base.Boolean] = None,
+ can_change_info: typing.Optional[base.Boolean] = None,
+ can_post_messages: typing.Optional[base.Boolean] = None,
+ can_edit_messages: typing.Optional[base.Boolean] = None,
+ can_delete_messages: typing.Optional[base.Boolean] = None,
+ can_manage_voice_chats: typing.Optional[base.Boolean] = None,
+ can_invite_users: typing.Optional[base.Boolean] = None,
+ can_restrict_members: typing.Optional[base.Boolean] = None,
+ can_pin_messages: typing.Optional[base.Boolean] = None,
+ can_promote_members: typing.Optional[base.Boolean] = None,
+ ) -> base.Boolean:
"""
Use this method to promote or demote a user in a supergroup or a channel.
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
@@ -1167,33 +1735,53 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Unique identifier for the target chat or username of the target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param user_id: Unique identifier of the target user
:type user_id: :obj:`base.Integer`
+
+ :param is_anonymous: Pass True, if the administrator's presence in the chat is hidden
+ :type is_anonymous: :obj:`typing.Optional[base.Boolean]`
+
+ :param can_manage_chat: Pass True, if the administrator can access the chat event log, chat statistics,
+ message statistics in channels, see channel members, see anonymous administrators in supergroups
+ and ignore slow mode. Implied by any other administrator privilege
+ :type can_manage_chat: :obj:`typing.Optional[base.Boolean]`
+
:param can_change_info: Pass True, if the administrator can change chat title, photo and other settings
- :type can_change_info: :obj:`typing.Union[base.Boolean, None]`
+ :type can_change_info: :obj:`typing.Optional[base.Boolean]`
+
:param can_post_messages: Pass True, if the administrator can create channel posts, channels only
- :type can_post_messages: :obj:`typing.Union[base.Boolean, None]`
+ :type can_post_messages: :obj:`typing.Optional[base.Boolean]`
+
:param can_edit_messages: Pass True, if the administrator can edit messages of other users, channels only
- :type can_edit_messages: :obj:`typing.Union[base.Boolean, None]`
+ :type can_edit_messages: :obj:`typing.Optional[base.Boolean]`
+
:param can_delete_messages: Pass True, if the administrator can delete messages of other users
- :type can_delete_messages: :obj:`typing.Union[base.Boolean, None]`
+ :type can_delete_messages: :obj:`typing.Optional[base.Boolean]`
+
+ :param can_manage_voice_chats: Pass True, if the administrator can manage voice chats, supergroups only
+ :type can_manage_voice_chats: :obj:`typing.Optional[base.Boolean]`
+
:param can_invite_users: Pass True, if the administrator can invite new users to the chat
- :type can_invite_users: :obj:`typing.Union[base.Boolean, None]`
+ :type can_invite_users: :obj:`typing.Optional[base.Boolean]`
+
:param can_restrict_members: Pass True, if the administrator can restrict, ban or unban chat members
- :type can_restrict_members: :obj:`typing.Union[base.Boolean, None]`
+ :type can_restrict_members: :obj:`typing.Optional[base.Boolean]`
+
:param can_pin_messages: Pass True, if the administrator can pin messages, supergroups only
- :type can_pin_messages: :obj:`typing.Union[base.Boolean, None]`
+ :type can_pin_messages: :obj:`typing.Optional[base.Boolean]`
+
:param can_promote_members: Pass True, if the administrator can add new administrators
with a subset of his own privileges or demote administrators that he has promoted,
directly or indirectly (promoted by administrators that were appointed by him)
- :type can_promote_members: :obj:`typing.Union[base.Boolean, None]`
+ :type can_promote_members: :obj:`typing.Optional[base.Boolean]`
+
:return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.PROMOTE_CHAT_MEMBER, payload)
- return result
+ return await self.request(api.Methods.PROMOTE_CHAT_MEMBER, payload)
async def set_chat_administrator_custom_title(self, chat_id: typing.Union[base.Integer, base.String],
user_id: base.Integer, custom_title: base.String) -> base.Boolean:
@@ -1211,8 +1799,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE, payload)
- return result
+ return await self.request(api.Methods.SET_CHAT_ADMINISTRATOR_CUSTOM_TITLE, payload)
async def set_chat_permissions(self, chat_id: typing.Union[base.Integer, base.String],
permissions: types.ChatPermissions) -> base.Boolean:
@@ -1230,8 +1817,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
permissions = prepare_arg(permissions)
payload = generate_payload(**locals())
- result = await self.request(api.Methods.SET_CHAT_PERMISSIONS, payload)
- return result
+ return await self.request(api.Methods.SET_CHAT_PERMISSIONS, payload)
async def export_chat_invite_link(self, chat_id: typing.Union[base.Integer, base.String]) -> base.String:
"""
@@ -1247,8 +1833,101 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.EXPORT_CHAT_INVITE_LINK, payload)
- return result
+ return await self.request(api.Methods.EXPORT_CHAT_INVITE_LINK, payload)
+
+ async def create_chat_invite_link(self,
+ chat_id: typing.Union[base.Integer, base.String],
+ expire_date: typing.Union[base.Integer, datetime.datetime,
+ datetime.timedelta, None] = None,
+ member_limit: typing.Optional[base.Integer] = None,
+ ) -> types.ChatInviteLink:
+ """
+ Use this method to create an additional invite link for a chat.
+ The bot must be an administrator in the chat for this to work and must have
+ the appropriate admin rights. The link can be revoked using the method
+ revokeChatInviteLink.
+
+ Source: https://core.telegram.org/bots/api#createchatinvitelink
+
+ :param chat_id: Unique identifier for the target chat or username of the
+ target channel (in the format @channelusername)
+ :type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
+ :param expire_date: Point in time when the link will expire
+ :type expire_date: :obj:`typing.Union[base.Integer, datetime.datetime,
+ datetime.timedelta, None]`
+
+ :param member_limit: Maximum number of users that can be members of the chat
+ simultaneously after joining the chat via this invite link; 1-99999
+ :type member_limit: :obj:`typing.Optional[base.Integer]`
+
+ :return: the new invite link as ChatInviteLink object.
+ :rtype: :obj:`types.ChatInviteLink`
+ """
+ expire_date = prepare_arg(expire_date)
+ payload = generate_payload(**locals())
+
+ result = await self.request(api.Methods.CREATE_CHAT_INVITE_LINK, payload)
+ return types.ChatInviteLink(**result)
+
+ async def edit_chat_invite_link(self,
+ chat_id: typing.Union[base.Integer, base.String],
+ invite_link: base.String,
+ expire_date: typing.Union[base.Integer, datetime.datetime,
+ datetime.timedelta, None] = None,
+ member_limit: typing.Optional[base.Integer] = None,
+ ) -> types.ChatInviteLink:
+ """
+ Use this method to edit a non-primary invite link created by the bot.
+ The bot must be an administrator in the chat for this to work and must have
+ the appropriate admin rights.
+
+ Source: https://core.telegram.org/bots/api#editchatinvitelink
+
+ :param chat_id: Unique identifier for the target chat or username of the
+ target channel (in the format @channelusername)
+ :type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
+ :param invite_link: The invite link to edit
+ :type invite_link: :obj:`base.String`
+
+ :param expire_date: Point in time (Unix timestamp) when the link will expire
+ :type expire_date: :obj:`typing.Union[base.Integer, datetime.datetime,
+ datetime.timedelta, None]`
+
+ :param member_limit: Maximum number of users that can be members of the chat
+ simultaneously after joining the chat via this invite link; 1-99999
+ :type member_limit: :obj:`typing.Optional[base.Integer]`
+
+ :return: edited invite link as a ChatInviteLink object.
+ """
+ expire_date = prepare_arg(expire_date)
+ payload = generate_payload(**locals())
+
+ result = await self.request(api.Methods.EDIT_CHAT_INVITE_LINK, payload)
+ return types.ChatInviteLink(**result)
+
+ async def revoke_chat_invite_link(self,
+ chat_id: typing.Union[base.Integer, base.String],
+ invite_link: base.String,
+ ) -> types.ChatInviteLink:
+ """
+ Use this method to revoke an invite link created by the bot.
+ If the primary link is revoked, a new link is automatically generated.
+ The bot must be an administrator in the chat for this to work and must have
+ the appropriate admin rights.
+
+ Source: https://core.telegram.org/bots/api#revokechatinvitelink
+
+ :param chat_id: Unique identifier for the target chat or username of the
+ target channel (in the format @channelusername)
+ :param invite_link: The invite link to revoke
+ :return: the revoked invite link as ChatInviteLink object
+ """
+ payload = generate_payload(**locals())
+
+ result = await self.request(api.Methods.REVOKE_CHAT_INVITE_LINK, payload)
+ return types.ChatInviteLink(**result)
async def set_chat_photo(self, chat_id: typing.Union[base.Integer, base.String],
photo: base.InputFile) -> base.Boolean:
@@ -1273,8 +1952,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
files = {}
prepare_file(payload, files, 'photo', photo)
- result = await self.request(api.Methods.SET_CHAT_PHOTO, payload, files)
- return result
+ return await self.request(api.Methods.SET_CHAT_PHOTO, payload, files)
async def delete_chat_photo(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Boolean:
"""
@@ -1293,8 +1971,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.DELETE_CHAT_PHOTO, payload)
- return result
+ return await self.request(api.Methods.DELETE_CHAT_PHOTO, payload)
async def set_chat_title(self, chat_id: typing.Union[base.Integer, base.String],
title: base.String) -> base.Boolean:
@@ -1316,11 +1993,10 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.SET_CHAT_TITLE, payload)
- return result
+ return await self.request(api.Methods.SET_CHAT_TITLE, payload)
async def set_chat_description(self, chat_id: typing.Union[base.Integer, base.String],
- description: typing.Union[base.String, None] = None) -> base.Boolean:
+ description: typing.Optional[base.String] = None) -> base.Boolean:
"""
Use this method to change the description of a supergroup or a channel.
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
@@ -1330,54 +2006,95 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Unique identifier for the target chat or username of the target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
:param description: New chat description, 0-255 characters
- :type description: :obj:`typing.Union[base.String, None]`
+ :type description: :obj:`typing.Optional[base.String]`
:return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.SET_CHAT_DESCRIPTION, payload)
- return result
+ return await self.request(api.Methods.SET_CHAT_DESCRIPTION, payload)
- async def pin_chat_message(self, chat_id: typing.Union[base.Integer, base.String], message_id: base.Integer,
- disable_notification: typing.Union[base.Boolean, None] = None) -> base.Boolean:
+ async def pin_chat_message(self,
+ chat_id: typing.Union[base.Integer, base.String],
+ message_id: base.Integer,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ ) -> base.Boolean:
"""
- Use this method to pin a message in a supergroup.
- The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
+ Use this method to add a message to the list of pinned messages in a chat.
+ If the chat is not a private chat, the bot must be an administrator in the
+ chat for this to work and must have the 'can_pin_messages' admin right in a
+ supergroup or 'can_edit_messages' admin right in a channel. Returns True on
+ success.
Source: https://core.telegram.org/bots/api#pinchatmessage
- :param chat_id: Unique identifier for the target chat or username of the target supergroup
+ :param chat_id: Unique identifier for the target chat or username of the
+ target channel (in the format @channelusername)
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param message_id: Identifier of a message to pin
:type message_id: :obj:`base.Integer`
- :param disable_notification: Pass True, if it is not necessary to send a notification to
- all group members about the new pinned message
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+
+ :param disable_notification: Pass True, if it is not necessary to send a
+ notification to all group members about the new pinned message
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.PIN_CHAT_MESSAGE, payload)
- return result
+ return await self.request(api.Methods.PIN_CHAT_MESSAGE, payload)
- async def unpin_chat_message(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Boolean:
+ async def unpin_chat_message(self,
+ chat_id: typing.Union[base.Integer, base.String],
+ message_id: typing.Optional[base.Integer] = None,
+ ) -> base.Boolean:
"""
- Use this method to unpin a message in a supergroup chat.
- The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
+ Use this method to remove a message from the list of pinned messages in a
+ chat. If the chat is not a private chat, the bot must be an administrator in
+ the chat for this to work and must have the 'can_pin_messages' admin right in
+ a supergroup or 'can_edit_messages' admin right in a channel. Returns True on
+ success.
Source: https://core.telegram.org/bots/api#unpinchatmessage
- :param chat_id: Unique identifier for the target chat or username of the target supergroup
+ :param chat_id: Unique identifier for the target chat or username of the
+ target channel (in the format @channelusername)
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
+ :param message_id: Identifier of a message to unpin. If not specified, the
+ most recent pinned message (by sending date) will be unpinned.
+ :type message_id: :obj:`typing.Optional[base.Integer]`
+
:return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.UNPIN_CHAT_MESSAGE, payload)
- return result
+ return await self.request(api.Methods.UNPIN_CHAT_MESSAGE, payload)
+
+ async def unpin_all_chat_messages(self,
+ chat_id: typing.Union[base.Integer, base.String],
+ ) -> base.Boolean:
+ """
+ Use this method to clear the list of pinned messages in a chat. If the chat
+ is not a private chat, the bot must be an administrator in the chat for this
+ to work and must have the 'can_pin_messages' admin right in a supergroup or
+ 'can_edit_messages' admin right in a channel. Returns True on success.
+
+ Source: https://core.telegram.org/bots/api#unpinallchatmessages
+
+ :param chat_id: Unique identifier for the target chat or username of the
+ target channel (in the format @channelusername)
+ :type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
+ :return: Returns True on success
+ :rtype: :obj:`base.Boolean`
+ """
+ payload = generate_payload(**locals())
+
+ return await self.request(api.Methods.UNPIN_ALL_CHAT_MESSAGES, payload)
async def leave_chat(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Boolean:
"""
@@ -1392,8 +2109,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.LEAVE_CHAT, payload)
- return result
+ return await self.request(api.Methods.LEAVE_CHAT, payload)
async def get_chat(self, chat_id: typing.Union[base.Integer, base.String]) -> types.Chat:
"""
@@ -1430,13 +2146,13 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
payload = generate_payload(**locals())
result = await self.request(api.Methods.GET_CHAT_ADMINISTRATORS, payload)
- return [types.ChatMember(**chatmember) for chatmember in result]
+ return [types.ChatMember.resolve(**chat_member) for chat_member in result]
- async def get_chat_members_count(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Integer:
+ async def get_chat_member_count(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Integer:
"""
Use this method to get the number of members in a chat.
- Source: https://core.telegram.org/bots/api#getchatmemberscount
+ Source: https://core.telegram.org/bots/api#getchatmembercount
:param chat_id: Unique identifier for the target chat or username of the target supergroup or channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
@@ -1445,8 +2161,11 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.GET_CHAT_MEMBERS_COUNT, payload)
- return result
+ return await self.request(api.Methods.GET_CHAT_MEMBER_COUNT, payload)
+
+ async def get_chat_members_count(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Integer:
+ """Renamed to get_chat_member_count."""
+ return await self.get_chat_member_count(chat_id)
async def get_chat_member(self, chat_id: typing.Union[base.Integer, base.String],
user_id: base.Integer) -> types.ChatMember:
@@ -1465,7 +2184,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
payload = generate_payload(**locals())
result = await self.request(api.Methods.GET_CHAT_MEMBER, payload)
- return types.ChatMember(**result)
+ return types.ChatMember.resolve(**result)
async def set_chat_sticker_set(self, chat_id: typing.Union[base.Integer, base.String],
sticker_set_name: base.String) -> base.Boolean:
@@ -1487,8 +2206,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.SET_CHAT_STICKER_SET, payload)
- return result
+ return await self.request(api.Methods.SET_CHAT_STICKER_SET, payload)
async def delete_chat_sticker_set(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Boolean:
"""
@@ -1507,14 +2225,13 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.DELETE_CHAT_STICKER_SET, payload)
- return result
+ return await self.request(api.Methods.DELETE_CHAT_STICKER_SET, payload)
async def answer_callback_query(self, callback_query_id: base.String,
- text: typing.Union[base.String, None] = None,
- show_alert: typing.Union[base.Boolean, None] = None,
- url: typing.Union[base.String, None] = None,
- cache_time: typing.Union[base.Integer, None] = None) -> base.Boolean:
+ text: typing.Optional[base.String] = None,
+ show_alert: typing.Optional[base.Boolean] = None,
+ url: typing.Optional[base.String] = None,
+ cache_time: typing.Optional[base.Integer] = None) -> base.Boolean:
"""
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.
@@ -1528,62 +2245,127 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param callback_query_id: Unique identifier for the query to be answered
:type callback_query_id: :obj:`base.String`
:param text: Text of the notification. If not specified, nothing will be shown to the user, 0-1024 characters
- :type text: :obj:`typing.Union[base.String, None]`
+ :type text: :obj:`typing.Optional[base.String]`
:param show_alert: If true, an alert will be shown by the client instead of a notification
at the top of the chat screen. Defaults to false.
- :type show_alert: :obj:`typing.Union[base.Boolean, None]`
+ :type show_alert: :obj:`typing.Optional[base.Boolean]`
:param url: URL that will be opened by the user's client
- :type url: :obj:`typing.Union[base.String, None]`
+ :type url: :obj:`typing.Optional[base.String]`
:param cache_time: The maximum amount of time in seconds that the
result of the callback query may be cached client-side.
- :type cache_time: :obj:`typing.Union[base.Integer, None]`
+ :type cache_time: :obj:`typing.Optional[base.Integer]`
:return: On success, True is returned
:rtype: :obj:`base.Boolean`
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.ANSWER_CALLBACK_QUERY, payload)
- return result
+ return await self.request(api.Methods.ANSWER_CALLBACK_QUERY, payload)
- async def set_my_commands(self, commands: typing.List[types.BotCommand]) -> base.Boolean:
+ async def set_my_commands(self,
+ commands: typing.List[types.BotCommand],
+ scope: typing.Optional[types.BotCommandScope] = None,
+ language_code: typing.Optional[base.String] = None,
+ ) -> base.Boolean:
"""
Use this method to change the list of the bot's commands.
Source: https://core.telegram.org/bots/api#setmycommands
- :param commands: A JSON-serialized list of bot commands to be set as the list of the bot's commands.
- At most 100 commands can be specified.
+ :param commands: A JSON-serialized list of bot commands to be
+ set as the list of the bot's commands. At most 100 commands
+ can be specified.
:type commands: :obj: `typing.List[types.BotCommand]`
+
+ :param scope: A JSON-serialized object, describing scope of
+ users for which the commands are relevant. Defaults to
+ BotCommandScopeDefault.
+ :type scope: :obj: `typing.Optional[types.BotCommandScope]`
+
+ :param language_code: A two-letter ISO 639-1 language code. If
+ empty, commands will be applied to all users from the given
+ scope, for whose language there are no dedicated commands
+ :type language_code: :obj: `typing.Optional[base.String]`
+
:return: Returns True on success.
:rtype: :obj:`base.Boolean`
"""
commands = prepare_arg(commands)
+ scope = prepare_arg(scope)
payload = generate_payload(**locals())
- result = await self.request(api.Methods.SET_MY_COMMANDS, payload)
- return result
+ return await self.request(api.Methods.SET_MY_COMMANDS, payload)
- async def get_my_commands(self) -> typing.List[types.BotCommand]:
+ async def delete_my_commands(self,
+ scope: typing.Optional[types.BotCommandScope] = None,
+ language_code: typing.Optional[base.String] = None,
+ ) -> base.Boolean:
"""
- Use this method to get the current list of the bot's commands.
+ Use this method to delete the list of the bot's commands for the
+ given scope and user language. After deletion, higher level
+ commands will be shown to affected users.
+
+ Source: https://core.telegram.org/bots/api#deletemycommands
+
+ :param scope: A JSON-serialized object, describing scope of
+ users for which the commands are relevant. Defaults to
+ BotCommandScopeDefault.
+ :type scope: :obj: `typing.Optional[types.BotCommandScope]`
+
+ :param language_code: A two-letter ISO 639-1 language code. If
+ empty, commands will be applied to all users from the given
+ scope, for whose language there are no dedicated commands
+ :type language_code: :obj: `typing.Optional[base.String]`
+
+ :return: Returns True on success.
+ :rtype: :obj:`base.Boolean`
+ """
+ scope = prepare_arg(scope)
+ payload = generate_payload(**locals())
+
+ return await self.request(api.Methods.DELETE_MY_COMMANDS, payload)
+
+ async def get_my_commands(self,
+ scope: typing.Optional[types.BotCommandScope] = None,
+ language_code: typing.Optional[base.String] = None,
+ ) -> typing.List[types.BotCommand]:
+ """
+ Use this method to get the current list of the bot's commands
+ for the given scope and user language. Returns Array of
+ BotCommand on success. If commands aren't set, an empty list is
+ returned.
Source: https://core.telegram.org/bots/api#getmycommands
- :return: Returns Array of BotCommand on success.
+
+ :param scope: A JSON-serialized object, describing scope of
+ users for which the commands are relevant. Defaults to
+ BotCommandScopeDefault.
+ :type scope: :obj: `typing.Optional[types.BotCommandScope]`
+
+ :param language_code: A two-letter ISO 639-1 language code. If
+ empty, commands will be applied to all users from the given
+ scope, for whose language there are no dedicated commands
+ :type language_code: :obj: `typing.Optional[base.String]`
+
+ :return: Returns Array of BotCommand on success or empty list.
:rtype: :obj:`typing.List[types.BotCommand]`
"""
+ scope = prepare_arg(scope)
payload = generate_payload(**locals())
result = await self.request(api.Methods.GET_MY_COMMANDS, payload)
return [types.BotCommand(**bot_command_data) for bot_command_data in result]
- async def edit_message_text(self, text: base.String,
+ async def edit_message_text(self,
+ text: base.String,
chat_id: typing.Union[base.Integer, base.String, None] = None,
- message_id: typing.Union[base.Integer, None] = None,
- inline_message_id: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- disable_web_page_preview: typing.Union[base.Boolean, None] = None,
+ message_id: typing.Optional[base.Integer] = None,
+ inline_message_id: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ entities: typing.Optional[typing.List[types.MessageEntity]] = None,
+ disable_web_page_preview: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
- None] = None) -> types.Message or base.Boolean:
+ None] = None,
+ ) -> types.Message or base.Boolean:
"""
Use this method to edit text and game messages sent by the bot or via the bot (for inline bots).
@@ -1592,26 +2374,38 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Required if inline_message_id is not specified
Unique identifier for the target chat or username of the target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String, None]`
+
:param message_id: Required if inline_message_id is not specified. Identifier of the sent message
- :type message_id: :obj:`typing.Union[base.Integer, None]`
+ :type message_id: :obj:`typing.Optional[base.Integer]`
+
:param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message
- :type inline_message_id: :obj:`typing.Union[base.String, None]`
+ :type inline_message_id: :obj:`typing.Optional[base.String]`
+
:param text: New text of the message
:type text: :obj:`base.String`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type entities: :obj:`typing.Optional[typing.List[types.MessageEntity]]`
+
:param disable_web_page_preview: Disables link previews for links in this message
- :type disable_web_page_preview: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_web_page_preview: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: A JSON-serialized object for an inline keyboard
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
+ :type reply_markup: :obj:`typing.Optional[types.InlineKeyboardMarkup]`
+
:return: On success, if edited message is sent by the bot,
the edited Message is returned, otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
"""
reply_markup = prepare_arg(reply_markup)
+ entities = prepare_arg(entities)
payload = generate_payload(**locals())
- if self.parse_mode:
+ if self.parse_mode and entities is None:
payload.setdefault('parse_mode', self.parse_mode)
result = await self.request(api.Methods.EDIT_MESSAGE_TEXT, payload)
@@ -1620,10 +2414,11 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
return types.Message(**result)
async def edit_message_caption(self, chat_id: typing.Union[base.Integer, base.String, None] = None,
- message_id: typing.Union[base.Integer, None] = None,
- inline_message_id: typing.Union[base.String, None] = None,
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
+ message_id: typing.Optional[base.Integer] = None,
+ inline_message_id: typing.Optional[base.String] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[types.MessageEntity]] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
None] = None) -> types.Message or base.Boolean:
"""
@@ -1634,24 +2429,35 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Required if inline_message_id is not specified
Unique identifier for the target chat or username of the target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String, None]`
+
:param message_id: Required if inline_message_id is not specified. Identifier of the sent message
- :type message_id: :obj:`typing.Union[base.Integer, None]`
+ :type message_id: :obj:`typing.Optional[base.Integer]`
+
:param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message
- :type inline_message_id: :obj:`typing.Union[base.String, None]`
+ :type inline_message_id: :obj:`typing.Optional[base.String]`
+
:param caption: New caption of the message
- :type caption: :obj:`typing.Union[base.String, None]`
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[types.MessageEntity]]`
+
:param reply_markup: A JSON-serialized object for an inline keyboard
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
+ :type reply_markup: :obj:`typing.Optional[types.InlineKeyboardMarkup]`
+
:return: On success, if edited message is sent by the bot, the edited Message is returned,
otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
"""
reply_markup = prepare_arg(reply_markup)
+ caption_entities = prepare_arg(caption_entities)
payload = generate_payload(**locals())
- if self.parse_mode:
+ if self.parse_mode and caption_entities is None:
payload.setdefault('parse_mode', self.parse_mode)
result = await self.request(api.Methods.EDIT_MESSAGE_CAPTION, payload)
@@ -1662,9 +2468,9 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
async def edit_message_media(self,
media: types.InputMedia,
chat_id: typing.Union[typing.Union[base.Integer, base.String], None] = None,
- message_id: typing.Union[base.Integer, None] = None,
- inline_message_id: typing.Union[base.String, None] = None,
- reply_markup: typing.Union[types.InlineKeyboardMarkup, None] = None,
+ message_id: typing.Optional[base.Integer] = None,
+ inline_message_id: typing.Optional[base.String] = None,
+ reply_markup: typing.Optional[types.InlineKeyboardMarkup] = None,
) -> typing.Union[types.Message, base.Boolean]:
"""
Use this method to edit audio, document, photo, or video messages.
@@ -1681,13 +2487,13 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Required if inline_message_id is not specified
:type chat_id: :obj:`typing.Union[typing.Union[base.Integer, base.String], None]`
:param message_id: Required if inline_message_id is not specified. Identifier of the sent message
- :type message_id: :obj:`typing.Union[base.Integer, None]`
+ :type message_id: :obj:`typing.Optional[base.Integer]`
:param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message
- :type inline_message_id: :obj:`typing.Union[base.String, None]`
+ :type inline_message_id: :obj:`typing.Optional[base.String]`
:param media: A JSON-serialized object for a new media content of the message
:type media: :obj:`types.InputMedia`
:param reply_markup: A JSON-serialized object for a new inline keyboard
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
+ :type reply_markup: :obj:`typing.Optional[types.InlineKeyboardMarkup]`
:return: On success, if the edited message was sent by the bot, the edited Message is returned,
otherwise True is returned
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
@@ -1707,8 +2513,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
async def edit_message_reply_markup(self,
chat_id: typing.Union[base.Integer, base.String, None] = None,
- message_id: typing.Union[base.Integer, None] = None,
- inline_message_id: typing.Union[base.String, None] = None,
+ message_id: typing.Optional[base.Integer] = None,
+ inline_message_id: typing.Optional[base.String] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
None] = None) -> types.Message or base.Boolean:
"""
@@ -1720,11 +2526,11 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
Unique identifier for the target chat or username of the target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String, None]`
:param message_id: Required if inline_message_id is not specified. Identifier of the sent message
- :type message_id: :obj:`typing.Union[base.Integer, None]`
+ :type message_id: :obj:`typing.Optional[base.Integer]`
:param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message
- :type inline_message_id: :obj:`typing.Union[base.String, None]`
+ :type inline_message_id: :obj:`typing.Optional[base.String]`
:param reply_markup: A JSON-serialized object for an inline keyboard
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
+ :type reply_markup: :obj:`typing.Optional[types.InlineKeyboardMarkup]`
:return: On success, if edited message is sent by the bot, the edited Message is returned,
otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
@@ -1739,7 +2545,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
async def stop_poll(self, chat_id: typing.Union[base.String, base.Integer],
message_id: base.Integer,
- reply_markup: typing.Union[types.InlineKeyboardMarkup, None] = None) -> types.Poll:
+ reply_markup: typing.Optional[types.InlineKeyboardMarkup] = None) -> types.Poll:
"""
Use this method to stop a poll which was sent by the bot.
On success, the stopped Poll with the final results is returned.
@@ -1749,7 +2555,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param message_id: Identifier of the original message with the poll
:type message_id: :obj:`base.Integer`
:param reply_markup: A JSON-serialized object for a new message inline keyboard.
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
+ :type reply_markup: :obj:`typing.Optional[types.InlineKeyboardMarkup]`
:return: On success, the stopped Poll with the final results is returned.
:rtype: :obj:`types.Poll`
"""
@@ -1780,16 +2586,16 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.DELETE_MESSAGE, payload)
- return result
+ return await self.request(api.Methods.DELETE_MESSAGE, payload)
# === Stickers ===
# https://core.telegram.org/bots/api#stickers
async def send_sticker(self, chat_id: typing.Union[base.Integer, base.String],
sticker: typing.Union[base.InputFile, base.String],
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_to_message_id: typing.Union[base.Integer, None] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove,
@@ -1801,16 +2607,25 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Unique identifier for the target chat or username of the target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param sticker: Sticker to send
:type sticker: :obj:`typing.Union[base.InputFile, base.String]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -1869,8 +2684,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
emojis: base.String,
png_sticker: typing.Union[base.InputFile, base.String] = None,
tgs_sticker: base.InputFile = None,
- contains_masks: typing.Union[base.Boolean, None] = None,
- mask_position: typing.Union[types.MaskPosition, None] = None) -> base.Boolean:
+ contains_masks: typing.Optional[base.Boolean] = None,
+ mask_position: typing.Optional[types.MaskPosition] = None) -> base.Boolean:
"""
Use this method to create a new sticker set owned by a user.
The bot will be able to edit the sticker set thus created.
@@ -1899,9 +2714,9 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param emojis: One or more emoji corresponding to the sticker
:type emojis: :obj:`base.String`
:param contains_masks: Pass True, if a set of mask stickers should be created
- :type contains_masks: :obj:`typing.Union[base.Boolean, None]`
+ :type contains_masks: :obj:`typing.Optional[base.Boolean]`
:param mask_position: A JSON-serialized object for position where the mask should be placed on faces
- :type mask_position: :obj:`typing.Union[types.MaskPosition, None]`
+ :type mask_position: :obj:`typing.Optional[types.MaskPosition]`
:return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
@@ -1912,8 +2727,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
prepare_file(payload, files, 'png_sticker', png_sticker)
prepare_file(payload, files, 'tgs_sticker', tgs_sticker)
- result = await self.request(api.Methods.CREATE_NEW_STICKER_SET, payload, files)
- return result
+ return await self.request(api.Methods.CREATE_NEW_STICKER_SET, payload, files)
async def add_sticker_to_set(self,
user_id: base.Integer,
@@ -1921,7 +2735,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
emojis: base.String,
png_sticker: typing.Union[base.InputFile, base.String] = None,
tgs_sticker: base.InputFile = None,
- mask_position: typing.Union[types.MaskPosition, None] = None) -> base.Boolean:
+ mask_position: typing.Optional[types.MaskPosition] = None) -> base.Boolean:
"""
Use this method to add a new sticker to a set created by the bot.
You must use exactly one of the fields png_sticker or tgs_sticker.
@@ -1947,7 +2761,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param emojis: One or more emoji corresponding to the sticker
:type emojis: :obj:`base.String`
:param mask_position: A JSON-serialized object for position where the mask should be placed on faces
- :type mask_position: :obj:`typing.Union[types.MaskPosition, None]`
+ :type mask_position: :obj:`typing.Optional[types.MaskPosition]`
:return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
@@ -1958,8 +2772,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
prepare_file(payload, files, 'png_sticker', png_sticker)
prepare_file(payload, files, 'tgs_sticker', tgs_sticker)
- result = await self.request(api.Methods.ADD_STICKER_TO_SET, payload, files)
- return result
+ return await self.request(api.Methods.ADD_STICKER_TO_SET, payload, files)
async def set_sticker_position_in_set(self, sticker: base.String, position: base.Integer) -> base.Boolean:
"""
@@ -1975,9 +2788,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:rtype: :obj:`base.Boolean`
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.SET_STICKER_POSITION_IN_SET, payload)
- return result
+ return await self.request(api.Methods.SET_STICKER_POSITION_IN_SET, payload)
async def delete_sticker_from_set(self, sticker: base.String) -> base.Boolean:
"""
@@ -1992,8 +2804,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.DELETE_STICKER_FROM_SET, payload)
- return result
+ return await self.request(api.Methods.DELETE_STICKER_FROM_SET, payload)
async def set_sticker_set_thumb(self,
name: base.String,
@@ -2025,16 +2836,15 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
files = {}
prepare_file(payload, files, 'thumb', thumb)
- result = await self.request(api.Methods.SET_STICKER_SET_THUMB, payload, files)
- return result
+ return await self.request(api.Methods.SET_STICKER_SET_THUMB, payload, files)
async def answer_inline_query(self, inline_query_id: base.String,
results: typing.List[types.InlineQueryResult],
- cache_time: typing.Union[base.Integer, None] = None,
- is_personal: typing.Union[base.Boolean, None] = None,
- next_offset: typing.Union[base.String, None] = None,
- switch_pm_text: typing.Union[base.String, None] = None,
- switch_pm_parameter: typing.Union[base.String, None] = None) -> base.Boolean:
+ cache_time: typing.Optional[base.Integer] = None,
+ is_personal: typing.Optional[base.Boolean] = None,
+ next_offset: typing.Optional[base.String] = None,
+ switch_pm_text: typing.Optional[base.String] = None,
+ switch_pm_parameter: typing.Optional[base.String] = None) -> base.Boolean:
"""
Use this method to send answers to an inline query.
No more than 50 results per query are allowed.
@@ -2047,113 +2857,175 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:type results: :obj:`typing.List[types.InlineQueryResult]`
:param cache_time: The maximum amount of time in seconds that the result of the
inline query may be cached on the server. Defaults to 300.
- :type cache_time: :obj:`typing.Union[base.Integer, None]`
+ :type cache_time: :obj:`typing.Optional[base.Integer]`
:param is_personal: Pass True, if results may be cached on the server side only
for the user that sent the query. By default, results may be returned to any user who sends the same query
- :type is_personal: :obj:`typing.Union[base.Boolean, None]`
+ :type is_personal: :obj:`typing.Optional[base.Boolean]`
:param next_offset: Pass the offset that a client should send in the
next query with the same text to receive more results.
Pass an empty string if there are no more results or if you don‘t support pagination.
Offset length can’t exceed 64 bytes.
- :type next_offset: :obj:`typing.Union[base.String, None]`
+ :type next_offset: :obj:`typing.Optional[base.String]`
:param switch_pm_text: If passed, clients will display a button with specified text that
switches the user to a private chat with the bot and sends the bot a start message
with the parameter switch_pm_parameter
- :type switch_pm_text: :obj:`typing.Union[base.String, None]`
+ :type switch_pm_text: :obj:`typing.Optional[base.String]`
:param switch_pm_parameter: Deep-linking parameter for the /start message sent to the bot when
user presses the switch button. 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed.
- :type switch_pm_parameter: :obj:`typing.Union[base.String, None]`
+ :type switch_pm_parameter: :obj:`typing.Optional[base.String]`
:return: On success, True is returned
:rtype: :obj:`base.Boolean`
"""
results = prepare_arg(results)
payload = generate_payload(**locals())
- result = await self.request(api.Methods.ANSWER_INLINE_QUERY, payload)
- return result
+ return await self.request(api.Methods.ANSWER_INLINE_QUERY, payload)
# === Payments ===
# https://core.telegram.org/bots/api#payments
- async def send_invoice(self, chat_id: base.Integer, title: base.String,
- description: base.String, payload: base.String,
- provider_token: base.String, start_parameter: base.String,
- currency: base.String, prices: typing.List[types.LabeledPrice],
- provider_data: typing.Union[typing.Dict, None] = None,
- photo_url: typing.Union[base.String, None] = None,
- photo_size: typing.Union[base.Integer, None] = None,
- photo_width: typing.Union[base.Integer, None] = None,
- photo_height: typing.Union[base.Integer, None] = None,
- need_name: typing.Union[base.Boolean, None] = None,
- need_phone_number: typing.Union[base.Boolean, None] = None,
- need_email: typing.Union[base.Boolean, None] = None,
- need_shipping_address: typing.Union[base.Boolean, None] = None,
- send_phone_number_to_provider: typing.Union[base.Boolean, None] = None,
- send_email_to_provider: typing.Union[base.Boolean, None] = None,
- is_flexible: typing.Union[base.Boolean, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_to_message_id: typing.Union[base.Integer, None] = None,
- reply_markup: typing.Union[types.InlineKeyboardMarkup, None] = None) -> types.Message:
+ async def send_invoice(self,
+ chat_id: typing.Union[base.Integer, base.String],
+ title: base.String,
+ description: base.String,
+ payload: base.String,
+ provider_token: base.String,
+ currency: base.String,
+ prices: typing.List[types.LabeledPrice],
+ max_tip_amount: typing.Optional[base.Integer] = None,
+ suggested_tip_amounts: typing.Optional[
+ typing.List[base.Integer]
+ ] = None,
+ start_parameter: typing.Optional[base.String] = None,
+ provider_data: typing.Optional[typing.Dict] = None,
+ photo_url: typing.Optional[base.String] = None,
+ photo_size: typing.Optional[base.Integer] = None,
+ photo_width: typing.Optional[base.Integer] = None,
+ photo_height: typing.Optional[base.Integer] = None,
+ need_name: typing.Optional[base.Boolean] = None,
+ need_phone_number: typing.Optional[base.Boolean] = None,
+ need_email: typing.Optional[base.Boolean] = None,
+ need_shipping_address: typing.Optional[base.Boolean] = None,
+ send_phone_number_to_provider: typing.Optional[base.Boolean] = None,
+ send_email_to_provider: typing.Optional[base.Boolean] = None,
+ is_flexible: typing.Optional[base.Boolean] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
+ reply_markup: typing.Optional[types.InlineKeyboardMarkup] = None,
+ ) -> types.Message:
"""
Use this method to send invoices.
Source: https://core.telegram.org/bots/api#sendinvoice
- :param chat_id: Unique identifier for the target private chat
- :type chat_id: :obj:`base.Integer`
+ :param chat_id: Unique identifier for the target chat or
+ username of the target channel (in the format
+ @channelusername)
+ :type chat_id: :obj:`typing.Union[base.Integer, base.String]`
+
:param title: Product name, 1-32 characters
:type title: :obj:`base.String`
+
:param description: Product description, 1-255 characters
:type description: :obj:`base.String`
+
:param payload: Bot-defined invoice payload, 1-128 bytes
This will not be displayed to the user, use for your internal processes.
:type payload: :obj:`base.String`
+
:param provider_token: Payments provider token, obtained via Botfather
:type provider_token: :obj:`base.String`
- :param start_parameter: Unique deep-linking parameter that can be used to generate this
- invoice when used as a start parameter
- :type start_parameter: :obj:`base.String`
+
:param currency: Three-letter ISO 4217 currency code, see more on currencies
:type currency: :obj:`base.String`
+
:param prices: Price breakdown, a list of components
(e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.)
:type prices: :obj:`typing.List[types.LabeledPrice]`
+
+ :param max_tip_amount: The maximum accepted amount for tips in
+ the smallest units of the currency (integer, not
+ float/double). For example, for a maximum tip of US$ 1.45
+ pass max_tip_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). Defaults to 0
+ :type max_tip_amount: :obj:`typing.Optional[base.Integer]`
+
+ :param suggested_tip_amounts: A JSON-serialized array of suggested
+ amounts of tips in the smallest units of the currency
+ (integer, not float/double). At most 4 suggested tip amounts
+ can be specified. The suggested tip amounts must be
+ positive, passed in a strictly increased order and must not
+ exceed max_tip_amount.
+ :type suggested_tip_amounts: :obj:`typing.Optional[typing.List[base.Integer]]`
+
+ :param start_parameter: Unique deep-linking parameter. If left
+ empty, forwarded copies of the sent message will have a Pay
+ button, allowing multiple users to pay directly from the
+ forwarded message, using the same invoice. If non-empty,
+ forwarded copies of the sent message will have a URL button
+ with a deep link to the bot (instead of a Pay button), with
+ the value used as the start parameter
+ :type start_parameter: :obj:`typing.Optional[base.String]`
+
:param provider_data: JSON-encoded data about the invoice, which will be shared with the payment provider
- :type provider_data: :obj:`typing.Union[typing.Dict, None]`
+ :type provider_data: :obj:`typing.Optional[typing.Dict]`
+
:param photo_url: URL of the product photo for the invoice
- :type photo_url: :obj:`typing.Union[base.String, None]`
+ :type photo_url: :obj:`typing.Optional[base.String]`
+
:param photo_size: Photo size
- :type photo_size: :obj:`typing.Union[base.Integer, None]`
+ :type photo_size: :obj:`typing.Optional[base.Integer]`
+
:param photo_width: Photo width
- :type photo_width: :obj:`typing.Union[base.Integer, None]`
+ :type photo_width: :obj:`typing.Optional[base.Integer]`
+
:param photo_height: Photo height
- :type photo_height: :obj:`typing.Union[base.Integer, None]`
+ :type photo_height: :obj:`typing.Optional[base.Integer]`
+
:param need_name: Pass True, if you require the user's full name to complete the order
- :type need_name: :obj:`typing.Union[base.Boolean, None]`
+ :type need_name: :obj:`typing.Optional[base.Boolean]`
+
:param need_phone_number: Pass True, if you require the user's phone number to complete the order
- :type need_phone_number: :obj:`typing.Union[base.Boolean, None]`
+ :type need_phone_number: :obj:`typing.Optional[base.Boolean]`
+
:param need_email: Pass True, if you require the user's email to complete the order
- :type need_email: :obj:`typing.Union[base.Boolean, None]`
+ :type need_email: :obj:`typing.Optional[base.Boolean]`
+
:param need_shipping_address: Pass True, if you require the user's shipping address to complete the order
- :type need_shipping_address: :obj:`typing.Union[base.Boolean, None]`
+ :type need_shipping_address: :obj:`typing.Optional[base.Boolean]`
+
:param send_phone_number_to_provider: Pass True, if user's phone number should be sent to provider
- :type send_phone_number_to_provider: :obj:`typing.Union[base.Boolean, None]`
+ :type send_phone_number_to_provider: :obj:`typing.Optional[base.Boolean]`
+
:param send_email_to_provider: Pass True, if user's email address should be sent to provider
- :type send_email_to_provider: :obj:`typing.Union[base.Boolean, None]`
+ :type send_email_to_provider: :obj:`typing.Optional[base.Boolean]`
+
:param is_flexible: Pass True, if the final price depends on the shipping method
- :type is_flexible: :obj:`typing.Union[base.Boolean, None]`
+ :type is_flexible: :obj:`typing.Optional[base.Boolean]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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.
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
+ :type reply_markup: :obj:`typing.Optional[types.InlineKeyboardMarkup]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
prices = prepare_arg([price.to_python() if hasattr(price, 'to_python') else price for price in prices])
reply_markup = prepare_arg(reply_markup)
+ provider_data = prepare_arg(provider_data)
payload_ = generate_payload(**locals())
result = await self.request(api.Methods.SEND_INVOICE, payload_)
@@ -2161,7 +3033,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
async def answer_shipping_query(self, shipping_query_id: base.String, ok: base.Boolean,
shipping_options: typing.Union[typing.List[types.ShippingOption], None] = None,
- error_message: typing.Union[base.String, None] = None) -> base.Boolean:
+ error_message: typing.Optional[base.String] = None) -> base.Boolean:
"""
If you sent an invoice requesting a shipping address and the parameter is_flexible was specified,
the Bot API will send an Update with a shipping_query field to the bot.
@@ -2179,7 +3051,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
Error message in human readable form that explains why it is impossible to complete the order
(e.g. "Sorry, delivery to your desired address is unavailable').
Telegram will display this message to the user.
- :type error_message: :obj:`typing.Union[base.String, None]`
+ :type error_message: :obj:`typing.Optional[base.String]`
:return: On success, True is returned
:rtype: :obj:`base.Boolean`
"""
@@ -2190,11 +3062,10 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
for shipping_option in shipping_options])
payload = generate_payload(**locals())
- result = await self.request(api.Methods.ANSWER_SHIPPING_QUERY, payload)
- return result
+ return await self.request(api.Methods.ANSWER_SHIPPING_QUERY, payload)
async def answer_pre_checkout_query(self, pre_checkout_query_id: base.String, ok: base.Boolean,
- error_message: typing.Union[base.String, None] = None) -> base.Boolean:
+ error_message: typing.Optional[base.String] = None) -> base.Boolean:
"""
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.
@@ -2212,14 +3083,13 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
(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.
- :type error_message: :obj:`typing.Union[base.String, None]`
+ :type error_message: :obj:`typing.Optional[base.String]`
:return: On success, True is returned
:rtype: :obj:`base.Boolean`
"""
payload = generate_payload(**locals())
- result = await self.request(api.Methods.ANSWER_PRE_CHECKOUT_QUERY, payload)
- return result
+ return await self.request(api.Methods.ANSWER_PRE_CHECKOUT_QUERY, payload)
# === Games ===
# https://core.telegram.org/bots/api#games
@@ -2250,16 +3120,19 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
errors = prepare_arg(errors)
payload = generate_payload(**locals())
- result = await self.request(api.Methods.SET_PASSPORT_DATA_ERRORS, payload)
- return result
+ return await self.request(api.Methods.SET_PASSPORT_DATA_ERRORS, payload)
# === Games ===
# https://core.telegram.org/bots/api#games
- async def send_game(self, chat_id: base.Integer, game_short_name: base.String,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_to_message_id: typing.Union[base.Integer, None] = None,
- reply_markup: typing.Union[types.InlineKeyboardMarkup, None] = None) -> types.Message:
+ async def send_game(self,
+ chat_id: base.Integer,
+ game_short_name: base.String,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
+ reply_markup: typing.Optional[types.InlineKeyboardMarkup] = None,
+ ) -> types.Message:
"""
Use this method to send a game.
@@ -2267,16 +3140,25 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param chat_id: Unique identifier for the target chat
:type chat_id: :obj:`base.Integer`
- :param game_short_name: Short name of the game, serves as the unique identifier for the game. \
+
+ :param game_short_name: Short name of the game, serves as the unique identifier for the game.
Set up your games via Botfather.
:type game_short_name: :obj:`base.String`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: A JSON-serialized object for an inline keyboard
If empty, one ‘Play game_title’ button will be shown. If not empty, the first button must launch the game.
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
+ :type reply_markup: :obj:`typing.Optional[types.InlineKeyboardMarkup]`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -2287,10 +3169,10 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
return types.Message(**result)
async def set_game_score(self, user_id: base.Integer, score: base.Integer,
- force: typing.Union[base.Boolean, None] = None,
- disable_edit_message: typing.Union[base.Boolean, None] = None,
- chat_id: typing.Union[base.Integer, None] = None,
- message_id: typing.Union[base.Integer, None] = None,
+ force: typing.Optional[base.Boolean] = None,
+ disable_edit_message: typing.Optional[base.Boolean] = None,
+ chat_id: typing.Optional[base.Integer] = None,
+ message_id: typing.Optional[base.Integer] = None,
inline_message_id: typing.Union[base.String,
None] = None) -> types.Message or base.Boolean:
"""
@@ -2304,16 +3186,16 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:type score: :obj:`base.Integer`
:param force: Pass True, if the high score is allowed to decrease
This can be useful when fixing mistakes or banning cheaters
- :type force: :obj:`typing.Union[base.Boolean, None]`
+ :type force: :obj:`typing.Optional[base.Boolean]`
:param disable_edit_message: Pass True, if the game message should not be automatically
edited to include the current scoreboard
- :type disable_edit_message: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_edit_message: :obj:`typing.Optional[base.Boolean]`
:param chat_id: Required if inline_message_id is not specified. Unique identifier for the target chat
- :type chat_id: :obj:`typing.Union[base.Integer, None]`
+ :type chat_id: :obj:`typing.Optional[base.Integer]`
:param message_id: Required if inline_message_id is not specified. Identifier of the sent message
- :type message_id: :obj:`typing.Union[base.Integer, None]`
+ :type message_id: :obj:`typing.Optional[base.Integer]`
:param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message
- :type inline_message_id: :obj:`typing.Union[base.String, None]`
+ :type inline_message_id: :obj:`typing.Optional[base.String]`
:return: On success, if the message was sent by the bot, returns the edited Message, otherwise returns True
Returns an error, if the new score is not greater than the user's
current score in the chat and force is False.
@@ -2327,8 +3209,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
return types.Message(**result)
async def get_game_high_scores(self, user_id: base.Integer,
- chat_id: typing.Union[base.Integer, None] = None,
- message_id: typing.Union[base.Integer, None] = None,
+ chat_id: typing.Optional[base.Integer] = None,
+ message_id: typing.Optional[base.Integer] = None,
inline_message_id: typing.Union[base.String,
None] = None) -> typing.List[types.GameHighScore]:
"""
@@ -2343,11 +3225,11 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:param user_id: Target user id
:type user_id: :obj:`base.Integer`
:param chat_id: Required if inline_message_id is not specified. Unique identifier for the target chat
- :type chat_id: :obj:`typing.Union[base.Integer, None]`
+ :type chat_id: :obj:`typing.Optional[base.Integer]`
:param message_id: Required if inline_message_id is not specified. Identifier of the sent message
- :type message_id: :obj:`typing.Union[base.Integer, None]`
+ :type message_id: :obj:`typing.Optional[base.Integer]`
:param inline_message_id: Required if chat_id and message_id are not specified. Identifier of the inline message
- :type inline_message_id: :obj:`typing.Union[base.String, None]`
+ :type inline_message_id: :obj:`typing.Optional[base.String]`
:return: Will return the score of the specified user and several of his neighbors in a game
On success, returns an Array of GameHighScore objects.
This method will currently return scores for the target user,
diff --git a/aiogram/contrib/fsm_storage/memory.py b/aiogram/contrib/fsm_storage/memory.py
index 2940f3fa..a5686a34 100644
--- a/aiogram/contrib/fsm_storage/memory.py
+++ b/aiogram/contrib/fsm_storage/memory.py
@@ -35,7 +35,7 @@ class MemoryStorage(BaseStorage):
user: typing.Union[str, int, None] = None,
default: typing.Optional[str] = None) -> typing.Optional[str]:
chat, user = self.resolve_address(chat=chat, user=user)
- return self.data[chat][user]['state']
+ return self.data[chat][user].get("state", self.resolve_state(default))
async def get_data(self, *,
chat: typing.Union[str, int, None] = None,
@@ -58,7 +58,7 @@ class MemoryStorage(BaseStorage):
user: typing.Union[str, int, None] = None,
state: typing.AnyStr = None):
chat, user = self.resolve_address(chat=chat, user=user)
- self.data[chat][user]['state'] = state
+ self.data[chat][user]['state'] = self.resolve_state(state)
async def set_data(self, *,
chat: typing.Union[str, int, None] = None,
@@ -66,6 +66,7 @@ class MemoryStorage(BaseStorage):
data: typing.Dict = None):
chat, user = self.resolve_address(chat=chat, user=user)
self.data[chat][user]['data'] = copy.deepcopy(data)
+ self._cleanup(chat, user)
async def reset_state(self, *,
chat: typing.Union[str, int, None] = None,
@@ -74,6 +75,7 @@ class MemoryStorage(BaseStorage):
await self.set_state(chat=chat, user=user, state=None)
if with_data:
await self.set_data(chat=chat, user=user, data={})
+ self._cleanup(chat, user)
def has_bucket(self):
return True
@@ -91,6 +93,7 @@ class MemoryStorage(BaseStorage):
bucket: typing.Dict = None):
chat, user = self.resolve_address(chat=chat, user=user)
self.data[chat][user]['bucket'] = copy.deepcopy(bucket)
+ self._cleanup(chat, user)
async def update_bucket(self, *,
chat: typing.Union[str, int, None] = None,
@@ -100,3 +103,10 @@ class MemoryStorage(BaseStorage):
bucket = {}
chat, user = self.resolve_address(chat=chat, user=user)
self.data[chat][user]['bucket'].update(bucket, **kwargs)
+
+ def _cleanup(self, chat, user):
+ chat, user = self.resolve_address(chat=chat, user=user)
+ if self.data[chat][user] == {'state': None, 'data': {}, 'bucket': {}}:
+ del self.data[chat][user]
+ if not self.data[chat]:
+ del self.data[chat]
diff --git a/aiogram/contrib/fsm_storage/mongo.py b/aiogram/contrib/fsm_storage/mongo.py
index a7601cc4..0055743a 100644
--- a/aiogram/contrib/fsm_storage/mongo.py
+++ b/aiogram/contrib/fsm_storage/mongo.py
@@ -5,9 +5,9 @@ This module has mongo storage for finite-state machine
from typing import Union, Dict, Optional, List, Tuple, AnyStr
-import pymongo
try:
+ import pymongo
import motor
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
except ModuleNotFoundError as e:
@@ -26,6 +26,7 @@ COLLECTIONS = (STATE, DATA, BUCKET)
class MongoStorage(BaseStorage):
"""
Mongo-based storage for FSM.
+
Usage:
.. code-block:: python3
@@ -39,7 +40,6 @@ class MongoStorage(BaseStorage):
await dp.storage.close()
await dp.storage.wait_closed()
-
"""
def __init__(self, host='localhost', port=27017, db_name='aiogram_fsm', uri=None,
@@ -65,7 +65,7 @@ class MongoStorage(BaseStorage):
try:
self._mongo = AsyncIOMotorClient(self._uri)
except pymongo.errors.ConfigurationError as e:
- if "query() got an unexpected keyword argument 'lifetime'" in e.args[0]:
+ if "query() got an unexpected keyword argument 'lifetime'" in e.args[0]:
import logging
logger = logging.getLogger("aiogram")
logger.warning("Run `pip install dnspython==1.16.0` in order to fix ConfigurationError. More information: https://github.com/mongodb/mongo-python-driver/pull/423#issuecomment-528998245")
@@ -114,7 +114,9 @@ class MongoStorage(BaseStorage):
async def wait_closed(self):
return True
- async def set_state(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
+ async def set_state(self, *,
+ chat: Union[str, int, None] = None,
+ user: Union[str, int, None] = None,
state: Optional[AnyStr] = None):
chat, user = self.check_address(chat=chat, user=user)
db = await self.get_db()
@@ -122,8 +124,11 @@ class MongoStorage(BaseStorage):
if state is None:
await db[STATE].delete_one(filter={'chat': chat, 'user': user})
else:
- await db[STATE].update_one(filter={'chat': chat, 'user': user},
- update={'$set': {'state': state}}, upsert=True)
+ await db[STATE].update_one(
+ filter={'chat': chat, 'user': user},
+ update={'$set': {'state': self.resolve_state(state)}},
+ upsert=True,
+ )
async def get_state(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
default: Optional[str] = None) -> Optional[str]:
@@ -131,15 +136,17 @@ class MongoStorage(BaseStorage):
db = await self.get_db()
result = await db[STATE].find_one(filter={'chat': chat, 'user': user})
- return result.get('state') if result else default
+ return result.get('state') if result else self.resolve_state(default)
async def set_data(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
data: Dict = None):
chat, user = self.check_address(chat=chat, user=user)
db = await self.get_db()
-
- await db[DATA].update_one(filter={'chat': chat, 'user': user},
- update={'$set': {'data': data}}, upsert=True)
+ if not data:
+ await db[DATA].delete_one(filter={'chat': chat, 'user': user})
+ else:
+ await db[DATA].update_one(filter={'chat': chat, 'user': user},
+ update={'$set': {'data': data}}, upsert=True)
async def get_data(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
default: Optional[dict] = None) -> Dict:
@@ -206,12 +213,5 @@ class MongoStorage(BaseStorage):
:return: list of tuples where first element is chat id and second is user id
"""
db = await self.get_db()
- result = []
-
items = await db[STATE].find().to_list()
- for item in items:
- result.append(
- (int(item['chat']), int(item['user']))
- )
-
- return result
+ return [(int(item['chat']), int(item['user'])) for item in items]
diff --git a/aiogram/contrib/fsm_storage/redis.py b/aiogram/contrib/fsm_storage/redis.py
index bf88eff7..5d0b762c 100644
--- a/aiogram/contrib/fsm_storage/redis.py
+++ b/aiogram/contrib/fsm_storage/redis.py
@@ -110,24 +110,29 @@ class RedisStorage(BaseStorage):
chat, user = self.check_address(chat=chat, user=user)
addr = f"fsm:{chat}:{user}"
- record = {'state': state, 'data': data, 'bucket': bucket}
-
conn = await self.redis()
- await conn.execute('SET', addr, json.dumps(record))
+ if state is None and data == bucket == {}:
+ await conn.execute('DEL', addr)
+ else:
+ record = {'state': state, 'data': data, 'bucket': bucket}
+ await conn.execute('SET', addr, json.dumps(record))
async def get_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
default: typing.Optional[str] = None) -> typing.Optional[str]:
record = await self.get_record(chat=chat, user=user)
- return record['state']
+ return record.get('state', self.resolve_state(default))
async def get_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
default: typing.Optional[str] = None) -> typing.Dict:
record = await self.get_record(chat=chat, user=user)
return record['data']
- async def set_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
+ async def set_state(self, *,
+ chat: typing.Union[str, int, None] = None,
+ user: typing.Union[str, int, None] = None,
state: typing.Optional[typing.AnyStr] = None):
record = await self.get_record(chat=chat, user=user)
+ state = self.resolve_state(state)
await self.set_record(chat=chat, user=user, state=state, data=record['data'])
async def set_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
@@ -208,7 +213,7 @@ class RedisStorage2(BaseStorage):
.. code-block:: python3
- storage = RedisStorage('localhost', 6379, db=5, pool_size=10, prefix='my_fsm_key')
+ storage = RedisStorage2('localhost', 6379, db=5, pool_size=10, prefix='my_fsm_key')
dp = Dispatcher(bot, storage=storage)
And need to close Redis connection when shutdown
@@ -219,7 +224,7 @@ class RedisStorage2(BaseStorage):
await dp.storage.wait_closed()
"""
- def __init__(self, host: str = 'localhost', port=6379, db=None, password=None,
+ def __init__(self, host: str = 'localhost', port=6379, db=None, password=None,
ssl=None, pool_size=10, loop=None, prefix='fsm',
state_ttl: int = 0,
data_ttl: int = 0,
@@ -274,7 +279,7 @@ class RedisStorage2(BaseStorage):
chat, user = self.check_address(chat=chat, user=user)
key = self.generate_key(chat, user, STATE_KEY)
redis = await self.redis()
- return await redis.get(key, encoding='utf8') or None
+ return await redis.get(key, encoding='utf8') or self.resolve_state(default)
async def get_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
default: typing.Optional[dict] = None) -> typing.Dict:
@@ -294,14 +299,17 @@ class RedisStorage2(BaseStorage):
if state is None:
await redis.delete(key)
else:
- await redis.set(key, state, expire=self._state_ttl)
+ await redis.set(key, self.resolve_state(state), expire=self._state_ttl)
async def set_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
data: typing.Dict = None):
chat, user = self.check_address(chat=chat, user=user)
key = self.generate_key(chat, user, STATE_DATA_KEY)
redis = await self.redis()
- await redis.set(key, json.dumps(data), expire=self._data_ttl)
+ if data:
+ await redis.set(key, json.dumps(data), expire=self._data_ttl)
+ else:
+ await redis.delete(key)
async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
data: typing.Dict = None, **kwargs):
@@ -329,7 +337,10 @@ class RedisStorage2(BaseStorage):
chat, user = self.check_address(chat=chat, user=user)
key = self.generate_key(chat, user, STATE_BUCKET_KEY)
redis = await self.redis()
- await redis.set(key, json.dumps(bucket), expire=self._bucket_ttl)
+ if bucket:
+ await redis.set(key, json.dumps(bucket), expire=self._bucket_ttl)
+ else:
+ await redis.delete(key)
async def update_bucket(self, *, chat: typing.Union[str, int, None] = None,
user: typing.Union[str, int, None] = None,
diff --git a/aiogram/contrib/fsm_storage/rethinkdb.py b/aiogram/contrib/fsm_storage/rethinkdb.py
index 38d24efa..c600074e 100644
--- a/aiogram/contrib/fsm_storage/rethinkdb.py
+++ b/aiogram/contrib/fsm_storage/rethinkdb.py
@@ -7,7 +7,7 @@ from rethinkdb.asyncio_net.net_asyncio import Connection
from ...dispatcher.storage import BaseStorage
-__all__ = ['RethinkDBStorage']
+__all__ = ('RethinkDBStorage',)
r = rethinkdb.RethinkDB()
r.set_loop_type('asyncio')
@@ -19,16 +19,17 @@ class RethinkDBStorage(BaseStorage):
Usage:
- ..code-block:: python3
+ .. code-block:: python3
storage = RethinkDBStorage(db='aiogram', table='aiogram', user='aiogram', password='aiogram_secret')
dispatcher = Dispatcher(bot, storage=storage)
And need to close connection when shutdown
- ..code-clock:: python3
+ .. code-block:: python3
await storage.close()
+ await storage.wait_closed()
"""
@@ -54,7 +55,7 @@ class RethinkDBStorage(BaseStorage):
self._ssl = ssl or {}
self._loop = loop
- self._conn: typing.Union[Connection, None] = None
+ self._conn: typing.Optional[Connection] = None
async def connect(self) -> Connection:
"""
@@ -94,7 +95,9 @@ class RethinkDBStorage(BaseStorage):
default: typing.Optional[str] = None) -> typing.Optional[str]:
chat, user = map(str, self.check_address(chat=chat, user=user))
async with self.connection() as conn:
- return await r.table(self._table).get(chat)[user]['state'].default(default or None).run(conn)
+ return await r.table(self._table).get(chat)[user]['state'].default(
+ self.resolve_state(default) or None
+ ).run(conn)
async def get_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
default: typing.Optional[str] = None) -> typing.Dict:
@@ -102,11 +105,16 @@ class RethinkDBStorage(BaseStorage):
async with self.connection() as conn:
return await r.table(self._table).get(chat)[user]['data'].default(default or {}).run(conn)
- async def set_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
+ async def set_state(self, *,
+ chat: typing.Union[str, int, None] = None,
+ user: typing.Union[str, int, None] = None,
state: typing.Optional[typing.AnyStr] = None):
chat, user = map(str, self.check_address(chat=chat, user=user))
async with self.connection() as conn:
- await r.table(self._table).insert({'id': chat, user: {'state': state}}, conflict="update").run(conn)
+ await r.table(self._table).insert(
+ {'id': chat, user: {'state': self.resolve_state(state)}},
+ conflict="update",
+ ).run(conn)
async def set_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
data: typing.Dict = None):
diff --git a/aiogram/contrib/middlewares/environment.py b/aiogram/contrib/middlewares/environment.py
index 0427a739..f6ad56dd 100644
--- a/aiogram/contrib/middlewares/environment.py
+++ b/aiogram/contrib/middlewares/environment.py
@@ -1,3 +1,5 @@
+import asyncio
+
from aiogram.dispatcher.middlewares import BaseMiddleware
@@ -14,7 +16,7 @@ class EnvironmentMiddleware(BaseMiddleware):
data.update(
bot=dp.bot,
dispatcher=dp,
- loop=dp.loop
+ loop=dp.loop or asyncio.get_event_loop()
)
if self.context:
data.update(self.context)
diff --git a/aiogram/contrib/middlewares/i18n.py b/aiogram/contrib/middlewares/i18n.py
index 63f54510..1c50a603 100644
--- a/aiogram/contrib/middlewares/i18n.py
+++ b/aiogram/contrib/middlewares/i18n.py
@@ -1,7 +1,7 @@
import gettext
import os
from contextvars import ContextVar
-from typing import Any, Dict, Tuple
+from typing import Any, Dict, Tuple, Optional
from babel import Locale
from babel.support import LazyProxy
@@ -59,13 +59,13 @@ class I18nMiddleware(BaseMiddleware):
with open(mo_path, 'rb') as fp:
translations[name] = gettext.GNUTranslations(fp)
elif os.path.exists(mo_path[:-2] + 'po'):
- raise RuntimeError(f"Found locale '{name} but this language is not compiled!")
+ raise RuntimeError(f"Found locale '{name}' but this language is not compiled!")
return translations
def reload(self):
"""
- Hot reload locles
+ Hot reload locales
"""
self.locales = self.find_locales()
@@ -119,22 +119,24 @@ class I18nMiddleware(BaseMiddleware):
return LazyProxy(self.gettext, singular, plural, n, locale, enable_cache=enable_cache)
# noinspection PyMethodMayBeStatic,PyUnusedLocal
- async def get_user_locale(self, action: str, args: Tuple[Any]) -> str:
+ async def get_user_locale(self, action: str, args: Tuple[Any]) -> Optional[str]:
"""
User locale getter
- You can override the method if you want to use different way of getting user language.
+ You can override the method if you want to use different way of
+ getting user language.
:param action: event name
:param args: event arguments
- :return: locale name
+ :return: locale name or None
"""
- user: types.User = types.User.get_current()
- locale: Locale = user.locale
+ user: Optional[types.User] = types.User.get_current()
+ locale: Optional[Locale] = user.locale if user else None
- if locale:
+ if locale and locale.language in self.locales:
*_, data = args
language = data['locale'] = locale.language
return language
+ return self.default
async def trigger(self, action, args):
"""
diff --git a/aiogram/contrib/middlewares/logging.py b/aiogram/contrib/middlewares/logging.py
index 308d0e10..82c2b50a 100644
--- a/aiogram/contrib/middlewares/logging.py
+++ b/aiogram/contrib/middlewares/logging.py
@@ -160,6 +160,26 @@ class LoggingMiddleware(BaseMiddleware):
self.logger.debug(f"{HANDLED_STR[bool(len(results))]} poll answer [ID:{poll_answer.poll_id}] "
f"from user [ID:{poll_answer.user.id}]")
+ async def on_pre_process_my_chat_member(self, my_chat_member_update, data):
+ self.logger.info(f"Received chat member update "
+ f"for user [ID:{my_chat_member_update.from_user.id}]. "
+ f"Old state: {my_chat_member_update.old_chat_member.to_python()} "
+ f"New state: {my_chat_member_update.new_chat_member.to_python()} ")
+
+ async def on_post_process_my_chat_member(self, my_chat_member_update, results, data):
+ self.logger.debug(f"{HANDLED_STR[bool(len(results))]} my_chat_member "
+ f"for user [ID:{my_chat_member_update.from_user.id}]")
+
+ async def on_pre_process_chat_member(self, chat_member_update, data):
+ self.logger.info(f"Received chat member update "
+ f"for user [ID:{chat_member_update.from_user.id}]. "
+ f"Old state: {chat_member_update.old_chat_member.to_python()} "
+ f"New state: {chat_member_update.new_chat_member.to_python()} ")
+
+ async def on_post_process_chat_member(self, chat_member_update, results, data):
+ self.logger.debug(f"{HANDLED_STR[bool(len(results))]} chat_member "
+ f"for user [ID:{chat_member_update.from_user.id}]")
+
class LoggingFilter(logging.Filter):
"""
diff --git a/aiogram/dispatcher/__init__.py b/aiogram/dispatcher/__init__.py
index 6ad43bbe..e412dd36 100644
--- a/aiogram/dispatcher/__init__.py
+++ b/aiogram/dispatcher/__init__.py
@@ -5,7 +5,7 @@ from . import storage
from . import webhook
from .dispatcher import Dispatcher, FSMContext, DEFAULT_RATE_LIMIT
-__all__ = [
+__all__ = (
'DEFAULT_RATE_LIMIT',
'Dispatcher',
'FSMContext',
@@ -14,4 +14,4 @@ __all__ = [
'middlewares',
'storage',
'webhook'
-]
+)
diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py
index 7a3aa5b3..1e36f202 100644
--- a/aiogram/dispatcher/dispatcher.py
+++ b/aiogram/dispatcher/dispatcher.py
@@ -11,7 +11,7 @@ from aiohttp.helpers import sentinel
from aiogram.utils.deprecated import renamed_argument
from .filters import Command, ContentTypeFilter, ExceptionsFilter, FiltersFactory, HashTag, Regexp, \
RegexpCommandsFilter, StateFilter, Text, IDFilter, AdminFilter, IsReplyFilter, ForwardedMessageFilter, \
- IsSenderContact, ChatTypeFilter, AbstractFilter
+ IsSenderContact, ChatTypeFilter, MediaGroupFilter, AbstractFilter
from .handler import Handler
from .middlewares import MiddlewareManager
from .storage import BaseStorage, DELTA, DisabledStorage, EXCEEDED_COUNT, FSMContext, \
@@ -27,6 +27,13 @@ log = logging.getLogger(__name__)
DEFAULT_RATE_LIMIT = .1
+def _ensure_loop(x: "asyncio.AbstractEventLoop"):
+ assert isinstance(
+ x, asyncio.AbstractEventLoop
+ ), f"Loop must be the implementation of {asyncio.AbstractEventLoop!r}, " \
+ f"not {type(x)!r}"
+
+
class Dispatcher(DataMixin, ContextInstanceMixin):
"""
Simple Updates dispatcher
@@ -43,15 +50,15 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
if not isinstance(bot, Bot):
raise TypeError(f"Argument 'bot' must be an instance of Bot, not '{type(bot).__name__}'")
- if loop is None:
- loop = bot.loop
if storage is None:
storage = DisabledStorage()
if filters_factory is None:
filters_factory = FiltersFactory(self)
self.bot: Bot = bot
- self.loop = loop
+ if loop is not None:
+ _ensure_loop(loop)
+ self._main_loop = loop
self.storage = storage
self.run_tasks_by_default = run_tasks_by_default
@@ -71,6 +78,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
self.pre_checkout_query_handlers = Handler(self, middleware_key='pre_checkout_query')
self.poll_handlers = Handler(self, middleware_key='poll')
self.poll_answer_handlers = Handler(self, middleware_key='poll_answer')
+ self.my_chat_member_handlers = Handler(self, middleware_key='my_chat_member')
+ self.chat_member_handlers = Handler(self, middleware_key='chat_member')
self.errors_handlers = Handler(self, once=False, middleware_key='error')
self.middleware = MiddlewareManager(self)
@@ -79,10 +88,27 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
self._polling = False
self._closed = True
- self._close_waiter = loop.create_future()
+ self._dispatcher_close_waiter = None
self._setup_filters()
+ @property
+ def loop(self) -> typing.Optional[asyncio.AbstractEventLoop]:
+ # for the sake of backward compatibility
+ # lib internally must delegate tasks with respect to _main_loop attribute
+ # however should never be used by the library itself
+ # use more generic approaches from asyncio's namespace
+ return self._main_loop
+
+ @property
+ def _close_waiter(self) -> "asyncio.Future":
+ if self._dispatcher_close_waiter is None:
+ if self._main_loop is not None:
+ self._dispatcher_close_waiter = self._main_loop.create_future()
+ else:
+ self._dispatcher_close_waiter = asyncio.get_event_loop().create_future()
+ return self._dispatcher_close_waiter
+
def _setup_filters(self):
filters_factory = self.filters_factory
@@ -139,6 +165,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
self.edited_channel_post_handlers,
self.callback_query_handlers,
self.inline_query_handlers,
+ self.chat_member_handlers,
])
filters_factory.bind(IDFilter, event_handlers=[
self.message_handlers,
@@ -147,6 +174,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
self.edited_channel_post_handlers,
self.callback_query_handlers,
self.inline_query_handlers,
+ self.chat_member_handlers,
+ self.my_chat_member_handlers,
])
filters_factory.bind(IsReplyFilter, event_handlers=[
self.message_handlers,
@@ -172,6 +201,14 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
self.channel_post_handlers,
self.edited_channel_post_handlers,
self.callback_query_handlers,
+ self.my_chat_member_handlers,
+ self.chat_member_handlers
+ ])
+ filters_factory.bind(MediaGroupFilter, event_handlers=[
+ self.message_handlers,
+ self.edited_channel_post_handlers,
+ self.channel_post_handlers,
+ self.edited_channel_post_handlers
])
def __del__(self):
@@ -195,9 +232,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
:return:
"""
if fast:
- tasks = []
- for update in updates:
- tasks.append(self.updates_handler.notify(update))
+ tasks = [self.updates_handler.notify(update) for update in updates]
return await asyncio.gather(*tasks)
results = []
@@ -262,6 +297,14 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
types.PollAnswer.set_current(update.poll_answer)
types.User.set_current(update.poll_answer.user)
return await self.poll_answer_handlers.notify(update.poll_answer)
+ if update.my_chat_member:
+ types.ChatMemberUpdated.set_current(update.my_chat_member)
+ types.User.set_current(update.my_chat_member.from_user)
+ return await self.my_chat_member_handlers.notify(update.my_chat_member)
+ if update.chat_member:
+ types.ChatMemberUpdated.set_current(update.chat_member)
+ types.User.set_current(update.chat_member.from_user)
+ return await self.chat_member_handlers.notify(update.chat_member)
except Exception as e:
err = await self.errors_handlers.notify(update, e)
if err:
@@ -282,13 +325,20 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
return await self.bot.delete_webhook()
+ def _loop_create_task(self, coro):
+ if self._main_loop is None:
+ return asyncio.create_task(coro)
+ _ensure_loop(self._main_loop)
+ return self._main_loop.create_task(coro)
+
async def start_polling(self,
timeout=20,
relax=0.1,
limit=None,
reset_webhook=None,
fast: typing.Optional[bool] = True,
- error_sleep: int = 5):
+ error_sleep: int = 5,
+ allowed_updates: typing.Optional[typing.List[str]] = None):
"""
Start long-polling
@@ -297,6 +347,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
:param limit:
:param reset_webhook:
:param fast:
+ :param error_sleep:
+ :param allowed_updates:
:return:
"""
if self._polling:
@@ -325,10 +377,15 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
while self._polling:
try:
with self.bot.request_timeout(request_timeout):
- updates = await self.bot.get_updates(limit=limit, offset=offset, timeout=timeout)
+ updates = await self.bot.get_updates(
+ limit=limit,
+ offset=offset,
+ timeout=timeout,
+ allowed_updates=allowed_updates
+ )
except asyncio.CancelledError:
break
- except:
+ except Exception as e:
log.exception('Cause exception while getting updates.')
await asyncio.sleep(error_sleep)
continue
@@ -337,7 +394,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
log.debug(f"Received {len(updates)} updates.")
offset = updates[-1].update_id + 1
- self.loop.create_task(self._process_polling_updates(updates, fast))
+ self._loop_create_task(self._process_polling_updates(updates, fast))
if relax:
await asyncio.sleep(relax)
@@ -381,7 +438,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
:return:
"""
- await asyncio.shield(self._close_waiter, loop=self.loop)
+ await asyncio.shield(self._close_waiter)
def is_polling(self):
"""
@@ -974,6 +1031,118 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
return decorator
+ def register_my_chat_member_handler(self,
+ callback: typing.Callable,
+ *custom_filters,
+ run_task: typing.Optional[bool] = None,
+ **kwargs) -> None:
+ """
+ Register handler for my_chat_member
+
+ Example:
+
+ .. code-block:: python3
+
+ dp.register_my_chat_member_handler(some_my_chat_member_handler)
+
+ :param callback:
+ :param custom_filters:
+ :param run_task: run callback in task (no wait results)
+ :param kwargs:
+ """
+ filters_set = self.filters_factory.resolve(
+ self.my_chat_member_handlers,
+ *custom_filters,
+ **kwargs,
+ )
+ self.my_chat_member_handlers.register(
+ handler=self._wrap_async_task(callback, run_task),
+ filters=filters_set,
+ )
+
+ def my_chat_member_handler(self, *custom_filters, run_task=None, **kwargs):
+ """
+ Decorator for my_chat_member handler
+
+ Example:
+
+ .. code-block:: python3
+
+ @dp.my_chat_member_handler()
+ async def some_handler(my_chat_member: types.ChatMemberUpdated)
+
+ :param custom_filters:
+ :param run_task: run callback in task (no wait results)
+ :param kwargs:
+ """
+
+ def decorator(callback):
+ self.register_my_chat_member_handler(
+ callback,
+ *custom_filters,
+ run_task=run_task,
+ **kwargs,
+ )
+ return callback
+
+ return decorator
+
+ def register_chat_member_handler(self,
+ callback: typing.Callable,
+ *custom_filters,
+ run_task: typing.Optional[bool] = None,
+ **kwargs) -> None:
+ """
+ Register handler for chat_member
+
+ Example:
+
+ .. code-block:: python3
+
+ dp.register_chat_member_handler(some_chat_member_handler)
+
+ :param callback:
+ :param custom_filters:
+ :param run_task: run callback in task (no wait results)
+ :param kwargs:
+ """
+ filters_set = self.filters_factory.resolve(
+ self.chat_member_handlers,
+ *custom_filters,
+ **kwargs,
+ )
+ self.chat_member_handlers.register(
+ handler=self._wrap_async_task(callback, run_task),
+ filters=filters_set,
+ )
+
+ def chat_member_handler(self, *custom_filters, run_task=None, **kwargs):
+ """
+ Decorator for chat_member handler
+
+ Example:
+
+ .. code-block:: python3
+
+ @dp.chat_member_handler()
+ async def some_handler(chat_member: types.ChatMemberUpdated)
+
+ :param custom_filters:
+ :param run_task: run callback in task (no wait results)
+ :param kwargs:
+ """
+
+ def decorator(callback):
+ self.register_chat_member_handler(
+ callback,
+ *custom_filters,
+ run_task=run_task,
+ **kwargs,
+ )
+ return callback
+
+ return decorator
+
def register_errors_handler(self, callback, *custom_filters, exception=None, run_task=None, **kwargs):
"""
Register handler for errors
@@ -1053,8 +1222,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
if rate is None:
rate = self.throttling_rate_limit
if user_id is None and chat_id is None:
- user_id = types.User.get_current().id
- chat_id = types.Chat.get_current().id
+ chat_obj = types.Chat.get_current()
+ chat_id = chat_obj.id if chat_obj else None
+
+ user_obj = types.User.get_current()
+ user_id = user_obj.id if user_obj else None
# Detect current time
now = time.time()
@@ -1105,8 +1277,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
raise RuntimeError('This storage does not provide Leaky Bucket')
if user_id is None and chat_id is None:
- user_id = types.User.get_current()
- chat_id = types.Chat.get_current()
+ chat_obj = types.Chat.get_current()
+ chat_id = chat_obj.id if chat_obj else None
+
+ user_obj = types.User.get_current()
+ user_id = user_obj.id if user_obj else None
bucket = await self.storage.get_bucket(chat=chat_id, user=user_id)
data = bucket.get(key, {})
@@ -1127,8 +1302,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
raise RuntimeError('This storage does not provide Leaky Bucket')
if user_id is None and chat_id is None:
- user_id = types.User.get_current()
- chat_id = types.Chat.get_current()
+ chat_obj = types.Chat.get_current()
+ chat_id = chat_obj.id if chat_obj else None
+
+ user_obj = types.User.get_current()
+ user_id = user_obj.id if user_obj else None
bucket = await self.storage.get_bucket(chat=chat_id, user=user_id)
if bucket and key in bucket:
@@ -1158,15 +1336,15 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
try:
response = task.result()
except Exception as e:
- self.loop.create_task(
+ self._loop_create_task(
self.errors_handlers.notify(types.Update.get_current(), e))
else:
if isinstance(response, BaseResponse):
- self.loop.create_task(response.execute_response(self.bot))
+ self._loop_create_task(response.execute_response(self.bot))
@functools.wraps(func)
async def wrapper(*args, **kwargs):
- task = self.loop.create_task(func(*args, **kwargs))
+ task = self._loop_create_task(func(*args, **kwargs))
task.add_done_callback(process_response)
return wrapper
@@ -1213,29 +1391,26 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
no_error=True)
if is_not_throttled:
return await func(*args, **kwargs)
- else:
- kwargs.update(
- {
- 'rate': rate,
- 'key': key,
- 'user_id': user_id,
- 'chat_id': chat_id
- }
- ) # update kwargs with parameters which were given to throttled
+ kwargs.update(
+ {
+ 'rate': rate,
+ 'key': key,
+ 'user_id': user_id,
+ 'chat_id': chat_id,
+ }
+ ) # update kwargs with parameters which were given to throttled
- if on_throttled:
- if asyncio.iscoroutinefunction(on_throttled):
- await on_throttled(*args, **kwargs)
- else:
- kwargs.update(
- {
- 'loop': asyncio.get_running_loop()
- }
- )
- partial_func = functools.partial(on_throttled, *args, **kwargs)
- asyncio.get_running_loop().run_in_executor(None,
- partial_func
- )
+ if on_throttled:
+ if asyncio.iscoroutinefunction(on_throttled):
+ await on_throttled(*args, **kwargs)
+ else:
+ kwargs.update({'loop': asyncio.get_running_loop()})
+ partial_func = functools.partial(
+ on_throttled, *args, **kwargs
+ )
+ asyncio.get_running_loop().run_in_executor(
+ None, partial_func
+ )
return wrapped
return decorator
diff --git a/aiogram/dispatcher/filters/__init__.py b/aiogram/dispatcher/filters/__init__.py
index 5f839662..d07d953b 100644
--- a/aiogram/dispatcher/filters/__init__.py
+++ b/aiogram/dispatcher/filters/__init__.py
@@ -1,38 +1,39 @@
from .builtin import Command, CommandHelp, CommandPrivacy, CommandSettings, CommandStart, ContentTypeFilter, \
ExceptionsFilter, HashTag, Regexp, RegexpCommandsFilter, StateFilter, \
Text, IDFilter, AdminFilter, IsReplyFilter, IsSenderContact, ForwardedMessageFilter, \
- ChatTypeFilter
+ ChatTypeFilter, MediaGroupFilter
from .factory import FiltersFactory
from .filters import AbstractFilter, BoundFilter, Filter, FilterNotPassed, FilterRecord, execute_filter, \
check_filters, get_filter_spec, get_filters_spec
-__all__ = [
- 'AbstractFilter',
- 'BoundFilter',
+__all__ = (
'Command',
- 'CommandStart',
'CommandHelp',
'CommandPrivacy',
'CommandSettings',
+ 'CommandStart',
'ContentTypeFilter',
'ExceptionsFilter',
'HashTag',
- 'Filter',
- 'FilterNotPassed',
- 'FilterRecord',
- 'FiltersFactory',
- 'RegexpCommandsFilter',
'Regexp',
+ 'RegexpCommandsFilter',
'StateFilter',
'Text',
'IDFilter',
+ 'AdminFilter',
'IsReplyFilter',
'IsSenderContact',
- 'AdminFilter',
- 'get_filter_spec',
- 'get_filters_spec',
- 'execute_filter',
- 'check_filters',
'ForwardedMessageFilter',
'ChatTypeFilter',
-]
+ 'MediaGroupFilter',
+ 'FiltersFactory',
+ 'AbstractFilter',
+ 'BoundFilter',
+ 'Filter',
+ 'FilterNotPassed',
+ 'FilterRecord',
+ 'execute_filter',
+ 'check_filters',
+ 'get_filter_spec',
+ 'get_filters_spec',
+)
diff --git a/aiogram/dispatcher/filters/builtin.py b/aiogram/dispatcher/filters/builtin.py
index fcaaf786..7a21ca3f 100644
--- a/aiogram/dispatcher/filters/builtin.py
+++ b/aiogram/dispatcher/filters/builtin.py
@@ -4,13 +4,13 @@ import typing
import warnings
from contextvars import ContextVar
from dataclasses import dataclass, field
-from typing import Any, Dict, Iterable, Optional, Union
+from typing import Any, Dict, Iterable, List, Optional, Union
from babel.support import LazyProxy
from aiogram import types
from aiogram.dispatcher.filters.filters import BoundFilter, Filter
-from aiogram.types import CallbackQuery, ChatType, InlineQuery, Message, Poll
+from aiogram.types import CallbackQuery, ChatType, InlineQuery, Message, Poll, ChatMemberUpdated
ChatIDArgumentType = typing.Union[typing.Iterable[typing.Union[int, str]], str, int]
@@ -110,7 +110,8 @@ class Command(Filter):
if not text:
return False
- full_command = text.split()[0]
+ full_command, *args_list = text.split(maxsplit=1)
+ args = args_list[0] if args_list else None
prefix, (command, _, mention) = full_command[0], full_command[1:].partition('@')
if not ignore_mention and mention and (await message.bot.me).username.lower() != mention.lower():
@@ -120,7 +121,7 @@ class Command(Filter):
if (command.lower() if ignore_case else command) not in commands:
return False
- return {'command': cls.CommandObj(command=command, prefix=prefix, mention=mention)}
+ return {'command': cls.CommandObj(command=command, prefix=prefix, mention=mention, args=args)}
@dataclass
class CommandObj:
@@ -278,9 +279,10 @@ class Text(Filter):
elif check == 0:
raise ValueError(f"No one mode is specified!")
- equals, contains, endswith, startswith = map(lambda e: [e] if isinstance(e, str) or isinstance(e, LazyProxy)
- else e,
- (equals, contains, endswith, startswith))
+ equals, contains, endswith, startswith = map(
+ lambda e: [e] if isinstance(e, (str, LazyProxy)) else e,
+ (equals, contains, endswith, startswith),
+ )
self.equals = equals
self.contains = contains
self.endswith = endswith
@@ -529,6 +531,8 @@ class StateFilter(BoundFilter):
self.states = states
def get_target(self, obj):
+ if isinstance(obj, CallbackQuery):
+ return getattr(getattr(getattr(obj, 'message', None),'chat', None), 'id', None), getattr(getattr(obj, 'from_user', None), 'id', None)
return getattr(getattr(obj, 'chat', None), 'id', None), getattr(getattr(obj, 'from_user', None), 'id', None)
async def check(self, obj):
@@ -604,7 +608,7 @@ class IDFilter(Filter):
return result
- async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]):
+ async def check(self, obj: Union[Message, CallbackQuery, InlineQuery, ChatMemberUpdated]):
if isinstance(obj, Message):
user_id = None
if obj.from_user is not None:
@@ -619,6 +623,9 @@ class IDFilter(Filter):
elif isinstance(obj, InlineQuery):
user_id = obj.from_user.id
chat_id = None
+ elif isinstance(obj, ChatMemberUpdated):
+ user_id = obj.from_user.id
+ chat_id = obj.chat.id
else:
return False
@@ -663,19 +670,21 @@ class AdminFilter(Filter):
return result
- async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]) -> bool:
+ async def check(self, obj: Union[Message, CallbackQuery, InlineQuery, ChatMemberUpdated]) -> bool:
user_id = obj.from_user.id
if self._check_current:
if isinstance(obj, Message):
- message = obj
+ chat = obj.chat
elif isinstance(obj, CallbackQuery) and obj.message:
- message = obj.message
+ chat = obj.message.chat
+ elif isinstance(obj, ChatMemberUpdated):
+ chat = obj.chat
else:
return False
- if ChatType.is_private(message): # there is no admin in private chats
+ if chat.type == ChatType.PRIVATE: # there is no admin in private chats
return False
- chat_ids = [message.chat.id]
+ chat_ids = [chat.id]
else:
chat_ids = self._chat_ids
@@ -719,13 +728,32 @@ class ChatTypeFilter(BoundFilter):
self.chat_type: typing.Set[str] = set(chat_type)
- async def check(self, obj: Union[Message, CallbackQuery]):
+ async def check(self, obj: Union[Message, CallbackQuery, ChatMemberUpdated]):
if isinstance(obj, Message):
obj = obj.chat
elif isinstance(obj, CallbackQuery):
obj = obj.message.chat
+ elif isinstance(obj, ChatMemberUpdated):
+ obj = obj.chat
else:
warnings.warn("ChatTypeFilter doesn't support %s as input", type(obj))
return False
return obj.type in self.chat_type
+
+
+class MediaGroupFilter(BoundFilter):
+ """
+ Check if message is part of a media group.
+
+ `is_media_group=True` - the message is part of a media group
+ `is_media_group=False` - the message is NOT part of a media group
+ """
+
+ key = "is_media_group"
+
+ def __init__(self, is_media_group: bool):
+ self.is_media_group = is_media_group
+
+ async def check(self, message: types.Message) -> bool:
+ return bool(getattr(message, "media_group_id")) is self.is_media_group
diff --git a/aiogram/dispatcher/filters/factory.py b/aiogram/dispatcher/filters/factory.py
index 564e7f89..d53047d5 100644
--- a/aiogram/dispatcher/filters/factory.py
+++ b/aiogram/dispatcher/filters/factory.py
@@ -48,9 +48,13 @@ class FiltersFactory:
:param full_config:
:return:
"""
- filters_set = []
- filters_set.extend(self._resolve_registered(event_handler,
- {k: v for k, v in full_config.items() if v is not None}))
+ filters_set = list(
+ self._resolve_registered(
+ event_handler,
+ {k: v for k, v in full_config.items() if v is not None},
+ )
+ )
+
if custom_filters:
filters_set.extend(custom_filters)
diff --git a/aiogram/dispatcher/filters/filters.py b/aiogram/dispatcher/filters/filters.py
index 220ef96c..47a4f22e 100644
--- a/aiogram/dispatcher/filters/filters.py
+++ b/aiogram/dispatcher/filters/filters.py
@@ -242,7 +242,7 @@ class NotFilter(_LogicFilter):
class AndFilter(_LogicFilter):
def __init__(self, *targets):
- self.targets = list(wrap_async(target) for target in targets)
+ self.targets = [wrap_async(target) for target in targets]
async def check(self, *args):
"""
@@ -268,7 +268,7 @@ class AndFilter(_LogicFilter):
class OrFilter(_LogicFilter):
def __init__(self, *targets):
- self.targets = list(wrap_async(target) for target in targets)
+ self.targets = [wrap_async(target) for target in targets]
async def check(self, *args):
"""
diff --git a/aiogram/dispatcher/handler.py b/aiogram/dispatcher/handler.py
index cd5e9b50..10a94924 100644
--- a/aiogram/dispatcher/handler.py
+++ b/aiogram/dispatcher/handler.py
@@ -25,15 +25,14 @@ class CancelHandler(Exception):
def _get_spec(func: callable):
while hasattr(func, '__wrapped__'): # Try to resolve decorated callbacks
func = func.__wrapped__
- spec = inspect.getfullargspec(func)
- return spec
+ return inspect.getfullargspec(func)
def _check_spec(spec: inspect.FullArgSpec, kwargs: dict):
if spec.varkw:
return kwargs
- return {k: v for k, v in kwargs.items() if k in spec.args}
+ return {k: v for k, v in kwargs.items() if k in set(spec.args + spec.kwonlyargs)}
class Handler:
diff --git a/aiogram/dispatcher/middlewares.py b/aiogram/dispatcher/middlewares.py
index dba3db4c..5fa09830 100644
--- a/aiogram/dispatcher/middlewares.py
+++ b/aiogram/dispatcher/middlewares.py
@@ -16,11 +16,14 @@ class MiddlewareManager:
:param dispatcher: instance of Dispatcher
"""
self.dispatcher = dispatcher
- self.loop = dispatcher.loop
self.bot = dispatcher.bot
self.storage = dispatcher.storage
self.applications = []
+ @property
+ def loop(self):
+ return self.dispatcher.loop
+
def setup(self, middleware):
"""
Setup middleware
diff --git a/aiogram/dispatcher/storage.py b/aiogram/dispatcher/storage.py
index a2992322..eb248e34 100644
--- a/aiogram/dispatcher/storage.py
+++ b/aiogram/dispatcher/storage.py
@@ -40,24 +40,27 @@ class BaseStorage:
@classmethod
def check_address(cls, *,
chat: typing.Union[str, int, None] = None,
- user: typing.Union[str, int, None] = None) -> (typing.Union[str, int], typing.Union[str, int]):
+ user: typing.Union[str, int, None] = None,
+ ) -> (typing.Union[str, int], typing.Union[str, int]):
"""
In all storage's methods chat or user is always required.
If one of them is not provided, you have to set missing value based on the provided one.
This method performs the check described above.
- :param chat:
- :param user:
+ :param chat: chat_id
+ :param user: user_id
:return:
"""
if chat is None and user is None:
raise ValueError('`user` or `chat` parameter is required but no one is provided!')
- if user is None and chat is not None:
+ if user is None:
user = chat
- elif user is not None and chat is None:
+
+ elif chat is None:
chat = user
+
return chat, user
async def get_state(self, *,
@@ -270,6 +273,21 @@ class BaseStorage:
"""
await self.set_data(chat=chat, user=user, data={})
+ @staticmethod
+ def resolve_state(value):
+ from .filters.state import State
+
+ if value is None:
+ return
+
+ if isinstance(value, str):
+ return value
+
+ if isinstance(value, State):
+ return value.state
+
+ return str(value)
+
class FSMContext:
def __init__(self, storage, chat, user):
@@ -279,20 +297,8 @@ class FSMContext:
def proxy(self):
return FSMContextProxy(self)
- @staticmethod
- def _resolve_state(value):
- from .filters.state import State
-
- if value is None:
- return
- elif isinstance(value, str):
- return value
- elif isinstance(value, State):
- return value.state
- return str(value)
-
async def get_state(self, default: typing.Optional[str] = None) -> typing.Optional[str]:
- return await self.storage.get_state(chat=self.chat, user=self.user, default=self._resolve_state(default))
+ return await self.storage.get_state(chat=self.chat, user=self.user, default=default)
async def get_data(self, default: typing.Optional[str] = None) -> typing.Dict:
return await self.storage.get_data(chat=self.chat, user=self.user, default=default)
@@ -300,8 +306,8 @@ class FSMContext:
async def update_data(self, data: typing.Dict = None, **kwargs):
await self.storage.update_data(chat=self.chat, user=self.user, data=data, **kwargs)
- async def set_state(self, state: typing.Union[typing.AnyStr, None] = None):
- await self.storage.set_state(chat=self.chat, user=self.user, state=self._resolve_state(state))
+ async def set_state(self, state: typing.Optional[typing.AnyStr] = None):
+ await self.storage.set_state(chat=self.chat, user=self.user, state=state)
async def set_data(self, data: typing.Dict = None):
await self.storage.set_data(chat=self.chat, user=self.user, data=data)
@@ -397,7 +403,7 @@ class FSMContextProxy:
def setdefault(self, key, default):
self._check_closed()
- self._data.setdefault(key, default)
+ return self._data.setdefault(key, default)
def update(self, data=None, **kwargs):
self._check_closed()
diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py
index ed2ebf99..c76ffae2 100644
--- a/aiogram/dispatcher/webhook.py
+++ b/aiogram/dispatcher/webhook.py
@@ -116,8 +116,7 @@ class WebhookRequestHandler(web.View):
:return: :class:`aiogram.types.Update`
"""
data = await self.request.json()
- update = types.Update(**data)
- return update
+ return types.Update(**data)
async def post(self):
"""
@@ -169,7 +168,7 @@ class WebhookRequestHandler(web.View):
:return:
"""
dispatcher = self.get_dispatcher()
- loop = dispatcher.loop
+ loop = dispatcher.loop or asyncio.get_event_loop()
# Analog of `asyncio.wait_for` but without cancelling task
waiter = loop.create_future()
@@ -189,10 +188,9 @@ class WebhookRequestHandler(web.View):
if fut.done():
return fut.result()
- else:
- # context.set_value(WEBHOOK_CONNECTION, False)
- fut.remove_done_callback(cb)
- fut.add_done_callback(self.respond_via_request)
+ # context.set_value(WEBHOOK_CONNECTION, False)
+ fut.remove_done_callback(cb)
+ fut.add_done_callback(self.respond_via_request)
finally:
timeout_handle.cancel()
@@ -209,7 +207,7 @@ class WebhookRequestHandler(web.View):
TimeoutWarning)
dispatcher = self.get_dispatcher()
- loop = dispatcher.loop
+ loop = dispatcher.loop or asyncio.get_event_loop()
try:
results = task.result()
@@ -620,7 +618,7 @@ class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin):
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.
:param caption: String (Optional) - Photo caption (may also be used when resending photos by file_id),
- 0-200 characters
+ 0-1024 characters after entities parsing
:param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive
a notification with no sound.
:param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message
@@ -673,7 +671,7 @@ class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin):
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.
- :param caption: String (Optional) - Audio caption, 0-200 characters
+ :param caption: String (Optional) - Audio caption, 0-1024 characters after entities parsing
:param duration: Integer (Optional) - Duration of the audio in seconds
:param performer: String (Optional) - Performer
:param title: String (Optional) - Track name
@@ -732,7 +730,7 @@ class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin):
as a String for Telegram to get a file from the Internet, or upload a new one
using multipart/form-data.
:param caption: String (Optional) - Document caption
- (may also be used when resending documents by file_id), 0-200 characters
+ (may also be used when resending documents by file_id), 0-1024 characters after entities parsing
:param disable_notification: Boolean (Optional) - Sends the message silently.
Users will receive a notification with no sound.
:param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message
@@ -789,7 +787,7 @@ class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin):
:param width: Integer (Optional) - Video width
:param height: Integer (Optional) - Video height
:param caption: String (Optional) - Video caption (may also be used when resending videos by file_id),
- 0-200 characters
+ 0-1024 characters after entities parsing
:param disable_notification: Boolean (Optional) - Sends the message silently.
Users will receive a notification with no sound.
:param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message
@@ -846,7 +844,7 @@ class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
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.
- :param caption: String (Optional) - Voice message caption, 0-200 characters
+ :param caption: String (Optional) - Voice message caption, 0-1024 characters after entities parsing
:param duration: Integer (Optional) - Duration of the voice message in seconds
:param disable_notification: Boolean (Optional) - Sends the message silently.
Users will receive a notification with no sound.
@@ -939,8 +937,8 @@ class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin):
def __init__(self, chat_id: Union[Integer, String],
media: Union[types.MediaGroup, List] = None,
- disable_notification: typing.Union[Boolean, None] = None,
- reply_to_message_id: typing.Union[Integer, None] = None):
+ disable_notification: typing.Optional[Boolean] = None,
+ reply_to_message_id: typing.Optional[Integer] = None):
"""
Use this method to send a group of photos or videos as an album.
@@ -951,9 +949,9 @@ class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin):
:param media: A JSON-serialized array describing photos and videos to be sent
:type media: :obj:`typing.Union[types.MediaGroup, typing.List]`
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
:param reply_to_message_id: If the message is a reply, ID of the original message
- :type reply_to_message_id: :obj:`typing.Union[base.Integer, None]`
+ :type reply_to_message_id: :obj:`typing.Optional[base.Integer]`
:return: On success, an array of the sent Messages is returned.
:rtype: typing.List[types.Message]
"""
diff --git a/aiogram/types/__init__.py b/aiogram/types/__init__.py
index 1221ec72..1b289698 100644
--- a/aiogram/types/__init__.py
+++ b/aiogram/types/__init__.py
@@ -4,10 +4,19 @@ from .animation import Animation
from .audio import Audio
from .auth_widget_data import AuthWidgetData
from .bot_command import BotCommand
+from .bot_command_scope import BotCommandScope, BotCommandScopeAllChatAdministrators, \
+ BotCommandScopeAllGroupChats, BotCommandScopeAllPrivateChats, BotCommandScopeChat, \
+ BotCommandScopeChatAdministrators, BotCommandScopeChatMember, \
+ BotCommandScopeDefault, BotCommandScopeType
from .callback_game import CallbackGame
from .callback_query import CallbackQuery
from .chat import Chat, ChatActions, ChatType
-from .chat_member import ChatMember, ChatMemberStatus
+from .chat_invite_link import ChatInviteLink
+from .chat_location import ChatLocation
+from .chat_member import ChatMember, ChatMemberAdministrator, ChatMemberBanned, \
+ ChatMemberLeft, ChatMemberMember, ChatMemberOwner, ChatMemberRestricted, \
+ ChatMemberStatus
+from .chat_member_updated import ChatMemberUpdated
from .chat_permissions import ChatPermissions
from .chat_photo import ChatPhoto
from .chosen_inline_result import ChosenInlineResult
@@ -32,14 +41,16 @@ from .input_file import InputFile
from .input_media import InputMedia, InputMediaAnimation, InputMediaAudio, InputMediaDocument, InputMediaPhoto, \
InputMediaVideo, MediaGroup
from .input_message_content import InputContactMessageContent, InputLocationMessageContent, InputMessageContent, \
- InputTextMessageContent, InputVenueMessageContent
+ InputTextMessageContent, InputVenueMessageContent, InputInvoiceMessageContent
from .invoice import Invoice
from .labeled_price import LabeledPrice
from .location import Location
from .login_url import LoginUrl
from .mask_position import MaskPosition
from .message import ContentType, ContentTypes, Message, ParseMode
+from .message_auto_delete_timer_changed import MessageAutoDeleteTimerChanged
from .message_entity import MessageEntity, MessageEntityType
+from .message_id import MessageId
from .order_info import OrderInfo
from .passport_data import PassportData
from .passport_element_error import PassportElementError, PassportElementErrorDataField, PassportElementErrorFile, \
@@ -49,6 +60,7 @@ from .passport_file import PassportFile
from .photo_size import PhotoSize
from .poll import PollOption, Poll, PollAnswer, PollType
from .pre_checkout_query import PreCheckoutQuery
+from .proximity_alert_triggered import ProximityAlertTriggered
from .reply_keyboard import KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, KeyboardButtonPollType
from .response_parameters import ResponseParameters
from .shipping_address import ShippingAddress
@@ -64,6 +76,10 @@ from .venue import Venue
from .video import Video
from .video_note import VideoNote
from .voice import Voice
+from .voice_chat_ended import VoiceChatEnded
+from .voice_chat_participants_invited import VoiceChatParticipantsInvited
+from .voice_chat_scheduled import VoiceChatScheduled
+from .voice_chat_started import VoiceChatStarted
from .webhook_info import WebhookInfo
__all__ = (
@@ -72,12 +88,31 @@ __all__ = (
'Audio',
'AuthWidgetData',
'BotCommand',
+ 'BotCommandScope',
+ 'BotCommandScopeAllChatAdministrators',
+ 'BotCommandScopeAllGroupChats',
+ 'BotCommandScopeAllPrivateChats',
+ 'BotCommandScopeChat',
+ 'BotCommandScopeChatAdministrators',
+ 'BotCommandScopeChatMember',
+ 'BotCommandScopeDefault',
+ 'BotCommandScopeType',
'CallbackGame',
'CallbackQuery',
'Chat',
'ChatActions',
+ 'ChatInviteLink',
+ 'ChatLocation',
'ChatMember',
'ChatMemberStatus',
+ 'ChatMemberUpdated',
+ 'ChatMemberOwner',
+ 'ChatMemberAdministrator',
+ 'ChatMemberMember',
+ 'ChatMemberRestricted',
+ 'ChatMemberLeft',
+ 'ChatMemberBanned',
+ 'ChatPermissions',
'ChatPhoto',
'ChatType',
'ChosenInlineResult',
@@ -118,6 +153,7 @@ __all__ = (
'InlineQueryResultVideo',
'InlineQueryResultVoice',
'InputContactMessageContent',
+ 'InputInvoiceMessageContent',
'InputFile',
'InputLocationMessageContent',
'InputMedia',
@@ -138,8 +174,10 @@ __all__ = (
'MaskPosition',
'MediaGroup',
'Message',
+ 'MessageAutoDeleteTimerChanged',
'MessageEntity',
'MessageEntityType',
+ 'MessageId',
'OrderInfo',
'ParseMode',
'PassportData',
@@ -157,6 +195,7 @@ __all__ = (
'PollOption',
'PollType',
'PreCheckoutQuery',
+ 'ProximityAlertTriggered',
'ReplyKeyboardMarkup',
'ReplyKeyboardRemove',
'ResponseParameters',
@@ -173,6 +212,10 @@ __all__ = (
'Video',
'VideoNote',
'Voice',
+ 'VoiceChatEnded',
+ 'VoiceChatParticipantsInvited',
+ 'VoiceChatScheduled',
+ 'VoiceChatStarted',
'WebhookInfo',
'base',
'fields',
diff --git a/aiogram/types/animation.py b/aiogram/types/animation.py
index 78f5235a..b08089c1 100644
--- a/aiogram/types/animation.py
+++ b/aiogram/types/animation.py
@@ -15,6 +15,9 @@ class Animation(base.TelegramObject, mixins.Downloadable):
file_id: base.String = fields.Field()
file_unique_id: base.String = fields.Field()
+ width: base.Integer = fields.Field()
+ height: base.Integer = fields.Field()
+ duration: base.Integer = fields.Field()
thumb: PhotoSize = fields.Field(base=PhotoSize)
file_name: base.String = fields.Field()
mime_type: base.String = fields.Field()
diff --git a/aiogram/types/audio.py b/aiogram/types/audio.py
index 6859668f..1657c9cc 100644
--- a/aiogram/types/audio.py
+++ b/aiogram/types/audio.py
@@ -15,6 +15,7 @@ class Audio(base.TelegramObject, mixins.Downloadable):
duration: base.Integer = fields.Field()
performer: base.String = fields.Field()
title: base.String = fields.Field()
+ file_name: base.String = fields.Field()
mime_type: base.String = fields.Field()
file_size: base.Integer = fields.Field()
thumb: PhotoSize = fields.Field(base=PhotoSize)
diff --git a/aiogram/types/base.py b/aiogram/types/base.py
index e64d3398..5ef774dd 100644
--- a/aiogram/types/base.py
+++ b/aiogram/types/base.py
@@ -1,6 +1,7 @@
from __future__ import annotations
import io
+import logging
import typing
from typing import TypeVar
@@ -26,6 +27,9 @@ Float = TypeVar('Float', bound=float)
Boolean = TypeVar('Boolean', bound=bool)
T = TypeVar('T')
+# Main aiogram logger
+log = logging.getLogger('aiogram')
+
class MetaTelegramObject(type):
"""
@@ -135,14 +139,18 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
return type(self).telegram_types
@classmethod
- def to_object(cls: typing.Type[T], data: typing.Dict[str, typing.Any]) -> T:
+ def to_object(cls: typing.Type[T],
+ data: typing.Dict[str, typing.Any],
+ conf: typing.Dict[str, typing.Any] = None
+ ) -> T:
"""
Deserialize object
:param data:
+ :param conf:
:return:
"""
- return cls(**data)
+ return cls(conf=conf, **data)
@property
def bot(self) -> Bot:
@@ -212,7 +220,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
"""
if item in self.props:
return self.props[item].get_value(self)
- raise KeyError(item)
+ return self.values[item]
def __setitem__(self, key: str, value: typing.Any) -> None:
"""
@@ -224,7 +232,10 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
"""
if key in self.props:
return self.props[key].set_value(self, value, self.conf.get('parent', None))
- raise KeyError(key)
+ self.values[key] = value
+
+ # Log warning when Telegram silently adds new Fields
+ log.warning("Field '%s' doesn't exist in %s", key, self.__class__)
def __contains__(self, item: str) -> bool:
"""
@@ -242,8 +253,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
:return:
"""
- for item in self.to_python().items():
- yield item
+ yield from self.to_python().items()
def iter_keys(self) -> typing.Generator[typing.Any, None, None]:
"""
diff --git a/aiogram/types/bot_command_scope.py b/aiogram/types/bot_command_scope.py
new file mode 100644
index 00000000..cb9bc78e
--- /dev/null
+++ b/aiogram/types/bot_command_scope.py
@@ -0,0 +1,121 @@
+import typing
+
+from . import base, fields
+from ..utils import helper
+
+
+class BotCommandScopeType(helper.Helper):
+ mode = helper.HelperMode.snake_case
+
+ DEFAULT = helper.Item() # default
+ ALL_PRIVATE_CHATS = helper.Item() # all_private_chats
+ ALL_GROUP_CHATS = helper.Item() # all_group_chats
+ ALL_CHAT_ADMINISTRATORS = helper.Item() # all_chat_administrators
+ CHAT = helper.Item() # chat
+ CHAT_ADMINISTRATORS = helper.Item() # chat_administrators
+ CHAT_MEMBER = helper.Item() # chat_member
+
+
+class BotCommandScope(base.TelegramObject):
+ """
+ This object represents the scope to which bot commands are applied.
+ Currently, the following 7 scopes are supported:
+ BotCommandScopeDefault
+ BotCommandScopeAllPrivateChats
+ BotCommandScopeAllGroupChats
+ BotCommandScopeAllChatAdministrators
+ BotCommandScopeChat
+ BotCommandScopeChatAdministrators
+ BotCommandScopeChatMember
+
+ https://core.telegram.org/bots/api#botcommandscope
+ """
+ type: base.String = fields.Field()
+
+ @classmethod
+ def from_type(cls, type: str, **kwargs: typing.Any):
+ if type == BotCommandScopeType.DEFAULT:
+ return BotCommandScopeDefault(type=type, **kwargs)
+ if type == BotCommandScopeType.ALL_PRIVATE_CHATS:
+ return BotCommandScopeAllPrivateChats(type=type, **kwargs)
+ if type == BotCommandScopeType.ALL_GROUP_CHATS:
+ return BotCommandScopeAllGroupChats(type=type, **kwargs)
+ if type == BotCommandScopeType.ALL_CHAT_ADMINISTRATORS:
+ return BotCommandScopeAllChatAdministrators(type=type, **kwargs)
+ if type == BotCommandScopeType.CHAT:
+ return BotCommandScopeChat(type=type, **kwargs)
+ if type == BotCommandScopeType.CHAT_ADMINISTRATORS:
+ return BotCommandScopeChatAdministrators(type=type, **kwargs)
+ if type == BotCommandScopeType.CHAT_MEMBER:
+ return BotCommandScopeChatMember(type=type, **kwargs)
+ raise ValueError(f"Unknown BotCommandScope type {type!r}")
+
+
+class BotCommandScopeDefault(BotCommandScope):
+ """
+ Represents the default scope of bot commands.
+ Default commands are used if no commands with a narrower scope are
+ specified for the user.
+ """
+ type = fields.Field(default=BotCommandScopeType.DEFAULT)
+
+
+class BotCommandScopeAllPrivateChats(BotCommandScope):
+ """
+ Represents the scope of bot commands, covering all private chats.
+ """
+ type = fields.Field(default=BotCommandScopeType.ALL_PRIVATE_CHATS)
+
+
+class BotCommandScopeAllGroupChats(BotCommandScope):
+ """
+ Represents the scope of bot commands, covering all group and
+ supergroup chats.
+ """
+ type = fields.Field(default=BotCommandScopeType.ALL_GROUP_CHATS)
+
+
+class BotCommandScopeAllChatAdministrators(BotCommandScope):
+ """
+ Represents the scope of bot commands, covering all group and
+ supergroup chat administrators.
+ """
+ type = fields.Field(default=BotCommandScopeType.ALL_CHAT_ADMINISTRATORS)
+
+
+class BotCommandScopeChat(BotCommandScope):
+ """
+ Represents the scope of bot commands, covering a specific chat.
+ """
+ type = fields.Field(default=BotCommandScopeType.CHAT)
+ chat_id: typing.Union[base.String, base.Integer] = fields.Field()
+
+ def __init__(self, chat_id: typing.Union[base.String, base.Integer], **kwargs):
+ super().__init__(chat_id=chat_id, **kwargs)
+
+
+class BotCommandScopeChatAdministrators(BotCommandScopeChat):
+ """
+ Represents the scope of bot commands, covering all administrators
+ of a specific group or supergroup chat.
+ """
+ type = fields.Field(default=BotCommandScopeType.CHAT_ADMINISTRATORS)
+ chat_id: typing.Union[base.String, base.Integer] = fields.Field()
+
+
+class BotCommandScopeChatMember(BotCommandScopeChat):
+ """
+ Represents the scope of bot commands, covering a specific member of
+ a group or supergroup chat.
+ """
+ type = fields.Field(default=BotCommandScopeType.CHAT_MEMBER)
+ chat_id: typing.Union[base.String, base.Integer] = fields.Field()
+ user_id: base.Integer = fields.Field()
+
+ def __init__(
+ self,
+ chat_id: typing.Union[base.String, base.Integer],
+ user_id: base.Integer,
+ **kwargs,
+ ):
+ super().__init__(chat_id=chat_id, user_id=user_id, **kwargs)
diff --git a/aiogram/types/callback_query.py b/aiogram/types/callback_query.py
index 51ba1f17..5eeb2f0c 100644
--- a/aiogram/types/callback_query.py
+++ b/aiogram/types/callback_query.py
@@ -28,10 +28,10 @@ class CallbackQuery(base.TelegramObject):
data: base.String = fields.Field()
game_short_name: base.String = fields.Field()
- async def answer(self, text: typing.Union[base.String, None] = None,
- show_alert: typing.Union[base.Boolean, None] = None,
- url: typing.Union[base.String, None] = None,
- cache_time: typing.Union[base.Integer, None] = None):
+ async def answer(self, text: typing.Optional[base.String] = None,
+ show_alert: typing.Optional[base.Boolean] = None,
+ url: typing.Optional[base.String] = None,
+ cache_time: typing.Optional[base.Integer] = None):
"""
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.
@@ -43,19 +43,22 @@ class CallbackQuery(base.TelegramObject):
Source: https://core.telegram.org/bots/api#answercallbackquery
:param text: Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters
- :type text: :obj:`typing.Union[base.String, None]`
+ :type text: :obj:`typing.Optional[base.String]`
:param show_alert: If true, an alert will be shown by the client instead of a notification
at the top of the chat screen. Defaults to false.
- :type show_alert: :obj:`typing.Union[base.Boolean, None]`
+ :type show_alert: :obj:`typing.Optional[base.Boolean]`
:param url: URL that will be opened by the user's client.
- :type url: :obj:`typing.Union[base.String, None]`
+ :type url: :obj:`typing.Optional[base.String]`
:param cache_time: The maximum amount of time in seconds that the
result of the callback query may be cached client-side.
- :type cache_time: :obj:`typing.Union[base.Integer, None]`
+ :type cache_time: :obj:`typing.Optional[base.Integer]`
:return: On success, True is returned.
:rtype: :obj:`base.Boolean`"""
- await self.bot.answer_callback_query(callback_query_id=self.id, text=text,
- show_alert=show_alert, url=url, cache_time=cache_time)
+ return await self.bot.answer_callback_query(callback_query_id=self.id,
+ text=text,
+ show_alert=show_alert,
+ url=url,
+ cache_time=cache_time)
def __hash__(self):
return hash(self.id)
diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py
index 28cc5ed0..2cd19a0f 100644
--- a/aiogram/types/chat.py
+++ b/aiogram/types/chat.py
@@ -4,13 +4,15 @@ import asyncio
import datetime
import typing
-from ..utils import helper, markdown
from . import base, fields
+from .chat_invite_link import ChatInviteLink
+from .chat_location import ChatLocation
from .chat_member import ChatMember
from .chat_permissions import ChatPermissions
from .chat_photo import ChatPhoto
from .input_file import InputFile
-from ..utils.deprecated import deprecated
+from ..utils import helper, markdown
+from ..utils.deprecated import deprecated, DeprecatedReadOnlyClassVar
class Chat(base.TelegramObject):
@@ -27,13 +29,17 @@ class Chat(base.TelegramObject):
last_name: base.String = fields.Field()
all_members_are_administrators: base.Boolean = fields.Field()
photo: ChatPhoto = fields.Field(base=ChatPhoto)
+ bio: base.String = fields.Field()
description: base.String = fields.Field()
invite_link: base.String = fields.Field()
pinned_message: 'Message' = fields.Field(base='Message')
permissions: ChatPermissions = fields.Field(base=ChatPermissions)
slow_mode_delay: base.Integer = fields.Field()
+ message_auto_delete_time: base.Integer = fields.Field()
sticker_set_name: base.String = fields.Field()
can_set_sticker_set: base.Boolean = fields.Field()
+ linked_chat_id: base.Integer = fields.Field()
+ location: ChatLocation = fields.Field()
def __hash__(self):
return self.id
@@ -48,7 +54,7 @@ class Chat(base.TelegramObject):
return self.title
@property
- def mention(self) -> typing.Union[base.String, None]:
+ def mention(self) -> typing.Optional[base.String]:
"""
Get mention if a Chat has a username, or get full name if this is a Private Chat, otherwise None is returned
"""
@@ -109,7 +115,7 @@ class Chat(base.TelegramObject):
async def update_chat(self):
"""
- User this method to update Chat data
+ Use this method to update Chat data
:return: None
"""
@@ -175,59 +181,91 @@ class Chat(base.TelegramObject):
Source: https://core.telegram.org/bots/api#setchatdescription
:param description: New chat description, 0-255 characters
- :type description: :obj:`typing.Union[base.String, None]`
+ :type description: :obj:`typing.Optional[base.String]`
:return: Returns True on success.
:rtype: :obj:`base.Boolean`
"""
return await self.bot.set_chat_description(self.id, description)
- async def kick(self, user_id: base.Integer,
- until_date: typing.Union[base.Integer, datetime.datetime, datetime.timedelta, None] = None) -> base.Boolean:
+ async def kick(self,
+ user_id: base.Integer,
+ until_date: typing.Union[base.Integer, datetime.datetime,
+ datetime.timedelta, None] = None,
+ revoke_messages: typing.Optional[base.Boolean] = None,
+ ) -> base.Boolean:
"""
Use this method to kick a user from a group, a supergroup or a channel.
- In the case of supergroups and channels, the user will not be able to return to the group
- on their own using invite links, etc., unless unbanned first.
+ In the case of supergroups and channels, the user will not be able to return
+ to the chat on their own using invite links, etc., unless unbanned first.
- The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
-
- Note: In regular groups (non-supergroups), this method will only work if the ‘All Members Are Admins’ setting
- is off in the target group.
- Otherwise members may only be removed by the group's creator or by the member that added them.
+ The bot must be an administrator in the chat for this to work and must have
+ the appropriate admin rights.
Source: https://core.telegram.org/bots/api#kickchatmember
:param user_id: Unique identifier of the target user
:type user_id: :obj:`base.Integer`
- :param until_date: Date when the user will be unbanned, unix time.
- :type until_date: :obj:`typing.Union[base.Integer, None]`
- :return: Returns True on success.
+
+ :param until_date: Date when the user will be unbanned. If user is banned
+ for more than 366 days or less than 30 seconds from the current time they
+ are considered to be banned forever. Applied for supergroups and channels
+ only.
+ :type until_date: :obj:`typing.Union[base.Integer, datetime.datetime,
+ datetime.timedelta, None]`
+
+ :param revoke_messages: Pass True to delete all messages from the chat for
+ the user that is being removed. If False, the user will be able to see
+ messages in the group that were sent before the user was removed. Always
+ True for supergroups and channels.
+ :type revoke_messages: :obj:`typing.Optional[base.Boolean]`
+
+ :return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
- return await self.bot.kick_chat_member(self.id, user_id=user_id, until_date=until_date)
+ return await self.bot.kick_chat_member(
+ chat_id=self.id,
+ user_id=user_id,
+ until_date=until_date,
+ revoke_messages=revoke_messages,
+ )
- async def unban(self, user_id: base.Integer) -> base.Boolean:
+ async def unban(self,
+ user_id: base.Integer,
+ only_if_banned: typing.Optional[base.Boolean] = None,
+ ) -> base.Boolean:
"""
- 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.
+ 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. By default, this method guarantees that after the call the user is not
+ a member of the chat, but will be able to join it. So if the user is a member
+ of the chat they will also be removed from the chat. If you don't want this,
+ use the parameter only_if_banned. Returns True on success.
Source: https://core.telegram.org/bots/api#unbanchatmember
:param user_id: Unique identifier of the target user
:type user_id: :obj:`base.Integer`
+
+ :param only_if_banned: Do nothing if the user is not banned
+ :type only_if_banned: :obj:`typing.Optional[base.Boolean]`
+
:return: Returns True on success.
:rtype: :obj:`base.Boolean`
"""
- return await self.bot.unban_chat_member(self.id, user_id=user_id)
+ return await self.bot.unban_chat_member(
+ chat_id=self.id,
+ user_id=user_id,
+ only_if_banned=only_if_banned,
+ )
async def restrict(self, user_id: base.Integer,
permissions: typing.Optional[ChatPermissions] = None,
until_date: typing.Union[base.Integer, datetime.datetime, datetime.timedelta, None] = None,
- can_send_messages: typing.Union[base.Boolean, None] = None,
- can_send_media_messages: typing.Union[base.Boolean, None] = None,
- can_send_other_messages: typing.Union[base.Boolean, None] = None,
- can_add_web_page_previews: typing.Union[base.Boolean, None] = None) -> base.Boolean:
+ can_send_messages: typing.Optional[base.Boolean] = None,
+ can_send_media_messages: typing.Optional[base.Boolean] = None,
+ can_send_other_messages: typing.Optional[base.Boolean] = None,
+ can_add_web_page_previews: typing.Optional[base.Boolean] = None) -> base.Boolean:
"""
Use this method to restrict a user in a supergroup.
The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights.
@@ -240,18 +278,18 @@ class Chat(base.TelegramObject):
:param permissions: New user permissions
:type permissions: :obj:`ChatPermissions`
:param until_date: Date when restrictions will be lifted for the user, unix time.
- :type until_date: :obj:`typing.Union[base.Integer, None]`
+ :type until_date: :obj:`typing.Optional[base.Integer]`
:param can_send_messages: Pass True, if the user can send text messages, contacts, locations and venues
- :type can_send_messages: :obj:`typing.Union[base.Boolean, None]`
+ :type can_send_messages: :obj:`typing.Optional[base.Boolean]`
:param can_send_media_messages: Pass True, if the user can send audios, documents, photos, videos,
video notes and voice notes, implies can_send_messages
- :type can_send_media_messages: :obj:`typing.Union[base.Boolean, None]`
+ :type can_send_media_messages: :obj:`typing.Optional[base.Boolean]`
:param can_send_other_messages: Pass True, if the user can send animations, games, stickers and
use inline bots, implies can_send_media_messages
- :type can_send_other_messages: :obj:`typing.Union[base.Boolean, None]`
+ :type can_send_other_messages: :obj:`typing.Optional[base.Boolean]`
:param can_add_web_page_previews: Pass True, if the user may add web page previews to their messages,
implies can_send_media_messages
- :type can_add_web_page_previews: :obj:`typing.Union[base.Boolean, None]`
+ :type can_add_web_page_previews: :obj:`typing.Optional[base.Boolean]`
:return: Returns True on success.
:rtype: :obj:`base.Boolean`
"""
@@ -263,15 +301,17 @@ class Chat(base.TelegramObject):
can_send_other_messages=can_send_other_messages,
can_add_web_page_previews=can_add_web_page_previews)
- async def promote(self, user_id: base.Integer,
- can_change_info: typing.Union[base.Boolean, None] = None,
- can_post_messages: typing.Union[base.Boolean, None] = None,
- can_edit_messages: typing.Union[base.Boolean, None] = None,
- can_delete_messages: typing.Union[base.Boolean, None] = None,
- can_invite_users: typing.Union[base.Boolean, None] = None,
- can_restrict_members: typing.Union[base.Boolean, None] = None,
- can_pin_messages: typing.Union[base.Boolean, None] = None,
- can_promote_members: typing.Union[base.Boolean, None] = None) -> base.Boolean:
+ async def promote(self,
+ user_id: base.Integer,
+ is_anonymous: typing.Optional[base.Boolean] = None,
+ can_change_info: typing.Optional[base.Boolean] = None,
+ can_post_messages: typing.Optional[base.Boolean] = None,
+ can_edit_messages: typing.Optional[base.Boolean] = None,
+ can_delete_messages: typing.Optional[base.Boolean] = None,
+ can_invite_users: typing.Optional[base.Boolean] = None,
+ can_restrict_members: typing.Optional[base.Boolean] = None,
+ can_pin_messages: typing.Optional[base.Boolean] = None,
+ can_promote_members: typing.Optional[base.Boolean] = None) -> base.Boolean:
"""
Use this method to promote or demote a user in a supergroup or a channel.
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
@@ -281,29 +321,42 @@ class Chat(base.TelegramObject):
:param user_id: Unique identifier of the target user
:type user_id: :obj:`base.Integer`
+
+ :param is_anonymous: Pass True, if the administrator's presence in the chat is hidden
+ :type is_anonymous: :obj:`typing.Optional[base.Boolean]`
+
:param can_change_info: Pass True, if the administrator can change chat title, photo and other settings
- :type can_change_info: :obj:`typing.Union[base.Boolean, None]`
+ :type can_change_info: :obj:`typing.Optional[base.Boolean]`
+
:param can_post_messages: Pass True, if the administrator can create channel posts, channels only
- :type can_post_messages: :obj:`typing.Union[base.Boolean, None]`
+ :type can_post_messages: :obj:`typing.Optional[base.Boolean]`
+
:param can_edit_messages: Pass True, if the administrator can edit messages of other users, channels only
- :type can_edit_messages: :obj:`typing.Union[base.Boolean, None]`
+ :type can_edit_messages: :obj:`typing.Optional[base.Boolean]`
+
:param can_delete_messages: Pass True, if the administrator can delete messages of other users
- :type can_delete_messages: :obj:`typing.Union[base.Boolean, None]`
+ :type can_delete_messages: :obj:`typing.Optional[base.Boolean]`
+
:param can_invite_users: Pass True, if the administrator can invite new users to the chat
- :type can_invite_users: :obj:`typing.Union[base.Boolean, None]`
+ :type can_invite_users: :obj:`typing.Optional[base.Boolean]`
+
:param can_restrict_members: Pass True, if the administrator can restrict, ban or unban chat members
- :type can_restrict_members: :obj:`typing.Union[base.Boolean, None]`
+ :type can_restrict_members: :obj:`typing.Optional[base.Boolean]`
+
:param can_pin_messages: Pass True, if the administrator can pin messages, supergroups only
- :type can_pin_messages: :obj:`typing.Union[base.Boolean, None]`
+ :type can_pin_messages: :obj:`typing.Optional[base.Boolean]`
+
:param can_promote_members: Pass True, if the administrator can add new administrators
with a subset of his own privileges or demote administrators that he has promoted,
directly or indirectly (promoted by administrators that were appointed by him)
- :type can_promote_members: :obj:`typing.Union[base.Boolean, None]`
+ :type can_promote_members: :obj:`typing.Optional[base.Boolean]`
+
:return: Returns True on success.
:rtype: :obj:`base.Boolean`
"""
return await self.bot.promote_chat_member(self.id,
user_id=user_id,
+ is_anonymous=is_anonymous,
can_change_info=can_change_info,
can_post_messages=can_post_messages,
can_edit_messages=can_edit_messages,
@@ -338,36 +391,73 @@ class Chat(base.TelegramObject):
:param custom_title: New custom title for the administrator; 0-16 characters, emoji are not allowed
:return: True on success.
"""
- return await self.bot.set_chat_administrator_custom_title(chat_id=self.id, user_id=user_id, custom_title=custom_title)
+ return await self.bot.set_chat_administrator_custom_title(chat_id=self.id, user_id=user_id,
+ custom_title=custom_title)
- async def pin_message(self, message_id: base.Integer, disable_notification: base.Boolean = False) -> base.Boolean:
+ async def pin_message(self,
+ message_id: base.Integer,
+ disable_notification: typing.Optional[base.Boolean] = False,
+ ) -> base.Boolean:
"""
- Use this method to pin a message in a supergroup.
- The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
+ Use this method to add a message to the list of pinned messages in a chat.
+ If the chat is not a private chat, the bot must be an administrator in the
+ chat for this to work and must have the 'can_pin_messages' admin right in a
+ supergroup or 'can_edit_messages' admin right in a channel. Returns True on
+ success.
Source: https://core.telegram.org/bots/api#pinchatmessage
:param message_id: Identifier of a message to pin
:type message_id: :obj:`base.Integer`
- :param disable_notification: Pass True, if it is not necessary to send a notification to
- all group members about the new pinned message
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
- :return: Returns True on success.
+
+ :param disable_notification: Pass True, if it is not necessary to send a
+ notification to all group members about the new pinned message
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
return await self.bot.pin_chat_message(self.id, message_id, disable_notification)
- async def unpin_message(self) -> base.Boolean:
+ async def unpin_message(self,
+ message_id: typing.Optional[base.Integer] = None,
+ ) -> base.Boolean:
"""
- Use this method to unpin a message in a supergroup chat.
- The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
+ Use this method to remove a message from the list of pinned messages in a
+ chat. If the chat is not a private chat, the bot must be an administrator in
+ the chat for this to work and must have the 'can_pin_messages' admin right in
+ a supergroup or 'can_edit_messages' admin right in a channel. Returns True on
+ success.
Source: https://core.telegram.org/bots/api#unpinchatmessage
- :return: Returns True on success.
+ :param message_id: Identifier of a message to unpin. If not specified, the
+ most recent pinned message (by sending date) will be unpinned.
+ :type message_id: :obj:`typing.Optional[base.Integer]`
+
+ :return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
- return await self.bot.unpin_chat_message(self.id)
+ return await self.bot.unpin_chat_message(
+ chat_id=self.id,
+ message_id=message_id,
+ )
+
+ async def unpin_all_messages(self):
+ """
+ Use this method to clear the list of pinned messages in a chat. If the chat
+ is not a private chat, the bot must be an administrator in the chat for this
+ to work and must have the 'can_pin_messages' admin right in a supergroup or
+ 'can_edit_messages' admin right in a channel. Returns True on success.
+
+ Source: https://core.telegram.org/bots/api#unpinallchatmessages
+
+ :return: Returns True on success
+ :rtype: :obj:`base.Boolean`
+ """
+ return await self.bot.unpin_all_chat_messages(
+ chat_id=self.id,
+ )
async def leave(self) -> base.Boolean:
"""
@@ -394,16 +484,20 @@ class Chat(base.TelegramObject):
"""
return await self.bot.get_chat_administrators(self.id)
- async def get_members_count(self) -> base.Integer:
+ async def get_member_count(self) -> base.Integer:
"""
Use this method to get the number of members in a chat.
- Source: https://core.telegram.org/bots/api#getchatmemberscount
+ Source: https://core.telegram.org/bots/api#getchatmembercount
:return: Returns Int on success.
:rtype: :obj:`base.Integer`
"""
- return await self.bot.get_chat_members_count(self.id)
+ return await self.bot.get_chat_member_count(self.id)
+
+ async def get_members_count(self) -> base.Integer:
+ """Renamed to get_member_count."""
+ return await self.get_member_count(self.id)
async def get_member(self, user_id: base.Integer) -> ChatMember:
"""
@@ -483,6 +577,50 @@ class Chat(base.TelegramObject):
return self.invite_link
+ async def create_invite_link(self,
+ expire_date: typing.Union[base.Integer, datetime.datetime,
+ datetime.timedelta, None] = None,
+ member_limit: typing.Optional[base.Integer] = None,
+ ) -> ChatInviteLink:
+ """ Shortcut for createChatInviteLink method. """
+ return await self.bot.create_chat_invite_link(
+ chat_id=self.id,
+ expire_date=expire_date,
+ member_limit=member_limit,
+ )
+
+ async def edit_invite_link(self,
+ invite_link: base.String,
+ expire_date: typing.Union[base.Integer, datetime.datetime,
+ datetime.timedelta, None] = None,
+ member_limit: typing.Optional[base.Integer] = None,
+ ) -> ChatInviteLink:
+ """ Shortcut for editChatInviteLink method. """
+ return await self.bot.edit_chat_invite_link(
+ chat_id=self.id,
+ invite_link=invite_link,
+ expire_date=expire_date,
+ member_limit=member_limit,
+ )
+
+ async def revoke_invite_link(self,
+ invite_link: base.String,
+ ) -> ChatInviteLink:
+ """ Shortcut for revokeChatInviteLink method. """
+ return await self.bot.revoke_chat_invite_link(
+ chat_id=self.id,
+ invite_link=invite_link,
+ )
+
+ async def delete_message(self,
+ message_id: base.Integer,
+ ) -> base.Boolean:
+ """ Shortcut for deleteMessage method. """
+ return await self.bot.delete_message(
+ chat_id=self.id,
+ message_id=message_id,
+ )
+
def __int__(self):
return self.id
@@ -494,6 +632,7 @@ class ChatType(helper.Helper):
:key: PRIVATE
:key: GROUP
:key: SUPER_GROUP
+ :key: SUPERGROUP
:key: CHANNEL
"""
@@ -501,9 +640,14 @@ class ChatType(helper.Helper):
PRIVATE = helper.Item() # private
GROUP = helper.Item() # group
- SUPER_GROUP = helper.Item() # supergroup
+ SUPERGROUP = helper.Item() # supergroup
CHANNEL = helper.Item() # channel
+ SUPER_GROUP: DeprecatedReadOnlyClassVar[ChatType, helper.Item] \
+ = DeprecatedReadOnlyClassVar(
+ "SUPER_GROUP chat type is deprecated, use SUPERGROUP instead.",
+ new_value_getter=lambda cls: cls.SUPERGROUP)
+
@staticmethod
def _check(obj, chat_types) -> bool:
if hasattr(obj, 'chat'):
@@ -543,7 +687,7 @@ class ChatType(helper.Helper):
:param obj:
:return:
"""
- return cls._check(obj, [cls.SUPER_GROUP])
+ return cls._check(obj, [cls.SUPER_GROUP, cls.SUPERGROUP])
@classmethod
@deprecated("This filter was moved to ChatTypeFilter, and will be removed in aiogram v3.0")
@@ -554,7 +698,7 @@ class ChatType(helper.Helper):
:param obj:
:return:
"""
- return cls._check(obj, [cls.GROUP, cls.SUPER_GROUP])
+ return cls._check(obj, [cls.GROUP, cls.SUPER_GROUP, cls.SUPERGROUP])
@classmethod
@deprecated("This filter was moved to ChatTypeFilter, and will be removed in aiogram v3.0")
@@ -592,6 +736,8 @@ class ChatActions(helper.Helper):
UPLOAD_VIDEO: str = helper.Item() # upload_video
RECORD_AUDIO: str = helper.Item() # record_audio
UPLOAD_AUDIO: str = helper.Item() # upload_audio
+ RECORD_VOICE: str = helper.Item() # record_voice
+ UPLOAD_VOICE: str = helper.Item() # upload_voice
UPLOAD_DOCUMENT: str = helper.Item() # upload_document
FIND_LOCATION: str = helper.Item() # find_location
RECORD_VIDEO_NOTE: str = helper.Item() # record_video_note
@@ -677,6 +823,26 @@ class ChatActions(helper.Helper):
"""
await cls._do(cls.UPLOAD_AUDIO, sleep)
+ @classmethod
+ async def record_voice(cls, sleep=None):
+ """
+ Do record voice
+
+ :param sleep: sleep timeout
+ :return:
+ """
+ await cls._do(cls.RECORD_VOICE, sleep)
+
+ @classmethod
+ async def upload_voice(cls, sleep=None):
+ """
+ Do upload voice
+
+ :param sleep: sleep timeout
+ :return:
+ """
+ await cls._do(cls.UPLOAD_VOICE, sleep)
+
@classmethod
async def upload_document(cls, sleep=None):
"""
diff --git a/aiogram/types/chat_invite_link.py b/aiogram/types/chat_invite_link.py
new file mode 100644
index 00000000..55794780
--- /dev/null
+++ b/aiogram/types/chat_invite_link.py
@@ -0,0 +1,20 @@
+from datetime import datetime
+
+from . import base
+from . import fields
+from .user import User
+
+
+class ChatInviteLink(base.TelegramObject):
+ """
+ Represents an invite link for a chat.
+
+ https://core.telegram.org/bots/api#chatinvitelink
+ """
+
+ invite_link: base.String = fields.Field()
+ creator: User = fields.Field(base=User)
+ is_primary: base.Boolean = fields.Field()
+ is_revoked: base.Boolean = fields.Field()
+ expire_date: datetime = fields.DateTimeField()
+ member_limit: base.Integer = fields.Field()
diff --git a/aiogram/types/chat_location.py b/aiogram/types/chat_location.py
new file mode 100644
index 00000000..0438c544
--- /dev/null
+++ b/aiogram/types/chat_location.py
@@ -0,0 +1,16 @@
+from . import base
+from . import fields
+from .location import Location
+
+
+class ChatLocation(base.TelegramObject):
+ """
+ Represents a location to which a chat is connected.
+
+ https://core.telegram.org/bots/api#chatlocation
+ """
+ location: Location = fields.Field()
+ address: base.String = fields.Field()
+
+ def __init__(self, location: Location, address: base.String):
+ super().__init__(location=location, address=address)
diff --git a/aiogram/types/chat_member.py b/aiogram/types/chat_member.py
index 347b2750..372b3468 100644
--- a/aiogram/types/chat_member.py
+++ b/aiogram/types/chat_member.py
@@ -1,47 +1,13 @@
import datetime
-import warnings
+import typing
from typing import Optional
-from . import base
-from . import fields
+from . import base, fields
from .user import User
from ..utils import helper
-class ChatMember(base.TelegramObject):
- """
- This object contains information about one member of a chat.
-
- https://core.telegram.org/bots/api#chatmember
- """
- user: User = fields.Field(base=User)
- status: base.String = fields.Field()
- custom_title: base.String = fields.Field()
- until_date: datetime.datetime = fields.DateTimeField()
- can_be_edited: base.Boolean = fields.Field()
- can_change_info: base.Boolean = fields.Field()
- can_post_messages: base.Boolean = fields.Field()
- can_edit_messages: base.Boolean = fields.Field()
- can_delete_messages: base.Boolean = fields.Field()
- can_invite_users: base.Boolean = fields.Field()
- can_restrict_members: base.Boolean = fields.Field()
- can_pin_messages: base.Boolean = fields.Field()
- can_promote_members: base.Boolean = fields.Field()
- is_member: base.Boolean = fields.Field()
- can_send_messages: base.Boolean = fields.Field()
- can_send_media_messages: base.Boolean = fields.Field()
- can_send_polls: base.Boolean = fields.Field()
- can_send_other_messages: base.Boolean = fields.Field()
- can_add_web_page_previews: base.Boolean = fields.Field()
-
- def is_chat_admin(self) -> bool:
- return ChatMemberStatus.is_chat_admin(self.status)
-
- def is_chat_member(self) -> bool:
- return ChatMemberStatus.is_chat_member(self.status)
-
- def __int__(self) -> int:
- return self.user.id
+T = typing.TypeVar('T')
class ChatMemberStatus(helper.Helper):
@@ -51,16 +17,176 @@ class ChatMemberStatus(helper.Helper):
mode = helper.HelperMode.lowercase
CREATOR = helper.Item() # creator
+ OWNER = CREATOR # creator
ADMINISTRATOR = helper.Item() # administrator
MEMBER = helper.Item() # member
RESTRICTED = helper.Item() # restricted
LEFT = helper.Item() # left
KICKED = helper.Item() # kicked
+ BANNED = KICKED # kicked
+
+ @classmethod
+ def is_chat_creator(cls, role: str) -> bool:
+ return role == cls.CREATOR
@classmethod
def is_chat_admin(cls, role: str) -> bool:
- return role in [cls.ADMINISTRATOR, cls.CREATOR]
+ return role in (cls.ADMINISTRATOR, cls.CREATOR)
@classmethod
def is_chat_member(cls, role: str) -> bool:
- return role in [cls.MEMBER, cls.ADMINISTRATOR, cls.CREATOR, cls.RESTRICTED]
+ return role in (cls.MEMBER, cls.ADMINISTRATOR, cls.CREATOR, cls.RESTRICTED)
+
+ @classmethod
+ def get_class_by_status(cls, status: str) -> Optional["ChatMember"]:
+ return {
+ cls.OWNER: ChatMemberOwner,
+ cls.ADMINISTRATOR: ChatMemberAdministrator,
+ cls.MEMBER: ChatMemberMember,
+ cls.RESTRICTED: ChatMemberRestricted,
+ cls.LEFT: ChatMemberLeft,
+ cls.BANNED: ChatMemberBanned,
+ }.get(status)
+
+
+class ChatMember(base.TelegramObject):
+ """
+ This object contains information about one member of a chat.
+ Currently, the following 6 types of chat members are supported:
+ ChatMemberOwner
+ ChatMemberAdministrator
+ ChatMemberMember
+ ChatMemberRestricted
+ ChatMemberLeft
+ ChatMemberBanned
+
+ https://core.telegram.org/bots/api#chatmember
+ """
+ status: base.String = fields.Field()
+ user: User = fields.Field(base=User)
+
+ def __int__(self) -> int:
+ return self.user.id
+
+ @classmethod
+ def resolve(cls, **kwargs) -> "ChatMember":
+ status = kwargs.get("status")
+ mapping = {
+ ChatMemberStatus.OWNER: ChatMemberOwner,
+ ChatMemberStatus.ADMINISTRATOR: ChatMemberAdministrator,
+ ChatMemberStatus.MEMBER: ChatMemberMember,
+ ChatMemberStatus.RESTRICTED: ChatMemberRestricted,
+ ChatMemberStatus.LEFT: ChatMemberLeft,
+ ChatMemberStatus.BANNED: ChatMemberBanned,
+ }
+ class_ = mapping.get(status)
+ if class_ is None:
+ raise ValueError(f"Can't find `ChatMember` class for status `{status}`")
+
+ return class_(**kwargs)
+
+ @classmethod
+ def to_object(cls,
+ data: typing.Dict[str, typing.Any],
+ conf: typing.Dict[str, typing.Any] = None
+ ) -> "ChatMember":
+ return cls.resolve(**data)
+
+ def is_chat_creator(self) -> bool:
+ return ChatMemberStatus.is_chat_creator(self.status)
+
+ def is_chat_admin(self) -> bool:
+ return ChatMemberStatus.is_chat_admin(self.status)
+
+ def is_chat_member(self) -> bool:
+ return ChatMemberStatus.is_chat_member(self.status)
+
+
+class ChatMemberOwner(ChatMember):
+ """
+ Represents a chat member that owns the chat and has all
+ administrator privileges.
+ https://core.telegram.org/bots/api#chatmemberowner
+ """
+ status: base.String = fields.Field(default=ChatMemberStatus.OWNER)
+ user: User = fields.Field(base=User)
+ custom_title: base.String = fields.Field()
+ is_anonymous: base.Boolean = fields.Field()
+
+
+class ChatMemberAdministrator(ChatMember):
+ """
+ Represents a chat member that has some additional privileges.
+
+ https://core.telegram.org/bots/api#chatmemberadministrator
+ """
+ status: base.String = fields.Field(default=ChatMemberStatus.ADMINISTRATOR)
+ user: User = fields.Field(base=User)
+ can_be_edited: base.Boolean = fields.Field()
+ custom_title: base.String = fields.Field()
+ is_anonymous: base.Boolean = fields.Field()
+ can_manage_chat: base.Boolean = fields.Field()
+ can_post_messages: base.Boolean = fields.Field()
+ can_edit_messages: base.Boolean = fields.Field()
+ can_delete_messages: base.Boolean = fields.Field()
+ can_manage_voice_chats: base.Boolean = fields.Field()
+ can_restrict_members: base.Boolean = fields.Field()
+ can_promote_members: base.Boolean = fields.Field()
+ can_change_info: base.Boolean = fields.Field()
+ can_invite_users: base.Boolean = fields.Field()
+ can_pin_messages: base.Boolean = fields.Field()
+
+
+class ChatMemberMember(ChatMember):
+ """
+ Represents a chat member that has no additional privileges or
+ restrictions.
+
+ https://core.telegram.org/bots/api#chatmembermember
+ """
+ status: base.String = fields.Field(default=ChatMemberStatus.MEMBER)
+ user: User = fields.Field(base=User)
+
+
+class ChatMemberRestricted(ChatMember):
+ """
+ Represents a chat member that is under certain restrictions in the
+ chat. Supergroups only.
+
+ https://core.telegram.org/bots/api#chatmemberrestricted
+ """
+ status: base.String = fields.Field(default=ChatMemberStatus.RESTRICTED)
+ user: User = fields.Field(base=User)
+ is_member: base.Boolean = fields.Field()
+ can_change_info: base.Boolean = fields.Field()
+ can_invite_users: base.Boolean = fields.Field()
+ can_pin_messages: base.Boolean = fields.Field()
+ can_send_messages: base.Boolean = fields.Field()
+ can_send_media_messages: base.Boolean = fields.Field()
+ can_send_polls: base.Boolean = fields.Field()
+ can_send_other_messages: base.Boolean = fields.Field()
+ can_add_web_page_previews: base.Boolean = fields.Field()
+ until_date: datetime.datetime = fields.DateTimeField()
+
+
+class ChatMemberLeft(ChatMember):
+ """
+ Represents a chat member that isn't currently a member of the chat,
+ but may join it themselves.
+
+ https://core.telegram.org/bots/api#chatmemberleft
+ """
+ status: base.String = fields.Field(default=ChatMemberStatus.LEFT)
+ user: User = fields.Field(base=User)
+
+
+class ChatMemberBanned(ChatMember):
+ """
+ Represents a chat member that was banned in the chat and can't
+ return to the chat or view chat messages.
+
+ https://core.telegram.org/bots/api#chatmemberbanned
+ """
+ status: base.String = fields.Field(default=ChatMemberStatus.BANNED)
+ user: User = fields.Field(base=User)
+ until_date: datetime.datetime = fields.DateTimeField()
diff --git a/aiogram/types/chat_member_updated.py b/aiogram/types/chat_member_updated.py
new file mode 100644
index 00000000..67c75616
--- /dev/null
+++ b/aiogram/types/chat_member_updated.py
@@ -0,0 +1,22 @@
+import datetime
+
+from . import base
+from . import fields
+from .chat import Chat
+from .chat_invite_link import ChatInviteLink
+from .chat_member import ChatMember
+from .user import User
+
+
+class ChatMemberUpdated(base.TelegramObject):
+ """
+ This object represents changes in the status of a chat member.
+
+ https://core.telegram.org/bots/api#chatmemberupdated
+ """
+ chat: Chat = fields.Field(base=Chat)
+ from_user: User = fields.Field(alias="from", base=User)
+ date: datetime.datetime = fields.DateTimeField()
+ old_chat_member: ChatMember = fields.Field(base=ChatMember)
+ new_chat_member: ChatMember = fields.Field(base=ChatMember)
+ invite_link: ChatInviteLink = fields.Field(base=ChatInviteLink)
diff --git a/aiogram/types/dice.py b/aiogram/types/dice.py
index 7b3f1727..c4f2725e 100644
--- a/aiogram/types/dice.py
+++ b/aiogram/types/dice.py
@@ -3,9 +3,7 @@ from . import base, fields
class Dice(base.TelegramObject):
"""
- This object represents a dice with random value from 1 to 6.
- (Yes, we're aware of the “proper” singular of die.
- But it's awkward, and we decided to help it change. One dice at a time!)
+ This object represents an animated emoji that displays a random value.
https://core.telegram.org/bots/api#dice
"""
@@ -17,3 +15,6 @@ class DiceEmoji:
DICE = '🎲'
DART = '🎯'
BASKETBALL = '🏀'
+ FOOTBALL = '⚽'
+ SLOT_MACHINE = '🎰'
+ BOWLING = '🎳'
diff --git a/aiogram/types/document.py b/aiogram/types/document.py
index e15b745d..c3d19fa9 100644
--- a/aiogram/types/document.py
+++ b/aiogram/types/document.py
@@ -2,6 +2,7 @@ from . import base
from . import fields
from . import mixins
from .photo_size import PhotoSize
+from ..utils import helper
class Document(base.TelegramObject, mixins.Downloadable):
@@ -16,3 +17,34 @@ class Document(base.TelegramObject, mixins.Downloadable):
file_name: base.String = fields.Field()
mime_type: base.String = fields.Field()
file_size: base.Integer = fields.Field()
+
+ @property
+ def mime_base(self) -> str:
+ base_type, _, _ = self.mime_type.partition('/')
+ return base_type
+
+ @property
+ def mime_subtype(self) -> str:
+ _, _, subtype = self.mime_type.partition('/')
+ return subtype
+
+
+class MimeBase(helper.Helper):
+ """
+ List of mime base types registered in IANA
+
+ https://www.iana.org/assignments/media-types/media-types.xhtml
+ """
+
+ mode = helper.HelperMode.lowercase
+
+ APPLICATION = helper.Item() # application
+ AUDIO = helper.Item() # audio
+ EXAMPLE = helper.Item() # example
+ FONT = helper.Item() # font
+ IMAGE = helper.Item() # image
+ MESSAGE = helper.Item() # message
+ MODEL = helper.Item() # model
+ MULTIPART = helper.Item() # multipart
+ TEXT = helper.Item() # text
+ VIDEO = helper.Item() # video
diff --git a/aiogram/types/fields.py b/aiogram/types/fields.py
index 022b9b72..f898fc62 100644
--- a/aiogram/types/fields.py
+++ b/aiogram/types/fields.py
@@ -112,7 +112,7 @@ class Field(BaseField):
and not hasattr(value, 'to_python'):
if not isinstance(parent, weakref.ReferenceType):
parent = weakref.ref(parent)
- return self.base_object(conf={'parent':parent}, **value)
+ return self.base_object.to_object(conf={'parent': parent}, data=value)
return value
@@ -129,18 +129,16 @@ class ListField(Field):
super(ListField, self).__init__(*args, default=default, **kwargs)
def serialize(self, value):
- result = []
+ if value is None:
+ return None
serialize = super(ListField, self).serialize
- for item in value:
- result.append(serialize(item))
- return result
+ return [serialize(item) for item in value]
def deserialize(self, value, parent=None):
- result = []
+ if value is None:
+ return None
deserialize = super(ListField, self).deserialize
- for item in value:
- result.append(deserialize(item, parent=parent))
- return result
+ return [deserialize(item, parent=parent) for item in value]
class ListOfLists(Field):
@@ -148,9 +146,7 @@ class ListOfLists(Field):
result = []
serialize = super(ListOfLists, self).serialize
for row in value:
- row_result = []
- for item in row:
- row_result.append(serialize(item))
+ row_result = [serialize(item) for item in row]
result.append(row_result)
return result
@@ -159,9 +155,7 @@ class ListOfLists(Field):
deserialize = super(ListOfLists, self).deserialize
if hasattr(value, '__iter__'):
for row in value:
- row_result = []
- for item in row:
- row_result.append(deserialize(item, parent=parent))
+ row_result = [deserialize(item, parent=parent) for item in row]
result.append(row_result)
return result
diff --git a/aiogram/types/force_reply.py b/aiogram/types/force_reply.py
index 97ec16c6..d6b4f19f 100644
--- a/aiogram/types/force_reply.py
+++ b/aiogram/types/force_reply.py
@@ -6,31 +6,28 @@ from . import fields
class ForceReply(base.TelegramObject):
"""
- 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
+ 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
-
- 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.
-
https://core.telegram.org/bots/api#forcereply
"""
force_reply: base.Boolean = fields.Field(default=True)
+ input_field_placeholder: base.String = fields.Field()
selective: base.Boolean = fields.Field()
@classmethod
- def create(cls, selective: typing.Optional[base.Boolean] = None):
+ def create(cls,
+ input_field_placeholder: typing.Optional[base.String] = None,
+ selective: typing.Optional[base.Boolean] = None,
+ ) -> 'ForceReply':
"""
Create new force reply
:param selective:
+ :param input_field_placeholder:
:return:
"""
- return cls(selective=selective)
+ return cls(selective=selective, input_field_placeholder=input_field_placeholder)
diff --git a/aiogram/types/inline_keyboard.py b/aiogram/types/inline_keyboard.py
index 97ad35da..195c1e67 100644
--- a/aiogram/types/inline_keyboard.py
+++ b/aiogram/types/inline_keyboard.py
@@ -50,7 +50,7 @@ class InlineKeyboardMarkup(base.TelegramObject):
if index % self.row_width == 0:
self.inline_keyboard.append(row)
row = []
- if len(row) > 0:
+ if row:
self.inline_keyboard.append(row)
return self
@@ -62,9 +62,7 @@ class InlineKeyboardMarkup(base.TelegramObject):
:return: self
:rtype: :obj:`types.InlineKeyboardMarkup`
"""
- btn_array = []
- for button in args:
- btn_array.append(button)
+ btn_array = [button for button in args]
self.inline_keyboard.append(btn_array)
return self
diff --git a/aiogram/types/inline_query.py b/aiogram/types/inline_query.py
index 379394a0..63f4ab32 100644
--- a/aiogram/types/inline_query.py
+++ b/aiogram/types/inline_query.py
@@ -17,17 +17,18 @@ class InlineQuery(base.TelegramObject):
"""
id: base.String = fields.Field()
from_user: User = fields.Field(alias='from', base=User)
- location: Location = fields.Field(base=Location)
query: base.String = fields.Field()
offset: base.String = fields.Field()
+ chat_type: base.String = fields.Field()
+ location: Location = fields.Field(base=Location)
async def answer(self,
results: typing.List[InlineQueryResult],
- cache_time: typing.Union[base.Integer, None] = None,
- is_personal: typing.Union[base.Boolean, None] = None,
- next_offset: typing.Union[base.String, None] = None,
- switch_pm_text: typing.Union[base.String, None] = None,
- switch_pm_parameter: typing.Union[base.String, None] = None):
+ cache_time: typing.Optional[base.Integer] = None,
+ is_personal: typing.Optional[base.Boolean] = None,
+ next_offset: typing.Optional[base.String] = None,
+ switch_pm_text: typing.Optional[base.String] = None,
+ switch_pm_parameter: typing.Optional[base.String] = None):
"""
Use this method to send answers to an inline query.
No more than 50 results per query are allowed.
@@ -38,22 +39,22 @@ class InlineQuery(base.TelegramObject):
:type results: :obj:`typing.List[types.InlineQueryResult]`
:param cache_time: The maximum amount of time in seconds that the result of the
inline query may be cached on the server. Defaults to 300.
- :type cache_time: :obj:`typing.Union[base.Integer, None]`
+ :type cache_time: :obj:`typing.Optional[base.Integer]`
:param is_personal: Pass True, if results may be cached on the server side only
for the user that sent the query. By default, results may be returned to any user who sends the same query
- :type is_personal: :obj:`typing.Union[base.Boolean, None]`
+ :type is_personal: :obj:`typing.Optional[base.Boolean]`
:param next_offset: Pass the offset that a client should send in the
next query with the same text to receive more results.
Pass an empty string if there are no more results or if you don‘t support pagination.
Offset length can’t exceed 64 bytes.
- :type next_offset: :obj:`typing.Union[base.String, None]`
+ :type next_offset: :obj:`typing.Optional[base.String]`
:param switch_pm_text: If passed, clients will display a button with specified text that
switches the user to a private chat with the bot and sends the bot a start message
with the parameter switch_pm_parameter
- :type switch_pm_text: :obj:`typing.Union[base.String, None]`
+ :type switch_pm_text: :obj:`typing.Optional[base.String]`
:param switch_pm_parameter: Deep-linking parameter for the /start message sent to the bot when
user presses the switch button. 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed.
- :type switch_pm_parameter: :obj:`typing.Union[base.String, None]`
+ :type switch_pm_parameter: :obj:`typing.Optional[base.String]`
:return: On success, True is returned
:rtype: :obj:`base.Boolean`
"""
diff --git a/aiogram/types/inline_query_result.py b/aiogram/types/inline_query_result.py
index fccaa2a1..da09db9d 100644
--- a/aiogram/types/inline_query_result.py
+++ b/aiogram/types/inline_query_result.py
@@ -4,6 +4,7 @@ from . import base
from . import fields
from .inline_keyboard import InlineKeyboardMarkup
from .input_message_content import InputMessageContent
+from .message_entity import MessageEntity
class InlineQueryResult(base.TelegramObject):
@@ -83,23 +84,29 @@ class InlineQueryResultPhoto(InlineQueryResult):
caption: base.String = fields.Field()
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
- def __init__(self, *,
- id: base.String,
- photo_url: base.String,
- thumb_url: base.String,
- photo_width: typing.Optional[base.Integer] = None,
- photo_height: typing.Optional[base.Integer] = None,
- title: typing.Optional[base.String] = None,
- description: typing.Optional[base.String] = None,
- caption: typing.Optional[base.String] = None,
- parse_mode: typing.Optional[base.String] = None,
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
- input_message_content: typing.Optional[InputMessageContent] = None):
- super(InlineQueryResultPhoto, self).__init__(id=id, photo_url=photo_url, thumb_url=thumb_url,
- photo_width=photo_width, photo_height=photo_height, title=title,
- description=description, caption=caption,
- parse_mode=parse_mode, reply_markup=reply_markup,
- input_message_content=input_message_content)
+ def __init__(
+ self,
+ *,
+ id: base.String,
+ photo_url: base.String,
+ thumb_url: base.String,
+ photo_width: typing.Optional[base.Integer] = None,
+ photo_height: typing.Optional[base.Integer] = None,
+ title: typing.Optional[base.String] = None,
+ description: typing.Optional[base.String] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
+ input_message_content: typing.Optional[InputMessageContent] = None,
+ ):
+ super().__init__(
+ id=id, photo_url=photo_url, thumb_url=thumb_url,
+ photo_width=photo_width, photo_height=photo_height, title=title,
+ description=description, caption=caption,
+ parse_mode=parse_mode, caption_entities=caption_entities,
+ reply_markup=reply_markup, input_message_content=input_message_content,
+ )
class InlineQueryResultGif(InlineQueryResult):
@@ -123,23 +130,29 @@ class InlineQueryResultGif(InlineQueryResult):
caption: base.String = fields.Field()
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
- def __init__(self, *,
- id: base.String,
- gif_url: base.String,
- gif_width: typing.Optional[base.Integer] = None,
- gif_height: typing.Optional[base.Integer] = None,
- gif_duration: typing.Optional[base.Integer] = None,
- thumb_url: typing.Optional[base.String] = None,
- title: typing.Optional[base.String] = None,
- caption: typing.Optional[base.String] = None,
- parse_mode: typing.Optional[base.String] = None,
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
- input_message_content: typing.Optional[InputMessageContent] = None):
- super(InlineQueryResultGif, self).__init__(id=id, gif_url=gif_url, gif_width=gif_width,
- gif_height=gif_height, gif_duration=gif_duration,
- thumb_url=thumb_url, title=title, caption=caption,
- parse_mode=parse_mode, reply_markup=reply_markup,
- input_message_content=input_message_content)
+ def __init__(
+ self,
+ *,
+ id: base.String,
+ gif_url: base.String,
+ gif_width: typing.Optional[base.Integer] = None,
+ gif_height: typing.Optional[base.Integer] = None,
+ gif_duration: typing.Optional[base.Integer] = None,
+ thumb_url: typing.Optional[base.String] = None,
+ title: typing.Optional[base.String] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ input_message_content: typing.Optional[InputMessageContent] = None,
+ ):
+ super().__init__(
+ id=id, gif_url=gif_url, gif_width=gif_width, gif_height=gif_height,
+ gif_duration=gif_duration, thumb_url=thumb_url, title=title,
+ caption=caption, parse_mode=parse_mode, reply_markup=reply_markup,
+ caption_entities=caption_entities,
+ input_message_content=input_message_content,
+ )
class InlineQueryResultMpeg4Gif(InlineQueryResult):
@@ -163,23 +176,30 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult):
caption: base.String = fields.Field()
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
- def __init__(self, *,
- id: base.String,
- mpeg4_url: base.String,
- thumb_url: base.String,
- mpeg4_width: typing.Optional[base.Integer] = None,
- mpeg4_height: typing.Optional[base.Integer] = None,
- mpeg4_duration: typing.Optional[base.Integer] = None,
- title: typing.Optional[base.String] = None,
- caption: typing.Optional[base.String] = None,
- parse_mode: typing.Optional[base.String] = None,
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
- input_message_content: typing.Optional[InputMessageContent] = None):
- super(InlineQueryResultMpeg4Gif, self).__init__(id=id, mpeg4_url=mpeg4_url, mpeg4_width=mpeg4_width,
- mpeg4_height=mpeg4_height, mpeg4_duration=mpeg4_duration,
- thumb_url=thumb_url, title=title, caption=caption,
- parse_mode=parse_mode, reply_markup=reply_markup,
- input_message_content=input_message_content)
+ def __init__(
+ self,
+ *,
+ id: base.String,
+ mpeg4_url: base.String,
+ thumb_url: base.String,
+ mpeg4_width: typing.Optional[base.Integer] = None,
+ mpeg4_height: typing.Optional[base.Integer] = None,
+ mpeg4_duration: typing.Optional[base.Integer] = None,
+ title: typing.Optional[base.String] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ input_message_content: typing.Optional[InputMessageContent] = None,
+ ):
+ super().__init__(
+ id=id, mpeg4_url=mpeg4_url, mpeg4_width=mpeg4_width,
+ mpeg4_height=mpeg4_height, mpeg4_duration=mpeg4_duration,
+ thumb_url=thumb_url, title=title, caption=caption,
+ parse_mode=parse_mode, reply_markup=reply_markup,
+ caption_entities=caption_entities,
+ input_message_content=input_message_content,
+ )
class InlineQueryResultVideo(InlineQueryResult):
@@ -207,26 +227,32 @@ class InlineQueryResultVideo(InlineQueryResult):
description: base.String = fields.Field()
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
- def __init__(self, *,
- id: base.String,
- video_url: base.String,
- mime_type: base.String,
- thumb_url: base.String,
- title: base.String,
- caption: typing.Optional[base.String] = None,
- parse_mode: typing.Optional[base.String] = None,
- video_width: typing.Optional[base.Integer] = None,
- video_height: typing.Optional[base.Integer] = None,
- video_duration: typing.Optional[base.Integer] = None,
- description: typing.Optional[base.String] = None,
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
- input_message_content: typing.Optional[InputMessageContent] = None):
- super(InlineQueryResultVideo, self).__init__(id=id, video_url=video_url, mime_type=mime_type,
- thumb_url=thumb_url, title=title, caption=caption,
- video_width=video_width, video_height=video_height,
- video_duration=video_duration, description=description,
- parse_mode=parse_mode, reply_markup=reply_markup,
- input_message_content=input_message_content)
+ def __init__(
+ self,
+ *,
+ id: base.String,
+ video_url: base.String,
+ mime_type: base.String,
+ thumb_url: base.String,
+ title: base.String,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ video_width: typing.Optional[base.Integer] = None,
+ video_height: typing.Optional[base.Integer] = None,
+ video_duration: typing.Optional[base.Integer] = None,
+ description: typing.Optional[base.String] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ input_message_content: typing.Optional[InputMessageContent] = None,
+ ):
+ super().__init__(
+ id=id, video_url=video_url, mime_type=mime_type, thumb_url=thumb_url,
+ title=title, caption=caption, video_width=video_width,
+ video_height=video_height, video_duration=video_duration,
+ description=description, parse_mode=parse_mode,
+ reply_markup=reply_markup, caption_entities=caption_entities,
+ input_message_content=input_message_content,
+ )
class InlineQueryResultAudio(InlineQueryResult):
@@ -248,21 +274,27 @@ class InlineQueryResultAudio(InlineQueryResult):
audio_duration: base.Integer = fields.Field()
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
- def __init__(self, *,
- id: base.String,
- audio_url: base.String,
- title: base.String,
- caption: typing.Optional[base.String] = None,
- parse_mode: typing.Optional[base.String] = None,
- performer: typing.Optional[base.String] = None,
- audio_duration: typing.Optional[base.Integer] = None,
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
- input_message_content: typing.Optional[InputMessageContent] = None):
- super(InlineQueryResultAudio, self).__init__(id=id, audio_url=audio_url, title=title,
- caption=caption, parse_mode=parse_mode,
- performer=performer, audio_duration=audio_duration,
- reply_markup=reply_markup,
- input_message_content=input_message_content)
+ def __init__(
+ self,
+ *,
+ id: base.String,
+ audio_url: base.String,
+ title: base.String,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ performer: typing.Optional[base.String] = None,
+ audio_duration: typing.Optional[base.Integer] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ input_message_content: typing.Optional[InputMessageContent] = None,
+ ):
+ super().__init__(
+ id=id, audio_url=audio_url, title=title,
+ caption=caption, parse_mode=parse_mode,
+ performer=performer, audio_duration=audio_duration,
+ reply_markup=reply_markup, caption_entities=caption_entities,
+ input_message_content=input_message_content,
+ )
class InlineQueryResultVoice(InlineQueryResult):
@@ -285,19 +317,25 @@ class InlineQueryResultVoice(InlineQueryResult):
voice_duration: base.Integer = fields.Field()
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
- def __init__(self, *,
- id: base.String,
- voice_url: base.String,
- title: base.String,
- caption: typing.Optional[base.String] = None,
- parse_mode: typing.Optional[base.String] = None,
- voice_duration: typing.Optional[base.Integer] = None,
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
- input_message_content: typing.Optional[InputMessageContent] = None):
- super(InlineQueryResultVoice, self).__init__(id=id, voice_url=voice_url, title=title,
- caption=caption, voice_duration=voice_duration,
- parse_mode=parse_mode, reply_markup=reply_markup,
- input_message_content=input_message_content)
+ def __init__(
+ self,
+ *,
+ id: base.String,
+ voice_url: base.String,
+ title: base.String,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ voice_duration: typing.Optional[base.Integer] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ input_message_content: typing.Optional[InputMessageContent] = None,
+ ):
+ super().__init__(
+ id=id, voice_url=voice_url, title=title, caption=caption,
+ voice_duration=voice_duration, parse_mode=parse_mode,
+ reply_markup=reply_markup, caption_entities=caption_entities,
+ input_message_content=input_message_content,
+ )
class InlineQueryResultDocument(InlineQueryResult):
@@ -323,25 +361,31 @@ class InlineQueryResultDocument(InlineQueryResult):
thumb_width: base.Integer = fields.Field()
thumb_height: base.Integer = fields.Field()
- def __init__(self, *,
- id: base.String,
- title: base.String,
- caption: typing.Optional[base.String] = None,
- parse_mode: typing.Optional[base.String] = None,
- document_url: typing.Optional[base.String] = None,
- mime_type: typing.Optional[base.String] = None,
- description: typing.Optional[base.String] = None,
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
- input_message_content: typing.Optional[InputMessageContent] = None,
- thumb_url: typing.Optional[base.String] = None,
- thumb_width: typing.Optional[base.Integer] = None,
- thumb_height: typing.Optional[base.Integer] = None):
- super(InlineQueryResultDocument, self).__init__(id=id, title=title, caption=caption,
- document_url=document_url, mime_type=mime_type,
- description=description, reply_markup=reply_markup,
- input_message_content=input_message_content,
- thumb_url=thumb_url, thumb_width=thumb_width,
- thumb_height=thumb_height, parse_mode=parse_mode)
+ def __init__(
+ self,
+ *,
+ id: base.String,
+ title: base.String,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ document_url: typing.Optional[base.String] = None,
+ mime_type: typing.Optional[base.String] = None,
+ description: typing.Optional[base.String] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
+ input_message_content: typing.Optional[InputMessageContent] = None,
+ thumb_url: typing.Optional[base.String] = None,
+ thumb_width: typing.Optional[base.Integer] = None,
+ thumb_height: typing.Optional[base.Integer] = None,
+ ):
+ super().__init__(
+ id=id, title=title, caption=caption, parse_mode=parse_mode,
+ caption_entities=caption_entities, document_url=document_url,
+ mime_type=mime_type, description=description, reply_markup=reply_markup,
+ input_message_content=input_message_content,
+ thumb_url=thumb_url, thumb_width=thumb_width,
+ thumb_height=thumb_height,
+ )
class InlineQueryResultLocation(InlineQueryResult):
@@ -352,16 +396,16 @@ class InlineQueryResultLocation(InlineQueryResult):
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.
-
https://core.telegram.org/bots/api#inlinequeryresultlocation
"""
type: base.String = fields.Field(alias='type', default='location')
latitude: base.Float = fields.Field()
longitude: base.Float = fields.Field()
title: base.String = fields.Field()
+ horizontal_accuracy: typing.Optional[base.Float] = fields.Field()
live_period: base.Integer = fields.Field()
+ heading: typing.Optional[base.Integer] = fields.Field()
+ proximity_alert_radius: typing.Optional[base.Integer] = fields.Field()
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
thumb_url: base.String = fields.Field()
thumb_width: base.Integer = fields.Field()
@@ -372,18 +416,31 @@ class InlineQueryResultLocation(InlineQueryResult):
latitude: base.Float,
longitude: base.Float,
title: base.String,
+ horizontal_accuracy: typing.Optional[base.Float] = None,
live_period: typing.Optional[base.Integer] = None,
+ heading: typing.Optional[base.Integer] = None,
+ proximity_alert_radius: typing.Optional[base.Integer] = None,
reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
input_message_content: typing.Optional[InputMessageContent] = None,
thumb_url: typing.Optional[base.String] = None,
thumb_width: typing.Optional[base.Integer] = None,
- thumb_height: typing.Optional[base.Integer] = None):
- super(InlineQueryResultLocation, self).__init__(id=id, latitude=latitude, longitude=longitude,
- title=title, live_period=live_period,
- reply_markup=reply_markup,
- input_message_content=input_message_content,
- thumb_url=thumb_url, thumb_width=thumb_width,
- thumb_height=thumb_height)
+ thumb_height: typing.Optional[base.Integer] = None,
+ ):
+ super().__init__(
+ id=id,
+ latitude=latitude,
+ longitude=longitude,
+ title=title,
+ horizontal_accuracy=horizontal_accuracy,
+ live_period=live_period,
+ heading=heading,
+ proximity_alert_radius=proximity_alert_radius,
+ reply_markup=reply_markup,
+ input_message_content=input_message_content,
+ thumb_url=thumb_url,
+ thumb_width=thumb_width,
+ thumb_height=thumb_height
+ )
class InlineQueryResultVenue(InlineQueryResult):
@@ -404,31 +461,40 @@ class InlineQueryResultVenue(InlineQueryResult):
title: base.String = fields.Field()
address: base.String = fields.Field()
foursquare_id: base.String = fields.Field()
+ foursquare_type: base.String = fields.Field()
+ google_place_id: base.String = fields.Field()
+ google_place_type: base.String = fields.Field()
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
thumb_url: base.String = fields.Field()
thumb_width: base.Integer = fields.Field()
thumb_height: base.Integer = fields.Field()
- foursquare_type: base.String = fields.Field()
- def __init__(self, *,
- id: base.String,
- latitude: base.Float,
- longitude: base.Float,
- title: base.String,
- address: base.String,
- foursquare_id: typing.Optional[base.String] = None,
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
- input_message_content: typing.Optional[InputMessageContent] = None,
- thumb_url: typing.Optional[base.String] = None,
- thumb_width: typing.Optional[base.Integer] = None,
- thumb_height: typing.Optional[base.Integer] = None,
- foursquare_type: typing.Optional[base.String] = None):
- super(InlineQueryResultVenue, self).__init__(id=id, latitude=latitude, longitude=longitude,
- title=title, address=address, foursquare_id=foursquare_id,
- reply_markup=reply_markup,
- input_message_content=input_message_content, thumb_url=thumb_url,
- thumb_width=thumb_width, thumb_height=thumb_height,
- foursquare_type=foursquare_type)
+ def __init__(
+ self,
+ *,
+ id: base.String,
+ latitude: base.Float,
+ longitude: base.Float,
+ title: base.String,
+ address: base.String,
+ foursquare_id: typing.Optional[base.String] = None,
+ foursquare_type: typing.Optional[base.String] = None,
+ google_place_id: typing.Optional[base.String] = None,
+ google_place_type: typing.Optional[base.String] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
+ input_message_content: typing.Optional[InputMessageContent] = None,
+ thumb_url: typing.Optional[base.String] = None,
+ thumb_width: typing.Optional[base.Integer] = None,
+ thumb_height: typing.Optional[base.Integer] = None,
+ ):
+ super().__init__(
+ id=id, latitude=latitude, longitude=longitude, title=title,
+ address=address, foursquare_id=foursquare_id,
+ foursquare_type=foursquare_type, google_place_id=google_place_id,
+ google_place_type=google_place_type, reply_markup=reply_markup,
+ input_message_content=input_message_content, thumb_url=thumb_url,
+ thumb_width=thumb_width, thumb_height=thumb_height,
+ )
class InlineQueryResultContact(InlineQueryResult):
@@ -510,19 +576,24 @@ class InlineQueryResultCachedPhoto(InlineQueryResult):
caption: base.String = fields.Field()
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
- def __init__(self, *,
- id: base.String,
- photo_file_id: base.String,
- title: typing.Optional[base.String] = None,
- description: typing.Optional[base.String] = None,
- caption: typing.Optional[base.String] = None,
- parse_mode: typing.Optional[base.String] = None,
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
- input_message_content: typing.Optional[InputMessageContent] = None):
- super(InlineQueryResultCachedPhoto, self).__init__(id=id, photo_file_id=photo_file_id, title=title,
- description=description, caption=caption,
- parse_mode=parse_mode, reply_markup=reply_markup,
- input_message_content=input_message_content)
+ def __init__(
+ self,
+ *,
+ id: base.String,
+ photo_file_id: base.String,
+ title: typing.Optional[base.String] = None,
+ description: typing.Optional[base.String] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
+ input_message_content: typing.Optional[InputMessageContent] = None,
+ ):
+ super().__init__(
+ id=id, photo_file_id=photo_file_id, title=title, description=description,
+ caption=caption, parse_mode=parse_mode, caption_entities=caption_entities,
+ reply_markup=reply_markup, input_message_content=input_message_content,
+ )
class InlineQueryResultCachedGif(InlineQueryResult):
@@ -541,18 +612,23 @@ class InlineQueryResultCachedGif(InlineQueryResult):
caption: base.String = fields.Field()
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
- def __init__(self, *,
- id: base.String,
- gif_file_id: base.String,
- title: typing.Optional[base.String] = None,
- caption: typing.Optional[base.String] = None,
- parse_mode: typing.Optional[base.String] = None,
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
- input_message_content: typing.Optional[InputMessageContent] = None):
- super(InlineQueryResultCachedGif, self).__init__(id=id, gif_file_id=gif_file_id,
- title=title, caption=caption,
- parse_mode=parse_mode, reply_markup=reply_markup,
- input_message_content=input_message_content)
+ def __init__(
+ self,
+ *,
+ id: base.String,
+ gif_file_id: base.String,
+ title: typing.Optional[base.String] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
+ input_message_content: typing.Optional[InputMessageContent] = None,
+ ):
+ super().__init__(
+ id=id, gif_file_id=gif_file_id, title=title, caption=caption,
+ parse_mode=parse_mode, caption_entities=caption_entities,
+ reply_markup=reply_markup, input_message_content=input_message_content,
+ )
class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
@@ -571,18 +647,23 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult):
caption: base.String = fields.Field()
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
- def __init__(self, *,
- id: base.String,
- mpeg4_file_id: base.String,
- title: typing.Optional[base.String] = None,
- caption: typing.Optional[base.String] = None,
- parse_mode: typing.Optional[base.String] = None,
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
- input_message_content: typing.Optional[InputMessageContent] = None):
- super(InlineQueryResultCachedMpeg4Gif, self).__init__(id=id, mpeg4_file_id=mpeg4_file_id,
- title=title, caption=caption,
- parse_mode=parse_mode, reply_markup=reply_markup,
- input_message_content=input_message_content)
+ def __init__(
+ self,
+ *,
+ id: base.String,
+ mpeg4_file_id: base.String,
+ title: typing.Optional[base.String] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
+ input_message_content: typing.Optional[InputMessageContent] = None,
+ ):
+ super().__init__(
+ id=id, mpeg4_file_id=mpeg4_file_id, title=title, caption=caption,
+ parse_mode=parse_mode, caption_entities=caption_entities,
+ reply_markup=reply_markup, input_message_content=input_message_content,
+ )
class InlineQueryResultCachedSticker(InlineQueryResult):
@@ -631,20 +712,25 @@ class InlineQueryResultCachedDocument(InlineQueryResult):
caption: base.String = fields.Field()
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
- def __init__(self, *,
- id: base.String,
- title: base.String,
- document_file_id: base.String,
- description: typing.Optional[base.String] = None,
- caption: typing.Optional[base.String] = None,
- parse_mode: typing.Optional[base.String] = None,
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
- input_message_content: typing.Optional[InputMessageContent] = None):
- super(InlineQueryResultCachedDocument, self).__init__(id=id, title=title,
- document_file_id=document_file_id,
- description=description, caption=caption,
- parse_mode=parse_mode, reply_markup=reply_markup,
- input_message_content=input_message_content)
+ def __init__(
+ self,
+ *,
+ id: base.String,
+ title: base.String,
+ document_file_id: base.String,
+ description: typing.Optional[base.String] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
+ input_message_content: typing.Optional[InputMessageContent] = None,
+ ):
+ super().__init__(
+ id=id, title=title, document_file_id=document_file_id,
+ description=description, caption=caption, parse_mode=parse_mode,
+ caption_entities=caption_entities, reply_markup=reply_markup,
+ input_message_content=input_message_content,
+ )
class InlineQueryResultCachedVideo(InlineQueryResult):
@@ -664,19 +750,24 @@ class InlineQueryResultCachedVideo(InlineQueryResult):
caption: base.String = fields.Field()
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
- def __init__(self, *,
- id: base.String,
- video_file_id: base.String,
- title: base.String,
- description: typing.Optional[base.String] = None,
- caption: typing.Optional[base.String] = None,
- parse_mode: typing.Optional[base.String] = None,
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
- input_message_content: typing.Optional[InputMessageContent] = None):
- super(InlineQueryResultCachedVideo, self).__init__(id=id, video_file_id=video_file_id, title=title,
- description=description, caption=caption,
- parse_mode=parse_mode, reply_markup=reply_markup,
- input_message_content=input_message_content)
+ def __init__(
+ self,
+ *,
+ id: base.String,
+ video_file_id: base.String,
+ title: base.String,
+ description: typing.Optional[base.String] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
+ input_message_content: typing.Optional[InputMessageContent] = None,
+ ):
+ super().__init__(
+ id=id, video_file_id=video_file_id, title=title, description=description,
+ caption=caption, parse_mode=parse_mode, caption_entities=caption_entities,
+ reply_markup=reply_markup, input_message_content=input_message_content,
+ )
class InlineQueryResultCachedVoice(InlineQueryResult):
@@ -697,18 +788,23 @@ class InlineQueryResultCachedVoice(InlineQueryResult):
caption: base.String = fields.Field()
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
- def __init__(self, *,
- id: base.String,
- voice_file_id: base.String,
- title: base.String,
- caption: typing.Optional[base.String] = None,
- parse_mode: typing.Optional[base.String] = None,
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
- input_message_content: typing.Optional[InputMessageContent] = None):
- super(InlineQueryResultCachedVoice, self).__init__(id=id, voice_file_id=voice_file_id,
- title=title, caption=caption,
- parse_mode=parse_mode, reply_markup=reply_markup,
- input_message_content=input_message_content)
+ def __init__(
+ self,
+ *,
+ id: base.String,
+ voice_file_id: base.String,
+ title: base.String,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
+ input_message_content: typing.Optional[InputMessageContent] = None,
+ ):
+ super().__init__(
+ id=id, voice_file_id=voice_file_id, title=title, caption=caption,
+ parse_mode=parse_mode, caption_entities=caption_entities,
+ reply_markup=reply_markup, input_message_content=input_message_content,
+ )
class InlineQueryResultCachedAudio(InlineQueryResult):
@@ -729,14 +825,19 @@ class InlineQueryResultCachedAudio(InlineQueryResult):
caption: base.String = fields.Field()
input_message_content: InputMessageContent = fields.Field(base=InputMessageContent)
- def __init__(self, *,
- id: base.String,
- audio_file_id: base.String,
- caption: typing.Optional[base.String] = None,
- parse_mode: typing.Optional[base.String] = None,
- reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
- input_message_content: typing.Optional[InputMessageContent] = None):
- super(InlineQueryResultCachedAudio, self).__init__(id=id, audio_file_id=audio_file_id,
- caption=caption, parse_mode=parse_mode,
- reply_markup=reply_markup,
- input_message_content=input_message_content)
+ def __init__(
+ self,
+ *,
+ id: base.String,
+ audio_file_id: base.String,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
+ input_message_content: typing.Optional[InputMessageContent] = None,
+ ):
+ super().__init__(
+ id=id, audio_file_id=audio_file_id, caption=caption,
+ parse_mode=parse_mode, caption_entities=caption_entities,
+ reply_markup=reply_markup, input_message_content=input_message_content,
+ )
diff --git a/aiogram/types/input_file.py b/aiogram/types/input_file.py
index 3c397395..c974025a 100644
--- a/aiogram/types/input_file.py
+++ b/aiogram/types/input_file.py
@@ -4,6 +4,8 @@ import io
import logging
import os
import secrets
+from pathlib import Path
+from typing import Union
import aiohttp
@@ -25,7 +27,7 @@ class InputFile(base.TelegramObject):
https://core.telegram.org/bots/api#inputfile
"""
- def __init__(self, path_or_bytesio, filename=None, conf=None):
+ def __init__(self, path_or_bytesio: Union[str, io.IOBase, Path], filename=None, conf=None):
"""
:param path_or_bytesio:
@@ -39,12 +41,14 @@ class InputFile(base.TelegramObject):
self._path = path_or_bytesio
if filename is None:
filename = os.path.split(path_or_bytesio)[-1]
- elif isinstance(path_or_bytesio, io.IOBase):
- self._path = None
- self._file = path_or_bytesio
- elif isinstance(path_or_bytesio, _WebPipe):
+ elif isinstance(path_or_bytesio, (io.IOBase, _WebPipe)):
self._path = None
self._file = path_or_bytesio
+ elif isinstance(path_or_bytesio, Path):
+ self._file = path_or_bytesio.open("rb")
+ self._path = path_or_bytesio.resolve()
+ if filename is None:
+ filename = path_or_bytesio.name
else:
raise TypeError('Not supported file type.')
@@ -166,10 +170,7 @@ class _WebPipe:
def name(self):
if not self._name:
*_, part = self.url.rpartition('/')
- if part:
- self._name = part
- else:
- self._name = secrets.token_urlsafe(24)
+ self._name = part or secrets.token_urlsafe(24)
return self._name
async def open(self):
diff --git a/aiogram/types/input_media.py b/aiogram/types/input_media.py
index 9a77658f..6804b460 100644
--- a/aiogram/types/input_media.py
+++ b/aiogram/types/input_media.py
@@ -5,6 +5,7 @@ import typing
from . import base
from . import fields
from .input_file import InputFile
+from .message_entity import MessageEntity
ATTACHMENT_PREFIX = 'attach://'
@@ -27,6 +28,7 @@ class InputMedia(base.TelegramObject):
thumb: typing.Union[base.InputFile, base.String] = fields.Field(alias='thumb', on_change='_thumb_changed')
caption: base.String = fields.Field()
parse_mode: base.String = fields.Field()
+ caption_entities: typing.List[MessageEntity] = fields.ListField(base=MessageEntity)
def __init__(self, *args, **kwargs):
self._thumb_file = None
@@ -106,28 +108,48 @@ class InputMediaAnimation(InputMedia):
height: base.Integer = fields.Field()
duration: base.Integer = fields.Field()
- def __init__(self, media: base.InputFile,
- thumb: typing.Union[base.InputFile, base.String] = None,
- caption: base.String = None,
- width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None,
- parse_mode: base.String = None, **kwargs):
- super(InputMediaAnimation, self).__init__(type='animation', media=media, thumb=thumb, caption=caption,
- width=width, height=height, duration=duration,
- parse_mode=parse_mode, conf=kwargs)
+ def __init__(
+ self,
+ media: base.InputFile,
+ thumb: typing.Union[base.InputFile, base.String] = None,
+ caption: base.String = None,
+ width: base.Integer = None,
+ height: base.Integer = None,
+ duration: base.Integer = None,
+ parse_mode: base.String = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ **kwargs,
+ ):
+ super().__init__(
+ type='animation', media=media, thumb=thumb, caption=caption, width=width,
+ height=height, duration=duration, parse_mode=parse_mode,
+ caption_entities=caption_entities, conf=kwargs,
+ )
class InputMediaDocument(InputMedia):
"""
- Represents a photo to be sent.
+ Represents a general file to be sent.
https://core.telegram.org/bots/api#inputmediadocument
"""
- def __init__(self, media: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None,
- caption: base.String = None, parse_mode: base.String = None, **kwargs):
- super(InputMediaDocument, self).__init__(type='document', media=media, thumb=thumb,
- caption=caption, parse_mode=parse_mode,
- conf=kwargs)
+ def __init__(
+ self,
+ media: base.InputFile,
+ thumb: typing.Union[base.InputFile, base.String, None] = None,
+ caption: base.String = None,
+ parse_mode: base.String = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ disable_content_type_detection: typing.Optional[base.Boolean] = None,
+ **kwargs,
+ ):
+ super().__init__(
+ type='document', media=media, thumb=thumb, caption=caption,
+ parse_mode=parse_mode, caption_entities=caption_entities,
+ disable_content_type_detection=disable_content_type_detection,
+ conf=kwargs,
+ )
class InputMediaAudio(InputMedia):
@@ -141,17 +163,23 @@ class InputMediaAudio(InputMedia):
performer: base.String = fields.Field()
title: base.String = fields.Field()
- def __init__(self, media: base.InputFile,
- thumb: typing.Union[base.InputFile, base.String] = None,
- caption: base.String = None,
- duration: base.Integer = None,
- performer: base.String = None,
- title: base.String = None,
- parse_mode: base.String = None, **kwargs):
- super(InputMediaAudio, self).__init__(type='audio', media=media, thumb=thumb,
- caption=caption, duration=duration,
- performer=performer, title=title,
- parse_mode=parse_mode, conf=kwargs)
+ def __init__(
+ self,
+ media: base.InputFile,
+ thumb: typing.Union[base.InputFile, base.String] = None,
+ caption: base.String = None,
+ duration: base.Integer = None,
+ performer: base.String = None,
+ title: base.String = None,
+ parse_mode: base.String = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ **kwargs,
+ ):
+ super().__init__(
+ type='audio', media=media, thumb=thumb, caption=caption,
+ duration=duration, performer=performer, title=title,
+ parse_mode=parse_mode, caption_entities=caption_entities, conf=kwargs,
+ )
class InputMediaPhoto(InputMedia):
@@ -161,11 +189,18 @@ class InputMediaPhoto(InputMedia):
https://core.telegram.org/bots/api#inputmediaphoto
"""
- def __init__(self, media: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None,
- caption: base.String = None, parse_mode: base.String = None, **kwargs):
- super(InputMediaPhoto, self).__init__(type='photo', media=media, thumb=thumb,
- caption=caption, parse_mode=parse_mode,
- conf=kwargs)
+ def __init__(
+ self,
+ media: base.InputFile,
+ caption: base.String = None,
+ parse_mode: base.String = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ **kwargs,
+ ):
+ super().__init__(
+ type='photo', media=media, caption=caption, parse_mode=parse_mode,
+ caption_entities=caption_entities, conf=kwargs,
+ )
class InputMediaVideo(InputMedia):
@@ -179,16 +214,25 @@ class InputMediaVideo(InputMedia):
duration: base.Integer = fields.Field()
supports_streaming: base.Boolean = fields.Field()
- def __init__(self, media: base.InputFile,
- thumb: typing.Union[base.InputFile, base.String] = None,
- caption: base.String = None,
- width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None,
- parse_mode: base.String = None,
- supports_streaming: base.Boolean = None, **kwargs):
- super(InputMediaVideo, self).__init__(type='video', media=media, thumb=thumb, caption=caption,
- width=width, height=height, duration=duration,
- parse_mode=parse_mode,
- supports_streaming=supports_streaming, conf=kwargs)
+ def __init__(
+ self,
+ media: base.InputFile,
+ thumb: typing.Union[base.InputFile, base.String] = None,
+ caption: base.String = None,
+ width: base.Integer = None,
+ height: base.Integer = None,
+ duration: base.Integer = None,
+ parse_mode: base.String = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ supports_streaming: base.Boolean = None,
+ **kwargs,
+ ):
+ super().__init__(
+ type='video', media=media, thumb=thumb, caption=caption,
+ width=width, height=height, duration=duration,
+ parse_mode=parse_mode, caption_entities=caption_entities,
+ supports_streaming=supports_streaming, conf=kwargs
+ )
class MediaGroup(base.TelegramObject):
@@ -227,10 +271,10 @@ class MediaGroup(base.TelegramObject):
media = InputMediaPhoto(**media)
elif media_type == 'video':
media = InputMediaVideo(**media)
- # elif media_type == 'document':
- # media = InputMediaDocument(**media)
- # elif media_type == 'audio':
- # media = InputMediaAudio(**media)
+ elif media_type == 'document':
+ media = InputMediaDocument(**media)
+ elif media_type == 'audio':
+ media = InputMediaAudio(**media)
# elif media_type == 'animation':
# media = InputMediaAnimation(**media)
else:
@@ -239,8 +283,8 @@ class MediaGroup(base.TelegramObject):
elif not isinstance(media, InputMedia):
raise TypeError(f"Media must be an instance of InputMedia or dict, not {type(media).__name__}")
- elif media.type in ['document', 'audio', 'animation']:
- raise ValueError(f"This type of media is not supported by media groups!")
+ elif media.type == 'animation':
+ raise ValueError("This type of media is not supported by media groups!")
self.media.append(media)
@@ -266,78 +310,98 @@ class MediaGroup(base.TelegramObject):
width=width, height=height, duration=duration,
parse_mode=parse_mode)
self.attach(animation)
+ '''
- def attach_audio(self, audio: base.InputFile,
+ def attach_audio(self, audio: typing.Union[InputMediaAudio, base.InputFile],
thumb: typing.Union[base.InputFile, base.String] = None,
caption: base.String = None,
- width: base.Integer = None, height: base.Integer = None,
duration: base.Integer = None,
performer: base.String = None,
title: base.String = None,
- parse_mode: base.String = None):
+ parse_mode: base.String = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None):
"""
- Attach animation
+ Attach audio
:param audio:
:param thumb:
:param caption:
- :param width:
- :param height:
:param duration:
:param performer:
:param title:
:param parse_mode:
+ :param caption_entities:
"""
if not isinstance(audio, InputMedia):
audio = InputMediaAudio(media=audio, thumb=thumb, caption=caption,
- width=width, height=height, duration=duration,
+ duration=duration,
performer=performer, title=title,
- parse_mode=parse_mode)
+ parse_mode=parse_mode,
+ caption_entities=caption_entities)
self.attach(audio)
- def attach_document(self, document: base.InputFile, thumb: typing.Union[base.InputFile, base.String] = None,
- caption: base.String = None, parse_mode: base.String = None):
+ def attach_document(self, document: typing.Union[InputMediaDocument, base.InputFile],
+ thumb: typing.Union[base.InputFile, base.String] = None,
+ caption: base.String = None, parse_mode: base.String = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ disable_content_type_detection: typing.Optional[base.Boolean] = None):
"""
Attach document
-
- :param parse_mode:
+
+ :param document:
:param caption:
:param thumb:
- :param document:
+ :param parse_mode:
+ :param caption_entities:
+ :param disable_content_type_detection:
"""
if not isinstance(document, InputMedia):
- document = InputMediaDocument(media=document, thumb=thumb, caption=caption, parse_mode=parse_mode)
+ document = InputMediaDocument(media=document, thumb=thumb, caption=caption,
+ parse_mode=parse_mode, caption_entities=caption_entities,
+ disable_content_type_detection=disable_content_type_detection)
self.attach(document)
- '''
def attach_photo(self, photo: typing.Union[InputMediaPhoto, base.InputFile],
- caption: base.String = None):
+ caption: base.String = None, parse_mode: base.String = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None):
"""
Attach photo
:param photo:
:param caption:
+ :param parse_mode:
+ :param caption_entities:
"""
if not isinstance(photo, InputMedia):
- photo = InputMediaPhoto(media=photo, caption=caption)
+ photo = InputMediaPhoto(media=photo, caption=caption, parse_mode=parse_mode,
+ caption_entities=caption_entities)
self.attach(photo)
def attach_video(self, video: typing.Union[InputMediaVideo, base.InputFile],
thumb: typing.Union[base.InputFile, base.String] = None,
caption: base.String = None,
- width: base.Integer = None, height: base.Integer = None, duration: base.Integer = None):
+ width: base.Integer = None, height: base.Integer = None,
+ duration: base.Integer = None, parse_mode: base.String = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ supports_streaming: base.Boolean = None):
"""
Attach video
:param video:
+ :param thumb:
:param caption:
:param width:
:param height:
:param duration:
+ :param parse_mode:
+ :param caption_entities:
+ :param supports_streaming:
"""
if not isinstance(video, InputMedia):
video = InputMediaVideo(media=video, thumb=thumb, caption=caption,
- width=width, height=height, duration=duration)
+ width=width, height=height, duration=duration,
+ parse_mode=parse_mode, caption_entities=caption_entities,
+ supports_streaming=supports_streaming)
self.attach(video)
def to_python(self) -> typing.List:
diff --git a/aiogram/types/input_message_content.py b/aiogram/types/input_message_content.py
index 736a4454..f0c452cd 100644
--- a/aiogram/types/input_message_content.py
+++ b/aiogram/types/input_message_content.py
@@ -2,6 +2,9 @@ import typing
from . import base
from . import fields
+from .message_entity import MessageEntity
+from .labeled_price import LabeledPrice
+from ..utils.payload import generate_payload
class InputMessageContent(base.TelegramObject):
@@ -26,31 +29,112 @@ class InputContactMessageContent(InputMessageContent):
"""
phone_number: base.String = fields.Field()
first_name: base.String = fields.Field()
- last_name: base.String = fields.Field()
- vcard: base.String = fields.Field()
+ last_name: typing.Optional[base.String] = fields.Field()
+ vcard: typing.Optional[base.String] = fields.Field()
- def __init__(self, phone_number: base.String,
- first_name: typing.Optional[base.String] = None,
- last_name: typing.Optional[base.String] = None):
- super(InputContactMessageContent, self).__init__(phone_number=phone_number, first_name=first_name,
- last_name=last_name)
+ def __init__(self,
+ phone_number: base.String,
+ first_name: base.String = None,
+ last_name: typing.Optional[base.String] = None,
+ vcard: typing.Optional[base.String] = None,
+ ):
+ super().__init__(
+ phone_number=phone_number,
+ first_name=first_name,
+ last_name=last_name,
+ vcard=vcard
+ )
+
+
+class InputInvoiceMessageContent(InputMessageContent):
+ """
+ Represents the content of an invoice message to be sent as the
+ result of an inline query.
+
+ https://core.telegram.org/bots/api#inputinvoicemessagecontent
+ """
+
+ title: base.String = fields.Field()
+ description: base.String = fields.Field()
+ payload: base.String = fields.Field()
+ provider_token: base.String = fields.Field()
+ currency: base.String = fields.Field()
+ prices: typing.List[LabeledPrice] = fields.ListField(base=LabeledPrice)
+ max_tip_amount: typing.Optional[base.Integer] = fields.Field()
+ suggested_tip_amounts: typing.Optional[
+ typing.List[base.Integer]
+ ] = fields.ListField(base=base.Integer)
+ provider_data: typing.Optional[base.String] = fields.Field()
+ photo_url: typing.Optional[base.String] = fields.Field()
+ photo_size: typing.Optional[base.Integer] = fields.Field()
+ photo_width: typing.Optional[base.Integer] = fields.Field()
+ photo_height: typing.Optional[base.Integer] = fields.Field()
+ need_name: typing.Optional[base.Boolean] = fields.Field()
+ need_phone_number: typing.Optional[base.Boolean] = fields.Field()
+ need_email: typing.Optional[base.Boolean] = fields.Field()
+ need_shipping_address: typing.Optional[base.Boolean] = fields.Field()
+ send_phone_number_to_provider: typing.Optional[base.Boolean] = fields.Field()
+ send_email_to_provider: typing.Optional[base.Boolean] = fields.Field()
+ is_flexible: typing.Optional[base.Boolean] = fields.Field()
+
+ def __init__(
+ self,
+ title: base.String,
+ description: base.String,
+ payload: base.String,
+ provider_token: base.String,
+ currency: base.String,
+ prices: typing.List[LabeledPrice] = None,
+ max_tip_amount: typing.Optional[base.Integer] = None,
+ suggested_tip_amounts: typing.Optional[typing.List[base.Integer]] = None,
+ provider_data: typing.Optional[base.String] = None,
+ photo_url: typing.Optional[base.String] = None,
+ photo_size: typing.Optional[base.Integer] = None,
+ photo_width: typing.Optional[base.Integer] = None,
+ photo_height: typing.Optional[base.Integer] = None,
+ need_name: typing.Optional[base.Boolean] = None,
+ need_phone_number: typing.Optional[base.Boolean] = None,
+ need_email: typing.Optional[base.Boolean] = None,
+ need_shipping_address: typing.Optional[base.Boolean] = None,
+ send_phone_number_to_provider: typing.Optional[base.Boolean] = None,
+ send_email_to_provider: typing.Optional[base.Boolean] = None,
+ is_flexible: typing.Optional[base.Boolean] = None,
+ ):
+ if prices is None:
+ prices = []
+ payload = generate_payload(**locals())
+ super().__init__(**payload)
class InputLocationMessageContent(InputMessageContent):
"""
Represents the content of a location message to be sent as the result of an inline query.
- Note: This will only work in Telegram versions released after 9 April, 2016.
- Older clients will ignore them.
-
https://core.telegram.org/bots/api#inputlocationmessagecontent
"""
latitude: base.Float = fields.Field()
longitude: base.Float = fields.Field()
+ horizontal_accuracy: typing.Optional[base.Float] = fields.Field()
+ live_period: typing.Optional[base.Integer] = fields.Field()
+ heading: typing.Optional[base.Integer] = fields.Field()
+ proximity_alert_radius: typing.Optional[base.Integer] = fields.Field()
- def __init__(self, latitude: base.Float,
- longitude: base.Float):
- super(InputLocationMessageContent, self).__init__(latitude=latitude, longitude=longitude)
+ def __init__(self,
+ latitude: base.Float,
+ longitude: base.Float,
+ horizontal_accuracy: typing.Optional[base.Float] = None,
+ live_period: typing.Optional[base.Integer] = None,
+ heading: typing.Optional[base.Integer] = None,
+ proximity_alert_radius: typing.Optional[base.Integer] = None,
+ ):
+ super().__init__(
+ latitude=latitude,
+ longitude=longitude,
+ horizontal_accuracy=horizontal_accuracy,
+ live_period=live_period,
+ heading=heading,
+ proximity_alert_radius=proximity_alert_radius,
+ )
class InputTextMessageContent(InputMessageContent):
@@ -60,7 +144,8 @@ class InputTextMessageContent(InputMessageContent):
https://core.telegram.org/bots/api#inputtextmessagecontent
"""
message_text: base.String = fields.Field()
- parse_mode: base.String = fields.Field()
+ parse_mode: typing.Optional[base.String] = fields.Field()
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = fields.Field()
disable_web_page_preview: base.Boolean = fields.Field()
def safe_get_parse_mode(self):
@@ -69,14 +154,22 @@ class InputTextMessageContent(InputMessageContent):
except RuntimeError:
pass
- def __init__(self, message_text: typing.Optional[base.String] = None,
- parse_mode: typing.Optional[base.String] = None,
- disable_web_page_preview: typing.Optional[base.Boolean] = None):
+ def __init__(
+ self,
+ message_text: base.String,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ disable_web_page_preview: typing.Optional[base.Boolean] = None,
+ ):
if parse_mode is None:
parse_mode = self.safe_get_parse_mode()
- super(InputTextMessageContent, self).__init__(message_text=message_text, parse_mode=parse_mode,
- disable_web_page_preview=disable_web_page_preview)
+ super().__init__(
+ message_text=message_text,
+ parse_mode=parse_mode,
+ caption_entities=caption_entities,
+ disable_web_page_preview=disable_web_page_preview,
+ )
class InputVenueMessageContent(InputMessageContent):
@@ -92,12 +185,29 @@ class InputVenueMessageContent(InputMessageContent):
longitude: base.Float = fields.Field()
title: base.String = fields.Field()
address: base.String = fields.Field()
- foursquare_id: base.String = fields.Field()
+ foursquare_id: typing.Optional[base.String] = fields.Field()
+ foursquare_type: typing.Optional[base.String] = fields.Field()
+ google_place_id: typing.Optional[base.String] = fields.Field()
+ google_place_type: typing.Optional[base.String] = fields.Field()
- def __init__(self, latitude: typing.Optional[base.Float] = None,
- longitude: typing.Optional[base.Float] = None,
- title: typing.Optional[base.String] = None,
- address: typing.Optional[base.String] = None,
- foursquare_id: typing.Optional[base.String] = None):
- super(InputVenueMessageContent, self).__init__(latitude=latitude, longitude=longitude, title=title,
- address=address, foursquare_id=foursquare_id)
+ def __init__(
+ self,
+ latitude: base.Float,
+ longitude: base.Float,
+ title: base.String,
+ address: base.String,
+ foursquare_id: typing.Optional[base.String] = None,
+ foursquare_type: typing.Optional[base.String] = None,
+ google_place_id: typing.Optional[base.String] = None,
+ google_place_type: typing.Optional[base.String] = None,
+ ):
+ super().__init__(
+ latitude=latitude,
+ longitude=longitude,
+ title=title,
+ address=address,
+ foursquare_id=foursquare_id,
+ foursquare_type=foursquare_type,
+ google_place_id=google_place_id,
+ google_place_type=google_place_type,
+ )
diff --git a/aiogram/types/location.py b/aiogram/types/location.py
index ea2f81c4..5f159e33 100644
--- a/aiogram/types/location.py
+++ b/aiogram/types/location.py
@@ -1,3 +1,5 @@
+import typing
+
from . import base
from . import fields
@@ -10,3 +12,7 @@ class Location(base.TelegramObject):
"""
longitude: base.Float = fields.Field()
latitude: base.Float = fields.Field()
+ horizontal_accuracy: typing.Optional[base.Float] = fields.Field()
+ live_period: typing.Optional[base.Integer] = fields.Field()
+ heading: typing.Optional[base.Integer] = fields.Field()
+ proximity_alert_radius: typing.Optional[base.Integer] = fields.Field()
diff --git a/aiogram/types/message.py b/aiogram/types/message.py
index fedd656e..c95b14b1 100644
--- a/aiogram/types/message.py
+++ b/aiogram/types/message.py
@@ -4,9 +4,6 @@ import datetime
import functools
import typing
-from ..utils import helper
-from ..utils import markdown as md
-from ..utils.text_decorations import html_decoration, markdown_decoration
from . import base, fields
from .animation import Animation
from .audio import Audio
@@ -20,10 +17,13 @@ from .inline_keyboard import InlineKeyboardMarkup
from .input_media import InputMedia, MediaGroup
from .invoice import Invoice
from .location import Location
+from .message_auto_delete_timer_changed import MessageAutoDeleteTimerChanged
from .message_entity import MessageEntity
+from .message_id import MessageId
from .passport_data import PassportData
from .photo_size import PhotoSize
from .poll import Poll
+from .proximity_alert_triggered import ProximityAlertTriggered
from .reply_keyboard import ReplyKeyboardMarkup, ReplyKeyboardRemove
from .sticker import Sticker
from .successful_payment import SuccessfulPayment
@@ -32,6 +32,13 @@ from .venue import Venue
from .video import Video
from .video_note import VideoNote
from .voice import Voice
+from .voice_chat_ended import VoiceChatEnded
+from .voice_chat_participants_invited import VoiceChatParticipantsInvited
+from .voice_chat_scheduled import VoiceChatScheduled
+from .voice_chat_started import VoiceChatStarted
+from ..utils import helper
+from ..utils import markdown as md
+from ..utils.text_decorations import html_decoration, markdown_decoration
class Message(base.TelegramObject):
@@ -43,6 +50,7 @@ class Message(base.TelegramObject):
message_id: base.Integer = fields.Field()
from_user: User = fields.Field(alias="from", base=User)
+ sender_chat: Chat = fields.Field(base=Chat)
date: datetime.datetime = fields.DateTimeField()
chat: Chat = fields.Field(base=Chat)
forward_from: User = fields.Field(base=User)
@@ -82,6 +90,7 @@ class Message(base.TelegramObject):
group_chat_created: base.Boolean = fields.Field()
supergroup_chat_created: base.Boolean = fields.Field()
channel_chat_created: base.Boolean = fields.Field()
+ message_auto_delete_timer_changed: MessageAutoDeleteTimerChanged = fields.Field(base=MessageAutoDeleteTimerChanged)
migrate_to_chat_id: base.Integer = fields.Field()
migrate_from_chat_id: base.Integer = fields.Field()
pinned_message: Message = fields.Field(base="Message")
@@ -89,6 +98,11 @@ class Message(base.TelegramObject):
successful_payment: SuccessfulPayment = fields.Field(base=SuccessfulPayment)
connected_website: base.String = fields.Field()
passport_data: PassportData = fields.Field(base=PassportData)
+ proximity_alert_triggered: ProximityAlertTriggered = fields.Field(base=ProximityAlertTriggered)
+ voice_chat_scheduled: VoiceChatScheduled = fields.Field(base=VoiceChatScheduled)
+ voice_chat_started: VoiceChatStarted = fields.Field(base=VoiceChatStarted)
+ voice_chat_ended: VoiceChatEnded = fields.Field(base=VoiceChatEnded)
+ voice_chat_participants_invited: VoiceChatParticipantsInvited = fields.Field(base=VoiceChatParticipantsInvited)
reply_markup: InlineKeyboardMarkup = fields.Field(base=InlineKeyboardMarkup)
@property
@@ -134,6 +148,8 @@ class Message(base.TelegramObject):
return ContentType.SUCCESSFUL_PAYMENT
if self.connected_website:
return ContentType.CONNECTED_WEBSITE
+ if self.message_auto_delete_timer_changed:
+ return ContentType.MESSAGE_AUTO_DELETE_TIMER_CHANGED
if self.migrate_from_chat_id:
return ContentType.MIGRATE_FROM_CHAT_ID
if self.migrate_to_chat_id:
@@ -150,29 +166,50 @@ class Message(base.TelegramObject):
return ContentType.GROUP_CHAT_CREATED
if self.passport_data:
return ContentType.PASSPORT_DATA
+ if self.proximity_alert_triggered:
+ return ContentType.PROXIMITY_ALERT_TRIGGERED
+ if self.voice_chat_scheduled:
+ return ContentType.VOICE_CHAT_SCHEDULED
+ if self.voice_chat_started:
+ return ContentType.VOICE_CHAT_STARTED
+ if self.voice_chat_ended:
+ return ContentType.VOICE_CHAT_ENDED
+ if self.voice_chat_participants_invited:
+ return ContentType.VOICE_CHAT_PARTICIPANTS_INVITED
return ContentType.UNKNOWN
- def is_command(self):
+ def is_forward(self) -> bool:
+ """
+ Check that the message is forwarded.
+ Only `forward_date` is required to be in forwarded message.
+
+ :return: bool
+ """
+ return bool(self.forward_date)
+
+ def is_command(self) -> bool:
"""
Check message text is command
:return: bool
"""
- return self.text and self.text.startswith("/")
+ text = self.text or self.caption
+ return text and text.startswith("/")
- def get_full_command(self):
+ def get_full_command(self) -> typing.Optional[typing.Tuple[str, str]]:
"""
Split command and args
:return: tuple of (command, args)
"""
if self.is_command():
- command, *args = self.text.split(maxsplit=1)
- args = args[-1] if args else ""
+ text = self.text or self.caption
+ command, *args = text.split(maxsplit=1)
+ args = args[0] if args else ""
return command, args
- def get_command(self, pure=False):
+ def get_command(self, pure=False) -> typing.Optional[str]:
"""
Get command from message
@@ -185,7 +222,7 @@ class Message(base.TelegramObject):
command, _, _ = command[1:].partition("@")
return command
- def get_args(self):
+ def get_args(self) -> typing.Optional[str]:
"""
Get arguments
@@ -195,7 +232,7 @@ class Message(base.TelegramObject):
if command:
return command[1]
- def parse_entities(self, as_html=True):
+ def parse_entities(self, as_html=True) -> str:
"""
Text or caption formatted as HTML or Markdown.
@@ -236,9 +273,9 @@ class Message(base.TelegramObject):
:return: str
"""
- if ChatType.is_private(self.chat):
- raise TypeError("Invalid chat type!")
+ if self.chat.type == ChatType.PRIVATE:
+ raise TypeError("Invalid chat type!")
url = "https://t.me/"
if self.chat.username:
# Generates public link
@@ -272,9 +309,11 @@ class Message(base.TelegramObject):
async def answer(
self,
text: base.String,
- parse_mode: typing.Union[base.String, None] = None,
- disable_web_page_preview: typing.Union[base.Boolean, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ entities: typing.Optional[typing.List[MessageEntity]] = None,
+ disable_web_page_preview: typing.Optional[base.Boolean] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -289,19 +328,33 @@ class Message(base.TelegramObject):
:param text: Text of the message to be sent
:type text: :obj:`base.String`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
:param disable_web_page_preview: Disables link previews for links in this message
- :type disable_web_page_preview: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_web_page_preview: :obj:`typing.Optional[base.Boolean]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
:type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -309,18 +362,22 @@ class Message(base.TelegramObject):
chat_id=self.chat.id,
text=text,
parse_mode=parse_mode,
+ entities=entities,
disable_web_page_preview=disable_web_page_preview,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def answer_photo(
self,
photo: typing.Union[base.InputFile, base.String],
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -337,19 +394,33 @@ class Message(base.TelegramObject):
:param photo: Photo to send
:type photo: :obj:`typing.Union[base.InputFile, base.String]`
+
:param caption: Photo caption (may also be used when resending photos by file_id), 0-1024 characters
- :type caption: :obj:`typing.Union[base.String, None]`
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
:type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -358,21 +429,25 @@ class Message(base.TelegramObject):
photo=photo,
caption=caption,
parse_mode=parse_mode,
+ caption_entities=caption_entities,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def answer_audio(
self,
audio: typing.Union[base.InputFile, base.String],
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- duration: typing.Union[base.Integer, None] = None,
- performer: typing.Union[base.String, None] = None,
- title: typing.Union[base.String, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ duration: typing.Optional[base.Integer] = None,
+ performer: typing.Optional[base.String] = None,
+ title: typing.Optional[base.String] = None,
thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -392,27 +467,46 @@ class Message(base.TelegramObject):
:param audio: Audio file to send.
:type audio: :obj:`typing.Union[base.InputFile, base.String]`
- :param caption: Audio caption, 0-200 characters
- :type caption: :obj:`typing.Union[base.String, None]`
+
+ :param caption: Audio caption, 0-1024 characters after entities parsing
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
:param duration: Duration of the audio in seconds
- :type duration: :obj:`typing.Union[base.Integer, None]`
+ :type duration: :obj:`typing.Optional[base.Integer]`
+
:param performer: Performer
- :type performer: :obj:`typing.Union[base.String, None]`
+ :type performer: :obj:`typing.Optional[base.String]`
+
:param title: Track name
- :type title: :obj:`typing.Union[base.String, None]`
+ :type title: :obj:`typing.Optional[base.String]`
+
:param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail‘s width and height should not exceed 320.
:type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -421,25 +515,29 @@ class Message(base.TelegramObject):
audio=audio,
caption=caption,
parse_mode=parse_mode,
+ caption_entities=caption_entities,
duration=duration,
performer=performer,
title=title,
thumb=thumb,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def answer_animation(
self,
animation: typing.Union[base.InputFile, base.String],
- duration: typing.Union[base.Integer, None] = None,
- width: typing.Union[base.Integer, None] = None,
- height: typing.Union[base.Integer, None] = None,
+ duration: typing.Optional[base.Integer] = None,
+ width: typing.Optional[base.Integer] = None,
+ height: typing.Optional[base.Integer] = None,
thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None,
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -461,27 +559,46 @@ class Message(base.TelegramObject):
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
:type animation: :obj:`typing.Union[base.InputFile, base.String]`
+
:param duration: Duration of sent animation in seconds
- :type duration: :obj:`typing.Union[base.Integer, None]`
+ :type duration: :obj:`typing.Optional[base.Integer]`
+
:param width: Animation width
- :type width: :obj:`typing.Union[base.Integer, None]`
+ :type width: :obj:`typing.Optional[base.Integer]`
+
:param height: Animation height
- :type height: :obj:`typing.Union[base.Integer, None]`
+ :type height: :obj:`typing.Optional[base.Integer]`
+
:param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail‘s width and height should not exceed 320.
:type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]`
+
:param caption: Animation caption (may also be used when resending animation by file_id), 0-1024 characters
- :type caption: :obj:`typing.Union[base.String, None]`
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in the media caption
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove, types.ForceReply], None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -494,8 +611,10 @@ class Message(base.TelegramObject):
thumb=thumb,
caption=caption,
parse_mode=parse_mode,
+ caption_entities=caption_entities,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
@@ -503,9 +622,12 @@ class Message(base.TelegramObject):
self,
document: typing.Union[base.InputFile, base.String],
thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None,
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ disable_content_type_detection: typing.Optional[base.Boolean] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -516,30 +638,53 @@ class Message(base.TelegramObject):
reply: base.Boolean = False,
) -> Message:
"""
- Use this method to send general files.
-
- Bots can currently send files of any type of up to 50 MB in size, this limit may be changed in the future.
+ 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
- :param document: File to send.
+ :param document: File to send
:type document: :obj:`typing.Union[base.InputFile, base.String]`
- :param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
- A thumbnail‘s width and height should not exceed 320.
- :type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]`
- :param caption: Document caption (may also be used when resending documents by file_id), 0-200 characters
- :type caption: :obj:`typing.Union[base.String, None]`
- :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
- fixed-width text or inline URLs in the media caption
- :type parse_mode: :obj:`typing.Union[base.String, None]`
- :param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
- :param reply_markup: 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
+
+ :param thumb: Thumbnail of the file sent
+ :type thumb: :obj:`typing.Union[base.InputFile, base.String, None]`
+
+ :param caption: Document caption (may also be used when resending documents
+ by file_id), 0-1024 characters
+ :type caption: :obj:`typing.Optional[base.String]`
+
+ :param disable_content_type_detection: Disables automatic server-side content
+ type detection for files uploaded using multipart/form-data
+ :type disable_content_type_detection: :obj:`typing.Optional[base.Boolean]`
+
+ :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show
+ bold, italic, fixed-width text or inline URLs in your bot's message.
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
+ :param disable_notification: Sends the message silently. Users will receive a
+ notification with no sound
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply], None]`
- :param reply: fill 'reply_to_message_id'
- :return: On success, the sent Message is returned.
+ types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply],
+ None]`
+
+ :param reply: True if the message is a reply
+ :type reply: :obj:`typing.Optional[base.Boolean]`
+
+ :return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
return await self.bot.send_document(
@@ -548,21 +693,27 @@ class Message(base.TelegramObject):
document=document,
caption=caption,
parse_mode=parse_mode,
+ caption_entities=caption_entities,
+ disable_content_type_detection=disable_content_type_detection,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def answer_video(
self,
video: typing.Union[base.InputFile, base.String],
- duration: typing.Union[base.Integer, None] = None,
- width: typing.Union[base.Integer, None] = None,
- height: typing.Union[base.Integer, None] = None,
- thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None,
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ duration: typing.Optional[base.Integer] = None,
+ width: typing.Optional[base.Integer] = None,
+ height: typing.Optional[base.Integer] = None,
+ thumb: typing.Union[base.InputFile, base.String, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ supports_streaming: typing.Optional[base.Boolean] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -580,27 +731,49 @@ class Message(base.TelegramObject):
:param video: Video to send.
:type video: :obj:`typing.Union[base.InputFile, base.String]`
+
:param duration: Duration of sent video in seconds
- :type duration: :obj:`typing.Union[base.Integer, None]`
+ :type duration: :obj:`typing.Optional[base.Integer]`
+
:param width: Video width
- :type width: :obj:`typing.Union[base.Integer, None]`
+ :type width: :obj:`typing.Optional[base.Integer]`
+
:param height: Video height
- :type height: :obj:`typing.Union[base.Integer, None]`
+ :type height: :obj:`typing.Optional[base.Integer]`
+
:param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail‘s width and height should not exceed 320.
- :type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]`
- :param caption: Video caption (may also be used when resending videos by file_id), 0-200 characters
- :type caption: :obj:`typing.Union[base.String, None]`
+ :type thumb: :obj:`typing.Union[base.InputFile, base.String, None]`
+
+ :param caption: Video caption (may also be used when resending videos by file_id), 0-1024 characters after entities parsing
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in the media caption
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
+ :param supports_streaming: Pass True, if the uploaded video is suitable for streaming
+ :type supports_streaming: :obj:`typing.Optional[base.Boolean]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -613,18 +786,23 @@ class Message(base.TelegramObject):
thumb=thumb,
caption=caption,
parse_mode=parse_mode,
+ caption_entities=caption_entities,
+ supports_streaming=supports_streaming,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def answer_voice(
self,
voice: typing.Union[base.InputFile, base.String],
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- duration: typing.Union[base.Integer, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ duration: typing.Optional[base.Integer] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -645,20 +823,36 @@ class Message(base.TelegramObject):
:param voice: Audio file to send.
:type voice: :obj:`typing.Union[base.InputFile, base.String]`
- :param caption: Voice message caption, 0-200 characters
- :type caption: :obj:`typing.Union[base.String, None]`
+
+ :param caption: Voice message caption, 0-1024 characters after entities parsing
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in the media caption
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
:param duration: Duration of the voice message in seconds
- :type duration: :obj:`typing.Union[base.Integer, None]`
+ :type duration: :obj:`typing.Optional[base.Integer]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -667,19 +861,22 @@ class Message(base.TelegramObject):
voice=voice,
caption=caption,
parse_mode=parse_mode,
+ caption_entities=caption_entities,
duration=duration,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def answer_video_note(
self,
video_note: typing.Union[base.InputFile, base.String],
- duration: typing.Union[base.Integer, None] = None,
- length: typing.Union[base.Integer, None] = None,
+ duration: typing.Optional[base.Integer] = None,
+ length: typing.Optional[base.Integer] = None,
thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -697,20 +894,32 @@ class Message(base.TelegramObject):
:param video_note: Video note to send.
:type video_note: :obj:`typing.Union[base.InputFile, base.String]`
+
:param duration: Duration of sent video in seconds
- :type duration: :obj:`typing.Union[base.Integer, None]`
+ :type duration: :obj:`typing.Optional[base.Integer]`
+
:param length: Video width and height
- :type length: :obj:`typing.Union[base.Integer, None]`
+ :type length: :obj:`typing.Optional[base.Integer]`
+
:param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail‘s width and height should not exceed 320.
:type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -722,25 +931,39 @@ class Message(base.TelegramObject):
thumb=thumb,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def answer_media_group(
self,
media: typing.Union[MediaGroup, typing.List],
- disable_notification: typing.Union[base.Boolean, None] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply: base.Boolean = False,
) -> typing.List[Message]:
"""
- Use this method to send a group of photos or videos as an album.
+ Use this method to send a group of photos, videos, documents or audios as
+ an album. Documents and audio files can be only group in an album with
+ messages of the same type. On success, an array of Messages that were sent
+ is returned.
Source: https://core.telegram.org/bots/api#sendmediagroup
:param media: A JSON-serialized array describing photos and videos to be sent
:type media: :obj:`typing.Union[types.MediaGroup, typing.List]`
- :param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+
+ :param disable_notification: Sends the message silently. Users will receive
+ a notification with no sound.
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, an array of the sent Messages is returned.
:rtype: typing.List[types.Message]
"""
@@ -749,14 +972,19 @@ class Message(base.TelegramObject):
media=media,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
)
async def answer_location(
self,
latitude: base.Float,
longitude: base.Float,
- live_period: typing.Union[base.Integer, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ live_period: typing.Optional[base.Integer] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
+ horizontal_accuracy: typing.Optional[base.Float] = None,
+ heading: typing.Optional[base.Integer] = None,
+ proximity_alert_radius: typing.Optional[base.Integer] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -773,17 +1001,41 @@ class Message(base.TelegramObject):
:param latitude: Latitude of the location
:type latitude: :obj:`base.Float`
+
:param longitude: Longitude of the location
:type longitude: :obj:`base.Float`
+
+ :param horizontal_accuracy: The radius of uncertainty for the location,
+ measured in meters; 0-1500
+ :type horizontal_accuracy: :obj:`typing.Optional[base.Float]`
+
:param live_period: Period in seconds for which the location will be updated
- :type live_period: :obj:`typing.Union[base.Integer, None]`
+ :type live_period: :obj:`typing.Optional[base.Integer]`
+
+ :param heading: For live locations, a direction in which the user is moving,
+ in degrees. Must be between 1 and 360 if specified.
+ :type heading: :obj:`typing.Optional[base.Integer]`
+
+ :param proximity_alert_radius: For live locations, a maximum distance for
+ proximity alerts about approaching another chat member, in meters. Must
+ be between 1 and 100000 if specified.
+ :type proximity_alert_radius: :obj:`typing.Optional[base.Integer]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -791,9 +1043,13 @@ class Message(base.TelegramObject):
chat_id=self.chat.id,
latitude=latitude,
longitude=longitude,
+ horizontal_accuracy=horizontal_accuracy,
live_period=live_period,
+ heading=heading,
+ proximity_alert_radius=proximity_alert_radius,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
@@ -803,8 +1059,12 @@ class Message(base.TelegramObject):
longitude: base.Float,
title: base.String,
address: base.String,
- foursquare_id: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ foursquare_id: typing.Optional[base.String] = None,
+ foursquare_type: typing.Optional[base.String] = None,
+ google_place_id: typing.Optional[base.String] = None,
+ google_place_type: typing.Optional[base.String] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -821,21 +1081,47 @@ class Message(base.TelegramObject):
:param latitude: Latitude of the venue
:type latitude: :obj:`base.Float`
+
:param longitude: Longitude of the venue
:type longitude: :obj:`base.Float`
+
:param title: Name of the venue
:type title: :obj:`base.String`
+
:param address: Address of the venue
:type address: :obj:`base.String`
+
:param foursquare_id: Foursquare identifier of the venue
- :type foursquare_id: :obj:`typing.Union[base.String, None]`
- :param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
- :param reply_markup: 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
+ :type foursquare_id: :obj:`typing.Optional[base.String]`
+
+ :param foursquare_type: Foursquare type of the venue, if known
+ :type foursquare_type: :obj:`typing.Optional[base.String]`
+
+ :param google_place_id: Google Places identifier of the venue
+ :type google_place_id: :obj:`typing.Optional[base.String]`
+
+ :param google_place_type: Google Places type of the venue. See supported
+ types: https://developers.google.com/places/web-service/supported_types
+ :type google_place_type: :obj:`typing.Optional[base.String]`
+
+ :param disable_notification: Sends the message silently. Users will receive
+ a notification with no sound
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+ types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply,
+ None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -846,8 +1132,12 @@ class Message(base.TelegramObject):
title=title,
address=address,
foursquare_id=foursquare_id,
+ foursquare_type=foursquare_type,
+ google_place_id=google_place_id,
+ google_place_type=google_place_type,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
@@ -855,8 +1145,9 @@ class Message(base.TelegramObject):
self,
phone_number: base.String,
first_name: base.String,
- last_name: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ last_name: typing.Optional[base.String] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -873,17 +1164,28 @@ class Message(base.TelegramObject):
:param phone_number: Contact's phone number
:type phone_number: :obj:`base.String`
+
:param first_name: Contact's first name
:type first_name: :obj:`base.String`
+
:param last_name: Contact's last name
- :type last_name: :obj:`typing.Union[base.String, None]`
+ :type last_name: :obj:`typing.Optional[base.String]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -894,13 +1196,15 @@ class Message(base.TelegramObject):
last_name=last_name,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def answer_sticker(
self,
sticker: typing.Union[base.InputFile, base.String],
- disable_notification: typing.Union[base.Boolean, None] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -917,13 +1221,22 @@ class Message(base.TelegramObject):
:param sticker: Sticker to send.
:type sticker: :obj:`typing.Union[base.InputFile, base.String]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -932,6 +1245,7 @@ class Message(base.TelegramObject):
sticker=sticker,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
@@ -945,10 +1259,12 @@ class Message(base.TelegramObject):
correct_option_id: typing.Optional[base.Integer] = None,
explanation: typing.Optional[base.String] = None,
explanation_parse_mode: typing.Optional[base.String] = None,
- open_period: typing.Union[base.Integer, None] = None,
+ explanation_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ open_period: typing.Optional[base.Integer] = None,
close_date: typing.Union[base.Integer, datetime.datetime, datetime.timedelta, None] = None,
is_closed: typing.Optional[base.Boolean] = None,
disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -959,40 +1275,75 @@ class Message(base.TelegramObject):
reply: base.Boolean = False,
) -> Message:
"""
- Use this method to send a native poll. A native poll can't be sent to a private chat.
- On success, the sent Message is returned.
+ Use this method to send a native poll. On success, the sent Message is
+ returned.
Source: https://core.telegram.org/bots/api#sendpoll
:param question: Poll question, 1-255 characters
:type question: :obj:`base.String`
+
:param options: List of answer options, 2-10 strings 1-100 characters each
:type options: :obj:`typing.List[base.String]`
+
:param is_anonymous: True, if the poll needs to be anonymous, defaults to True
:type is_anonymous: :obj:`typing.Optional[base.Boolean]`
+
:param type: Poll type, “quiz” or “regular”, defaults to “regular”
:type type: :obj:`typing.Optional[base.String]`
- :param allows_multiple_answers: True, if the poll allows multiple answers, ignored for polls in quiz mode, defaults to False
+
+ :param allows_multiple_answers: True, if the poll allows multiple answers,
+ ignored for polls in quiz mode, defaults to False
:type allows_multiple_answers: :obj:`typing.Optional[base.Boolean]`
- :param correct_option_id: 0-based identifier of the correct answer option, required for polls in quiz mode
+
+ :param correct_option_id: 0-based identifier of the correct answer option,
+ required for polls in quiz mode
:type correct_option_id: :obj:`typing.Optional[base.Integer]`
- :param explanation: Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-200 characters with at most 2 line feeds after entities parsing
+
+ :param explanation: Text that is shown when a user chooses an incorrect
+ answer or taps on the lamp icon in a quiz-style poll, 0-200 characters
+ with at most 2 line feeds after entities parsing
:type explanation: :obj:`typing.Optional[base.String]`
- :param explanation_parse_mode: Mode for parsing entities in the explanation. See formatting options for more details.
+
+ :param explanation_parse_mode: Mode for parsing entities in the explanation.
+ See formatting options for more details.
:type explanation_parse_mode: :obj:`typing.Optional[base.String]`
- :param open_period: Amount of time in seconds the poll will be active after creation, 5-600. Can't be used together with close_date.
- :type open_period: :obj:`typing.Union[base.Integer, None]`
- :param close_date: Point in time (Unix timestamp) when the poll will be automatically closed. Must be at least 5 and no more than 600 seconds in the future. Can't be used together with open_period.
- :type close_date: :obj:`typing.Union[base.Integer, datetime.datetime, datetime.timedelta, None]`
+
+ :param explanation_entities: List of special entities that appear in message
+ text, which can be specified instead of parse_mode
+ :type explanation_entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
+ :param open_period: Amount of time in seconds the poll will be active after
+ creation, 5-600. Can't be used together with close_date.
+ :type open_period: :obj:`typing.Optional[base.Integer]`
+
+ :param close_date: Point in time (Unix timestamp) when the poll will be
+ automatically closed. Must be at least 5 and no more than 600 seconds in
+ the future. Can't be used together with open_period.
+ :type close_date: :obj:`typing.Union[base.Integer, datetime.datetime,
+ datetime.timedelta, None]`
+
:param is_closed: Pass True, if the poll needs to be immediately closed
:type is_closed: :obj:`typing.Optional[base.Boolean]`
- :param disable_notification: Sends the message silently. Users will receive a notification with no sound.
+
+ :param disable_notification: Sends the message silently. Users will receive
+ a notification with no sound.
:type disable_notification: :obj:`typing.Optional[Boolean]`
- :param reply_markup: 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
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+ types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply,
+ None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -1006,18 +1357,21 @@ class Message(base.TelegramObject):
correct_option_id=correct_option_id,
explanation=explanation,
explanation_parse_mode=explanation_parse_mode,
+ explanation_entities=explanation_entities,
open_period=open_period,
close_date=close_date,
is_closed=is_closed,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def answer_dice(
self,
- emoji: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ emoji: typing.Optional[base.String] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -1028,39 +1382,79 @@ class Message(base.TelegramObject):
reply: base.Boolean = False,
) -> Message:
"""
- Use this method to send a dice, which will have a random value from 1 to 6.
+ Use this method to send an animated emoji that will display a random value.
On success, the sent Message is returned.
- (Yes, we're aware of the “proper” singular of die.
- But it's awkward, and we decided to help it change. One dice at a time!)
Source: https://core.telegram.org/bots/api#senddice
- :param emoji: Emoji on which the dice throw animation is based. Currently, must be one of “🎲” or “🎯”. Defauts to “🎲”
- :type emoji: :obj:`typing.Union[base.String, None]`
- :param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
- :param reply_markup: 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
+ :param emoji: Emoji on which the dice throw animation is based. Currently,
+ must be one of “🎲”, “🎯”, “🏀”, “⚽”, or “🎰”. Dice can have values 1-6
+ for “🎲” and “🎯”, values 1-5 for “🏀” and “⚽”, and values 1-64 for “🎰”.
+ Defaults to “🎲”
+ :type emoji: :obj:`typing.Optional[base.String]`
+
+ :param disable_notification: Sends the message silently. Users will receive
+ a notification with no sound
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+ types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply,
+ None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
return await self.bot.send_dice(
chat_id=self.chat.id,
- disable_notification=disable_notification,
emoji=emoji,
+ disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
+ async def answer_chat_action(
+ self,
+ action: base.String,
+ ) -> base.Boolean:
+ """
+ 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).
+
+ 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
+
+ :param action: Type of action to broadcast
+ :type action: :obj:`base.String`
+ :return: Returns True on success
+ :rtype: :obj:`base.Boolean`
+ """
+ return await self.bot.send_chat_action(
+ chat_id=self.chat.id,
+ action=action,
+ )
+
async def reply(
self,
text: base.String,
- parse_mode: typing.Union[base.String, None] = None,
- disable_web_page_preview: typing.Union[base.Boolean, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ entities: typing.Optional[typing.List[MessageEntity]] = None,
+ disable_web_page_preview: typing.Optional[base.Boolean] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -1075,19 +1469,33 @@ class Message(base.TelegramObject):
:param text: Text of the message to be sent
:type text: :obj:`base.String`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
:param disable_web_page_preview: Disables link previews for links in this message
- :type disable_web_page_preview: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_web_page_preview: :obj:`typing.Optional[base.Boolean]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
:type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -1095,18 +1503,22 @@ class Message(base.TelegramObject):
chat_id=self.chat.id,
text=text,
parse_mode=parse_mode,
+ entities=entities,
disable_web_page_preview=disable_web_page_preview,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def reply_photo(
self,
photo: typing.Union[base.InputFile, base.String],
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -1123,19 +1535,33 @@ class Message(base.TelegramObject):
:param photo: Photo to send
:type photo: :obj:`typing.Union[base.InputFile, base.String]`
+
:param caption: Photo caption (may also be used when resending photos by file_id), 0-1024 characters
- :type caption: :obj:`typing.Union[base.String, None]`
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
:type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -1144,21 +1570,25 @@ class Message(base.TelegramObject):
photo=photo,
caption=caption,
parse_mode=parse_mode,
+ caption_entities=caption_entities,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def reply_audio(
self,
audio: typing.Union[base.InputFile, base.String],
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- duration: typing.Union[base.Integer, None] = None,
- performer: typing.Union[base.String, None] = None,
- title: typing.Union[base.String, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ duration: typing.Optional[base.Integer] = None,
+ performer: typing.Optional[base.String] = None,
+ title: typing.Optional[base.String] = None,
thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -1178,27 +1608,46 @@ class Message(base.TelegramObject):
:param audio: Audio file to send.
:type audio: :obj:`typing.Union[base.InputFile, base.String]`
- :param caption: Audio caption, 0-200 characters
- :type caption: :obj:`typing.Union[base.String, None]`
+
+ :param caption: Audio caption, 0-1024 characters after entities parsing
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
:param duration: Duration of the audio in seconds
- :type duration: :obj:`typing.Union[base.Integer, None]`
+ :type duration: :obj:`typing.Optional[base.Integer]`
+
:param performer: Performer
- :type performer: :obj:`typing.Union[base.String, None]`
+ :type performer: :obj:`typing.Optional[base.String]`
+
:param title: Track name
- :type title: :obj:`typing.Union[base.String, None]`
+ :type title: :obj:`typing.Optional[base.String]`
+
:param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail‘s width and height should not exceed 320.
:type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -1207,25 +1656,29 @@ class Message(base.TelegramObject):
audio=audio,
caption=caption,
parse_mode=parse_mode,
+ caption_entities=caption_entities,
duration=duration,
performer=performer,
title=title,
thumb=thumb,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def reply_animation(
self,
animation: typing.Union[base.InputFile, base.String],
- duration: typing.Union[base.Integer, None] = None,
- width: typing.Union[base.Integer, None] = None,
- height: typing.Union[base.Integer, None] = None,
+ duration: typing.Optional[base.Integer] = None,
+ width: typing.Optional[base.Integer] = None,
+ height: typing.Optional[base.Integer] = None,
thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None,
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -1247,27 +1700,46 @@ class Message(base.TelegramObject):
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
:type animation: :obj:`typing.Union[base.InputFile, base.String]`
+
:param duration: Duration of sent animation in seconds
- :type duration: :obj:`typing.Union[base.Integer, None]`
+ :type duration: :obj:`typing.Optional[base.Integer]`
+
:param width: Animation width
- :type width: :obj:`typing.Union[base.Integer, None]`
+ :type width: :obj:`typing.Optional[base.Integer]`
+
:param height: Animation height
- :type height: :obj:`typing.Union[base.Integer, None]`
+ :type height: :obj:`typing.Optional[base.Integer]`
+
:param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail‘s width and height should not exceed 320.
:type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]`
+
:param caption: Animation caption (may also be used when resending animation by file_id), 0-1024 characters
- :type caption: :obj:`typing.Union[base.String, None]`
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in the media caption
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[typing.Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup,
types.ReplyKeyboardRemove, types.ForceReply], None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -1280,8 +1752,10 @@ class Message(base.TelegramObject):
thumb=thumb,
caption=caption,
parse_mode=parse_mode,
+ caption_entities=caption_entities,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
@@ -1289,9 +1763,12 @@ class Message(base.TelegramObject):
self,
document: typing.Union[base.InputFile, base.String],
thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None,
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ disable_content_type_detection: typing.Optional[base.Boolean] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -1302,30 +1779,53 @@ class Message(base.TelegramObject):
reply: base.Boolean = True,
) -> Message:
"""
- Use this method to send general files.
-
- Bots can currently send files of any type of up to 50 MB in size, this limit may be changed in the future.
+ 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
- :param document: File to send.
+ :param document: File to send
:type document: :obj:`typing.Union[base.InputFile, base.String]`
- :param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
- A thumbnail‘s width and height should not exceed 320.
- :type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]`
- :param caption: Document caption (may also be used when resending documents by file_id), 0-200 characters
- :type caption: :obj:`typing.Union[base.String, None]`
- :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
- fixed-width text or inline URLs in the media caption
- :type parse_mode: :obj:`typing.Union[base.String, None]`
- :param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
- :param reply_markup: 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
+
+ :param thumb: Thumbnail of the file sent
+ :type thumb: :obj:`typing.Union[base.InputFile, base.String, None]`
+
+ :param caption: Document caption (may also be used when resending documents
+ by file_id), 0-1024 characters
+ :type caption: :obj:`typing.Optional[base.String]`
+
+ :param disable_content_type_detection: Disables automatic server-side content
+ type detection for files uploaded using multipart/form-data
+ :type disable_content_type_detection: :obj:`typing.Optional[base.Boolean]`
+
+ :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show
+ bold, italic, fixed-width text or inline URLs in your bot's message.
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
+ :param disable_notification: Sends the message silently. Users will receive a
+ notification with no sound
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply], None]`
- :param reply: fill 'reply_to_message_id'
- :return: On success, the sent Message is returned.
+ types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply],
+ None]`
+
+ :param reply: True if the message is a reply
+ :type reply: :obj:`typing.Optional[base.Boolean]`
+
+ :return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
return await self.bot.send_document(
@@ -1334,21 +1834,27 @@ class Message(base.TelegramObject):
thumb=thumb,
caption=caption,
parse_mode=parse_mode,
+ caption_entities=caption_entities,
+ disable_content_type_detection=disable_content_type_detection,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def reply_video(
self,
video: typing.Union[base.InputFile, base.String],
- duration: typing.Union[base.Integer, None] = None,
- width: typing.Union[base.Integer, None] = None,
- height: typing.Union[base.Integer, None] = None,
- thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None,
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ duration: typing.Optional[base.Integer] = None,
+ width: typing.Optional[base.Integer] = None,
+ height: typing.Optional[base.Integer] = None,
+ thumb: typing.Union[base.InputFile, base.String, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ supports_streaming: typing.Optional[base.Boolean] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -1366,27 +1872,49 @@ class Message(base.TelegramObject):
:param video: Video to send.
:type video: :obj:`typing.Union[base.InputFile, base.String]`
+
:param duration: Duration of sent video in seconds
- :type duration: :obj:`typing.Union[base.Integer, None]`
+ :type duration: :obj:`typing.Optional[base.Integer]`
+
:param width: Video width
- :type width: :obj:`typing.Union[base.Integer, None]`
+ :type width: :obj:`typing.Optional[base.Integer]`
+
:param height: Video height
- :type height: :obj:`typing.Union[base.Integer, None]`
+ :type height: :obj:`typing.Optional[base.Integer]`
+
:param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail‘s width and height should not exceed 320.
- :type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]`
- :param caption: Video caption (may also be used when resending videos by file_id), 0-200 characters
- :type caption: :obj:`typing.Union[base.String, None]`
+ :type thumb: :obj:`typing.Union[base.InputFile, base.String, None]`
+
+ :param caption: Video caption (may also be used when resending videos by file_id), 0-1024 characters after entities parsing
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in the media caption
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
+ :param supports_streaming: Pass True, if the uploaded video is suitable for streaming
+ :type supports_streaming: :obj:`typing.Optional[base.Boolean]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -1399,18 +1927,23 @@ class Message(base.TelegramObject):
thumb=thumb,
caption=caption,
parse_mode=parse_mode,
+ caption_entities=caption_entities,
+ supports_streaming=supports_streaming,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def reply_voice(
self,
voice: typing.Union[base.InputFile, base.String],
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- duration: typing.Union[base.Integer, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ duration: typing.Optional[base.Integer] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -1431,20 +1964,36 @@ class Message(base.TelegramObject):
:param voice: Audio file to send.
:type voice: :obj:`typing.Union[base.InputFile, base.String]`
- :param caption: Voice message caption, 0-200 characters
- :type caption: :obj:`typing.Union[base.String, None]`
+
+ :param caption: Voice message caption, 0-1024 characters after entities parsing
+ :type caption: :obj:`typing.Optional[base.String]`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in the media caption
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
:param duration: Duration of the voice message in seconds
- :type duration: :obj:`typing.Union[base.Integer, None]`
+ :type duration: :obj:`typing.Optional[base.Integer]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -1453,19 +2002,22 @@ class Message(base.TelegramObject):
voice=voice,
caption=caption,
parse_mode=parse_mode,
+ caption_entities=caption_entities,
duration=duration,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def reply_video_note(
self,
video_note: typing.Union[base.InputFile, base.String],
- duration: typing.Union[base.Integer, None] = None,
- length: typing.Union[base.Integer, None] = None,
+ duration: typing.Optional[base.Integer] = None,
+ length: typing.Optional[base.Integer] = None,
thumb: typing.Union[typing.Union[base.InputFile, base.String], None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -1483,20 +2035,32 @@ class Message(base.TelegramObject):
:param video_note: Video note to send.
:type video_note: :obj:`typing.Union[base.InputFile, base.String]`
+
:param duration: Duration of sent video in seconds
- :type duration: :obj:`typing.Union[base.Integer, None]`
+ :type duration: :obj:`typing.Optional[base.Integer]`
+
:param length: Video width and height
- :type length: :obj:`typing.Union[base.Integer, None]`
+ :type length: :obj:`typing.Optional[base.Integer]`
+
:param thumb: Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 kB in size.
A thumbnail‘s width and height should not exceed 320.
:type thumb: :obj:`typing.Union[typing.Union[base.InputFile, base.String], None]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+ types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]
+ `
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -1508,25 +2072,39 @@ class Message(base.TelegramObject):
thumb=thumb,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def reply_media_group(
self,
media: typing.Union[MediaGroup, typing.List],
- disable_notification: typing.Union[base.Boolean, None] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply: base.Boolean = True,
) -> typing.List[Message]:
"""
- Use this method to send a group of photos or videos as an album.
+ Use this method to send a group of photos, videos, documents or audios as
+ an album. Documents and audio files can be only group in an album with
+ messages of the same type. On success, an array of Messages that were sent
+ is returned.
Source: https://core.telegram.org/bots/api#sendmediagroup
:param media: A JSON-serialized array describing photos and videos to be sent
:type media: :obj:`typing.Union[types.MediaGroup, typing.List]`
- :param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+
+ :param disable_notification: Sends the message silently. Users will receive
+ a notification with no sound.
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, an array of the sent Messages is returned.
:rtype: typing.List[types.Message]
"""
@@ -1535,14 +2113,18 @@ class Message(base.TelegramObject):
media=media,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
)
async def reply_location(
self,
latitude: base.Float,
longitude: base.Float,
- live_period: typing.Union[base.Integer, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ live_period: typing.Optional[base.Integer] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ horizontal_accuracy: typing.Optional[base.Float] = None,
+ heading: typing.Optional[base.Integer] = None,
+ proximity_alert_radius: typing.Optional[base.Integer] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -1559,17 +2141,37 @@ class Message(base.TelegramObject):
:param latitude: Latitude of the location
:type latitude: :obj:`base.Float`
+
:param longitude: Longitude of the location
:type longitude: :obj:`base.Float`
+
+ :param horizontal_accuracy: The radius of uncertainty for the location,
+ measured in meters; 0-1500
+ :type horizontal_accuracy: :obj:`typing.Optional[base.Float]`
+
:param live_period: Period in seconds for which the location will be updated
- :type live_period: :obj:`typing.Union[base.Integer, None]`
+ :type live_period: :obj:`typing.Optional[base.Integer]`
+
+ :param heading: For live locations, a direction in which the user is moving,
+ in degrees. Must be between 1 and 360 if specified.
+ :type heading: :obj:`typing.Optional[base.Integer]`
+
+ :param proximity_alert_radius: For live locations, a maximum distance for
+ proximity alerts about approaching another chat member, in meters. Must
+ be between 1 and 100000 if specified.
+ :type proximity_alert_radius: :obj:`typing.Optional[base.Integer]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -1577,7 +2179,10 @@ class Message(base.TelegramObject):
chat_id=self.chat.id,
latitude=latitude,
longitude=longitude,
+ horizontal_accuracy=horizontal_accuracy,
live_period=live_period,
+ heading=heading,
+ proximity_alert_radius=proximity_alert_radius,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
reply_markup=reply_markup,
@@ -1589,8 +2194,12 @@ class Message(base.TelegramObject):
longitude: base.Float,
title: base.String,
address: base.String,
- foursquare_id: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ foursquare_id: typing.Optional[base.String] = None,
+ foursquare_type: typing.Optional[base.String] = None,
+ google_place_id: typing.Optional[base.String] = None,
+ google_place_type: typing.Optional[base.String] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -1607,21 +2216,47 @@ class Message(base.TelegramObject):
:param latitude: Latitude of the venue
:type latitude: :obj:`base.Float`
+
:param longitude: Longitude of the venue
:type longitude: :obj:`base.Float`
+
:param title: Name of the venue
:type title: :obj:`base.String`
+
:param address: Address of the venue
:type address: :obj:`base.String`
+
:param foursquare_id: Foursquare identifier of the venue
- :type foursquare_id: :obj:`typing.Union[base.String, None]`
- :param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
- :param reply_markup: 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
+ :type foursquare_id: :obj:`typing.Optional[base.String]`
+
+ :param foursquare_type: Foursquare type of the venue, if known
+ :type foursquare_type: :obj:`typing.Optional[base.String]`
+
+ :param google_place_id: Google Places identifier of the venue
+ :type google_place_id: :obj:`typing.Optional[base.String]`
+
+ :param google_place_type: Google Places type of the venue. See supported
+ types: https://developers.google.com/places/web-service/supported_types
+ :type google_place_type: :obj:`typing.Optional[base.String]`
+
+ :param disable_notification: Sends the message silently. Users will receive
+ a notification with no sound
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+ types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply,
+ None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -1632,8 +2267,12 @@ class Message(base.TelegramObject):
title=title,
address=address,
foursquare_id=foursquare_id,
+ foursquare_type=foursquare_type,
+ google_place_id=google_place_id,
+ google_place_type=google_place_type,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
@@ -1641,8 +2280,9 @@ class Message(base.TelegramObject):
self,
phone_number: base.String,
first_name: base.String,
- last_name: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ last_name: typing.Optional[base.String] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -1659,17 +2299,28 @@ class Message(base.TelegramObject):
:param phone_number: Contact's phone number
:type phone_number: :obj:`base.String`
+
:param first_name: Contact's first name
:type first_name: :obj:`base.String`
+
:param last_name: Contact's last name
- :type last_name: :obj:`typing.Union[base.String, None]`
+ :type last_name: :obj:`typing.Optional[base.String]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -1680,6 +2331,7 @@ class Message(base.TelegramObject):
last_name=last_name,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
@@ -1693,10 +2345,12 @@ class Message(base.TelegramObject):
correct_option_id: typing.Optional[base.Integer] = None,
explanation: typing.Optional[base.String] = None,
explanation_parse_mode: typing.Optional[base.String] = None,
- open_period: typing.Union[base.Integer, None] = None,
+ explanation_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ open_period: typing.Optional[base.Integer] = None,
close_date: typing.Union[base.Integer, datetime.datetime, datetime.timedelta, None] = None,
is_closed: typing.Optional[base.Boolean] = None,
disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -1707,40 +2361,75 @@ class Message(base.TelegramObject):
reply: base.Boolean = True,
) -> Message:
"""
- Use this method to send a native poll. A native poll can't be sent to a private chat.
- On success, the sent Message is returned.
+ Use this method to send a native poll. On success, the sent Message is
+ returned.
Source: https://core.telegram.org/bots/api#sendpoll
:param question: Poll question, 1-255 characters
:type question: :obj:`base.String`
+
:param options: List of answer options, 2-10 strings 1-100 characters each
:type options: :obj:`typing.List[base.String]`
+
:param is_anonymous: True, if the poll needs to be anonymous, defaults to True
:type is_anonymous: :obj:`typing.Optional[base.Boolean]`
+
:param type: Poll type, “quiz” or “regular”, defaults to “regular”
:type type: :obj:`typing.Optional[base.String]`
- :param allows_multiple_answers: True, if the poll allows multiple answers, ignored for polls in quiz mode, defaults to False
+
+ :param allows_multiple_answers: True, if the poll allows multiple answers,
+ ignored for polls in quiz mode, defaults to False
:type allows_multiple_answers: :obj:`typing.Optional[base.Boolean]`
- :param correct_option_id: 0-based identifier of the correct answer option, required for polls in quiz mode
+
+ :param correct_option_id: 0-based identifier of the correct answer option,
+ required for polls in quiz mode
:type correct_option_id: :obj:`typing.Optional[base.Integer]`
- :param explanation: Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll, 0-200 characters with at most 2 line feeds after entities parsing
+
+ :param explanation: Text that is shown when a user chooses an incorrect
+ answer or taps on the lamp icon in a quiz-style poll, 0-200 characters
+ with at most 2 line feeds after entities parsing
:type explanation: :obj:`typing.Optional[base.String]`
- :param explanation_parse_mode: Mode for parsing entities in the explanation. See formatting options for more details.
+
+ :param explanation_parse_mode: Mode for parsing entities in the explanation.
+ See formatting options for more details.
:type explanation_parse_mode: :obj:`typing.Optional[base.String]`
- :param open_period: Amount of time in seconds the poll will be active after creation, 5-600. Can't be used together with close_date.
- :type open_period: :obj:`typing.Union[base.Integer, None]`
- :param close_date: Point in time (Unix timestamp) when the poll will be automatically closed. Must be at least 5 and no more than 600 seconds in the future. Can't be used together with open_period.
- :type close_date: :obj:`typing.Union[base.Integer, datetime.datetime, datetime.timedelta, None]`
+
+ :param explanation_entities: List of special entities that appear in message
+ text, which can be specified instead of parse_mode
+ :type explanation_entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
+ :param open_period: Amount of time in seconds the poll will be active after
+ creation, 5-600. Can't be used together with close_date.
+ :type open_period: :obj:`typing.Optional[base.Integer]`
+
+ :param close_date: Point in time (Unix timestamp) when the poll will be
+ automatically closed. Must be at least 5 and no more than 600 seconds in
+ the future. Can't be used together with open_period.
+ :type close_date: :obj:`typing.Union[base.Integer, datetime.datetime,
+ datetime.timedelta, None]`
+
:param is_closed: Pass True, if the poll needs to be immediately closed
:type is_closed: :obj:`typing.Optional[base.Boolean]`
- :param disable_notification: Sends the message silently. Users will receive a notification with no sound.
+
+ :param disable_notification: Sends the message silently. Users will receive
+ a notification with no sound.
:type disable_notification: :obj:`typing.Optional[Boolean]`
- :param reply_markup: 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
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+ types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply,
+ None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -1754,18 +2443,21 @@ class Message(base.TelegramObject):
correct_option_id=correct_option_id,
explanation=explanation,
explanation_parse_mode=explanation_parse_mode,
+ explanation_entities=explanation_entities,
open_period=open_period,
close_date=close_date,
is_closed=is_closed,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def reply_sticker(
self,
sticker: typing.Union[base.InputFile, base.String],
- disable_notification: typing.Union[base.Boolean, None] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -1782,13 +2474,22 @@ class Message(base.TelegramObject):
:param sticker: Sticker to send.
:type sticker: :obj:`typing.Union[base.InputFile, base.String]`
+
:param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
@@ -1797,13 +2498,15 @@ class Message(base.TelegramObject):
sticker=sticker,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def reply_dice(
self,
- emoji: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
+ emoji: typing.Optional[base.String] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup,
ReplyKeyboardMarkup,
@@ -1814,36 +2517,51 @@ class Message(base.TelegramObject):
reply: base.Boolean = True,
) -> Message:
"""
- Use this method to send a dice, which will have a random value from 1 to 6.
+ Use this method to send an animated emoji that will display a random value.
On success, the sent Message is returned.
- (Yes, we're aware of the “proper” singular of die.
- But it's awkward, and we decided to help it change. One dice at a time!)
Source: https://core.telegram.org/bots/api#senddice
- :param emoji: Emoji on which the dice throw animation is based. Currently, must be one of “🎲” or “🎯”. Defauts to “🎲”
- :type emoji: :obj:`typing.Union[base.String, None]`
- :param disable_notification: Sends the message silently. Users will receive a notification with no sound.
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
- :param reply_markup: 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
+ :param emoji: Emoji on which the dice throw animation is based. Currently,
+ must be one of “🎲”, “🎯”, “🏀”, “⚽”, or “🎰”. Dice can have values 1-6
+ for “🎲” and “🎯”, values 1-5 for “🏀” and “⚽”, and values 1-64 for “🎰”.
+ Defaults to “🎲”
+ :type emoji: :obj:`typing.Optional[base.String]`
+
+ :param disable_notification: Sends the message silently. Users will receive
+ a notification with no sound
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
+ :param allow_sending_without_reply: Pass True, if the message should be sent
+ even if the specified replied-to message is not found
+ :type allow_sending_without_reply: :obj:`typing.Optional[base.Boolean]`
+
+ :param reply_markup: 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
:type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply, None]`
+ types.ReplyKeyboardMarkup, types.ReplyKeyboardRemove, types.ForceReply,
+ None]`
+
:param reply: fill 'reply_to_message_id'
+ :type reply: :obj:`base.Boolean`
+
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
return await self.bot.send_dice(
chat_id=self.chat.id,
+ emoji=emoji,
disable_notification=disable_notification,
reply_to_message_id=self.message_id if reply else None,
+ allow_sending_without_reply=allow_sending_without_reply,
reply_markup=reply_markup,
)
async def forward(
self,
chat_id: typing.Union[base.Integer, base.String],
- disable_notification: typing.Union[base.Boolean, None] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
) -> Message:
"""
Forward this message
@@ -1853,7 +2571,7 @@ class Message(base.TelegramObject):
:param chat_id: Unique identifier for the target chat or username of the target channel
:type chat_id: :obj:`typing.Union[base.Integer, base.String]`
:param disable_notification: Sends the message silently. Users will receive a notification with no sound
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
@@ -1864,9 +2582,10 @@ class Message(base.TelegramObject):
async def edit_text(
self,
text: base.String,
- parse_mode: typing.Union[base.String, None] = None,
- disable_web_page_preview: typing.Union[base.Boolean, None] = None,
- reply_markup: typing.Union[InlineKeyboardMarkup, None] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ entities: typing.Optional[typing.List[MessageEntity]] = None,
+ disable_web_page_preview: typing.Optional[base.Boolean] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
) -> typing.Union[Message, base.Boolean]:
"""
Use this method to edit text and game messages sent by the bot or via the bot (for inline bots).
@@ -1875,13 +2594,21 @@ class Message(base.TelegramObject):
:param text: New text of the message
:type text: :obj:`base.String`
+
:param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
:param disable_web_page_preview: Disables link previews for links in this message
- :type disable_web_page_preview: :obj:`typing.Union[base.Boolean, None]`
+ :type disable_web_page_preview: :obj:`typing.Optional[base.Boolean]`
+
:param reply_markup: A JSON-serialized object for an inline keyboard.
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
+ :type reply_markup: :obj:`typing.Optional[types.InlineKeyboardMarkup]`
+
:return: On success, if edited message is sent by the bot,
the edited Message is returned, otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
@@ -1891,6 +2618,7 @@ class Message(base.TelegramObject):
chat_id=self.chat.id,
message_id=self.message_id,
parse_mode=parse_mode,
+ entities=entities,
disable_web_page_preview=disable_web_page_preview,
reply_markup=reply_markup,
)
@@ -1898,23 +2626,32 @@ class Message(base.TelegramObject):
async def edit_caption(
self,
caption: base.String,
- parse_mode: typing.Union[base.String, None] = None,
- reply_markup: typing.Union[InlineKeyboardMarkup, None] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
) -> typing.Union[Message, base.Boolean]:
"""
- Use this method to edit captions of messages sent by the bot or via the bot (for inline bots).
+ Use this method to edit captions of messages sent by the bot or via the bot
+ (for inline bots).
Source: https://core.telegram.org/bots/api#editmessagecaption
:param caption: New caption of the message
- :type caption: :obj:`typing.Union[base.String, None]`
- :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show bold, italic,
- fixed-width text or inline URLs in your bot's message.
- :type parse_mode: :obj:`typing.Union[base.String, None]`
+ :type caption: :obj:`typing.Optional[base.String]`
+
+ :param parse_mode: Send Markdown or HTML, if you want Telegram apps to show
+ bold, italic, fixed-width text or inline URLs in your bot's message.
+ :type parse_mode: :obj:`typing.Optional[base.String]`
+
+ :param caption_entities: List of special entities that appear in message text,
+ which can be specified instead of parse_mode
+ :type caption_entities: :obj:`typing.Optional[typing.List[MessageEntity]]`
+
:param reply_markup: A JSON-serialized object for an inline keyboard
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
- :return: On success, if edited message is sent by the bot, the edited Message is returned,
- otherwise True is returned.
+ :type reply_markup: :obj:`typing.Optional[types.InlineKeyboardMarkup]`
+
+ :return: On success, if edited message is sent by the bot, the edited Message
+ is returned, otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
"""
return await self.bot.edit_message_caption(
@@ -1922,13 +2659,14 @@ class Message(base.TelegramObject):
message_id=self.message_id,
caption=caption,
parse_mode=parse_mode,
+ caption_entities=caption_entities,
reply_markup=reply_markup,
)
async def edit_media(
self,
media: InputMedia,
- reply_markup: typing.Union[InlineKeyboardMarkup, None] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
) -> typing.Union[Message, base.Boolean]:
"""
Use this method to edit audio, document, photo, or video messages.
@@ -1945,7 +2683,7 @@ class Message(base.TelegramObject):
:param media: A JSON-serialized object for a new media content of the message
:type media: :obj:`types.InputMedia`
:param reply_markup: A JSON-serialized object for a new inline keyboard
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
+ :type reply_markup: :obj:`typing.Optional[types.InlineKeyboardMarkup]`
:return: On success, if the edited message was sent by the bot, the edited Message is returned,
otherwise True is returned
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
@@ -1958,7 +2696,7 @@ class Message(base.TelegramObject):
)
async def edit_reply_markup(
- self, reply_markup: typing.Union[InlineKeyboardMarkup, None] = None
+ self, reply_markup: typing.Optional[InlineKeyboardMarkup] = None
) -> typing.Union[Message, base.Boolean]:
"""
Use this method to edit only the reply markup of messages sent by the bot or via the bot (for inline bots).
@@ -1966,7 +2704,7 @@ class Message(base.TelegramObject):
Source: https://core.telegram.org/bots/api#editmessagereplymarkup
:param reply_markup: A JSON-serialized object for an inline keyboard
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
+ :type reply_markup: :obj:`typing.Optional[types.InlineKeyboardMarkup]`
:return: On success, if edited message is sent by the bot, the edited Message is returned,
otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
@@ -1991,7 +2729,7 @@ class Message(base.TelegramObject):
self,
latitude: base.Float,
longitude: base.Float,
- reply_markup: typing.Union[InlineKeyboardMarkup, None] = None,
+ reply_markup: typing.Optional[InlineKeyboardMarkup] = None,
) -> typing.Union[Message, base.Boolean]:
"""
Use this method to edit live location messages sent by the bot or via the bot (for inline bots).
@@ -2005,7 +2743,7 @@ class Message(base.TelegramObject):
:param longitude: Longitude of new location
:type longitude: :obj:`base.Float`
:param reply_markup: A JSON-serialized object for a new inline keyboard.
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
+ :type reply_markup: :obj:`typing.Optional[types.InlineKeyboardMarkup]`
:return: On success, if the edited message was sent by the bot, the edited Message is returned,
otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
@@ -2019,7 +2757,7 @@ class Message(base.TelegramObject):
)
async def stop_live_location(
- self, reply_markup: typing.Union[InlineKeyboardMarkup, None] = None
+ self, reply_markup: typing.Optional[InlineKeyboardMarkup] = None
) -> typing.Union[Message, base.Boolean]:
"""
Use this method to stop updating a live location message sent by the bot or via the bot
@@ -2028,7 +2766,7 @@ class Message(base.TelegramObject):
Source: https://core.telegram.org/bots/api#stopmessagelivelocation
:param reply_markup: A JSON-serialized object for a new inline keyboard.
- :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]`
+ :type reply_markup: :obj:`typing.Optional[types.InlineKeyboardMarkup]`
:return: On success, if the message was sent by the bot, the sent Message is returned,
otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
@@ -2055,27 +2793,50 @@ class Message(base.TelegramObject):
return await self.bot.delete_message(self.chat.id, self.message_id)
async def pin(
- self, disable_notification: typing.Union[base.Boolean, None] = None
+ self, disable_notification: typing.Optional[base.Boolean] = None,
) -> base.Boolean:
"""
- Use this method to pin a message in a supergroup.
- The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
+ Use this method to add a message to the list of pinned messages in a chat.
+ If the chat is not a private chat, the bot must be an administrator in the
+ chat for this to work and must have the 'can_pin_messages' admin right in a
+ supergroup or 'can_edit_messages' admin right in a channel. Returns True on
+ success.
Source: https://core.telegram.org/bots/api#pinchatmessage
- :param disable_notification: Pass True, if it is not necessary to send a notification to
- all group members about the new pinned message
- :type disable_notification: :obj:`typing.Union[base.Boolean, None]`
+ :param disable_notification: Pass True, if it is not necessary to send a
+ notification to all group members about the new pinned message
+ :type disable_notification: :obj:`typing.Optional[base.Boolean]`
+
:return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
return await self.chat.pin_message(self.message_id, disable_notification)
+ async def unpin(self) -> base.Boolean:
+ """
+ Use this method to remove a message from the list of pinned messages in a
+ chat. If the chat is not a private chat, the bot must be an administrator in
+ the chat for this to work and must have the 'can_pin_messages' admin right in
+ a supergroup or 'can_edit_messages' admin right in a channel. Returns True on
+ success.
+
+ Source: https://core.telegram.org/bots/api#unpinchatmessage
+
+ :return: Returns True on success
+ :rtype: :obj:`base.Boolean`
+ """
+ return await self.chat.unpin_message(
+ message_id=self.message_id,
+ )
+
async def send_copy(
self: Message,
chat_id: typing.Union[str, int],
disable_notification: typing.Optional[bool] = None,
+ disable_web_page_preview: typing.Optional[bool] = None,
reply_to_message_id: typing.Optional[int] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
reply_markup: typing.Union[
InlineKeyboardMarkup, ReplyKeyboardMarkup, None
] = None,
@@ -2085,12 +2846,15 @@ class Message(base.TelegramObject):
:param chat_id:
:param disable_notification:
+ :param disable_web_page_preview: for text messages only
:param reply_to_message_id:
+ :param allow_sending_without_reply:
:param reply_markup:
:return:
"""
kwargs = {
"chat_id": chat_id,
+ "allow_sending_without_reply": allow_sending_without_reply,
"reply_markup": reply_markup or self.reply_markup,
"parse_mode": ParseMode.HTML,
"disable_notification": disable_notification,
@@ -2099,6 +2863,7 @@ class Message(base.TelegramObject):
text = self.html_text if (self.text or self.caption) else None
if self.text:
+ kwargs["disable_web_page_preview"] = disable_web_page_preview
return await self.bot.send_message(text=text, **kwargs)
elif self.audio:
return await self.bot.send_audio(
@@ -2167,11 +2932,46 @@ class Message(base.TelegramObject):
return await self.bot.send_poll(
question=self.poll.question,
options=[option.text for option in self.poll.options],
+ is_anonymous=self.poll.is_anonymous,
+ allows_multiple_answers=self.poll.allows_multiple_answers,
+ **kwargs,
+ )
+ elif self.dice:
+ kwargs.pop("parse_mode")
+ return await self.bot.send_dice(
+ emoji=self.dice.emoji,
**kwargs,
)
else:
raise TypeError("This type of message can't be copied.")
+ async def copy_to(
+ self,
+ chat_id: typing.Union[base.Integer, base.String],
+ caption: typing.Optional[base.String] = None,
+ parse_mode: typing.Optional[base.String] = None,
+ caption_entities: typing.Optional[typing.List[MessageEntity]] = None,
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ allow_sending_without_reply: typing.Optional[base.Boolean] = None,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ ) -> MessageId:
+ return await self.bot.copy_message(
+ chat_id=chat_id,
+ from_chat_id=self.chat.id,
+ message_id=self.message_id,
+ caption=caption,
+ parse_mode=parse_mode,
+ caption_entities=caption_entities,
+ disable_notification=disable_notification,
+ reply_to_message_id=reply_to_message_id,
+ allow_sending_without_reply=allow_sending_without_reply,
+ reply_markup=reply_markup
+ )
+
def __int__(self):
return self.message_id
@@ -2194,6 +2994,8 @@ class ContentType(helper.Helper):
:key: CONTACT
:key: LOCATION
:key: VENUE
+ :key: POLL
+ :key: DICE
:key: NEW_CHAT_MEMBERS
:key: LEFT_CHAT_MEMBER
:key: INVOICE
@@ -2227,6 +3029,7 @@ class ContentType(helper.Helper):
INVOICE = helper.Item() # invoice
SUCCESSFUL_PAYMENT = helper.Item() # successful_payment
CONNECTED_WEBSITE = helper.Item() # connected_website
+ MESSAGE_AUTO_DELETE_TIMER_CHANGED = helper.Item() # message_auto_delete_timer_changed
MIGRATE_TO_CHAT_ID = helper.Item() # migrate_to_chat_id
MIGRATE_FROM_CHAT_ID = helper.Item() # migrate_from_chat_id
PINNED_MESSAGE = helper.Item() # pinned_message
@@ -2235,6 +3038,11 @@ class ContentType(helper.Helper):
DELETE_CHAT_PHOTO = helper.Item() # delete_chat_photo
GROUP_CHAT_CREATED = helper.Item() # group_chat_created
PASSPORT_DATA = helper.Item() # passport_data
+ PROXIMITY_ALERT_TRIGGERED = helper.Item() # proximity_alert_triggered
+ VOICE_CHAT_SCHEDULED = helper.Item() # voice_chat_scheduled
+ VOICE_CHAT_STARTED = helper.Item() # voice_chat_started
+ VOICE_CHAT_ENDED = helper.Item() # voice_chat_ended
+ VOICE_CHAT_PARTICIPANTS_INVITED = helper.Item() # voice_chat_participants_invited
UNKNOWN = helper.Item() # unknown
ANY = helper.Item() # any
@@ -2258,6 +3066,8 @@ class ContentTypes(helper.Helper):
:key: CONTACT
:key: LOCATION
:key: VENUE
+ :key: POLL
+ :key: DICE
:key: NEW_CHAT_MEMBERS
:key: LEFT_CHAT_MEMBER
:key: INVOICE
@@ -2284,6 +3094,8 @@ class ContentTypes(helper.Helper):
CONTACT = helper.ListItem() # contact
LOCATION = helper.ListItem() # location
VENUE = helper.ListItem() # venue
+ POLL = helper.ListItem() # poll
+ DICE = helper.ListItem() # dice
NEW_CHAT_MEMBERS = helper.ListItem() # new_chat_member
LEFT_CHAT_MEMBER = helper.ListItem() # left_chat_member
INVOICE = helper.ListItem() # invoice
@@ -2297,7 +3109,6 @@ class ContentTypes(helper.Helper):
DELETE_CHAT_PHOTO = helper.ListItem() # delete_chat_photo
GROUP_CHAT_CREATED = helper.ListItem() # group_chat_created
PASSPORT_DATA = helper.ListItem() # passport_data
- POLL = helper.ListItem()
UNKNOWN = helper.ListItem() # unknown
ANY = helper.ListItem() # any
diff --git a/aiogram/types/message_auto_delete_timer_changed.py b/aiogram/types/message_auto_delete_timer_changed.py
new file mode 100644
index 00000000..8b882d1b
--- /dev/null
+++ b/aiogram/types/message_auto_delete_timer_changed.py
@@ -0,0 +1,11 @@
+from . import base
+from . import fields
+
+
+class MessageAutoDeleteTimerChanged(base.TelegramObject):
+ """
+ This object represents a service message about a change in auto-delete timer settings.
+
+ https://core.telegram.org/bots/api#messageautodeletetimerchanged
+ """
+ message_auto_delete_time: base.Integer = fields.Field()
diff --git a/aiogram/types/message_entity.py b/aiogram/types/message_entity.py
index 77b23c5c..9ee98c11 100644
--- a/aiogram/types/message_entity.py
+++ b/aiogram/types/message_entity.py
@@ -1,10 +1,9 @@
import sys
-from . import base
-from . import fields
-from .user import User
from ..utils import helper, markdown
from ..utils.deprecated import deprecated
+from . import base, fields
+from .user import User
class MessageEntity(base.TelegramObject):
@@ -13,6 +12,7 @@ class MessageEntity(base.TelegramObject):
https://core.telegram.org/bots/api#messageentity
"""
+
type: base.String = fields.Field()
offset: base.Integer = fields.Field()
length: base.Integer = fields.Field()
@@ -20,6 +20,26 @@ class MessageEntity(base.TelegramObject):
user: User = fields.Field(base=User)
language: base.String = fields.Field()
+ def __init__(
+ self,
+ type: base.String,
+ offset: base.Integer,
+ length: base.Integer,
+ url: base.String = None,
+ user: User = None,
+ language: base.String = None,
+ **kwargs
+ ):
+ super().__init__(
+ type=type,
+ offset=offset,
+ length=length,
+ url=url,
+ user=user,
+ language=language,
+ **kwargs
+ )
+
def get_text(self, text):
"""
Get value of entity
@@ -27,18 +47,18 @@ class MessageEntity(base.TelegramObject):
:param text: full text
:return: part of text
"""
- if sys.maxunicode == 0xffff:
- return text[self.offset:self.offset + self.length]
+ if sys.maxunicode == 0xFFFF:
+ return text[self.offset : self.offset + self.length]
- if not isinstance(text, bytes):
- entity_text = text.encode('utf-16-le')
- else:
- entity_text = text
+ entity_text = (
+ text.encode("utf-16-le") if not isinstance(text, bytes) else text
+ )
+ entity_text = entity_text[self.offset * 2 : (self.offset + self.length) * 2]
+ return entity_text.decode("utf-16-le")
- entity_text = entity_text[self.offset * 2:(self.offset + self.length) * 2]
- return entity_text.decode('utf-16-le')
-
- @deprecated("This method doesn't work with nested entities and will be removed in aiogram 3.0")
+ @deprecated(
+ "This method doesn't work with nested entities and will be removed in aiogram 3.0"
+ )
def parse(self, text, as_html=True):
"""
Get entity value with markup
@@ -95,6 +115,7 @@ class MessageEntityType(helper.Helper):
:key: TEXT_LINK
:key: TEXT_MENTION
"""
+
mode = helper.HelperMode.snake_case
MENTION = helper.Item() # mention - @username
diff --git a/aiogram/types/message_id.py b/aiogram/types/message_id.py
new file mode 100644
index 00000000..0157f5e7
--- /dev/null
+++ b/aiogram/types/message_id.py
@@ -0,0 +1,10 @@
+from . import base, fields
+
+
+class MessageId(base.TelegramObject):
+ """
+ This object represents a unique message identifier.
+
+ https://core.telegram.org/bots/api#messageid
+ """
+ message_id: base.String = fields.Field()
diff --git a/aiogram/types/proximity_alert_triggered.py b/aiogram/types/proximity_alert_triggered.py
new file mode 100644
index 00000000..240854d8
--- /dev/null
+++ b/aiogram/types/proximity_alert_triggered.py
@@ -0,0 +1,15 @@
+from . import base
+from . import fields
+from .user import User
+
+
+class ProximityAlertTriggered(base.TelegramObject):
+ """
+ This object represents the content of a service message, sent whenever a user in
+ the chat triggers a proximity alert set by another user.
+
+ https://core.telegram.org/bots/api#proximityalerttriggered
+ """
+ traveler: User = fields.Field()
+ watcher: User = fields.Field()
+ distance: base.Integer = fields.Field()
diff --git a/aiogram/types/reply_keyboard.py b/aiogram/types/reply_keyboard.py
index ffe07ae1..8455aff6 100644
--- a/aiogram/types/reply_keyboard.py
+++ b/aiogram/types/reply_keyboard.py
@@ -18,23 +18,32 @@ class KeyboardButtonPollType(base.TelegramObject):
class ReplyKeyboardMarkup(base.TelegramObject):
"""
- This object represents a custom keyboard with reply options (see Introduction to bots for details and examples).
+ This object represents a custom keyboard with reply options
+ (see https://core.telegram.org/bots#keyboards to bots for details
+ and examples).
https://core.telegram.org/bots/api#replykeyboardmarkup
"""
keyboard: 'typing.List[typing.List[KeyboardButton]]' = fields.ListOfLists(base='KeyboardButton', default=[])
resize_keyboard: base.Boolean = fields.Field()
one_time_keyboard: base.Boolean = fields.Field()
+ input_field_placeholder: base.String = fields.Field()
selective: base.Boolean = fields.Field()
def __init__(self, keyboard: 'typing.List[typing.List[KeyboardButton]]' = None,
resize_keyboard: base.Boolean = None,
one_time_keyboard: base.Boolean = None,
+ input_field_placeholder: base.String = None,
selective: base.Boolean = None,
row_width: base.Integer = 3):
- super(ReplyKeyboardMarkup, self).__init__(keyboard=keyboard, resize_keyboard=resize_keyboard,
- one_time_keyboard=one_time_keyboard, selective=selective,
- conf={'row_width': row_width})
+ super().__init__(
+ keyboard=keyboard,
+ resize_keyboard=resize_keyboard,
+ one_time_keyboard=one_time_keyboard,
+ input_field_placeholder=input_field_placeholder,
+ selective=selective,
+ conf={'row_width': row_width},
+ )
@property
def row_width(self):
@@ -58,7 +67,7 @@ class ReplyKeyboardMarkup(base.TelegramObject):
if index % self.row_width == 0:
self.keyboard.append(row)
row = []
- if len(row) > 0:
+ if row:
self.keyboard.append(row)
return self
@@ -70,9 +79,7 @@ class ReplyKeyboardMarkup(base.TelegramObject):
:return: self
:rtype: :obj:`types.ReplyKeyboardMarkup`
"""
- btn_array = []
- for button in args:
- btn_array.append(button)
+ btn_array = [button for button in args]
self.keyboard.append(btn_array)
return self
diff --git a/aiogram/types/update.py b/aiogram/types/update.py
index 2146cb9d..e2fd3a55 100644
--- a/aiogram/types/update.py
+++ b/aiogram/types/update.py
@@ -3,13 +3,14 @@ from __future__ import annotations
from . import base
from . import fields
from .callback_query import CallbackQuery
+from .chat_member_updated import ChatMemberUpdated
from .chosen_inline_result import ChosenInlineResult
from .inline_query import InlineQuery
from .message import Message
from .poll import Poll, PollAnswer
from .pre_checkout_query import PreCheckoutQuery
from .shipping_query import ShippingQuery
-from ..utils import helper
+from ..utils import helper, deprecated
class Update(base.TelegramObject):
@@ -31,6 +32,8 @@ class Update(base.TelegramObject):
pre_checkout_query: PreCheckoutQuery = fields.Field(base=PreCheckoutQuery)
poll: Poll = fields.Field(base=Poll)
poll_answer: PollAnswer = fields.Field(base=PollAnswer)
+ my_chat_member: ChatMemberUpdated = fields.Field(base=ChatMemberUpdated)
+ chat_member: ChatMemberUpdated = fields.Field(base=ChatMemberUpdated)
def __hash__(self):
return self.update_id
@@ -55,9 +58,21 @@ class AllowedUpdates(helper.Helper):
CHANNEL_POST = helper.ListItem() # channel_post
EDITED_CHANNEL_POST = helper.ListItem() # edited_channel_post
INLINE_QUERY = helper.ListItem() # inline_query
- CHOSEN_INLINE_QUERY = helper.ListItem() # chosen_inline_result
+ CHOSEN_INLINE_RESULT = helper.ListItem() # chosen_inline_result
CALLBACK_QUERY = helper.ListItem() # callback_query
SHIPPING_QUERY = helper.ListItem() # shipping_query
PRE_CHECKOUT_QUERY = helper.ListItem() # pre_checkout_query
POLL = helper.ListItem() # poll
POLL_ANSWER = helper.ListItem() # poll_answer
+ MY_CHAT_MEMBER = helper.ListItem() # my_chat_member
+ CHAT_MEMBER = helper.ListItem() # chat_member
+
+ CHOSEN_INLINE_QUERY = deprecated.DeprecatedReadOnlyClassVar(
+ "`CHOSEN_INLINE_QUERY` is a deprecated value for allowed update. "
+ "Use `CHOSEN_INLINE_RESULT`",
+ new_value_getter=lambda cls: cls.CHOSEN_INLINE_RESULT,
+ )
+
+ @classmethod
+ def default(cls):
+ return []
diff --git a/aiogram/types/venue.py b/aiogram/types/venue.py
index f7b2a277..b851650b 100644
--- a/aiogram/types/venue.py
+++ b/aiogram/types/venue.py
@@ -14,3 +14,5 @@ class Venue(base.TelegramObject):
address: base.String = fields.Field()
foursquare_id: base.String = fields.Field()
foursquare_type: base.String = fields.Field()
+ google_place_id: base.String = fields.Field()
+ google_place_type: base.String = fields.Field()
diff --git a/aiogram/types/video.py b/aiogram/types/video.py
index 97dbb90f..d4958761 100644
--- a/aiogram/types/video.py
+++ b/aiogram/types/video.py
@@ -16,5 +16,6 @@ class Video(base.TelegramObject, mixins.Downloadable):
height: base.Integer = fields.Field()
duration: base.Integer = fields.Field()
thumb: PhotoSize = fields.Field(base=PhotoSize)
+ file_name: base.String = fields.Field()
mime_type: base.String = fields.Field()
file_size: base.Integer = fields.Field()
diff --git a/aiogram/types/voice_chat_ended.py b/aiogram/types/voice_chat_ended.py
new file mode 100644
index 00000000..f1bb1f05
--- /dev/null
+++ b/aiogram/types/voice_chat_ended.py
@@ -0,0 +1,13 @@
+from . import base
+from . import fields
+from . import mixins
+
+
+class VoiceChatEnded(base.TelegramObject, mixins.Downloadable):
+ """
+ This object represents a service message about a voice chat ended in the chat.
+
+ https://core.telegram.org/bots/api#voicechatended
+ """
+
+ duration: base.Integer = fields.Field()
diff --git a/aiogram/types/voice_chat_participants_invited.py b/aiogram/types/voice_chat_participants_invited.py
new file mode 100644
index 00000000..fbd0a457
--- /dev/null
+++ b/aiogram/types/voice_chat_participants_invited.py
@@ -0,0 +1,16 @@
+import typing
+
+from . import base
+from . import fields
+from . import mixins
+from .user import User
+
+
+class VoiceChatParticipantsInvited(base.TelegramObject, mixins.Downloadable):
+ """
+ This object represents a service message about new members invited to a voice chat.
+
+ https://core.telegram.org/bots/api#voicechatparticipantsinvited
+ """
+
+ users: typing.List[User] = fields.ListField(base=User)
diff --git a/aiogram/types/voice_chat_scheduled.py b/aiogram/types/voice_chat_scheduled.py
new file mode 100644
index 00000000..c134eb0f
--- /dev/null
+++ b/aiogram/types/voice_chat_scheduled.py
@@ -0,0 +1,15 @@
+from datetime import datetime
+
+from . import base
+from . import fields
+from .user import User
+
+
+class VoiceChatScheduled(base.TelegramObject):
+ """
+ This object represents a service message about a voice chat scheduled in the chat.
+
+ https://core.telegram.org/bots/api#voicechatscheduled
+ """
+
+ start_date: datetime = fields.DateTimeField()
diff --git a/aiogram/types/voice_chat_started.py b/aiogram/types/voice_chat_started.py
new file mode 100644
index 00000000..3cd76322
--- /dev/null
+++ b/aiogram/types/voice_chat_started.py
@@ -0,0 +1,12 @@
+from . import base
+from . import mixins
+
+
+class VoiceChatStarted(base.TelegramObject, mixins.Downloadable):
+ """
+ This object represents a service message about a voice chat started in the chat.
+ Currently holds no information.
+
+ https://core.telegram.org/bots/api#voicechatstarted
+ """
+ pass
diff --git a/aiogram/types/webhook_info.py b/aiogram/types/webhook_info.py
index 995d3aaa..dc1a7cd9 100644
--- a/aiogram/types/webhook_info.py
+++ b/aiogram/types/webhook_info.py
@@ -13,6 +13,7 @@ class WebhookInfo(base.TelegramObject):
url: base.String = fields.Field()
has_custom_certificate: base.Boolean = fields.Field()
pending_update_count: base.Integer = fields.Field()
+ ip_address: base.String = fields.Field()
last_error_date: base.Integer = fields.Field()
last_error_message: base.String = fields.Field()
max_connections: base.Integer = fields.Field()
diff --git a/aiogram/utils/callback_data.py b/aiogram/utils/callback_data.py
index e24ad7b1..d44fa5b9 100644
--- a/aiogram/utils/callback_data.py
+++ b/aiogram/utils/callback_data.py
@@ -130,7 +130,6 @@ class CallbackDataFilter(Filter):
if isinstance(value, (list, tuple, set, frozenset)):
if data.get(key) not in value:
return False
- else:
- if data.get(key) != value:
- return False
+ elif data.get(key) != value:
+ return False
return {'callback_data': data}
diff --git a/aiogram/utils/deep_linking.py b/aiogram/utils/deep_linking.py
index acb105da..e8035d9a 100644
--- a/aiogram/utils/deep_linking.py
+++ b/aiogram/utils/deep_linking.py
@@ -1,10 +1,10 @@
"""
Deep linking
-Telegram bots have a deep linking mechanism, that allows for passing additional
-parameters to the bot on startup. It could be a command that launches the bot — or
-an auth token to connect the user's Telegram account to their account on some
-external service.
+Telegram bots have a deep linking mechanism, that allows for passing
+additional parameters to the bot on startup. It could be a command that
+launches the bot — or an auth token to connect the user's Telegram
+account to their account on some external service.
You can read detailed description in the source:
https://core.telegram.org/bots#deep-linking
@@ -16,86 +16,123 @@ Basic link example:
.. code-block:: python
from aiogram.utils.deep_linking import get_start_link
- link = await get_start_link('foo') # result: 'https://t.me/MyBot?start=foo'
+ link = await get_start_link('foo')
+
+ # result: 'https://t.me/MyBot?start=foo'
Encoded link example:
.. code-block:: python
- from aiogram.utils.deep_linking import get_start_link, decode_payload
- link = await get_start_link('foo', encode=True) # result: 'https://t.me/MyBot?start=Zm9v'
- # and decode it back:
- payload = decode_payload('Zm9v') # result: 'foo'
+ from aiogram.utils.deep_linking import get_start_link
+
+ link = await get_start_link('foo', encode=True)
+ # result: 'https://t.me/MyBot?start=Zm9v'
+
+Decode it back example:
+ .. code-block:: python
+
+ from aiogram.utils.deep_linking import decode_payload
+ from aiogram.types import Message
+
+ @dp.message_handler(commands=["start"])
+ async def handler(message: Message):
+ args = message.get_args()
+ payload = decode_payload(args)
+ await message.answer(f"Your payload: {payload}")
"""
+import re
+from base64 import urlsafe_b64decode, urlsafe_b64encode
+
+from ..bot import Bot
+
+BAD_PATTERN = re.compile(r"[^_A-z0-9-]")
async def get_start_link(payload: str, encode=False) -> str:
"""
- Use this method to handy get 'start' deep link with your payload.
- If you need to encode payload or pass special characters - set encode as True
+ Get 'start' deep link with your payload.
+
+ If you need to encode payload or pass special characters -
+ set encode as True
:param payload: args passed with /start
:param encode: encode payload with base64url
:return: link
"""
- return await _create_link('start', payload, encode)
+ return await _create_link(
+ link_type="start",
+ payload=payload,
+ encode=encode,
+ )
async def get_startgroup_link(payload: str, encode=False) -> str:
"""
- Use this method to handy get 'startgroup' deep link with your payload.
- If you need to encode payload or pass special characters - set encode as True
+ Get 'startgroup' deep link with your payload.
+
+ If you need to encode payload or pass special characters -
+ set encode as True
:param payload: args passed with /start
:param encode: encode payload with base64url
:return: link
"""
- return await _create_link('startgroup', payload, encode)
+ return await _create_link(
+ link_type="startgroup",
+ payload=payload,
+ encode=encode,
+ )
async def _create_link(link_type, payload: str, encode=False):
+ """
+ Create deep link.
+
+ :param link_type: `start` or `startgroup`
+ :param payload: any string-convertible data
+ :param encode: pass True to encode the payload
+ :return: deeplink
+ """
bot = await _get_bot_user()
- payload = filter_payload(payload)
- if encode:
- payload = encode_payload(payload)
- return f'https://t.me/{bot.username}?{link_type}={payload}'
-
-def encode_payload(payload: str) -> str:
- """ Encode payload with URL-safe base64url. """
- from base64 import urlsafe_b64encode
- result: bytes = urlsafe_b64encode(payload.encode())
- return result.decode()
-
-
-def decode_payload(payload: str) -> str:
- """ Decode payload with URL-safe base64url. """
- from base64 import urlsafe_b64decode
- result: bytes = urlsafe_b64decode(payload + '=' * (4 - len(payload) % 4))
- return result.decode()
-
-
-def filter_payload(payload: str) -> str:
- """ Convert payload to text and search for not allowed symbols. """
- import re
-
- # convert to string
if not isinstance(payload, str):
payload = str(payload)
- # search for not allowed characters
- if re.search(r'[^_A-z0-9-]', payload):
- message = ('Wrong payload! Only A-Z, a-z, 0-9, _ and - are allowed. '
- 'We recommend to encode parameters with binary and other '
- 'types of content.')
+ if encode:
+ payload = encode_payload(payload)
+
+ if re.search(BAD_PATTERN, payload):
+ message = (
+ "Wrong payload! Only A-Z, a-z, 0-9, _ and - are allowed. "
+ "Pass `encode=True` or encode payload manually."
+ )
raise ValueError(message)
- return payload
+ if len(payload) > 64:
+ message = "Payload must be up to 64 characters long."
+ raise ValueError(message)
+
+ return f"https://t.me/{bot.username}?{link_type}={payload}"
+
+
+def encode_payload(payload: str) -> str:
+ """Encode payload with URL-safe base64url."""
+ payload = str(payload)
+ bytes_payload: bytes = urlsafe_b64encode(payload.encode())
+ str_payload = bytes_payload.decode()
+ return str_payload.replace("=", "")
+
+
+def decode_payload(payload: str) -> str:
+ """Decode payload with URL-safe base64url."""
+ payload += "=" * (4 - len(payload) % 4)
+ result: bytes = urlsafe_b64decode(payload)
+ return result.decode()
async def _get_bot_user():
- """ Get current user of bot. """
- from ..bot import Bot
+ """Get current user of bot."""
bot = Bot.get_current()
return await bot.me
diff --git a/aiogram/utils/deprecated.py b/aiogram/utils/deprecated.py
index 83a9034c..6d0d7ee3 100644
--- a/aiogram/utils/deprecated.py
+++ b/aiogram/utils/deprecated.py
@@ -2,7 +2,7 @@ import asyncio
import inspect
import warnings
import functools
-from typing import Callable
+from typing import Callable, Generic, TypeVar, Type, Optional
def deprecated(reason, stacklevel=2) -> Callable:
@@ -129,3 +129,36 @@ def renamed_argument(old_name: str, new_name: str, until_version: str, stackleve
return wrapped
return decorator
+
+
+_VT = TypeVar("_VT")
+_OwnerCls = TypeVar("_OwnerCls")
+
+
+class DeprecatedReadOnlyClassVar(Generic[_OwnerCls, _VT]):
+ """
+ DeprecatedReadOnlyClassVar[Owner, ValueType]
+
+ :param warning_message: Warning message when getter gets called
+ :param new_value_getter: Any callable with (owner_class: Type[Owner]) -> ValueType
+ signature that will be executed
+
+ Usage example:
+
+ >>> class MyClass:
+ ... some_attribute: DeprecatedReadOnlyClassVar[MyClass, int] = \
+ ... DeprecatedReadOnlyClassVar(
+ ... "Warning message.", lambda owner: 15)
+ ...
+ >>> MyClass.some_attribute # does warning.warn with `Warning message` and returns 15 in the current case
+ """
+
+ __slots__ = "_new_value_getter", "_warning_message"
+
+ def __init__(self, warning_message: str, new_value_getter: Callable[[_OwnerCls], _VT]):
+ self._warning_message = warning_message
+ self._new_value_getter = new_value_getter
+
+ def __get__(self, instance: Optional[_OwnerCls], owner: Type[_OwnerCls]):
+ warn_deprecated(self._warning_message, stacklevel=3)
+ return self._new_value_getter(owner)
diff --git a/aiogram/utils/exceptions.py b/aiogram/utils/exceptions.py
index cee2820a..e3a1f313 100644
--- a/aiogram/utils/exceptions.py
+++ b/aiogram/utils/exceptions.py
@@ -6,11 +6,14 @@
- MessageError
- MessageNotModified
- MessageToForwardNotFound
+ - MessageIdInvalid
- MessageToDeleteNotFound
+ - MessageToPinNotFound
- MessageIdentifierNotSpecified
- MessageTextIsEmpty
- MessageCantBeEdited
- MessageCantBeDeleted
+ - MessageCantBeForwarded
- MessageToEditNotFound
- MessageToReplyNotFound
- ToMuchMessages
@@ -37,6 +40,7 @@
- URLHostIsEmpty
- StartParamInvalid
- ButtonDataInvalid
+ - FileIsTooBig
- WrongFileIdentifier
- GroupDeactivated
- BadWebhook
@@ -175,6 +179,11 @@ class MessageToForwardNotFound(MessageError):
match = 'message to forward not found'
+class MessageIdInvalid(MessageError):
+ text = 'Invalid message id'
+ match = 'message_id_invalid'
+
+
class MessageToDeleteNotFound(MessageError):
"""
Will be raised when you try to delete very old or deleted or unknown message.
@@ -182,11 +191,18 @@ class MessageToDeleteNotFound(MessageError):
match = 'message to delete not found'
+class MessageToPinNotFound(MessageError):
+ """
+ Will be raised when you try to pin deleted or unknown message.
+ """
+ match = 'message to pin not found'
+
+
class MessageToReplyNotFound(MessageError):
"""
Will be raised when you try to reply to very old or deleted or unknown message.
"""
- match = 'message to reply not found'
+ match = 'Reply message not found'
class MessageIdentifierNotSpecified(MessageError):
@@ -205,6 +221,10 @@ class MessageCantBeDeleted(MessageError):
match = 'message can\'t be deleted'
+class MessageCantBeForwarded(MessageError):
+ match = 'message can\'t be forwarded'
+
+
class MessageToEditNotFound(MessageError):
match = 'message to edit not found'
@@ -339,12 +359,16 @@ class ButtonDataInvalid(BadRequest):
text = 'Button data invalid'
+class FileIsTooBig(BadRequest):
+ match = 'File is too big'
+
+
class WrongFileIdentifier(BadRequest):
match = 'wrong file identifier/HTTP URL specified'
class GroupDeactivated(BadRequest):
- match = 'group is deactivated'
+ match = 'Group chat was deactivated'
class PhotoAsInputFileRequired(BadRequest):
@@ -470,6 +494,20 @@ class MethodIsNotAvailable(BadRequest):
match = "Method is available only for supergroups"
+class CantRestrictChatOwner(BadRequest):
+ """
+ Raises when bot restricts the chat owner
+ """
+ match = 'Can\'t remove chat owner'
+
+
+class UserIsAnAdministratorOfTheChat(BadRequest):
+ """
+ Raises when bot restricts the chat admin
+ """
+ match = 'User is an administrator of the chat'
+
+
class NotFound(TelegramAPIError, _MatchErrorMixin):
__group = True
@@ -497,7 +535,7 @@ class Unauthorized(TelegramAPIError, _MatchErrorMixin):
class BotKicked(Unauthorized):
- match = 'bot was kicked from a chat'
+ match = 'bot was kicked from'
class BotBlocked(Unauthorized):
diff --git a/aiogram/utils/executor.py b/aiogram/utils/executor.py
index fe3483f6..c74827b0 100644
--- a/aiogram/utils/executor.py
+++ b/aiogram/utils/executor.py
@@ -2,7 +2,7 @@ import asyncio
import datetime
import functools
import secrets
-from typing import Callable, Union, Optional, Any
+from typing import Callable, Union, Optional, Any, List
from warnings import warn
from aiohttp import web
@@ -23,7 +23,8 @@ def _setup_callbacks(executor: 'Executor', on_startup=None, on_shutdown=None):
def start_polling(dispatcher, *, loop=None, skip_updates=False, reset_webhook=True,
- on_startup=None, on_shutdown=None, timeout=20, relax=0.1, fast=True):
+ on_startup=None, on_shutdown=None, timeout=20, relax=0.1, fast=True,
+ allowed_updates: Optional[List[str]] = None):
"""
Start bot in long-polling mode
@@ -34,11 +35,20 @@ def start_polling(dispatcher, *, loop=None, skip_updates=False, reset_webhook=Tr
:param on_startup:
:param on_shutdown:
:param timeout:
+ :param relax:
+ :param fast:
+ :param allowed_updates:
"""
executor = Executor(dispatcher, skip_updates=skip_updates, loop=loop)
_setup_callbacks(executor, on_startup, on_shutdown)
- executor.start_polling(reset_webhook=reset_webhook, timeout=timeout, relax=relax, fast=fast)
+ executor.start_polling(
+ reset_webhook=reset_webhook,
+ timeout=timeout,
+ relax=relax,
+ fast=fast,
+ allowed_updates=allowed_updates
+ )
def set_webhook(dispatcher: Dispatcher, webhook_path: str, *, loop: Optional[asyncio.AbstractEventLoop] = None,
@@ -123,13 +133,13 @@ class Executor:
"""
def __init__(self, dispatcher, skip_updates=None, check_ip=False, retry_after=None, loop=None):
- if loop is None:
- loop = dispatcher.loop
+ if loop is not None:
+ self._loop = loop
+
self.dispatcher = dispatcher
self.skip_updates = skip_updates
self.check_ip = check_ip
self.retry_after = retry_after
- self.loop = loop
self._identity = secrets.token_urlsafe(16)
self._web_app = None
@@ -145,13 +155,17 @@ class Executor:
Bot.set_current(dispatcher.bot)
Dispatcher.set_current(dispatcher)
+ @property
+ def loop(self) -> asyncio.AbstractEventLoop:
+ return getattr(self, "_loop", asyncio.get_event_loop())
+
@property
def frozen(self):
return self._freeze
def set_web_app(self, application: web.Application):
"""
- Change instance of aiohttp.web.Applicaton
+ Change instance of aiohttp.web.Application
:param application:
"""
@@ -291,7 +305,8 @@ class Executor:
self.set_webhook(webhook_path=webhook_path, request_handler=request_handler, route_name=route_name)
self.run_app(**kwargs)
- def start_polling(self, reset_webhook=None, timeout=20, relax=0.1, fast=True):
+ def start_polling(self, reset_webhook=None, timeout=20, relax=0.1, fast=True,
+ allowed_updates: Optional[List[str]] = None):
"""
Start bot in long-polling mode
@@ -304,7 +319,7 @@ class Executor:
try:
loop.run_until_complete(self._startup_polling())
loop.create_task(self.dispatcher.start_polling(reset_webhook=reset_webhook, timeout=timeout,
- relax=relax, fast=fast))
+ relax=relax, fast=fast, allowed_updates=allowed_updates))
loop.run_forever()
except (KeyboardInterrupt, SystemExit):
# loop.stop()
@@ -350,7 +365,7 @@ class Executor:
self.dispatcher.stop_polling()
await self.dispatcher.storage.close()
await self.dispatcher.storage.wait_closed()
- await self.dispatcher.bot.close()
+ await self.dispatcher.bot.session.close()
async def _startup_polling(self):
await self._welcome()
diff --git a/aiogram/utils/helper.py b/aiogram/utils/helper.py
index 735afe5d..55a134a3 100644
--- a/aiogram/utils/helper.py
+++ b/aiogram/utils/helper.py
@@ -103,10 +103,7 @@ class HelperMode(Helper):
if symbol == '_' and pos > 0:
need_upper = True
else:
- if need_upper:
- result += symbol.upper()
- else:
- result += symbol.lower()
+ result += symbol.upper() if need_upper else symbol.lower()
need_upper = False
if first_upper:
result = result[0].upper() + result[1:]
@@ -201,10 +198,14 @@ class OrderedHelperMeta(type):
def __new__(mcs, name, bases, namespace, **kwargs):
cls = super().__new__(mcs, name, bases, namespace)
- props_keys = []
-
- for prop_name in (name for name, prop in namespace.items() if isinstance(prop, (Item, ListItem))):
- props_keys.append(prop_name)
+ props_keys = [
+ prop_name
+ for prop_name in (
+ name
+ for name, prop in namespace.items()
+ if isinstance(prop, (Item, ListItem))
+ )
+ ]
setattr(cls, PROPS_KEYS_ATTR_NAME, props_keys)
diff --git a/aiogram/utils/parts.py b/aiogram/utils/parts.py
index e03f7bcc..b4bb9d67 100644
--- a/aiogram/utils/parts.py
+++ b/aiogram/utils/parts.py
@@ -15,12 +15,13 @@ def split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[str]:
return [text[i:i + length] for i in range(0, len(text), length)]
-def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[str]:
+def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH, split_separator: str = ' ') -> typing.List[str]:
"""
Split long text
:param text:
:param length:
+ :param split_separator
:return:
"""
# TODO: More informative description
@@ -30,7 +31,7 @@ def safe_split_text(text: str, length: int = MAX_MESSAGE_LENGTH) -> typing.List[
while temp_text:
if len(temp_text) > length:
try:
- split_pos = temp_text[:length].rindex(' ')
+ split_pos = temp_text[:length].rindex(split_separator)
except ValueError:
split_pos = length
if split_pos < length // 4 * 3:
diff --git a/aiogram/utils/text_decorations.py b/aiogram/utils/text_decorations.py
index 3d22f637..40fe296b 100644
--- a/aiogram/utils/text_decorations.py
+++ b/aiogram/utils/text_decorations.py
@@ -9,11 +9,11 @@ if TYPE_CHECKING: # pragma: no cover
from aiogram.types import MessageEntity
__all__ = (
- "TextDecoration",
- "HtmlDecoration",
- "MarkdownDecoration",
- "html_decoration",
- "markdown_decoration",
+ 'HtmlDecoration',
+ 'MarkdownDecoration',
+ 'TextDecoration',
+ 'html_decoration',
+ 'markdown_decoration',
)
@@ -55,16 +55,15 @@ class TextDecoration(ABC):
:param entities: Array of MessageEntities
:return:
"""
- result = "".join(
+ return "".join(
self._unparse_entities(
- text, sorted(entities, key=lambda item: item.offset) if entities else []
+ self._add_surrogates(text), sorted(entities, key=lambda item: item.offset) if entities else []
)
)
- return result
def _unparse_entities(
self,
- text: str,
+ text: bytes,
entities: List[MessageEntity],
offset: Optional[int] = None,
length: Optional[int] = None,
@@ -74,15 +73,15 @@ class TextDecoration(ABC):
length = length or len(text)
for index, entity in enumerate(entities):
- if entity.offset < offset:
+ if entity.offset * 2 < offset:
continue
- if entity.offset > offset:
- yield self.quote(text[offset : entity.offset])
- start = entity.offset
- offset = entity.offset + entity.length
+ if entity.offset * 2 > offset:
+ yield self.quote(self._remove_surrogates(text[offset : entity.offset * 2]))
+ start = entity.offset * 2
+ offset = entity.offset * 2 + entity.length * 2
sub_entities = list(
- filter(lambda e: e.offset < (offset or 0), entities[index + 1 :])
+ filter(lambda e: e.offset * 2 < (offset or 0), entities[index + 1 :])
)
yield self.apply_entity(
entity,
@@ -94,7 +93,15 @@ class TextDecoration(ABC):
)
if offset < length:
- yield self.quote(text[offset:length])
+ yield self.quote(self._remove_surrogates(text[offset:length]))
+
+ @staticmethod
+ def _add_surrogates(text: str):
+ return text.encode('utf-16-le')
+
+ @staticmethod
+ def _remove_surrogates(text: bytes):
+ return text.decode('utf-16-le')
@abstractmethod
def link(self, value: str, link: str) -> str: # pragma: no cover
@@ -159,7 +166,7 @@ class HtmlDecoration(TextDecoration):
return f"{value}"
def quote(self, value: str) -> str:
- return html.escape(value)
+ return html.escape(value, quote=False)
class MarkdownDecoration(TextDecoration):
@@ -172,19 +179,19 @@ class MarkdownDecoration(TextDecoration):
return f"*{value}*"
def italic(self, value: str) -> str:
- return f"_{value}_\r"
+ return f"_\r{value}_\r"
def code(self, value: str) -> str:
return f"`{value}`"
def pre(self, value: str) -> str:
- return f"```{value}```"
+ return f"```\n{value}\n```"
def pre_language(self, value: str, language: str) -> str:
return f"```{language}\n{value}\n```"
def underline(self, value: str) -> str:
- return f"__{value}__"
+ return f"__\r{value}__\r"
def strikethrough(self, value: str) -> str:
return f"~{value}~"
diff --git a/dev_requirements.txt b/dev_requirements.txt
index c0c2a39d..26e410aa 100644
--- a/dev_requirements.txt
+++ b/dev_requirements.txt
@@ -3,11 +3,10 @@
ujson>=1.35
python-rapidjson>=0.7.0
emoji>=0.5.2
-pytest>=5.4
+pytest>=6.2.1
pytest-asyncio>=0.10.0
tox>=3.9.0
aresponses>=1.1.1
-uvloop>=0.12.2
aioredis>=1.2.0
wheel>=0.31.1
sphinx>=2.0.1
@@ -16,3 +15,5 @@ sphinxcontrib-programoutput>=0.14
aiohttp-socks>=0.3.4
rethinkdb>=2.4.1
coverage==4.5.3
+motor>=2.2.0
+pytest-lazy-fixture==0.6.*
diff --git a/docs/source/dispatcher/filters.rst b/docs/source/dispatcher/filters.rst
index f53a4c95..a47e396e 100644
--- a/docs/source/dispatcher/filters.rst
+++ b/docs/source/dispatcher/filters.rst
@@ -10,7 +10,7 @@ Filter factory greatly simplifies the reuse of filters when registering handlers
Filters factory
===============
-.. autoclass:: aiogram.dispatcher.filters.factory.FiltersFactory
+.. autoclass:: aiogram.dispatcher.filters.FiltersFactory
:members:
:show-inheritance:
@@ -21,28 +21,28 @@ Builtin filters
Command
-------
-.. autoclass:: aiogram.dispatcher.filters.builtin.Command
+.. autoclass:: aiogram.dispatcher.filters.Command
:members:
:show-inheritance:
CommandStart
------------
-.. autoclass:: aiogram.dispatcher.filters.builtin.CommandStart
+.. autoclass:: aiogram.dispatcher.filters.CommandStart
:members:
:show-inheritance:
CommandHelp
-----------
-.. autoclass:: aiogram.dispatcher.filters.builtin.CommandHelp
+.. autoclass:: aiogram.dispatcher.filters.CommandHelp
:members:
:show-inheritance:
CommandSettings
---------------
-.. autoclass:: aiogram.dispatcher.filters.builtin.CommandSettings
+.. autoclass:: aiogram.dispatcher.filters.CommandSettings
:members:
:show-inheritance:
@@ -50,7 +50,7 @@ CommandSettings
CommandPrivacy
--------------
-.. autoclass:: aiogram.dispatcher.filters.builtin.CommandPrivacy
+.. autoclass:: aiogram.dispatcher.filters.CommandPrivacy
:members:
:show-inheritance:
@@ -58,7 +58,7 @@ CommandPrivacy
Text
----
-.. autoclass:: aiogram.dispatcher.filters.builtin.Text
+.. autoclass:: aiogram.dispatcher.filters.Text
:members:
:show-inheritance:
@@ -66,7 +66,7 @@ Text
HashTag
-------
-.. autoclass:: aiogram.dispatcher.filters.builtin.HashTag
+.. autoclass:: aiogram.dispatcher.filters.HashTag
:members:
:show-inheritance:
@@ -74,7 +74,7 @@ HashTag
Regexp
------
-.. autoclass:: aiogram.dispatcher.filters.builtin.Regexp
+.. autoclass:: aiogram.dispatcher.filters.Regexp
:members:
:show-inheritance:
@@ -82,7 +82,7 @@ Regexp
RegexpCommandsFilter
--------------------
-.. autoclass:: aiogram.dispatcher.filters.builtin.RegexpCommandsFilter
+.. autoclass:: aiogram.dispatcher.filters.RegexpCommandsFilter
:members:
:show-inheritance:
@@ -90,21 +90,21 @@ RegexpCommandsFilter
ContentTypeFilter
-----------------
-.. autoclass:: aiogram.dispatcher.filters.builtin.ContentTypeFilter
+.. autoclass:: aiogram.dispatcher.filters.ContentTypeFilter
:members:
:show-inheritance:
IsSenderContact
---------------
-.. autoclass:: aiogram.dispatcher.filters.builtin.IsSenderContact
+.. autoclass:: aiogram.dispatcher.filters.IsSenderContact
:members:
:show-inheritance:
StateFilter
-----------
-.. autoclass:: aiogram.dispatcher.filters.builtin.StateFilter
+.. autoclass:: aiogram.dispatcher.filters.StateFilter
:members:
:show-inheritance:
@@ -112,13 +112,13 @@ StateFilter
ExceptionsFilter
----------------
-.. autoclass:: aiogram.dispatcher.filters.builtin.ExceptionsFilter
+.. autoclass:: aiogram.dispatcher.filters.ExceptionsFilter
:members:
:show-inheritance:
IDFilter
-----------------
+--------
.. autoclass:: aiogram.dispatcher.filters.builtin.IDFilter
:members:
@@ -126,9 +126,9 @@ IDFilter
AdminFilter
-----------------
+-----------
-.. autoclass:: aiogram.dispatcher.filters.builtin.AdminFilter
+.. autoclass:: aiogram.dispatcher.filters.AdminFilter
:members:
:show-inheritance:
@@ -136,23 +136,31 @@ AdminFilter
IsReplyFilter
-------------
-.. autoclass:: aiogram.dispatcher.filters.filters.IsReplyFilter
+.. autoclass:: aiogram.dispatcher.filters.IsReplyFilter
:members:
:show-inheritance:
ForwardedMessageFilter
--------------
+----------------------
-.. autoclass:: aiogram.dispatcher.filters.filters.ForwardedMessageFilter
+.. autoclass:: aiogram.dispatcher.filters.ForwardedMessageFilter
:members:
:show-inheritance:
ChatTypeFilter
+--------------
+
+.. autoclass:: aiogram.dispatcher.filters.ChatTypeFilter
+ :members:
+ :show-inheritance:
+
+
+MediaGroupFilter
-------------
-.. autoclass:: aiogram.dispatcher.filters.filters.ChatTypeFilter
+.. autoclass:: aiogram.dispatcher.filters.MediaGroupFilter
:members:
:show-inheritance:
@@ -170,19 +178,19 @@ Own filter can be:
AbstractFilter
--------------
-.. autoclass:: aiogram.dispatcher.filters.filters.AbstractFilter
+.. autoclass:: aiogram.dispatcher.filters.AbstractFilter
:members:
:show-inheritance:
Filter
------
-.. autoclass:: aiogram.dispatcher.filters.filters.Filter
+.. autoclass:: aiogram.dispatcher.filters.Filter
:members:
:show-inheritance:
BoundFilter
-----------
-.. autoclass:: aiogram.dispatcher.filters.filters.BoundFilter
+.. autoclass:: aiogram.dispatcher.filters.BoundFilter
:members:
:show-inheritance:
diff --git a/docs/source/dispatcher/fsm.rst b/docs/source/dispatcher/fsm.rst
index 93c94aa6..1b00e81e 100644
--- a/docs/source/dispatcher/fsm.rst
+++ b/docs/source/dispatcher/fsm.rst
@@ -12,15 +12,29 @@ Coming soon...
Memory storage
~~~~~~~~~~~~~~
-Coming soon...
+
+.. autoclass:: aiogram.contrib.fsm_storage.memory.MemoryStorage
+ :show-inheritance:
Redis storage
~~~~~~~~~~~~~
-Coming soon...
+
+.. autoclass:: aiogram.contrib.fsm_storage.redis.RedisStorage
+ :show-inheritance:
+
+Mongo storage
+~~~~~~~~~~~~~
+
+.. autoclass:: aiogram.contrib.fsm_storage.mongo.MongoStorage
+ :show-inheritance:
+
Rethink DB storage
~~~~~~~~~~~~~~~~~~
-Coming soon...
+
+.. autoclass:: aiogram.contrib.fsm_storage.rethinkdb.RethinkDBStorage
+ :show-inheritance:
+
Making own storage's
~~~~~~~~~~~~~~~~~~~~
diff --git a/docs/source/examples/advanced_executor_example.rst b/docs/source/examples/advanced_executor_example.rst
index 9eb5d950..a824ebaf 100644
--- a/docs/source/examples/advanced_executor_example.rst
+++ b/docs/source/examples/advanced_executor_example.rst
@@ -1,28 +1,8 @@
-.. Autogenerated file at 2018-10-28 19:31:48.335963
-
=========================
Advanced executor example
=========================
-!/usr/bin/env python3
-**This example is outdated**
-In this example used ArgumentParser for configuring Your bot.
-Provided to start bot with webhook:
-python advanced_executor_example.py \
---token TOKEN_HERE \
---host 0.0.0.0 \
---port 8084 \
---host-name example.com \
---webhook-port 443
-Or long polling:
-python advanced_executor_example.py --token TOKEN_HERE
-So... In this example found small trouble:
-can't get bot instance in handlers.
-If you want to automatic change getting updates method use executor utils (from aiogram.utils.executor)
- TODO: Move token to environment variables.
-
.. literalinclude:: ../../../examples/advanced_executor_example.py
:caption: advanced_executor_example.py
:language: python
:linenos:
- :lines: 25-
diff --git a/docs/source/examples/broadcast_example.rst b/docs/source/examples/broadcast_example.rst
index 97556e73..837ad02f 100644
--- a/docs/source/examples/broadcast_example.rst
+++ b/docs/source/examples/broadcast_example.rst
@@ -1,5 +1,3 @@
-.. Autogenerated file at 2018-09-08 02:07:37.593501
-
=================
Broadcast example
=================
diff --git a/docs/source/examples/check_user_language.rst b/docs/source/examples/check_user_language.rst
index 0fc2d90e..5d859d43 100644
--- a/docs/source/examples/check_user_language.rst
+++ b/docs/source/examples/check_user_language.rst
@@ -1,5 +1,3 @@
-.. Autogenerated file at 2018-09-08 02:07:37.558059
-
===================
Check user language
===================
@@ -10,4 +8,3 @@ Babel is required.
:caption: check_user_language.py
:language: python
:linenos:
- :lines: 5-
diff --git a/docs/source/examples/echo_bot.rst b/docs/source/examples/echo_bot.rst
index 18c45569..dfbecce1 100644
--- a/docs/source/examples/echo_bot.rst
+++ b/docs/source/examples/echo_bot.rst
@@ -1,8 +1,7 @@
+========
Echo bot
========
-Very simple example of the bot which will sent text of the received messages to the sender
-
.. literalinclude:: ../../../examples/echo_bot.py
:caption: echo_bot.py
:language: python
diff --git a/docs/source/examples/finite_state_machine_example.rst b/docs/source/examples/finite_state_machine_example.rst
index 3eea4a22..29e251b8 100644
--- a/docs/source/examples/finite_state_machine_example.rst
+++ b/docs/source/examples/finite_state_machine_example.rst
@@ -1,5 +1,3 @@
-.. Autogenerated file at 2018-09-08 02:07:37.595032
-
============================
Finite state machine example
============================
diff --git a/docs/source/examples/i18n_example.rst b/docs/source/examples/i18n_example.rst
index 875006be..dc794619 100644
--- a/docs/source/examples/i18n_example.rst
+++ b/docs/source/examples/i18n_example.rst
@@ -1,28 +1,8 @@
-.. Autogenerated file at 2018-09-08 02:07:37.591007
-
============
I18n example
============
-Internalize your bot
-Step 1: extract texts
-# pybabel extract i18n_example.py -o locales/mybot.pot
-Step 2: create *.po files. For e.g. create en, ru, uk locales.
-# echo {en,ru,uk} | xargs -n1 pybabel init -i locales/mybot.pot -d locales -D mybot -l
-Step 3: translate texts
-Step 4: compile translations
-# pybabel compile -d locales -D mybot
-Step 5: When you change the code of your bot you need to update po & mo files.
-Step 5.1: regenerate pot file:
-command from step 1
-Step 5.2: update po files
-# pybabel update -d locales -D mybot -i locales/mybot.pot
-Step 5.3: update your translations
-Step 5.4: compile mo files
-command from step 4
-
.. literalinclude:: ../../../examples/i18n_example.py
:caption: i18n_example.py
:language: python
:linenos:
- :lines: 22-
diff --git a/docs/source/examples/index.rst b/docs/source/examples/index.rst
index 2bedaa52..c5be4bfc 100644
--- a/docs/source/examples/index.rst
+++ b/docs/source/examples/index.rst
@@ -19,3 +19,4 @@ Examples
payments
broadcast_example
media_group
+ local_server
diff --git a/docs/source/examples/inline_bot.rst b/docs/source/examples/inline_bot.rst
index b6059974..ca3d227c 100644
--- a/docs/source/examples/inline_bot.rst
+++ b/docs/source/examples/inline_bot.rst
@@ -1,5 +1,3 @@
-.. Autogenerated file at 2018-09-08 02:07:37.561907
-
==========
Inline bot
==========
diff --git a/docs/source/examples/local_server.rst b/docs/source/examples/local_server.rst
new file mode 100644
index 00000000..6e056a6f
--- /dev/null
+++ b/docs/source/examples/local_server.rst
@@ -0,0 +1,8 @@
+============
+Local server
+============
+
+.. literalinclude:: ../../../examples/local_server.py
+ :caption: local_server.py
+ :language: python
+ :linenos:
diff --git a/docs/source/examples/media_group.rst b/docs/source/examples/media_group.rst
index ebf2d3f2..1eb12e4f 100644
--- a/docs/source/examples/media_group.rst
+++ b/docs/source/examples/media_group.rst
@@ -1,5 +1,3 @@
-.. Autogenerated file at 2018-09-08 02:07:37.566615
-
===========
Media group
===========
diff --git a/docs/source/examples/middleware_and_antiflood.rst b/docs/source/examples/middleware_and_antiflood.rst
index 4f634c93..ce9e99db 100644
--- a/docs/source/examples/middleware_and_antiflood.rst
+++ b/docs/source/examples/middleware_and_antiflood.rst
@@ -1,5 +1,3 @@
-.. Autogenerated file at 2018-09-08 02:07:37.560132
-
========================
Middleware and antiflood
========================
diff --git a/docs/source/examples/payments.rst b/docs/source/examples/payments.rst
index 7b634ce7..f663c8ee 100644
--- a/docs/source/examples/payments.rst
+++ b/docs/source/examples/payments.rst
@@ -1,5 +1,3 @@
-.. Autogenerated file at 2018-09-08 02:07:37.579017
-
========
Payments
========
diff --git a/docs/source/examples/proxy_and_emojize.rst b/docs/source/examples/proxy_and_emojize.rst
index 3dbaa268..96a4f9a7 100644
--- a/docs/source/examples/proxy_and_emojize.rst
+++ b/docs/source/examples/proxy_and_emojize.rst
@@ -1,5 +1,3 @@
-.. Autogenerated file at 2018-09-08 02:07:37.555359
-
=================
Proxy and emojize
=================
diff --git a/docs/source/examples/regexp_commands_filter_example.rst b/docs/source/examples/regexp_commands_filter_example.rst
index 36452350..e524640a 100644
--- a/docs/source/examples/regexp_commands_filter_example.rst
+++ b/docs/source/examples/regexp_commands_filter_example.rst
@@ -1,5 +1,3 @@
-.. Autogenerated file at 2018-09-08 02:07:37.568530
-
==============================
Regexp commands filter example
==============================
diff --git a/docs/source/examples/throtling_example.rst b/docs/source/examples/throtling_example.rst
index eaeb337e..258d3d22 100644
--- a/docs/source/examples/throtling_example.rst
+++ b/docs/source/examples/throtling_example.rst
@@ -1,14 +1,8 @@
-.. Autogenerated file at 2018-09-08 02:07:37.563878
+==================
+Throttling example
+==================
-=================
-Throtling example
-=================
-
-Example for throttling manager.
-You can use that for flood controlling.
-
-.. literalinclude:: ../../../examples/throtling_example.py
- :caption: throtling_example.py
+.. literalinclude:: ../../../examples/throttling_example.py
+ :caption: throttling_example.py
:language: python
:linenos:
- :lines: 7-
diff --git a/docs/source/examples/webhook_example.rst b/docs/source/examples/webhook_example.rst
index 48de8622..ce71339a 100644
--- a/docs/source/examples/webhook_example.rst
+++ b/docs/source/examples/webhook_example.rst
@@ -1,13 +1,8 @@
-.. Autogenerated file at 2018-10-28 19:31:48.341172
-
===============
Webhook example
===============
-Example outdated
-
.. literalinclude:: ../../../examples/webhook_example.py
:caption: webhook_example.py
:language: python
:linenos:
- :lines: 5-
diff --git a/docs/source/examples/webhook_example_2.rst b/docs/source/examples/webhook_example_2.rst
index 2ffdfed9..8c104f12 100644
--- a/docs/source/examples/webhook_example_2.rst
+++ b/docs/source/examples/webhook_example_2.rst
@@ -1,10 +1,8 @@
-.. Autogenerated file at 2018-09-08 02:07:37.576034
+===================
+Webhook example old
+===================
-=================
-Webhook example 2
-=================
-
-.. literalinclude:: ../../../examples/webhook_example_2.py
+.. literalinclude:: ../../../examples/webhook_example_old.py
:caption: webhook_example_2.py
:language: python
:linenos:
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 0ac6eccd..cd4b99d0 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -22,7 +22,7 @@ Welcome to aiogram's documentation!
:target: https://pypi.python.org/pypi/aiogram
:alt: Supported python versions
- .. image:: https://img.shields.io/badge/Telegram%20Bot%20API-4.9-blue.svg?style=flat-square&logo=telegram
+ .. image:: https://img.shields.io/badge/Telegram%20Bot%20API-5.3-blue.svg?style=flat-square&logo=telegram
:target: https://core.telegram.org/bots/api
:alt: Telegram Bot API
diff --git a/docs/source/install.rst b/docs/source/install.rst
index e4717a42..4eaa351f 100644
--- a/docs/source/install.rst
+++ b/docs/source/install.rst
@@ -13,9 +13,13 @@ Using Pipenv
$ pipenv install aiogram
-Using AUR
+Using Pacman
---------
-*aiogram* is also available in Arch User Repository, so you can install this framework on any Arch-based distribution like ArchLinux, Antergos, Manjaro, etc. To do this, use your favorite AUR-helper and install the `python-aiogram `_ package.
+*aiogram* is also available in Arch Linux Repository, so you can install this framework on any Arch-based distribution like Arch Linux, Antergos, Manjaro, etc. To do this, just use pacman to install the `python-aiogram `_ package:
+
+ .. code-block:: bash
+
+ $ pacman -S python-aiogram
From sources
------------
@@ -28,7 +32,7 @@ From sources
$ cd aiogram
$ python setup.py install
- Or if you want to install stable version (The same with version form PyPi):
+ Or if you want to install stable version (The same with version from PyPi):
.. code-block:: bash
diff --git a/docs/source/quick_start.rst b/docs/source/quick_start.rst
index b0724a78..eb8551db 100644
--- a/docs/source/quick_start.rst
+++ b/docs/source/quick_start.rst
@@ -24,17 +24,17 @@ Next step: interaction with bots starts with one command. Register your first co
:language: python
:lines: 20-25
-If you want to handle all messages in the chat simply add handler without filters:
+If you want to handle all text messages in the chat simply add handler without filters:
.. literalinclude:: ../../examples/echo_bot.py
:language: python
- :lines: 35-37
+ :lines: 44-49
Last step: run long polling.
.. literalinclude:: ../../examples/echo_bot.py
:language: python
- :lines: 40-41
+ :lines: 52-53
Summary
-------
@@ -42,4 +42,4 @@ Summary
.. literalinclude:: ../../examples/echo_bot.py
:language: python
:linenos:
- :lines: -19,27-
+ :lines: -27,43-
diff --git a/docs/source/utils/index.rst b/docs/source/utils/index.rst
index 1ac3777c..4865518e 100644
--- a/docs/source/utils/index.rst
+++ b/docs/source/utils/index.rst
@@ -13,3 +13,4 @@ Utils
parts
json
emoji
+ deep_linking
diff --git a/examples/callback_data_factory.py b/examples/callback_data_factory.py
index 4aaccae3..c95860f1 100644
--- a/examples/callback_data_factory.py
+++ b/examples/callback_data_factory.py
@@ -113,7 +113,7 @@ async def query_post_vote(query: types.CallbackQuery, callback_data: typing.Dict
@dp.errors_handler(exception=MessageNotModified)
async def message_not_modified_handler(update, error):
- return True
+ return True # errors_handler must return True if error was handled correctly
if __name__ == '__main__':
diff --git a/examples/callback_data_factory_simple.py b/examples/callback_data_factory_simple.py
index a17b09c6..d8c1a327 100644
--- a/examples/callback_data_factory_simple.py
+++ b/examples/callback_data_factory_simple.py
@@ -62,7 +62,7 @@ async def callback_vote_action(query: types.CallbackQuery, callback_data: typing
@dp.errors_handler(exception=MessageNotModified) # handle the cases when this exception raises
async def message_not_modified_handler(update, error):
- return True
+ return True # errors_handler must return True if error was handled correctly
if __name__ == '__main__':
diff --git a/examples/chat_type_filter.py b/examples/chat_type_filter.py
index 08bb1858..9db13cb8 100644
--- a/examples/chat_type_filter.py
+++ b/examples/chat_type_filter.py
@@ -19,12 +19,12 @@ bot = Bot(token=API_TOKEN)
dp = Dispatcher(bot)
-@dp.message_handler(chat_type=[ChatType.PRIVATE, ChatType.CHANNEL])
+@dp.message_handler(chat_type=[ChatType.PRIVATE, ChatType.SUPERGROUP])
async def send_welcome(message: types.Message):
"""
- This handler will be called when user sends `/start` or `/help` command
+ This handler will be called when user sends message in private chat or supergroup
"""
- await message.reply("Hi!\nI'm hearing your messages in private chats and channels")
+ await message.reply("Hi!\nI'm hearing your messages in private chats and supergroups")
# propagate message to the next handler
raise SkipHandler
@@ -33,7 +33,7 @@ async def send_welcome(message: types.Message):
@dp.message_handler(chat_type=ChatType.PRIVATE)
async def send_welcome(message: types.Message):
"""
- This handler will be called when user sends `/start` or `/help` command
+ This handler will be called when user sends message in private chat
"""
await message.reply("Hi!\nI'm hearing your messages only in private chats")
diff --git a/examples/i18n_example.py b/examples/i18n_example.py
index b626d048..29b43210 100644
--- a/examples/i18n_example.py
+++ b/examples/i18n_example.py
@@ -1,8 +1,8 @@
"""
-Internalize your bot
+Internationalize your bot
Step 1: extract texts
- # pybabel extract i18n_example.py -o locales/mybot.pot
+ # pybabel extract --input-dirs=. -o locales/mybot.pot
Some useful options:
- Extract texts with pluralization support
@@ -16,9 +16,14 @@ Step 1: extract texts
- Set version
# --version=2.2
-Step 2: create *.po files. For e.g. create en, ru, uk locales.
- # echo {en,ru,uk} | xargs -n1 pybabel init -i locales/mybot.pot -d locales -D mybot -l
-Step 3: translate texts
+Step 2: create *.po files. E.g. create en, ru, uk locales.
+ # pybabel init -i locales/mybot.pot -d locales -D mybot -l en
+ # pybabel init -i locales/mybot.pot -d locales -D mybot -l ru
+ # pybabel init -i locales/mybot.pot -d locales -D mybot -l uk
+
+Step 3: translate texts located in locales/{language}/LC_MESSAGES/mybot.po
+ To open .po file you can use basic text editor or any PO editor, e.g. https://poedit.net/
+
Step 4: compile translations
# pybabel compile -d locales -D mybot
@@ -27,7 +32,8 @@ Step 5: When you change the code of your bot you need to update po & mo files.
command from step 1
Step 5.2: update po files
# pybabel update -d locales -D mybot -i locales/mybot.pot
- Step 5.3: update your translations
+ Step 5.3: update your translations
+ location and tools you know from step 3
Step 5.4: compile mo files
command from step 4
"""
@@ -92,5 +98,6 @@ async def cmd_like(message: types.Message, locale):
# NOTE: This is comment for a translator
await message.reply(__('Aiogram has {number} like!', 'Aiogram has {number} likes!', likes).format(number=likes))
+
if __name__ == '__main__':
executor.start_polling(dp, skip_updates=True)
diff --git a/examples/local_server.py b/examples/local_server.py
new file mode 100644
index 00000000..7dbc4aca
--- /dev/null
+++ b/examples/local_server.py
@@ -0,0 +1,27 @@
+import logging
+
+from aiogram import Bot, Dispatcher, executor, types
+from aiogram.bot.api import TelegramAPIServer
+from aiogram.types import ContentType
+
+API_TOKEN = 'BOT TOKEN HERE'
+
+# Configure logging
+logging.basicConfig(level=logging.INFO)
+
+# Create private Bot API server endpoints wrapper
+local_server = TelegramAPIServer.from_base('http://localhost')
+
+# Initialize bot with using local server
+bot = Bot(token=API_TOKEN, server=local_server)
+# ... and dispatcher
+dp = Dispatcher(bot)
+
+
+@dp.message_handler(content_types=ContentType.ANY)
+async def echo(message: types.Message):
+ await message.copy_to(message.chat.id)
+
+
+if __name__ == '__main__':
+ executor.start_polling(dp, skip_updates=True)
diff --git a/examples/separate_api_route_example.py b/examples/separate_api_route_example.py
new file mode 100644
index 00000000..2f3cc61c
--- /dev/null
+++ b/examples/separate_api_route_example.py
@@ -0,0 +1,34 @@
+# NOTE: This is an example of an integration between
+# externally created Application object and the aiogram's dispatcher
+# This can be used for a custom route, for instance
+
+from aiogram import Bot, Dispatcher, types
+from aiogram.dispatcher.webhook import configure_app
+from aiohttp import web
+
+
+bot = Bot(token=config.bot_token)
+dp = Dispatcher(bot)
+
+
+
+@dp.message_handler(commands=["start"])
+async def cmd_start(message: types.Message):
+ await message.reply("start!")
+
+
+# handle /api route
+async def api_handler(request):
+ return web.json_response({"status": "OK"}, status=200)
+
+
+app = web.Application()
+# add a custom route
+app.add_routes([web.post('/api', api_handler)])
+# every request to /bot route will be retransmitted to dispatcher to be handled
+# as a bot update
+configure_app(dp, app, "/bot")
+
+
+if __name__ == '__main__':
+ web.run_app(app, port=9000)
diff --git a/examples/text_filter_example.py b/examples/text_filter_example.py
index 60d631e3..7a90d6f0 100644
--- a/examples/text_filter_example.py
+++ b/examples/text_filter_example.py
@@ -18,7 +18,7 @@ bot = Bot(token=API_TOKEN)
dp = Dispatcher(bot)
-# if the text from user in the list
+# if the text is equal to any string in the list
@dp.message_handler(text=['text1', 'text2'])
async def text_in_handler(message: types.Message):
await message.answer("The message text equals to one of in the list!")
diff --git a/requirements.txt b/requirements.txt
index 7f7dc1ac..6f393257 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,3 @@
-aiohttp>=3.5.4,<4.0.0
-Babel>=2.6.0
-certifi>=2019.3.9
+aiohttp>=3.7.2,<4.0.0
+Babel>=2.8.0
+certifi>=2020.6.20
diff --git a/setup.py b/setup.py
index b21b4e57..6f12b437 100755
--- a/setup.py
+++ b/setup.py
@@ -57,16 +57,17 @@ setup(
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
'Topic :: Software Development :: Libraries :: Application Frameworks',
],
install_requires=[
- 'aiohttp>=3.5.4,<4.0.0',
- 'Babel>=2.6.0',
- 'certifi>=2019.3.9',
+ 'aiohttp>=3.7.2,<4.0.0',
+ 'Babel>=2.8.0',
+ 'certifi>=2020.6.20',
],
extras_require={
'proxy': [
- 'aiohttp-socks>=0.3.4,<0.4.0',
+ 'aiohttp-socks>=0.5.3,<0.6.0',
],
'fast': [
'uvloop>=0.14.0,<0.15.0',
diff --git a/tests/contrib/fsm_storage/test_redis.py b/tests/contrib/fsm_storage/test_redis.py
deleted file mode 100644
index 527c905e..00000000
--- a/tests/contrib/fsm_storage/test_redis.py
+++ /dev/null
@@ -1,33 +0,0 @@
-import pytest
-
-from aiogram.contrib.fsm_storage.redis import RedisStorage2
-
-
-@pytest.fixture()
-async def store(redis_options):
- s = RedisStorage2(**redis_options)
- try:
- yield s
- finally:
- conn = await s.redis()
- await conn.flushdb()
- await s.close()
- await s.wait_closed()
-
-
-@pytest.mark.redis
-class TestRedisStorage2:
- @pytest.mark.asyncio
- async def test_set_get(self, store):
- assert await store.get_data(chat='1234') == {}
- await store.set_data(chat='1234', data={'foo': 'bar'})
- assert await store.get_data(chat='1234') == {'foo': 'bar'}
-
- @pytest.mark.asyncio
- async def test_close_and_open_connection(self, store):
- await store.set_data(chat='1234', data={'foo': 'bar'})
- assert await store.get_data(chat='1234') == {'foo': 'bar'}
- pool_id = id(store._redis)
- await store.close()
- assert await store.get_data(chat='1234') == {'foo': 'bar'} # new pool was opened at this point
- assert id(store._redis) != pool_id
diff --git a/tests/contrib/fsm_storage/test_storage.py b/tests/contrib/fsm_storage/test_storage.py
new file mode 100644
index 00000000..0cde2de2
--- /dev/null
+++ b/tests/contrib/fsm_storage/test_storage.py
@@ -0,0 +1,79 @@
+import pytest
+
+from aiogram.contrib.fsm_storage.memory import MemoryStorage
+from aiogram.contrib.fsm_storage.redis import RedisStorage2, RedisStorage
+
+
+@pytest.fixture()
+@pytest.mark.redis
+async def redis_store(redis_options):
+ s = RedisStorage(**redis_options)
+ try:
+ yield s
+ finally:
+ conn = await s.redis()
+ await conn.execute('FLUSHDB')
+ await s.close()
+ await s.wait_closed()
+
+
+@pytest.fixture()
+@pytest.mark.redis
+async def redis_store2(redis_options):
+ s = RedisStorage2(**redis_options)
+ try:
+ yield s
+ finally:
+ conn = await s.redis()
+ await conn.flushdb()
+ await s.close()
+ await s.wait_closed()
+
+
+@pytest.fixture()
+async def memory_store():
+ yield MemoryStorage()
+
+
+@pytest.mark.parametrize(
+ "store", [
+ pytest.lazy_fixture('redis_store'),
+ pytest.lazy_fixture('redis_store2'),
+ pytest.lazy_fixture('memory_store'),
+ ]
+)
+class TestStorage:
+ @pytest.mark.asyncio
+ async def test_set_get(self, store):
+ assert await store.get_data(chat='1234') == {}
+ await store.set_data(chat='1234', data={'foo': 'bar'})
+ assert await store.get_data(chat='1234') == {'foo': 'bar'}
+
+ @pytest.mark.asyncio
+ async def test_reset(self, store):
+ await store.set_data(chat='1234', data={'foo': 'bar'})
+ await store.reset_data(chat='1234')
+ assert await store.get_data(chat='1234') == {}
+
+ @pytest.mark.asyncio
+ async def test_reset_empty(self, store):
+ await store.reset_data(chat='1234')
+ assert await store.get_data(chat='1234') == {}
+
+
+@pytest.mark.parametrize(
+ "store", [
+ pytest.lazy_fixture('redis_store'),
+ pytest.lazy_fixture('redis_store2'),
+ ]
+)
+class TestRedisStorage2:
+ @pytest.mark.asyncio
+ async def test_close_and_open_connection(self, store):
+ await store.set_data(chat='1234', data={'foo': 'bar'})
+ assert await store.get_data(chat='1234') == {'foo': 'bar'}
+ pool_id = id(store._redis)
+ await store.close()
+ assert await store.get_data(chat='1234') == {
+ 'foo': 'bar'} # new pool was opened at this point
+ assert id(store._redis) != pool_id
diff --git a/tests/test_bot.py b/tests/test_bot.py
index cf1c3c3b..61abe962 100644
--- a/tests/test_bot.py
+++ b/tests/test_bot.py
@@ -6,87 +6,103 @@ from . import FakeTelegram, TOKEN, BOT_ID
pytestmark = pytest.mark.asyncio
-@pytest.yield_fixture(name='bot')
-async def bot_fixture(event_loop):
+@pytest.fixture(name='bot')
+async def bot_fixture():
""" Bot fixture """
- _bot = Bot(TOKEN, loop=event_loop, parse_mode=types.ParseMode.MARKDOWN)
+ _bot = Bot(TOKEN, parse_mode=types.ParseMode.MARKDOWN_V2)
yield _bot
await _bot.close()
-async def test_get_me(bot: Bot, event_loop):
+async def test_get_me(bot: Bot):
""" getMe method test """
from .types.dataset import USER
user = types.User(**USER)
- async with FakeTelegram(message_data=USER, loop=event_loop):
+ async with FakeTelegram(message_data=USER):
result = await bot.me
assert result == user
-async def test_send_message(bot: Bot, event_loop):
+async def test_log_out(bot: Bot):
+ """ logOut method test """
+
+ async with FakeTelegram(message_data=True):
+ result = await bot.log_out()
+ assert result is True
+
+
+async def test_close_bot(bot: Bot):
+ """ close method test """
+
+ async with FakeTelegram(message_data=True):
+ result = await bot.close_bot()
+ assert result is True
+
+
+async def test_send_message(bot: Bot):
""" sendMessage method test """
from .types.dataset import MESSAGE
msg = types.Message(**MESSAGE)
- async with FakeTelegram(message_data=MESSAGE, loop=event_loop):
+ async with FakeTelegram(message_data=MESSAGE):
result = await bot.send_message(chat_id=msg.chat.id, text=msg.text)
assert result == msg
-async def test_forward_message(bot: Bot, event_loop):
+async def test_forward_message(bot: Bot):
""" forwardMessage method test """
from .types.dataset import FORWARDED_MESSAGE
msg = types.Message(**FORWARDED_MESSAGE)
- async with FakeTelegram(message_data=FORWARDED_MESSAGE, loop=event_loop):
+ async with FakeTelegram(message_data=FORWARDED_MESSAGE):
result = await bot.forward_message(chat_id=msg.chat.id, from_chat_id=msg.forward_from_chat.id,
message_id=msg.forward_from_message_id)
assert result == msg
-async def test_send_photo(bot: Bot, event_loop):
+async def test_send_photo(bot: Bot):
""" sendPhoto method test with file_id """
from .types.dataset import MESSAGE_WITH_PHOTO, PHOTO
msg = types.Message(**MESSAGE_WITH_PHOTO)
photo = types.PhotoSize(**PHOTO)
- async with FakeTelegram(message_data=MESSAGE_WITH_PHOTO, loop=event_loop):
+ async with FakeTelegram(message_data=MESSAGE_WITH_PHOTO):
result = await bot.send_photo(msg.chat.id, photo=photo.file_id, caption=msg.caption,
parse_mode=types.ParseMode.HTML, disable_notification=False)
assert result == msg
-async def test_send_audio(bot: Bot, event_loop):
+async def test_send_audio(bot: Bot):
""" sendAudio method test with file_id """
from .types.dataset import MESSAGE_WITH_AUDIO
msg = types.Message(**MESSAGE_WITH_AUDIO)
- async with FakeTelegram(message_data=MESSAGE_WITH_AUDIO, loop=event_loop):
+ async with FakeTelegram(message_data=MESSAGE_WITH_AUDIO):
result = await bot.send_audio(chat_id=msg.chat.id, audio=msg.audio.file_id, caption=msg.caption,
parse_mode=types.ParseMode.HTML, duration=msg.audio.duration,
performer=msg.audio.performer, title=msg.audio.title, disable_notification=False)
assert result == msg
-async def test_send_document(bot: Bot, event_loop):
+async def test_send_document(bot: Bot):
""" sendDocument method test with file_id """
from .types.dataset import MESSAGE_WITH_DOCUMENT
msg = types.Message(**MESSAGE_WITH_DOCUMENT)
- async with FakeTelegram(message_data=MESSAGE_WITH_DOCUMENT, loop=event_loop):
+ async with FakeTelegram(message_data=MESSAGE_WITH_DOCUMENT):
result = await bot.send_document(chat_id=msg.chat.id, document=msg.document.file_id, caption=msg.caption,
parse_mode=types.ParseMode.HTML, disable_notification=False)
assert result == msg
-async def test_send_video(bot: Bot, event_loop):
+async def test_send_video(bot: Bot):
""" sendVideo method test with file_id """
from .types.dataset import MESSAGE_WITH_VIDEO, VIDEO
msg = types.Message(**MESSAGE_WITH_VIDEO)
video = types.Video(**VIDEO)
- async with FakeTelegram(message_data=MESSAGE_WITH_VIDEO, loop=event_loop):
+ async with FakeTelegram(message_data=MESSAGE_WITH_VIDEO):
result = await bot.send_video(chat_id=msg.chat.id, video=video.file_id, duration=video.duration,
width=video.width, height=video.height, caption=msg.caption,
parse_mode=types.ParseMode.HTML, supports_streaming=True,
@@ -94,204 +110,204 @@ async def test_send_video(bot: Bot, event_loop):
assert result == msg
-async def test_send_voice(bot: Bot, event_loop):
+async def test_send_voice(bot: Bot):
""" sendVoice method test with file_id """
from .types.dataset import MESSAGE_WITH_VOICE, VOICE
msg = types.Message(**MESSAGE_WITH_VOICE)
voice = types.Voice(**VOICE)
- async with FakeTelegram(message_data=MESSAGE_WITH_VOICE, loop=event_loop):
+ async with FakeTelegram(message_data=MESSAGE_WITH_VOICE):
result = await bot.send_voice(chat_id=msg.chat.id, voice=voice.file_id, caption=msg.caption,
parse_mode=types.ParseMode.HTML, duration=voice.duration,
disable_notification=False)
assert result == msg
-async def test_send_video_note(bot: Bot, event_loop):
+async def test_send_video_note(bot: Bot):
""" sendVideoNote method test with file_id """
from .types.dataset import MESSAGE_WITH_VIDEO_NOTE, VIDEO_NOTE
msg = types.Message(**MESSAGE_WITH_VIDEO_NOTE)
video_note = types.VideoNote(**VIDEO_NOTE)
- async with FakeTelegram(message_data=MESSAGE_WITH_VIDEO_NOTE, loop=event_loop):
+ async with FakeTelegram(message_data=MESSAGE_WITH_VIDEO_NOTE):
result = await bot.send_video_note(chat_id=msg.chat.id, video_note=video_note.file_id,
duration=video_note.duration, length=video_note.length,
disable_notification=False)
assert result == msg
-async def test_send_media_group(bot: Bot, event_loop):
+async def test_send_media_group(bot: Bot):
""" sendMediaGroup method test with file_id """
from .types.dataset import MESSAGE_WITH_MEDIA_GROUP, PHOTO
msg = types.Message(**MESSAGE_WITH_MEDIA_GROUP)
photo = types.PhotoSize(**PHOTO)
media = [types.InputMediaPhoto(media=photo.file_id), types.InputMediaPhoto(media=photo.file_id)]
- async with FakeTelegram(message_data=[MESSAGE_WITH_MEDIA_GROUP, MESSAGE_WITH_MEDIA_GROUP], loop=event_loop):
+ async with FakeTelegram(message_data=[MESSAGE_WITH_MEDIA_GROUP, MESSAGE_WITH_MEDIA_GROUP]):
result = await bot.send_media_group(msg.chat.id, media=media, disable_notification=False)
assert len(result) == len(media)
assert result.pop().media_group_id
-async def test_send_location(bot: Bot, event_loop):
+async def test_send_location(bot: Bot):
""" sendLocation method test """
from .types.dataset import MESSAGE_WITH_LOCATION, LOCATION
msg = types.Message(**MESSAGE_WITH_LOCATION)
location = types.Location(**LOCATION)
- async with FakeTelegram(message_data=MESSAGE_WITH_LOCATION, loop=event_loop):
+ async with FakeTelegram(message_data=MESSAGE_WITH_LOCATION):
result = await bot.send_location(msg.chat.id, latitude=location.latitude, longitude=location.longitude,
live_period=10, disable_notification=False)
assert result == msg
-async def test_edit_message_live_location_by_bot(bot: Bot, event_loop):
+async def test_edit_message_live_location_by_bot(bot: Bot):
""" editMessageLiveLocation method test """
from .types.dataset import MESSAGE_WITH_LOCATION, LOCATION
msg = types.Message(**MESSAGE_WITH_LOCATION)
location = types.Location(**LOCATION)
# editing bot message
- async with FakeTelegram(message_data=MESSAGE_WITH_LOCATION, loop=event_loop):
+ async with FakeTelegram(message_data=MESSAGE_WITH_LOCATION):
result = await bot.edit_message_live_location(chat_id=msg.chat.id, message_id=msg.message_id,
latitude=location.latitude, longitude=location.longitude)
assert result == msg
-async def test_edit_message_live_location_by_user(bot: Bot, event_loop):
+async def test_edit_message_live_location_by_user(bot: Bot):
""" editMessageLiveLocation method test """
from .types.dataset import MESSAGE_WITH_LOCATION, LOCATION
msg = types.Message(**MESSAGE_WITH_LOCATION)
location = types.Location(**LOCATION)
# editing user's message
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.edit_message_live_location(chat_id=msg.chat.id, message_id=msg.message_id,
latitude=location.latitude, longitude=location.longitude)
assert isinstance(result, bool) and result is True
-async def test_stop_message_live_location_by_bot(bot: Bot, event_loop):
+async def test_stop_message_live_location_by_bot(bot: Bot):
""" stopMessageLiveLocation method test """
from .types.dataset import MESSAGE_WITH_LOCATION
msg = types.Message(**MESSAGE_WITH_LOCATION)
# stopping bot message
- async with FakeTelegram(message_data=MESSAGE_WITH_LOCATION, loop=event_loop):
+ async with FakeTelegram(message_data=MESSAGE_WITH_LOCATION):
result = await bot.stop_message_live_location(chat_id=msg.chat.id, message_id=msg.message_id)
assert result == msg
-async def test_stop_message_live_location_by_user(bot: Bot, event_loop):
+async def test_stop_message_live_location_by_user(bot: Bot):
""" stopMessageLiveLocation method test """
from .types.dataset import MESSAGE_WITH_LOCATION
msg = types.Message(**MESSAGE_WITH_LOCATION)
# stopping user's message
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.stop_message_live_location(chat_id=msg.chat.id, message_id=msg.message_id)
assert isinstance(result, bool)
assert result is True
-async def test_send_venue(bot: Bot, event_loop):
+async def test_send_venue(bot: Bot):
""" sendVenue method test """
from .types.dataset import MESSAGE_WITH_VENUE, VENUE, LOCATION
msg = types.Message(**MESSAGE_WITH_VENUE)
location = types.Location(**LOCATION)
venue = types.Venue(**VENUE)
- async with FakeTelegram(message_data=MESSAGE_WITH_VENUE, loop=event_loop):
+ async with FakeTelegram(message_data=MESSAGE_WITH_VENUE):
result = await bot.send_venue(msg.chat.id, latitude=location.latitude, longitude=location.longitude,
title=venue.title, address=venue.address, foursquare_id=venue.foursquare_id,
disable_notification=False)
assert result == msg
-async def test_send_contact(bot: Bot, event_loop):
+async def test_send_contact(bot: Bot):
""" sendContact method test """
from .types.dataset import MESSAGE_WITH_CONTACT, CONTACT
msg = types.Message(**MESSAGE_WITH_CONTACT)
contact = types.Contact(**CONTACT)
- async with FakeTelegram(message_data=MESSAGE_WITH_CONTACT, loop=event_loop):
+ async with FakeTelegram(message_data=MESSAGE_WITH_CONTACT):
result = await bot.send_contact(msg.chat.id, phone_number=contact.phone_number, first_name=contact.first_name,
last_name=contact.last_name, disable_notification=False)
assert result == msg
-async def test_send_dice(bot: Bot, event_loop):
+async def test_send_dice(bot: Bot):
""" sendDice method test """
from .types.dataset import MESSAGE_WITH_DICE
msg = types.Message(**MESSAGE_WITH_DICE)
- async with FakeTelegram(message_data=MESSAGE_WITH_DICE, loop=event_loop):
+ async with FakeTelegram(message_data=MESSAGE_WITH_DICE):
result = await bot.send_dice(msg.chat.id, disable_notification=False)
assert result == msg
-async def test_send_chat_action(bot: Bot, event_loop):
+async def test_send_chat_action(bot: Bot):
""" sendChatAction method test """
from .types.dataset import CHAT
chat = types.Chat(**CHAT)
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.send_chat_action(chat_id=chat.id, action=types.ChatActions.TYPING)
assert isinstance(result, bool)
assert result is True
-async def test_get_user_profile_photo(bot: Bot, event_loop):
+async def test_get_user_profile_photo(bot: Bot):
""" getUserProfilePhotos method test """
from .types.dataset import USER_PROFILE_PHOTOS, USER
user = types.User(**USER)
- async with FakeTelegram(message_data=USER_PROFILE_PHOTOS, loop=event_loop):
+ async with FakeTelegram(message_data=USER_PROFILE_PHOTOS):
result = await bot.get_user_profile_photos(user_id=user.id, offset=1, limit=1)
assert isinstance(result, types.UserProfilePhotos)
-async def test_get_file(bot: Bot, event_loop):
+async def test_get_file(bot: Bot):
""" getFile method test """
from .types.dataset import FILE
file = types.File(**FILE)
- async with FakeTelegram(message_data=FILE, loop=event_loop):
+ async with FakeTelegram(message_data=FILE):
result = await bot.get_file(file_id=file.file_id)
assert isinstance(result, types.File)
-async def test_kick_chat_member(bot: Bot, event_loop):
+async def test_kick_chat_member(bot: Bot):
""" kickChatMember method test """
from .types.dataset import USER, CHAT
user = types.User(**USER)
chat = types.Chat(**CHAT)
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.kick_chat_member(chat_id=chat.id, user_id=user.id, until_date=123)
assert isinstance(result, bool)
assert result is True
-async def test_unban_chat_member(bot: Bot, event_loop):
+async def test_unban_chat_member(bot: Bot):
""" unbanChatMember method test """
from .types.dataset import USER, CHAT
user = types.User(**USER)
chat = types.Chat(**CHAT)
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.unban_chat_member(chat_id=chat.id, user_id=user.id)
assert isinstance(result, bool)
assert result is True
-async def test_restrict_chat_member(bot: Bot, event_loop):
+async def test_restrict_chat_member(bot: Bot):
""" restrictChatMember method test """
from .types.dataset import USER, CHAT
user = types.User(**USER)
chat = types.Chat(**CHAT)
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.restrict_chat_member(
chat_id=chat.id,
user_id=user.id,
@@ -305,13 +321,13 @@ async def test_restrict_chat_member(bot: Bot, event_loop):
assert result is True
-async def test_promote_chat_member(bot: Bot, event_loop):
+async def test_promote_chat_member(bot: Bot):
""" promoteChatMember method test """
from .types.dataset import USER, CHAT
user = types.User(**USER)
chat = types.Chat(**CHAT)
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.promote_chat_member(chat_id=chat.id, user_id=user.id, can_change_info=True,
can_delete_messages=True, can_edit_messages=True,
can_invite_users=True, can_pin_messages=True, can_post_messages=True,
@@ -320,208 +336,208 @@ async def test_promote_chat_member(bot: Bot, event_loop):
assert result is True
-async def test_export_chat_invite_link(bot: Bot, event_loop):
+async def test_export_chat_invite_link(bot: Bot):
""" exportChatInviteLink method test """
from .types.dataset import CHAT, INVITE_LINK
chat = types.Chat(**CHAT)
- async with FakeTelegram(message_data=INVITE_LINK, loop=event_loop):
+ async with FakeTelegram(message_data=INVITE_LINK):
result = await bot.export_chat_invite_link(chat_id=chat.id)
assert result == INVITE_LINK
-async def test_delete_chat_photo(bot: Bot, event_loop):
+async def test_delete_chat_photo(bot: Bot):
""" deleteChatPhoto method test """
from .types.dataset import CHAT
chat = types.Chat(**CHAT)
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.delete_chat_photo(chat_id=chat.id)
assert isinstance(result, bool)
assert result is True
-async def test_set_chat_title(bot: Bot, event_loop):
+async def test_set_chat_title(bot: Bot):
""" setChatTitle method test """
from .types.dataset import CHAT
chat = types.Chat(**CHAT)
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.set_chat_title(chat_id=chat.id, title='Test title')
assert isinstance(result, bool)
assert result is True
-async def test_set_chat_description(bot: Bot, event_loop):
+async def test_set_chat_description(bot: Bot):
""" setChatDescription method test """
from .types.dataset import CHAT
chat = types.Chat(**CHAT)
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.set_chat_description(chat_id=chat.id, description='Test description')
assert isinstance(result, bool)
assert result is True
-async def test_pin_chat_message(bot: Bot, event_loop):
+async def test_pin_chat_message(bot: Bot):
""" pinChatMessage method test """
from .types.dataset import MESSAGE
message = types.Message(**MESSAGE)
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.pin_chat_message(chat_id=message.chat.id, message_id=message.message_id,
disable_notification=False)
assert isinstance(result, bool)
assert result is True
-async def test_unpin_chat_message(bot: Bot, event_loop):
+async def test_unpin_chat_message(bot: Bot):
""" unpinChatMessage method test """
from .types.dataset import CHAT
chat = types.Chat(**CHAT)
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.unpin_chat_message(chat_id=chat.id)
assert isinstance(result, bool)
assert result is True
-async def test_leave_chat(bot: Bot, event_loop):
+async def test_leave_chat(bot: Bot):
""" leaveChat method test """
from .types.dataset import CHAT
chat = types.Chat(**CHAT)
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.leave_chat(chat_id=chat.id)
assert isinstance(result, bool)
assert result is True
-async def test_get_chat(bot: Bot, event_loop):
+async def test_get_chat(bot: Bot):
""" getChat method test """
from .types.dataset import CHAT
chat = types.Chat(**CHAT)
- async with FakeTelegram(message_data=CHAT, loop=event_loop):
+ async with FakeTelegram(message_data=CHAT):
result = await bot.get_chat(chat_id=chat.id)
assert result == chat
-async def test_get_chat_administrators(bot: Bot, event_loop):
+async def test_get_chat_administrators(bot: Bot):
""" getChatAdministrators method test """
from .types.dataset import CHAT, CHAT_MEMBER
chat = types.Chat(**CHAT)
- member = types.ChatMember(**CHAT_MEMBER)
+ member = types.ChatMember.resolve(**CHAT_MEMBER)
- async with FakeTelegram(message_data=[CHAT_MEMBER, CHAT_MEMBER], loop=event_loop):
+ async with FakeTelegram(message_data=[CHAT_MEMBER, CHAT_MEMBER]):
result = await bot.get_chat_administrators(chat_id=chat.id)
assert result[0] == member
assert len(result) == 2
-async def test_get_chat_members_count(bot: Bot, event_loop):
+async def test_get_chat_member_count(bot: Bot):
""" getChatMembersCount method test """
from .types.dataset import CHAT
chat = types.Chat(**CHAT)
count = 5
- async with FakeTelegram(message_data=count, loop=event_loop):
- result = await bot.get_chat_members_count(chat_id=chat.id)
+ async with FakeTelegram(message_data=count):
+ result = await bot.get_chat_member_count(chat_id=chat.id)
assert result == count
-async def test_get_chat_member(bot: Bot, event_loop):
+async def test_get_chat_member(bot: Bot):
""" getChatMember method test """
from .types.dataset import CHAT, CHAT_MEMBER
chat = types.Chat(**CHAT)
- member = types.ChatMember(**CHAT_MEMBER)
+ member = types.ChatMember.resolve(**CHAT_MEMBER)
- async with FakeTelegram(message_data=CHAT_MEMBER, loop=event_loop):
+ async with FakeTelegram(message_data=CHAT_MEMBER):
result = await bot.get_chat_member(chat_id=chat.id, user_id=member.user.id)
assert isinstance(result, types.ChatMember)
assert result == member
-async def test_set_chat_sticker_set(bot: Bot, event_loop):
+async def test_set_chat_sticker_set(bot: Bot):
""" setChatStickerSet method test """
from .types.dataset import CHAT
chat = types.Chat(**CHAT)
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.set_chat_sticker_set(chat_id=chat.id, sticker_set_name='aiogram_stickers')
assert isinstance(result, bool)
assert result is True
-async def test_delete_chat_sticker_set(bot: Bot, event_loop):
+async def test_delete_chat_sticker_set(bot: Bot):
""" setChatStickerSet method test """
from .types.dataset import CHAT
chat = types.Chat(**CHAT)
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.delete_chat_sticker_set(chat_id=chat.id)
assert isinstance(result, bool)
assert result is True
-async def test_answer_callback_query(bot: Bot, event_loop):
+async def test_answer_callback_query(bot: Bot):
""" answerCallbackQuery method test """
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.answer_callback_query(callback_query_id='QuERyId', text='Test Answer')
assert isinstance(result, bool)
assert result is True
-async def test_set_my_commands(bot: Bot, event_loop):
+async def test_set_my_commands(bot: Bot):
""" setMyCommands method test """
from .types.dataset import BOT_COMMAND
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
commands = [types.BotCommand(**BOT_COMMAND), types.BotCommand(**BOT_COMMAND)]
result = await bot.set_my_commands(commands)
assert isinstance(result, bool)
assert result is True
-async def test_get_my_commands(bot: Bot, event_loop):
+async def test_get_my_commands(bot: Bot):
""" getMyCommands method test """
from .types.dataset import BOT_COMMAND
command = types.BotCommand(**BOT_COMMAND)
commands = [command, command]
- async with FakeTelegram(message_data=commands, loop=event_loop):
+ async with FakeTelegram(message_data=commands):
result = await bot.get_my_commands()
assert isinstance(result, list)
assert all([isinstance(command, types.BotCommand) for command in result])
-async def test_edit_message_text_by_bot(bot: Bot, event_loop):
+async def test_edit_message_text_by_bot(bot: Bot):
""" editMessageText method test """
from .types.dataset import EDITED_MESSAGE
msg = types.Message(**EDITED_MESSAGE)
# message by bot
- async with FakeTelegram(message_data=EDITED_MESSAGE, loop=event_loop):
+ async with FakeTelegram(message_data=EDITED_MESSAGE):
result = await bot.edit_message_text(text=msg.text, chat_id=msg.chat.id, message_id=msg.message_id)
assert result == msg
-async def test_edit_message_text_by_user(bot: Bot, event_loop):
+async def test_edit_message_text_by_user(bot: Bot):
""" editMessageText method test """
from .types.dataset import EDITED_MESSAGE
msg = types.Message(**EDITED_MESSAGE)
# message by user
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.edit_message_text(text=msg.text, chat_id=msg.chat.id, message_id=msg.message_id)
assert isinstance(result, bool)
assert result is True
-async def test_set_sticker_set_thumb(bot: Bot, event_loop):
+async def test_set_sticker_set_thumb(bot: Bot):
""" setStickerSetThumb method test """
- async with FakeTelegram(message_data=True, loop=event_loop):
+ async with FakeTelegram(message_data=True):
result = await bot.set_sticker_set_thumb(name='test', user_id=123456789, thumb='file_id')
assert isinstance(result, bool)
assert result is True
diff --git a/tests/test_dispatcher.py b/tests/test_dispatcher.py
index 6ebaf472..81ae565c 100644
--- a/tests/test_dispatcher.py
+++ b/tests/test_dispatcher.py
@@ -5,11 +5,10 @@ from aiogram import Dispatcher, Bot
pytestmark = pytest.mark.asyncio
-@pytest.yield_fixture()
-async def bot(event_loop):
+@pytest.fixture(name='bot')
+async def bot_fixture():
""" Bot fixture """
- _bot = Bot(token='123456789:AABBCCDDEEFFaabbccddeeff-1234567890',
- loop=event_loop)
+ _bot = Bot(token='123456789:AABBCCDDEEFFaabbccddeeff-1234567890')
yield _bot
await _bot.close()
diff --git a/tests/test_dispatcher/test_handler.py b/tests/test_dispatcher/test_handler.py
new file mode 100644
index 00000000..b823c8f8
--- /dev/null
+++ b/tests/test_dispatcher/test_handler.py
@@ -0,0 +1,66 @@
+import functools
+
+import pytest
+
+from aiogram.dispatcher.handler import Handler, _check_spec, _get_spec
+
+
+def callback1(foo: int, bar: int, baz: int):
+ return locals()
+
+
+async def callback2(foo: int, bar: int, baz: int):
+ return locals()
+
+
+async def callback3(foo: int, **kwargs):
+ return locals()
+
+
+class TestHandlerObj:
+ def test_init_decorated(self):
+ def decorator(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ return func(*args, **kwargs)
+
+ return wrapper
+
+ @decorator
+ def callback1(foo, bar, baz):
+ pass
+
+ @decorator
+ @decorator
+ def callback2(foo, bar, baz):
+ pass
+
+ obj1 = Handler.HandlerObj(callback1, _get_spec(callback1))
+ obj2 = Handler.HandlerObj(callback2, _get_spec(callback2))
+
+ assert set(obj1.spec.args) == {"foo", "bar", "baz"}
+ assert obj1.handler == callback1
+ assert set(obj2.spec.args) == {"foo", "bar", "baz"}
+ assert obj2.handler == callback2
+
+ @pytest.mark.parametrize(
+ "callback,kwargs,result",
+ [
+ pytest.param(
+ callback1, {"foo": 42, "spam": True, "baz": "fuz"}, {"foo": 42, "baz": "fuz"}
+ ),
+ pytest.param(
+ callback2,
+ {"foo": 42, "spam": True, "baz": "fuz", "bar": "test"},
+ {"foo": 42, "baz": "fuz", "bar": "test"},
+ ),
+ pytest.param(
+ callback3,
+ {"foo": 42, "spam": True, "baz": "fuz", "bar": "test"},
+ {"foo": 42, "spam": True, "baz": "fuz", "bar": "test"},
+ ),
+ ],
+ )
+ def test__check_spec(self, callback, kwargs, result):
+ spec = _get_spec(callback)
+ assert _check_spec(spec, kwargs) == result
diff --git a/tests/test_message.py b/tests/test_message.py
index 32168d57..6fca789f 100644
--- a/tests/test_message.py
+++ b/tests/test_message.py
@@ -8,16 +8,16 @@ from . import FakeTelegram, TOKEN
pytestmark = pytest.mark.asyncio
-@pytest.yield_fixture()
-async def bot(event_loop):
+@pytest.fixture(name='bot')
+async def bot_fixture():
""" Bot fixture """
- _bot = Bot(TOKEN, loop=event_loop, parse_mode=types.ParseMode.HTML)
+ _bot = Bot(TOKEN, parse_mode=types.ParseMode.HTML)
yield _bot
await _bot.close()
-@pytest.yield_fixture()
-async def message(bot, event_loop):
+@pytest.fixture()
+async def message(bot):
"""
Message fixture
:param bot: Telegram bot fixture
@@ -28,7 +28,7 @@ async def message(bot, event_loop):
from .types.dataset import MESSAGE
msg = types.Message(**MESSAGE)
- async with FakeTelegram(message_data=MESSAGE, loop=event_loop):
+ async with FakeTelegram(message_data=MESSAGE):
_message = await bot.send_message(chat_id=msg.chat.id, text=msg.text)
yield _message
diff --git a/tests/test_utils/test_deep_linking.py b/tests/test_utils/test_deep_linking.py
index a1d01e4e..f4aa14f1 100644
--- a/tests/test_utils/test_deep_linking.py
+++ b/tests/test_utils/test_deep_linking.py
@@ -1,7 +1,11 @@
import pytest
-from aiogram.utils.deep_linking import decode_payload, encode_payload, filter_payload
-from aiogram.utils.deep_linking import get_start_link, get_startgroup_link
+from aiogram.utils.deep_linking import (
+ decode_payload,
+ encode_payload,
+ get_start_link,
+ get_startgroup_link,
+)
from tests.types import dataset
# enable asyncio mode
@@ -17,9 +21,11 @@ PAYLOADS = [
WRONG_PAYLOADS = [
'@BotFather',
+ "Some:special$characters#=",
'spaces spaces spaces',
1234567890123456789.0,
]
+USERNAME = dataset.USER["username"]
@pytest.fixture(params=PAYLOADS, name='payload')
@@ -47,7 +53,7 @@ def get_bot_user_fixture(monkeypatch):
class TestDeepLinking:
async def test_get_start_link(self, payload):
link = await get_start_link(payload)
- assert link == f'https://t.me/{dataset.USER["username"]}?start={payload}'
+ assert link == f'https://t.me/{USERNAME}?start={payload}'
async def test_wrong_symbols(self, wrong_payload):
with pytest.raises(ValueError):
@@ -55,20 +61,29 @@ class TestDeepLinking:
async def test_get_startgroup_link(self, payload):
link = await get_startgroup_link(payload)
- assert link == f'https://t.me/{dataset.USER["username"]}?startgroup={payload}'
+ assert link == f'https://t.me/{USERNAME}?startgroup={payload}'
async def test_filter_encode_and_decode(self, payload):
- _payload = filter_payload(payload)
- encoded = encode_payload(_payload)
+ encoded = encode_payload(payload)
decoded = decode_payload(encoded)
assert decoded == str(payload)
- async def test_get_start_link_with_encoding(self, payload):
+ async def test_get_start_link_with_encoding(self, wrong_payload):
# define link
- link = await get_start_link(payload, encode=True)
+ link = await get_start_link(wrong_payload, encode=True)
# define reference link
- payload = filter_payload(payload)
- encoded_payload = encode_payload(payload)
+ encoded_payload = encode_payload(wrong_payload)
- assert link == f'https://t.me/{dataset.USER["username"]}?start={encoded_payload}'
+ assert link == f'https://t.me/{USERNAME}?start={encoded_payload}'
+
+ async def test_64_len_payload(self):
+ payload = "p" * 64
+ link = await get_start_link(payload)
+ assert link
+
+ async def test_too_long_payload(self):
+ payload = "p" * 65
+ print(payload, len(payload))
+ with pytest.raises(ValueError):
+ await get_start_link(payload)
diff --git a/tests/test_utils/test_deprecated.py b/tests/test_utils/test_deprecated.py
new file mode 100644
index 00000000..114d6810
--- /dev/null
+++ b/tests/test_utils/test_deprecated.py
@@ -0,0 +1,14 @@
+import pytest
+
+from aiogram.utils.deprecated import DeprecatedReadOnlyClassVar
+
+
+def test_DeprecatedReadOnlyClassVarCD():
+ assert DeprecatedReadOnlyClassVar.__slots__ == ("_new_value_getter", "_warning_message")
+
+ new_value_of_deprecated_cls_cd = "mpa"
+ pseudo_owner_cls = type("OpekaCla$$", (), {})
+ deprecated_cd = DeprecatedReadOnlyClassVar("mopekaa", lambda owner: new_value_of_deprecated_cls_cd)
+
+ with pytest.warns(DeprecationWarning):
+ assert deprecated_cd.__get__(None, pseudo_owner_cls) == new_value_of_deprecated_cls_cd
diff --git a/tests/test_utils/test_text_decorations.py b/tests/test_utils/test_text_decorations.py
new file mode 100644
index 00000000..cc724dd7
--- /dev/null
+++ b/tests/test_utils/test_text_decorations.py
@@ -0,0 +1,25 @@
+from aiogram.types import MessageEntity, MessageEntityType
+from aiogram.utils import text_decorations
+
+
+class TestTextDecorations:
+ def test_unparse_entities_normal_text(self):
+ assert text_decorations.markdown_decoration.unparse(
+ "hi i'm bold and italic and still bold",
+ entities=[
+ MessageEntity(offset=3, length=34, type=MessageEntityType.BOLD),
+ MessageEntity(offset=12, length=10, type=MessageEntityType.ITALIC),
+ ]
+ ) == "hi *i'm bold _\rand italic_\r and still bold*"
+
+ def test_unparse_entities_emoji_text(self):
+ """
+ emoji is encoded as two chars in json
+ """
+ assert text_decorations.markdown_decoration.unparse(
+ "🚀 i'm bold and italic and still bold",
+ entities=[
+ MessageEntity(offset=3, length=34, type=MessageEntityType.BOLD),
+ MessageEntity(offset=12, length=10, type=MessageEntityType.ITALIC),
+ ]
+ ) == "🚀 *i'm bold _\rand italic_\r and still bold*"
diff --git a/tests/types/test_chat_member.py b/tests/types/test_chat_member.py
index 2cea44ce..2fe3e677 100644
--- a/tests/types/test_chat_member.py
+++ b/tests/types/test_chat_member.py
@@ -1,7 +1,7 @@
from aiogram import types
from .dataset import CHAT_MEMBER
-chat_member = types.ChatMember(**CHAT_MEMBER)
+chat_member = types.ChatMember.resolve(**CHAT_MEMBER)
def test_export():
diff --git a/tox.ini b/tox.ini
index aff44213..063a9e9c 100644
--- a/tox.ini
+++ b/tox.ini
@@ -1,5 +1,5 @@
[tox]
-envlist = py37
+envlist = py38
[testenv]
deps = -rdev_requirements.txt