diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index ab12d702..82ea7257 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1,8 +1,2 @@
-# These are supported funding model platforms
-
-github: [JRootJunior]# Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
-patreon: # Replace with a single Patreon username
-open_collective: aiogram # Replace with a single Open Collective username
-ko_fi: # Replace with a single Ko-fi username
-tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
-custom: # Replace with a single custom sponsorship URL
+github: [JRootJunior]
+open_collective: aiogram
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 65baad51..1c33729f 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -8,7 +8,7 @@ Fixes # (issue)
Please delete options that are not relevant.
-- [ ] Documentstion (typos, code examples or any documentation update)
+- [ ] Documentation (typos, code examples or any documentation update)
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
diff --git a/README.md b/README.md
index b3dd3a0b..02a9374f 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,17 @@
# AIOGram
+[](https://opencollective.com/aiogram)
[![\[Telegram\] aiogram live](https://img.shields.io/badge/telegram-aiogram-blue.svg?style=flat-square)](https://t.me/aiogram_live)
[](https://pypi.python.org/pypi/aiogram)
[](https://pypi.python.org/pypi/aiogram)
[](https://pypi.python.org/pypi/aiogram)
[](https://pypi.python.org/pypi/aiogram)
-[](https://core.telegram.org/bots/api)
-[](http://aiogram.readthedocs.io/en/latest/?badge=latest)
-[](https://github.com/python/black)
+[](https://core.telegram.org/bots/api)
+[](http://aiogram.readthedocs.io/en/latest/?badge=latest)
[](https://github.com/aiogram/aiogram/issues)
[](https://opensource.org/licenses/MIT)
-**aiogram** is a pretty simple and fully asynchronous library 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.
+**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://aiogram.readthedocs.io/en/latest/).
@@ -25,3 +25,33 @@ You can [read the docs here](http://aiogram.readthedocs.io/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)
+
+## Contributors
+
+### Code Contributors
+
+This project exists thanks to all the people who contribute. [[Code of conduct](CODE_OF_CONDUCT.md)].
+
+
+### Financial Contributors
+
+Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/aiogram/contribute)]
+
+#### Individuals
+
+
+
+#### Organizations
+
+Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/aiogram/contribute)]
+
+
+
+
+
+
+
+
+
+
+
diff --git a/README.rst b/README.rst
index 0070de61..f7c3e951 100644
--- a/README.rst
+++ b/README.rst
@@ -21,18 +21,14 @@ AIOGramBot
:target: https://pypi.python.org/pypi/aiogram
:alt: Supported python versions
-.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-4.3-blue.svg?style=flat-square&logo=telegram
+.. image:: https://img.shields.io/badge/Telegram%20Bot%20API-4.4-blue.svg?style=flat-square&logo=telegram
:target: https://core.telegram.org/bots/api
:alt: Telegram Bot API
-.. image:: https://img.shields.io/readthedocs/pip/stable.svg?style=flat-square
- :target: http://aiogram.readthedocs.io/en/latest/?badge=latest?style=flat-square
+.. image:: https://img.shields.io/readthedocs/aiogram?style=flat-square
+ :target: http://aiogram.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
-.. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square
- :target: https://github.com/python/black
- :alt: Code style: Black
-
.. image:: https://img.shields.io/github/issues/aiogram/aiogram.svg?style=flat-square
:target: https://github.com/aiogram/aiogram/issues
:alt: Github issues
@@ -42,7 +38,7 @@ AIOGramBot
:alt: MIT License
-**aiogram** is a pretty simple and fully asynchronous library for `Telegram Bot API `_ written in Python 3.7 with `asyncio `_ and `aiohttp `_. It helps you to make your bots faster and simpler.
+**aiogram** is a pretty simple and fully asynchronous framework for `Telegram Bot API `_ written in Python 3.7 with `asyncio `_ and `aiohttp `_. It helps you to make your bots faster and simpler.
You can `read the docs here `_.
diff --git a/aiogram/__init__.py b/aiogram/__init__.py
index 2f1849e6..edea1806 100644
--- a/aiogram/__init__.py
+++ b/aiogram/__init__.py
@@ -17,26 +17,26 @@ try:
except ImportError:
uvloop = None
else:
- if "DISABLE_UVLOOP" not in os.environ:
+ if 'DISABLE_UVLOOP' not in os.environ:
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
__all__ = [
- "Bot",
- "Dispatcher",
- "__api_version__",
- "__version__",
- "bot",
- "contrib",
- "dispatcher",
- "exceptions",
- "executor",
- "filters",
- "helper",
- "md",
- "middlewares",
- "types",
- "utils",
+ 'Bot',
+ 'Dispatcher',
+ '__api_version__',
+ '__version__',
+ 'bot',
+ 'contrib',
+ 'dispatcher',
+ 'exceptions',
+ 'executor',
+ 'filters',
+ 'helper',
+ 'md',
+ 'middlewares',
+ 'types',
+ 'utils'
]
-__version__ = "3.0.dev1"
-__api_version__ = "4.3"
+__version__ = '2.4'
+__api_version__ = '4.4'
diff --git a/aiogram/bot/api.py b/aiogram/bot/api.py
index 580a4a69..cf69be5d 100644
--- a/aiogram/bot/api.py
+++ b/aiogram/bot/api.py
@@ -155,7 +155,7 @@ class Methods(Helper):
"""
Helper for Telegram API Methods listed on https://core.telegram.org/bots/api
- List is updated to Bot API 4.3
+ List is updated to Bot API 4.4
"""
mode = HelperMode.lowerCamelCase
@@ -191,6 +191,7 @@ class Methods(Helper):
UNBAN_CHAT_MEMBER = Item() # unbanChatMember
RESTRICT_CHAT_MEMBER = Item() # restrictChatMember
PROMOTE_CHAT_MEMBER = Item() # promoteChatMember
+ SET_CHAT_PERMISSIONS = Item() # setChatPermissions
EXPORT_CHAT_INVITE_LINK = Item() # exportChatInviteLink
SET_CHAT_PHOTO = Item() # setChatPhoto
DELETE_CHAT_PHOTO = Item() # deleteChatPhoto
diff --git a/aiogram/bot/base.py b/aiogram/bot/base.py
index 1a4cf5b2..04ec9f43 100644
--- a/aiogram/bot/base.py
+++ b/aiogram/bot/base.py
@@ -13,7 +13,7 @@ from aiohttp.helpers import sentinel
from . import api
from ..types import ParseMode, base
from ..utils import json
-from ..utils.auth_widget import check_token
+from ..utils.auth_widget import check_integrity
class BaseBot:
@@ -115,6 +115,15 @@ class BaseBot:
self.parse_mode = parse_mode
+ def __del__(self):
+ if not hasattr(self, 'loop'):
+ return
+ if self.loop.is_running():
+ self.loop.create_task(self.close())
+ return
+ loop = asyncio.new_event_loop()
+ loop.run_until_complete(self.close())
+
@staticmethod
def _prepare_timeout(
value: typing.Optional[typing.Union[base.Integer, base.Float, aiohttp.ClientTimeout]]
@@ -302,4 +311,4 @@ class BaseBot:
self.parse_mode = None
def check_auth_widget(self, data):
- return check_token(data, self.__token)
+ return check_integrity(self.__token, data)
diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py
index cda742e1..5e8f05d9 100644
--- a/aiogram/bot/bot.py
+++ b/aiogram/bot/bot.py
@@ -1,6 +1,8 @@
from __future__ import annotations
+import datetime
import typing
+import warnings
from .base import BaseBot, api
from .. import types
@@ -21,9 +23,9 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:return: :class:`aiogram.types.User`
"""
- if not hasattr(self, "_me"):
- setattr(self, "_me", await self.get_me())
- return getattr(self, "_me")
+ if not hasattr(self, '_me'):
+ setattr(self, '_me', await self.get_me())
+ return getattr(self, '_me')
@me.deleter
def me(self):
@@ -36,17 +38,12 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:return: :obj:`aiogram.types.User`
"""
- if hasattr(self, "_me"):
- delattr(self, "_me")
+ if hasattr(self, '_me'):
+ delattr(self, '_me')
- async def download_file_by_id(
- self,
- file_id: base.String,
- destination=None,
- timeout: base.Integer = 30,
- chunk_size: base.Integer = 65536,
- seek: base.Boolean = True,
- ):
+ async def download_file_by_id(self, file_id: base.String, destination=None,
+ timeout: base.Integer = 30, chunk_size: base.Integer = 65536,
+ seek: base.Boolean = True):
"""
Download file by file_id to destination
@@ -61,24 +58,17 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:return: destination
"""
file = await self.get_file(file_id)
- return await self.download_file(
- file_path=file.file_path,
- destination=destination,
- timeout=timeout,
- chunk_size=chunk_size,
- seek=seek,
- )
+ return await self.download_file(file_path=file.file_path, destination=destination,
+ timeout=timeout, chunk_size=chunk_size, seek=seek)
# === 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,
- allowed_updates: typing.Union[typing.List[base.String], None] = None,
- ) -> typing.List[types.Update]:
+ 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,
+ allowed_updates:
+ typing.Union[typing.List[base.String], None] = None) -> typing.List[types.Update]:
"""
Use this method to receive incoming updates using long polling (wiki).
@@ -105,13 +95,10 @@ 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.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:
"""
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,
@@ -133,10 +120,10 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:rtype: :obj:`base.Boolean`
"""
allowed_updates = prepare_arg(allowed_updates)
- payload = generate_payload(**locals(), exclude=["certificate"])
+ payload = generate_payload(**locals(), exclude=['certificate'])
files = {}
- prepare_file(payload, files, "certificate", certificate)
+ prepare_file(payload, files, 'certificate', certificate)
result = await self.request(api.Methods.SET_WEBHOOK, payload, files)
return result
@@ -189,22 +176,15 @@ 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,
- reply_markup: typing.Union[
- types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup,
- types.ReplyKeyboardRemove,
- types.ForceReply,
- None,
- ] = None,
- ) -> types.Message:
+ 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,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup,
+ types.ReplyKeyboardRemove,
+ types.ForceReply, None] = None) -> types.Message:
"""
Use this method to send text messages.
@@ -233,18 +213,14 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
reply_markup = prepare_arg(reply_markup)
payload = generate_payload(**locals())
if self.parse_mode:
- payload.setdefault("parse_mode", self.parse_mode)
+ payload.setdefault('parse_mode', self.parse_mode)
result = await self.request(api.Methods.SEND_MESSAGE, payload)
return types.Message(**result)
- 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:
+ 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:
"""
Use this method to forward messages of any kind.
@@ -266,22 +242,16 @@ 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],
- 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,
- reply_markup: typing.Union[
- types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup,
- types.ReplyKeyboardRemove,
- types.ForceReply,
- None,
- ] = None,
- ) -> types.Message:
+ 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,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup,
+ types.ReplyKeyboardRemove,
+ types.ForceReply, None] = None) -> types.Message:
"""
Use this method to send photos.
@@ -308,36 +278,30 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:rtype: :obj:`types.Message`
"""
reply_markup = prepare_arg(reply_markup)
- payload = generate_payload(**locals(), exclude=["photo"])
+ payload = generate_payload(**locals(), exclude=['photo'])
if self.parse_mode:
- payload.setdefault("parse_mode", self.parse_mode)
+ payload.setdefault('parse_mode', self.parse_mode)
files = {}
- prepare_file(payload, files, "photo", photo)
+ prepare_file(payload, files, 'photo', photo)
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],
- 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,
- 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,
- reply_markup: typing.Union[
- types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup,
- types.ReplyKeyboardRemove,
- types.ForceReply,
- None,
- ] = None,
- ) -> types.Message:
+ 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,
+ 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,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup,
+ types.ReplyKeyboardRemove,
+ 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.
@@ -375,33 +339,28 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:rtype: :obj:`types.Message`
"""
reply_markup = prepare_arg(reply_markup)
- payload = generate_payload(**locals(), exclude=["audio"])
+ payload = generate_payload(**locals(), exclude=['audio', 'thumb'])
if self.parse_mode:
- payload.setdefault("parse_mode", self.parse_mode)
+ payload.setdefault('parse_mode', self.parse_mode)
files = {}
- prepare_file(payload, files, "audio", audio)
+ prepare_file(payload, files, 'audio', audio)
+ prepare_attachment(payload, files, 'thumb', thumb)
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],
- 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,
- reply_markup: typing.Union[
- types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup,
- types.ReplyKeyboardRemove,
- types.ForceReply,
- None,
- ] = None,
- ) -> types.Message:
+ 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,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup,
+ types.ReplyKeyboardRemove,
+ types.ForceReply, None] = None) -> types.Message:
"""
Use this method to send general files.
@@ -432,37 +391,31 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:rtype: :obj:`types.Message`
"""
reply_markup = prepare_arg(reply_markup)
- payload = generate_payload(**locals(), exclude=["document"])
+ payload = generate_payload(**locals(), exclude=['document'])
if self.parse_mode:
- payload.setdefault("parse_mode", self.parse_mode)
+ payload.setdefault('parse_mode', self.parse_mode)
files = {}
- prepare_file(payload, files, "document", document)
+ prepare_file(payload, files, 'document', document)
result = await self.request(api.Methods.SEND_DOCUMENT, payload, files)
return types.Message(**result)
- 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,
- 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,
- reply_markup: typing.Union[
- types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup,
- types.ReplyKeyboardRemove,
- types.ForceReply,
- None,
- ] = None,
- ) -> types.Message:
+ 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,
+ 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,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup,
+ types.ReplyKeyboardRemove,
+ types.ForceReply, None] = None) -> types.Message:
"""
Use this method to send video files, Telegram clients support mp4 videos
(other formats may be sent as Document).
@@ -500,39 +453,33 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:rtype: :obj:`types.Message`
"""
reply_markup = prepare_arg(reply_markup)
- payload = generate_payload(**locals(), exclude=["video", "thumb"])
+ payload = generate_payload(**locals(), exclude=['video', 'thumb'])
if self.parse_mode:
- payload.setdefault("parse_mode", self.parse_mode)
+ payload.setdefault('parse_mode', self.parse_mode)
files = {}
- prepare_file(payload, files, "video", video)
- prepare_attachment(payload, files, "thumb", thumb)
+ prepare_file(payload, files, 'video', video)
+ prepare_attachment(payload, files, 'thumb', thumb)
result = await self.request(api.Methods.SEND_VIDEO, payload, files)
return types.Message(**result)
- 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,
- 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,
- reply_markup: typing.Union[
- typing.Union[
- types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup,
- types.ReplyKeyboardRemove,
- types.ForceReply,
- ],
- None,
- ] = None,
- ) -> types.Message:
+ 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,
+ 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,
+ reply_markup: typing.Union[typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup,
+ types.ReplyKeyboardRemove,
+ types.ForceReply], None] = None
+ ) -> types.Message:
"""
Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound).
@@ -577,29 +524,23 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
payload = generate_payload(**locals(), exclude=["animation", "thumb"])
files = {}
- prepare_file(payload, files, "animation", animation)
- prepare_attachment(payload, files, "thumb", thumb)
+ prepare_file(payload, files, 'animation', animation)
+ prepare_attachment(payload, files, 'thumb', thumb)
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],
- 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,
- reply_markup: typing.Union[
- types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup,
- types.ReplyKeyboardRemove,
- types.ForceReply,
- None,
- ] = None,
- ) -> types.Message:
+ 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,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup,
+ types.ReplyKeyboardRemove,
+ 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.
@@ -632,33 +573,27 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:rtype: :obj:`types.Message`
"""
reply_markup = prepare_arg(reply_markup)
- payload = generate_payload(**locals(), exclude=["voice"])
+ payload = generate_payload(**locals(), exclude=['voice'])
if self.parse_mode:
- payload.setdefault("parse_mode", self.parse_mode)
+ payload.setdefault('parse_mode', self.parse_mode)
files = {}
- prepare_file(payload, files, "voice", voice)
+ prepare_file(payload, files, 'voice', voice)
result = await self.request(api.Methods.SEND_VOICE, payload, files)
return types.Message(**result)
- 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,
- 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,
- reply_markup: typing.Union[
- types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup,
- types.ReplyKeyboardRemove,
- types.ForceReply,
- None,
- ] = None,
- ) -> types.Message:
+ 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,
+ 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,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup,
+ types.ReplyKeyboardRemove,
+ types.ForceReply, None] = None) -> types.Message:
"""
As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1 minute long.
Use this method to send video messages.
@@ -687,21 +622,19 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:rtype: :obj:`types.Message`
"""
reply_markup = prepare_arg(reply_markup)
- payload = generate_payload(**locals(), exclude=["video_note"])
+ payload = generate_payload(**locals(), exclude=['video_note'])
files = {}
- prepare_file(payload, files, "video_note", video_note)
+ prepare_file(payload, files, 'video_note', video_note)
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],
- 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]:
+ 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]:
"""
Use this method to send a group of photos or videos as an album.
@@ -725,27 +658,20 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
files = dict(media.get_files())
media = prepare_arg(media)
- payload = generate_payload(**locals(), exclude=["files"])
+ payload = generate_payload(**locals(), exclude=['files'])
result = await self.request(api.Methods.SEND_MEDIA_GROUP, payload, files)
return [types.Message(**message) for message in result]
- 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,
- reply_markup: typing.Union[
- types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup,
- types.ReplyKeyboardRemove,
- types.ForceReply,
- None,
- ] = None,
- ) -> types.Message:
+ 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,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup,
+ types.ReplyKeyboardRemove,
+ types.ForceReply, None] = None) -> types.Message:
"""
Use this method to send point on the map.
@@ -776,15 +702,12 @@ 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,
- 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:
+ 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:
"""
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
@@ -816,13 +739,12 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
return result
return types.Message(**result)
- 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,
- reply_markup: typing.Union[types.InlineKeyboardMarkup, None] = None,
- ) -> types.Message or base.Boolean:
+ 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,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ None] = None) -> types.Message or base.Boolean:
"""
Use this method to stop updating a live location message sent by the bot or via the bot
(for inline bots) before live_period expires.
@@ -849,25 +771,17 @@ 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,
- reply_markup: typing.Union[
- types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup,
- types.ReplyKeyboardRemove,
- types.ForceReply,
- None,
- ] = None,
- ) -> types.Message:
+ 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,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup,
+ types.ReplyKeyboardRemove,
+ types.ForceReply, None] = None) -> types.Message:
"""
Use this method to send information about a venue.
@@ -904,23 +818,16 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.SEND_VENUE, payload)
return types.Message(**result)
- 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,
- reply_markup: typing.Union[
- types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup,
- types.ReplyKeyboardRemove,
- types.ForceReply,
- None,
- ] = None,
- ) -> types.Message:
+ 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,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup,
+ types.ReplyKeyboardRemove,
+ types.ForceReply, None] = None) -> types.Message:
"""
Use this method to send phone contacts.
@@ -953,21 +860,15 @@ 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],
- question: base.String,
- options: typing.List[base.String],
- disable_notification: typing.Optional[base.Boolean],
- reply_to_message_id: typing.Union[base.Integer, None],
- reply_markup: typing.Union[
- types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup,
- types.ReplyKeyboardRemove,
- types.ForceReply,
- None,
- ] = None,
- ) -> types.Message:
+ async def send_poll(self, chat_id: typing.Union[base.Integer, base.String],
+ question: base.String,
+ options: typing.List[base.String],
+ disable_notification: typing.Optional[base.Boolean] = None,
+ reply_to_message_id: typing.Optional[base.Integer] = None,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup,
+ types.ReplyKeyboardRemove,
+ 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.
@@ -997,9 +898,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.SEND_POLL, payload)
return types.Message(**result)
- async def send_chat_action(
- self, chat_id: typing.Union[base.Integer, base.String], action: base.String
- ) -> base.Boolean:
+ 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
@@ -1022,12 +922,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.SEND_CHAT_ACTION, payload)
return result
- 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.Union[base.Integer, None] = None,
+ limit: typing.Union[base.Integer, None] = None) -> types.UserProfilePhotos:
"""
Use this method to get a list of profile pictures for a user. Returns a UserProfilePhotos object.
@@ -1067,12 +963,9 @@ 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, None] = None,
- ) -> 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) -> 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
@@ -1101,9 +994,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.KICK_CHAT_MEMBER, payload)
return result
- async def unban_chat_member(
- self, chat_id: typing.Union[base.Integer, base.String], user_id: base.Integer
- ) -> base.Boolean:
+ async def unban_chat_member(self, chat_id: typing.Union[base.Integer, base.String],
+ user_id: base.Integer) -> 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.
@@ -1124,16 +1016,16 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.UNBAN_CHAT_MEMBER, payload)
return result
- async def restrict_chat_member(
- self,
- chat_id: typing.Union[base.Integer, base.String],
- user_id: base.Integer,
- until_date: typing.Union[base.Integer, 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:
+ async def restrict_chat_member(self, chat_id: typing.Union[base.Integer, base.String],
+ user_id: base.Integer,
+ permissions: typing.Optional[types.ChatPermissions] = None,
+ # 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:
"""
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.
@@ -1145,6 +1037,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
: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 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]`
:param can_send_messages: Pass True, if the user can send text messages, contacts, locations and venues
@@ -1162,24 +1056,32 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:rtype: :obj:`base.Boolean`
"""
until_date = prepare_arg(until_date)
+ permissions = prepare_arg(permissions)
payload = generate_payload(**locals())
+ for permission in ['can_send_messages',
+ 'can_send_media_messages',
+ 'can_send_other_messages',
+ '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
- 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:
+ 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:
"""
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.
@@ -1217,9 +1119,26 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.PROMOTE_CHAT_MEMBER, payload)
return result
- async def export_chat_invite_link(
- self, chat_id: typing.Union[base.Integer, base.String]
- ) -> base.String:
+ async def set_chat_permissions(self, chat_id: typing.Union[base.Integer, base.String],
+ permissions: types.ChatPermissions) -> base.Boolean:
+ """
+ Use this method to set default chat permissions for all members.
+ The bot must be an administrator in the group or a supergroup for this to work and must have the
+ can_restrict_members admin rights.
+
+ Returns True on success.
+
+ :param chat_id: Unique identifier for the target chat or username of the target supergroup
+ :param permissions: New default chat permissions
+ :return: True on success.
+ """
+ permissions = prepare_arg(permissions)
+ payload = generate_payload(**locals())
+
+ result = await self.request(api.Methods.SET_CHAT_PERMISSIONS, payload)
+ return result
+
+ async def export_chat_invite_link(self, chat_id: typing.Union[base.Integer, base.String]) -> base.String:
"""
Use this method to generate a new invite link for a chat; any previously generated link is revoked.
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
@@ -1236,9 +1155,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.EXPORT_CHAT_INVITE_LINK, payload)
return result
- async def set_chat_photo(
- self, chat_id: typing.Union[base.Integer, base.String], photo: base.InputFile
- ) -> base.Boolean:
+ async def set_chat_photo(self, chat_id: typing.Union[base.Integer, base.String],
+ photo: base.InputFile) -> base.Boolean:
"""
Use this method to set a new profile photo for the chat. Photos can't be changed for private chats.
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
@@ -1255,17 +1173,15 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:return: Returns True on success
:rtype: :obj:`base.Boolean`
"""
- payload = generate_payload(**locals(), exclude=["photo"])
+ payload = generate_payload(**locals(), exclude=['photo'])
files = {}
- prepare_file(payload, files, "photo", photo)
+ prepare_file(payload, files, 'photo', photo)
result = await self.request(api.Methods.SET_CHAT_PHOTO, payload, files)
return result
- async def delete_chat_photo(
- self, chat_id: typing.Union[base.Integer, base.String]
- ) -> base.Boolean:
+ async def delete_chat_photo(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Boolean:
"""
Use this method to delete a chat photo. Photos can't be changed for private chats.
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
@@ -1285,9 +1201,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.DELETE_CHAT_PHOTO, payload)
return result
- async def set_chat_title(
- self, chat_id: typing.Union[base.Integer, base.String], title: base.String
- ) -> base.Boolean:
+ async def set_chat_title(self, chat_id: typing.Union[base.Integer, base.String],
+ title: base.String) -> base.Boolean:
"""
Use this method to change the title of a chat. Titles can't be changed for private chats.
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
@@ -1309,11 +1224,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.SET_CHAT_TITLE, payload)
return result
- async def set_chat_description(
- self,
- chat_id: typing.Union[base.Integer, base.String],
- description: typing.Union[base.String, None] = None,
- ) -> base.Boolean:
+ async def set_chat_description(self, chat_id: typing.Union[base.Integer, base.String],
+ description: typing.Union[base.String, None] = 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.
@@ -1332,12 +1244,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.SET_CHAT_DESCRIPTION, payload)
return result
- 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.Union[base.Boolean, None] = 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.
@@ -1359,9 +1267,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.PIN_CHAT_MESSAGE, payload)
return result
- 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]) -> 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.
@@ -1411,9 +1317,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.GET_CHAT, payload)
return types.Chat(**result)
- async def get_chat_administrators(
- self, chat_id: typing.Union[base.Integer, base.String]
- ) -> typing.List[types.ChatMember]:
+ async def get_chat_administrators(self, chat_id: typing.Union[base.Integer, base.String]
+ ) -> typing.List[types.ChatMember]:
"""
Use this method to get a list of administrators in a chat.
@@ -1432,9 +1337,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.GET_CHAT_ADMINISTRATORS, payload)
return [types.ChatMember(**chatmember) for chatmember in result]
- async def get_chat_members_count(
- self, chat_id: typing.Union[base.Integer, base.String]
- ) -> base.Integer:
+ async def get_chat_members_count(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Integer:
"""
Use this method to get the number of members in a chat.
@@ -1450,9 +1353,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.GET_CHAT_MEMBERS_COUNT, payload)
return result
- async def get_chat_member(
- self, chat_id: typing.Union[base.Integer, base.String], user_id: base.Integer
- ) -> types.ChatMember:
+ async def get_chat_member(self, chat_id: typing.Union[base.Integer, base.String],
+ user_id: base.Integer) -> types.ChatMember:
"""
Use this method to get information about a member of a chat.
@@ -1470,9 +1372,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.GET_CHAT_MEMBER, payload)
return types.ChatMember(**result)
- async def set_chat_sticker_set(
- self, chat_id: typing.Union[base.Integer, base.String], sticker_set_name: base.String
- ) -> base.Boolean:
+ async def set_chat_sticker_set(self, chat_id: typing.Union[base.Integer, base.String],
+ sticker_set_name: base.String) -> base.Boolean:
"""
Use this method to set a new group sticker set for a supergroup.
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
@@ -1494,9 +1395,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.SET_CHAT_STICKER_SET, payload)
return result
- async def delete_chat_sticker_set(
- self, chat_id: typing.Union[base.Integer, base.String]
- ) -> base.Boolean:
+ async def delete_chat_sticker_set(self, chat_id: typing.Union[base.Integer, base.String]) -> base.Boolean:
"""
Use this method to delete a group sticker set from a supergroup.
The bot must be an administrator in the chat for this to work and must have the appropriate admin rights.
@@ -1516,14 +1415,11 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.DELETE_CHAT_STICKER_SET, payload)
return result
- 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:
+ 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:
"""
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.
@@ -1554,16 +1450,14 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.ANSWER_CALLBACK_QUERY, payload)
return result
- 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,
- reply_markup: typing.Union[types.InlineKeyboardMarkup, None] = None,
- ) -> types.Message or base.Boolean:
+ 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,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ 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,22 +1486,20 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
reply_markup = prepare_arg(reply_markup)
payload = generate_payload(**locals())
if self.parse_mode:
- payload.setdefault("parse_mode", self.parse_mode)
+ payload.setdefault('parse_mode', self.parse_mode)
result = await self.request(api.Methods.EDIT_MESSAGE_TEXT, payload)
if isinstance(result, bool):
return result
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,
- reply_markup: typing.Union[types.InlineKeyboardMarkup, None] = None,
- ) -> types.Message or base.Boolean:
+ 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,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ None] = None) -> types.Message or base.Boolean:
"""
Use this method to edit captions of messages sent by the bot or via the bot (for inline bots).
@@ -1634,21 +1526,20 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
reply_markup = prepare_arg(reply_markup)
payload = generate_payload(**locals())
if self.parse_mode:
- payload.setdefault("parse_mode", self.parse_mode)
+ payload.setdefault('parse_mode', self.parse_mode)
result = await self.request(api.Methods.EDIT_MESSAGE_CAPTION, payload)
if isinstance(result, bool):
return result
return types.Message(**result)
- 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,
- ) -> typing.Union[types.Message, base.Boolean]:
+ 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,
+ ) -> typing.Union[types.Message, base.Boolean]:
"""
Use this method to edit audio, document, photo, or video messages.
If a message is a part of a message album, then it can be edited only to a photo or a video.
@@ -1688,13 +1579,12 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
return result
return types.Message(**result)
- 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,
- reply_markup: typing.Union[types.InlineKeyboardMarkup, None] = None,
- ) -> types.Message or base.Boolean:
+ 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,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ None] = None) -> types.Message or base.Boolean:
"""
Use this method to edit only the reply markup of messages sent by the bot or via the bot (for inline bots).
@@ -1721,12 +1611,9 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
return result
return types.Message(**result)
- 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:
+ 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:
"""
Use this method to stop a poll which was sent by the bot.
On success, the stopped Poll with the final results is returned.
@@ -1745,9 +1632,8 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.STOP_POLL, payload)
return types.Poll(**result)
- async def delete_message(
- self, chat_id: typing.Union[base.Integer, base.String], message_id: base.Integer
- ) -> base.Boolean:
+ async def delete_message(self, chat_id: typing.Union[base.Integer, base.String],
+ message_id: base.Integer) -> base.Boolean:
"""
Use this method to delete a message, including service messages, with the following limitations:
- A message can only be deleted if it was sent less than 48 hours ago.
@@ -1774,20 +1660,14 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
# === 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,
- reply_markup: typing.Union[
- types.InlineKeyboardMarkup,
- types.ReplyKeyboardMarkup,
- types.ReplyKeyboardRemove,
- types.ForceReply,
- None,
- ] = None,
- ) -> types.Message:
+ 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,
+ reply_markup: typing.Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup,
+ types.ReplyKeyboardRemove,
+ types.ForceReply, None] = None) -> types.Message:
"""
Use this method to send .webp stickers.
@@ -1809,10 +1689,10 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:rtype: :obj:`types.Message`
"""
reply_markup = prepare_arg(reply_markup)
- payload = generate_payload(**locals(), exclude=["sticker"])
+ payload = generate_payload(**locals(), exclude=['sticker'])
files = {}
- prepare_file(payload, files, "sticker", sticker)
+ prepare_file(payload, files, 'sticker', sticker)
result = await self.request(api.Methods.SEND_STICKER, payload, files)
return types.Message(**result)
@@ -1833,9 +1713,7 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.GET_STICKER_SET, payload)
return types.StickerSet(**result)
- async def upload_sticker_file(
- self, user_id: base.Integer, png_sticker: base.InputFile
- ) -> types.File:
+ async def upload_sticker_file(self, user_id: base.Integer, png_sticker: base.InputFile) -> types.File:
"""
Use this method to upload a .png file with a sticker for later use in createNewStickerSet
and addStickerToSet methods (can be used multiple times).
@@ -1850,24 +1728,18 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:return: Returns the uploaded File on success
:rtype: :obj:`types.File`
"""
- payload = generate_payload(**locals(), exclude=["png_sticker"])
+ payload = generate_payload(**locals(), exclude=['png_sticker'])
files = {}
- prepare_file(payload, files, "png_sticker", png_sticker)
+ prepare_file(payload, files, 'png_sticker', png_sticker)
result = await self.request(api.Methods.UPLOAD_STICKER_FILE, payload, files)
return types.File(**result)
- async def create_new_sticker_set(
- self,
- user_id: base.Integer,
- name: base.String,
- title: base.String,
- png_sticker: typing.Union[base.InputFile, base.String],
- emojis: base.String,
- contains_masks: typing.Union[base.Boolean, None] = None,
- mask_position: typing.Union[types.MaskPosition, None] = None,
- ) -> base.Boolean:
+ async def create_new_sticker_set(self, user_id: base.Integer, name: base.String, title: base.String,
+ png_sticker: typing.Union[base.InputFile, base.String], emojis: base.String,
+ contains_masks: typing.Union[base.Boolean, None] = None,
+ mask_position: typing.Union[types.MaskPosition, None] = None) -> base.Boolean:
"""
Use this method to create new sticker set owned by a user. The bot will be able to edit the created sticker set.
@@ -1892,22 +1764,17 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:rtype: :obj:`base.Boolean`
"""
mask_position = prepare_arg(mask_position)
- payload = generate_payload(**locals(), exclude=["png_sticker"])
+ payload = generate_payload(**locals(), exclude=['png_sticker'])
files = {}
- prepare_file(payload, files, "png_sticker", png_sticker)
+ prepare_file(payload, files, 'png_sticker', png_sticker)
result = await self.request(api.Methods.CREATE_NEW_STICKER_SET, payload, files)
return result
- async def add_sticker_to_set(
- self,
- user_id: base.Integer,
- name: base.String,
- png_sticker: typing.Union[base.InputFile, base.String],
- emojis: base.String,
- mask_position: typing.Union[types.MaskPosition, None] = None,
- ) -> base.Boolean:
+ async def add_sticker_to_set(self, user_id: base.Integer, name: base.String,
+ png_sticker: typing.Union[base.InputFile, base.String], emojis: base.String,
+ mask_position: typing.Union[types.MaskPosition, None] = None) -> base.Boolean:
"""
Use this method to add a new sticker to a set created by the bot.
@@ -1928,17 +1795,15 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:rtype: :obj:`base.Boolean`
"""
mask_position = prepare_arg(mask_position)
- payload = generate_payload(**locals(), exclude=["png_sticker"])
+ payload = generate_payload(**locals(), exclude=['png_sticker'])
files = {}
- prepare_file(payload, files, "png_sticker", png_sticker)
+ prepare_file(payload, files, 'png_sticker', png_sticker)
result = await self.request(api.Methods.ADD_STICKER_TO_SET, payload, files)
return result
- async def set_sticker_position_in_set(
- self, sticker: base.String, position: base.Integer
- ) -> base.Boolean:
+ async def set_sticker_position_in_set(self, sticker: base.String, position: base.Integer) -> base.Boolean:
"""
Use this method to move a sticker in a set created by the bot to a specific position.
@@ -1974,16 +1839,13 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.DELETE_STICKER_FROM_SET, payload)
return result
- 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:
+ 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:
"""
Use this method to send answers to an inline query.
No more than 50 results per query are allowed.
@@ -2024,30 +1886,23 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
# === 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,
- 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: 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,
+ 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:
"""
Use this method to send invoices.
@@ -2102,22 +1957,16 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
: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]
- )
+ prices = prepare_arg([price.to_python() if hasattr(price, 'to_python') else price for price in prices])
reply_markup = prepare_arg(reply_markup)
payload_ = generate_payload(**locals())
result = await self.request(api.Methods.SEND_INVOICE, payload_)
return types.Message(**result)
- 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:
+ 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:
"""
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.
@@ -2140,25 +1989,17 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
:rtype: :obj:`base.Boolean`
"""
if shipping_options:
- shipping_options = prepare_arg(
- [
- shipping_option.to_python()
- if hasattr(shipping_option, "to_python")
- else shipping_option
- for shipping_option in shipping_options
- ]
- )
+ shipping_options = prepare_arg([shipping_option.to_python()
+ if hasattr(shipping_option, 'to_python')
+ else shipping_option
+ for shipping_option in shipping_options])
payload = generate_payload(**locals())
result = await self.request(api.Methods.ANSWER_SHIPPING_QUERY, payload)
return result
- 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:
+ 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:
"""
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.
@@ -2188,9 +2029,9 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
# === Games ===
# https://core.telegram.org/bots/api#games
- async def set_passport_data_errors(
- self, user_id: base.Integer, errors: typing.List[types.PassportElementError]
- ) -> base.Boolean:
+ async def set_passport_data_errors(self,
+ user_id: base.Integer,
+ errors: typing.List[types.PassportElementError]) -> base.Boolean:
"""
Informs a user that some of the Telegram Passport elements they provided contains errors.
The user will not be able to re-submit their Passport to you until the errors are fixed
@@ -2220,14 +2061,10 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
# === 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.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:
"""
Use this method to send a game.
@@ -2254,16 +2091,13 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
result = await self.request(api.Methods.SEND_GAME, payload)
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,
- inline_message_id: typing.Union[base.String, None] = None,
- ) -> types.Message or base.Boolean:
+ 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,
+ inline_message_id: typing.Union[base.String,
+ None] = None) -> types.Message or base.Boolean:
"""
Use this method to set the score of the specified user in a game.
@@ -2297,13 +2131,11 @@ class Bot(BaseBot, DataMixin, ContextInstanceMixin):
return result
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,
- inline_message_id: typing.Union[base.String, None] = None,
- ) -> typing.List[types.GameHighScore]:
+ 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,
+ inline_message_id: typing.Union[base.String,
+ None] = None) -> typing.List[types.GameHighScore]:
"""
Use this method to get data for high score tables.
diff --git a/aiogram/contrib/fsm_storage/files.py b/aiogram/contrib/fsm_storage/files.py
index f8df078a..ed9c7d57 100644
--- a/aiogram/contrib/fsm_storage/files.py
+++ b/aiogram/contrib/fsm_storage/files.py
@@ -20,7 +20,8 @@ class _FileStorage(MemoryStorage):
pass
async def close(self):
- self.write(self.path)
+ if self.data:
+ self.write(self.path)
await super(_FileStorage, self).close()
def read(self, path: pathlib.Path):
diff --git a/aiogram/contrib/fsm_storage/mongo.py b/aiogram/contrib/fsm_storage/mongo.py
new file mode 100644
index 00000000..9ec18090
--- /dev/null
+++ b/aiogram/contrib/fsm_storage/mongo.py
@@ -0,0 +1,200 @@
+"""
+This module has mongo storage for finite-state machine
+ based on `aiomongo AioMongoClient:
+ if isinstance(self._mongo, AioMongoClient):
+ return self._mongo
+
+ uri = 'mongodb://'
+
+ # set username + password
+ if self._username and self._password:
+ uri += f'{self._username}:{self._password}@'
+
+ # set host and port (optional)
+ uri += f'{self._host}:{self._port}' if self._host else f'localhost:{self._port}'
+
+ # define and return client
+ self._mongo = await aiomongo.create_client(uri)
+ return self._mongo
+
+ async def get_db(self) -> Database:
+ """
+ Get Mongo db
+
+ This property is awaitable.
+ """
+ if isinstance(self._db, Database):
+ return self._db
+
+ mongo = await self.get_client()
+ self._db = mongo.get_database(self._db_name)
+
+ if self._index:
+ await self.apply_index(self._db)
+ return self._db
+
+ @staticmethod
+ async def apply_index(db):
+ for collection in COLLECTIONS:
+ await db[collection].create_index(keys=[('chat', 1), ('user', 1)],
+ name="chat_user_idx", unique=True, background=True)
+
+ async def close(self):
+ if self._mongo:
+ self._mongo.close()
+
+ async def wait_closed(self):
+ if self._mongo:
+ return await self._mongo.wait_closed()
+ return True
+
+ 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()
+
+ 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)
+
+ async def get_state(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
+ default: Optional[str] = None) -> Optional[str]:
+ chat, user = self.check_address(chat=chat, user=user)
+ db = await self.get_db()
+ result = await db[STATE].find_one(filter={'chat': chat, 'user': user})
+
+ return result.get('state') if result else 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)
+
+ async def get_data(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
+ default: Optional[dict] = None) -> Dict:
+ chat, user = self.check_address(chat=chat, user=user)
+ db = await self.get_db()
+ result = await db[DATA].find_one(filter={'chat': chat, 'user': user})
+
+ return result.get('data') if result else default or {}
+
+ async def update_data(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
+ data: Dict = None, **kwargs):
+ if data is None:
+ data = {}
+ temp_data = await self.get_data(chat=chat, user=user, default={})
+ temp_data.update(data, **kwargs)
+ await self.set_data(chat=chat, user=user, data=temp_data)
+
+ def has_bucket(self):
+ return True
+
+ async def get_bucket(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
+ default: Optional[dict] = None) -> Dict:
+ chat, user = self.check_address(chat=chat, user=user)
+ db = await self.get_db()
+ result = await db[BUCKET].find_one(filter={'chat': chat, 'user': user})
+ return result.get('bucket') if result else default or {}
+
+ async def set_bucket(self, *, chat: Union[str, int, None] = None, user: Union[str, int, None] = None,
+ bucket: Dict = None):
+ chat, user = self.check_address(chat=chat, user=user)
+ db = await self.get_db()
+
+ await db[BUCKET].update_one(filter={'chat': chat, 'user': user},
+ update={'$set': {'bucket': bucket}}, upsert=True)
+
+ async def update_bucket(self, *, chat: Union[str, int, None] = None,
+ user: Union[str, int, None] = None,
+ bucket: Dict = None, **kwargs):
+ if bucket is None:
+ bucket = {}
+ temp_bucket = await self.get_bucket(chat=chat, user=user)
+ temp_bucket.update(bucket, **kwargs)
+ await self.set_bucket(chat=chat, user=user, bucket=temp_bucket)
+
+ async def reset_all(self, full=True):
+ """
+ Reset states in DB
+
+ :param full: clean DB or clean only states
+ :return:
+ """
+ db = await self.get_db()
+
+ await db[STATE].drop()
+
+ if full:
+ await db[DATA].drop()
+ await db[BUCKET].drop()
+
+ async def get_states_list(self) -> List[Tuple[int, int]]:
+ """
+ Get list of all stored chat's and user's
+
+ :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
diff --git a/aiogram/contrib/fsm_storage/redis.py b/aiogram/contrib/fsm_storage/redis.py
index ef0897b8..106a7b97 100644
--- a/aiogram/contrib/fsm_storage/redis.py
+++ b/aiogram/contrib/fsm_storage/redis.py
@@ -11,9 +11,9 @@ import aioredis
from ...dispatcher.storage import BaseStorage
from ...utils import json
-STATE_KEY = "state"
-STATE_DATA_KEY = "data"
-STATE_BUCKET_KEY = "bucket"
+STATE_KEY = 'state'
+STATE_DATA_KEY = 'data'
+STATE_BUCKET_KEY = 'bucket'
class RedisStorage(BaseStorage):
@@ -35,10 +35,7 @@ class RedisStorage(BaseStorage):
await dp.storage.wait_closed()
"""
-
- def __init__(
- self, host="localhost", port=6379, db=None, password=None, ssl=None, loop=None, **kwargs
- ):
+ def __init__(self, host='localhost', port=6379, db=None, password=None, ssl=None, loop=None, **kwargs):
self._host = host
self._port = port
self._db = db
@@ -64,28 +61,19 @@ class RedisStorage(BaseStorage):
async def redis(self) -> aioredis.RedisConnection:
"""
Get Redis connection
-
- This property is awaitable.
"""
# Use thread-safe asyncio Lock because this method without that is not safe
async with self._connection_lock:
if self._redis is None:
- self._redis = await aioredis.create_connection(
- (self._host, self._port),
- db=self._db,
- password=self._password,
- ssl=self._ssl,
- loop=self._loop,
- **self._kwargs,
- )
+ self._redis = await aioredis.create_connection((self._host, self._port),
+ db=self._db, password=self._password, ssl=self._ssl,
+ loop=self._loop,
+ **self._kwargs)
return self._redis
- async def get_record(
- self,
- *,
- chat: typing.Union[str, int, None] = None,
- user: typing.Union[str, int, None] = None,
- ) -> typing.Dict:
+ async def get_record(self, *,
+ chat: typing.Union[str, int, None] = None,
+ user: typing.Union[str, int, None] = None) -> typing.Dict:
"""
Get record from storage
@@ -97,20 +85,13 @@ class RedisStorage(BaseStorage):
addr = f"fsm:{chat}:{user}"
conn = await self.redis()
- data = await conn.execute("GET", addr)
+ data = await conn.execute('GET', addr)
if data is None:
- return {"state": None, "data": {}}
+ return {'state': None, 'data': {}}
return json.loads(data)
- async def set_record(
- self,
- *,
- chat: typing.Union[str, int, None] = None,
- user: typing.Union[str, int, None] = None,
- state=None,
- data=None,
- bucket=None,
- ):
+ async def set_record(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
+ state=None, data=None, bucket=None):
"""
Write record to storage
@@ -129,65 +110,39 @@ class RedisStorage(BaseStorage):
chat, user = self.check_address(chat=chat, user=user)
addr = f"fsm:{chat}:{user}"
- record = {"state": state, "data": data, "bucket": bucket}
+ record = {'state': state, 'data': data, 'bucket': bucket}
conn = await self.redis()
- await conn.execute("SET", addr, json.dumps(record))
+ 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]:
+ 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['state']
- 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:
+ 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"]
+ return record['data']
- 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,
- ):
+ 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)
- await self.set_record(chat=chat, user=user, state=state, data=record["data"])
+ 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,
- data: typing.Dict = None,
- ):
+ async def set_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
+ data: typing.Dict = None):
record = await self.get_record(chat=chat, user=user)
- await self.set_record(chat=chat, user=user, state=record["state"], data=data)
+ await self.set_record(chat=chat, user=user, state=record['state'], data=data)
- async def update_data(
- self,
- *,
- chat: typing.Union[str, int, None] = None,
- user: typing.Union[str, int, None] = None,
- data: typing.Dict = None,
- **kwargs,
- ):
+ async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
+ data: typing.Dict = None, **kwargs):
if data is None:
data = {}
record = await self.get_record(chat=chat, user=user)
- record_data = record.get("data", {})
+ record_data = record.get('data', {})
record_data.update(data, **kwargs)
- await self.set_record(chat=chat, user=user, state=record["state"], data=record_data)
+ await self.set_record(chat=chat, user=user, state=record['state'], data=record_data)
async def get_states_list(self) -> typing.List[typing.Tuple[int]]:
"""
@@ -198,9 +153,9 @@ class RedisStorage(BaseStorage):
conn = await self.redis()
result = []
- keys = await conn.execute("KEYS", "fsm:*")
+ keys = await conn.execute('KEYS', 'fsm:*')
for item in keys:
- *_, chat, user = item.decode("utf-8").split(":")
+ *_, chat, user = item.decode('utf-8').split(':')
result.append((chat, user))
return result
@@ -215,52 +170,33 @@ class RedisStorage(BaseStorage):
conn = await self.redis()
if full:
- await conn.execute("FLUSHDB")
+ await conn.execute('FLUSHDB')
else:
- keys = await conn.execute("KEYS", "fsm:*")
- await conn.execute("DEL", *keys)
+ keys = await conn.execute('KEYS', 'fsm:*')
+ await conn.execute('DEL', *keys)
def has_bucket(self):
return True
- async def get_bucket(
- self,
- *,
- chat: typing.Union[str, int, None] = None,
- user: typing.Union[str, int, None] = None,
- default: typing.Optional[str] = None,
- ) -> typing.Dict:
+ async def get_bucket(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.get("bucket", {})
+ return record.get('bucket', {})
- async def set_bucket(
- self,
- *,
- chat: typing.Union[str, int, None] = None,
- user: typing.Union[str, int, None] = None,
- bucket: typing.Dict = None,
- ):
+ async def set_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
+ bucket: typing.Dict = None):
record = await self.get_record(chat=chat, user=user)
- await self.set_record(
- chat=chat, user=user, state=record["state"], data=record["data"], bucket=bucket
- )
+ await self.set_record(chat=chat, user=user, state=record['state'], data=record['data'], bucket=bucket)
- async def update_bucket(
- self,
- *,
- chat: typing.Union[str, int, None] = None,
- user: typing.Union[str, int, None] = None,
- bucket: typing.Dict = None,
- **kwargs,
- ):
+ async def update_bucket(self, *, chat: typing.Union[str, int, None] = None,
+ user: typing.Union[str, int, None] = None,
+ bucket: typing.Dict = None, **kwargs):
record = await self.get_record(chat=chat, user=user)
- record_bucket = record.get("bucket", {})
+ record_bucket = record.get('bucket', {})
if bucket is None:
bucket = {}
record_bucket.update(bucket, **kwargs)
- await self.set_record(
- chat=chat, user=user, state=record["state"], data=record_bucket, bucket=bucket
- )
+ await self.set_record(chat=chat, user=user, state=record['state'], data=record_bucket, bucket=bucket)
class RedisStorage2(BaseStorage):
@@ -283,19 +219,12 @@ class RedisStorage2(BaseStorage):
await dp.storage.wait_closed()
"""
-
- def __init__(
- self,
- host="localhost",
- port=6379,
- db=None,
- password=None,
- ssl=None,
- pool_size=10,
- loop=None,
- prefix="fsm",
- **kwargs,
- ):
+ 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,
+ bucket_ttl: int = 0,
+ **kwargs):
self._host = host
self._port = port
self._db = db
@@ -306,32 +235,28 @@ class RedisStorage2(BaseStorage):
self._kwargs = kwargs
self._prefix = (prefix,)
+ self._state_ttl = state_ttl
+ self._data_ttl = data_ttl
+ self._bucket_ttl = bucket_ttl
+
self._redis: aioredis.RedisConnection = None
self._connection_lock = asyncio.Lock(loop=self._loop)
async def redis(self) -> aioredis.Redis:
"""
Get Redis connection
-
- This property is awaitable.
"""
# Use thread-safe asyncio Lock because this method without that is not safe
async with self._connection_lock:
if self._redis is None:
- self._redis = await aioredis.create_redis_pool(
- (self._host, self._port),
- db=self._db,
- password=self._password,
- ssl=self._ssl,
- minsize=1,
- maxsize=self._pool_size,
- loop=self._loop,
- **self._kwargs,
- )
+ self._redis = await aioredis.create_redis_pool((self._host, self._port),
+ db=self._db, password=self._password, ssl=self._ssl,
+ minsize=1, maxsize=self._pool_size,
+ loop=self._loop, **self._kwargs)
return self._redis
def generate_key(self, *parts):
- return ":".join(self._prefix + tuple(map(str, parts)))
+ return ':'.join(self._prefix + tuple(map(str, parts)))
async def close(self):
async with self._connection_lock:
@@ -346,68 +271,42 @@ class RedisStorage2(BaseStorage):
return await self._redis.wait_closed()
return True
- 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]:
+ 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]:
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 None
- 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:
+ 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:
chat, user = self.check_address(chat=chat, user=user)
key = self.generate_key(chat, user, STATE_DATA_KEY)
redis = await self.redis()
- raw_result = await redis.get(key, encoding="utf8")
+ raw_result = await redis.get(key, encoding='utf8')
if raw_result:
return json.loads(raw_result)
return default or {}
- 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,
- ):
+ 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 = self.check_address(chat=chat, user=user)
key = self.generate_key(chat, user, STATE_KEY)
redis = await self.redis()
if state is None:
await redis.delete(key)
else:
- await redis.set(key, state)
+ await redis.set(key, 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,
- ):
+ 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))
+ await redis.set(key, json.dumps(data), expire=self._data_ttl)
- async def update_data(
- self,
- *,
- chat: typing.Union[str, int, None] = None,
- user: typing.Union[str, int, None] = None,
- data: typing.Dict = None,
- **kwargs,
- ):
+ async def update_data(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
+ data: typing.Dict = None, **kwargs):
if data is None:
data = {}
temp_data = await self.get_data(chat=chat, user=user, default={})
@@ -417,46 +316,31 @@ class RedisStorage2(BaseStorage):
def has_bucket(self):
return True
- async def get_bucket(
- self,
- *,
- chat: typing.Union[str, int, None] = None,
- user: typing.Union[str, int, None] = None,
- default: typing.Optional[dict] = None,
- ) -> typing.Dict:
+ async def get_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
+ default: typing.Optional[dict] = None) -> typing.Dict:
chat, user = self.check_address(chat=chat, user=user)
key = self.generate_key(chat, user, STATE_BUCKET_KEY)
redis = await self.redis()
- raw_result = await redis.get(key, encoding="utf8")
+ raw_result = await redis.get(key, encoding='utf8')
if raw_result:
return json.loads(raw_result)
return default or {}
- async def set_bucket(
- self,
- *,
- chat: typing.Union[str, int, None] = None,
- user: typing.Union[str, int, None] = None,
- bucket: typing.Dict = None,
- ):
+ async def set_bucket(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None,
+ bucket: typing.Dict = None):
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))
+ await redis.set(key, json.dumps(bucket), expire=self._bucket_ttl)
- async def update_bucket(
- self,
- *,
- chat: typing.Union[str, int, None] = None,
- user: typing.Union[str, int, None] = None,
- bucket: typing.Dict = None,
- **kwargs,
- ):
+ async def update_bucket(self, *, chat: typing.Union[str, int, None] = None,
+ user: typing.Union[str, int, None] = None,
+ bucket: typing.Dict = None, **kwargs):
if bucket is None:
bucket = {}
temp_bucket = await self.get_bucket(chat=chat, user=user)
temp_bucket.update(bucket, **kwargs)
- await self.set_bucket(chat=chat, user=user, data=temp_bucket)
+ await self.set_bucket(chat=chat, user=user, bucket=temp_bucket)
async def reset_all(self, full=True):
"""
@@ -470,7 +354,7 @@ class RedisStorage2(BaseStorage):
if full:
await conn.flushdb()
else:
- keys = await conn.keys(self.generate_key("*"))
+ keys = await conn.keys(self.generate_key('*'))
await conn.delete(*keys)
async def get_states_list(self) -> typing.List[typing.Tuple[int]]:
@@ -482,9 +366,9 @@ class RedisStorage2(BaseStorage):
conn = await self.redis()
result = []
- keys = await conn.keys(self.generate_key("*", "*", STATE_KEY), encoding="utf8")
+ keys = await conn.keys(self.generate_key('*', '*', STATE_KEY), encoding='utf8')
for item in keys:
- *_, chat, user, _ = item.split(":")
+ *_, chat, user, _ = item.split(':')
result.append((chat, user))
return result
@@ -506,7 +390,7 @@ async def migrate_redis1_to_redis2(storage1: RedisStorage, storage2: RedisStorag
if not isinstance(storage2, RedisStorage):
raise TypeError(f"{type(storage2)} is not RedisStorage instance.")
- log = logging.getLogger("aiogram.RedisStorage")
+ log = logging.getLogger('aiogram.RedisStorage')
for chat, user in await storage1.get_states_list():
state = await storage1.get_state(chat=chat, user=user)
diff --git a/aiogram/contrib/middlewares/i18n.py b/aiogram/contrib/middlewares/i18n.py
index 8c9ddb3f..610edffa 100644
--- a/aiogram/contrib/middlewares/i18n.py
+++ b/aiogram/contrib/middlewares/i18n.py
@@ -97,17 +97,15 @@ class I18nMiddleware(BaseMiddleware):
if locale not in self.locales:
if n is 1:
return singular
- else:
- return plural
+ return plural
translator = self.locales[locale]
if plural is None:
return translator.gettext(singular)
- else:
- return translator.ngettext(singular, plural, n)
+ return translator.ngettext(singular, plural, n)
- def lazy_gettext(self, singular, plural=None, n=1, locale=None) -> LazyProxy:
+ def lazy_gettext(self, singular, plural=None, n=1, locale=None, enable_cache=False) -> LazyProxy:
"""
Lazy get text
@@ -115,9 +113,10 @@ class I18nMiddleware(BaseMiddleware):
:param plural:
:param n:
:param locale:
+ :param enable_cache:
:return:
"""
- return LazyProxy(self.gettext, singular, plural, n, locale)
+ 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:
diff --git a/aiogram/contrib/middlewares/logging.py b/aiogram/contrib/middlewares/logging.py
index 2fa8d58b..9f389b60 100644
--- a/aiogram/contrib/middlewares/logging.py
+++ b/aiogram/contrib/middlewares/logging.py
@@ -5,7 +5,7 @@ import logging
from aiogram import types
from aiogram.dispatcher.middlewares import BaseMiddleware
-HANDLED_STR = ["Unhandled", "Handled"]
+HANDLED_STR = ['Unhandled', 'Handled']
class LoggingMiddleware(BaseMiddleware):
@@ -18,181 +18,128 @@ class LoggingMiddleware(BaseMiddleware):
super(LoggingMiddleware, self).__init__()
def check_timeout(self, obj):
- start = obj.conf.get("_start", None)
+ start = obj.conf.get('_start', None)
if start:
- del obj.conf["_start"]
+ del obj.conf['_start']
return round((time.time() - start) * 1000)
return -1
async def on_pre_process_update(self, update: types.Update, data: dict):
- update.conf["_start"] = time.time()
+ update.conf['_start'] = time.time()
self.logger.debug(f"Received update [ID:{update.update_id}]")
async def on_post_process_update(self, update: types.Update, result, data: dict):
timeout = self.check_timeout(update)
if timeout > 0:
- self.logger.info(
- f"Process update [ID:{update.update_id}]: [success] (in {timeout} ms)"
- )
+ self.logger.info(f"Process update [ID:{update.update_id}]: [success] (in {timeout} ms)")
async def on_pre_process_message(self, message: types.Message, data: dict):
- self.logger.info(
- f"Received message [ID:{message.message_id}] in chat [{message.chat.type}:{message.chat.id}]"
- )
+ self.logger.info(f"Received message [ID:{message.message_id}] in chat [{message.chat.type}:{message.chat.id}]")
async def on_post_process_message(self, message: types.Message, results, data: dict):
- self.logger.debug(
- f"{HANDLED_STR[bool(len(results))]} "
- f"message [ID:{message.message_id}] in chat [{message.chat.type}:{message.chat.id}]"
- )
+ self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
+ f"message [ID:{message.message_id}] in chat [{message.chat.type}:{message.chat.id}]")
async def on_pre_process_edited_message(self, edited_message, data: dict):
- self.logger.info(
- f"Received edited message [ID:{edited_message.message_id}] "
- f"in chat [{edited_message.chat.type}:{edited_message.chat.id}]"
- )
+ self.logger.info(f"Received edited message [ID:{edited_message.message_id}] "
+ f"in chat [{edited_message.chat.type}:{edited_message.chat.id}]")
async def on_post_process_edited_message(self, edited_message, results, data: dict):
- self.logger.debug(
- f"{HANDLED_STR[bool(len(results))]} "
- f"edited message [ID:{edited_message.message_id}] "
- f"in chat [{edited_message.chat.type}:{edited_message.chat.id}]"
- )
+ self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
+ f"edited message [ID:{edited_message.message_id}] "
+ f"in chat [{edited_message.chat.type}:{edited_message.chat.id}]")
async def on_pre_process_channel_post(self, channel_post: types.Message, data: dict):
- self.logger.info(
- f"Received channel post [ID:{channel_post.message_id}] "
- f"in channel [ID:{channel_post.chat.id}]"
- )
+ self.logger.info(f"Received channel post [ID:{channel_post.message_id}] "
+ f"in channel [ID:{channel_post.chat.id}]")
async def on_post_process_channel_post(self, channel_post: types.Message, results, data: dict):
- self.logger.debug(
- f"{HANDLED_STR[bool(len(results))]} "
- f"channel post [ID:{channel_post.message_id}] "
- f"in chat [{channel_post.chat.type}:{channel_post.chat.id}]"
- )
+ self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
+ f"channel post [ID:{channel_post.message_id}] "
+ f"in chat [{channel_post.chat.type}:{channel_post.chat.id}]")
- async def on_pre_process_edited_channel_post(
- self, edited_channel_post: types.Message, data: dict
- ):
- self.logger.info(
- f"Received edited channel post [ID:{edited_channel_post.message_id}] "
- f"in channel [ID:{edited_channel_post.chat.id}]"
- )
+ async def on_pre_process_edited_channel_post(self, edited_channel_post: types.Message, data: dict):
+ self.logger.info(f"Received edited channel post [ID:{edited_channel_post.message_id}] "
+ f"in channel [ID:{edited_channel_post.chat.id}]")
- async def on_post_process_edited_channel_post(
- self, edited_channel_post: types.Message, results, data: dict
- ):
- self.logger.debug(
- f"{HANDLED_STR[bool(len(results))]} "
- f"edited channel post [ID:{edited_channel_post.message_id}] "
- f"in channel [ID:{edited_channel_post.chat.id}]"
- )
+ async def on_post_process_edited_channel_post(self, edited_channel_post: types.Message, results, data: dict):
+ self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
+ f"edited channel post [ID:{edited_channel_post.message_id}] "
+ f"in channel [ID:{edited_channel_post.chat.id}]")
async def on_pre_process_inline_query(self, inline_query: types.InlineQuery, data: dict):
- self.logger.info(
- f"Received inline query [ID:{inline_query.id}] "
- f"from user [ID:{inline_query.from_user.id}]"
- )
+ self.logger.info(f"Received inline query [ID:{inline_query.id}] "
+ f"from user [ID:{inline_query.from_user.id}]")
- async def on_post_process_inline_query(
- self, inline_query: types.InlineQuery, results, data: dict
- ):
- self.logger.debug(
- f"{HANDLED_STR[bool(len(results))]} "
- f"inline query [ID:{inline_query.id}] "
- f"from user [ID:{inline_query.from_user.id}]"
- )
+ async def on_post_process_inline_query(self, inline_query: types.InlineQuery, results, data: dict):
+ self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
+ f"inline query [ID:{inline_query.id}] "
+ f"from user [ID:{inline_query.from_user.id}]")
- async def on_pre_process_chosen_inline_result(
- self, chosen_inline_result: types.ChosenInlineResult, data: dict
- ):
- self.logger.info(
- f"Received chosen inline result [Inline msg ID:{chosen_inline_result.inline_message_id}] "
- f"from user [ID:{chosen_inline_result.from_user.id}] "
- f"result [ID:{chosen_inline_result.result_id}]"
- )
+ async def on_pre_process_chosen_inline_result(self, chosen_inline_result: types.ChosenInlineResult, data: dict):
+ self.logger.info(f"Received chosen inline result [Inline msg ID:{chosen_inline_result.inline_message_id}] "
+ f"from user [ID:{chosen_inline_result.from_user.id}] "
+ f"result [ID:{chosen_inline_result.result_id}]")
- async def on_post_process_chosen_inline_result(
- self, chosen_inline_result, results, data: dict
- ):
- self.logger.debug(
- f"{HANDLED_STR[bool(len(results))]} "
- f"chosen inline result [Inline msg ID:{chosen_inline_result.inline_message_id}] "
- f"from user [ID:{chosen_inline_result.from_user.id}] "
- f"result [ID:{chosen_inline_result.result_id}]"
- )
+ async def on_post_process_chosen_inline_result(self, chosen_inline_result, results, data: dict):
+ self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
+ f"chosen inline result [Inline msg ID:{chosen_inline_result.inline_message_id}] "
+ f"from user [ID:{chosen_inline_result.from_user.id}] "
+ f"result [ID:{chosen_inline_result.result_id}]")
async def on_pre_process_callback_query(self, callback_query: types.CallbackQuery, data: dict):
if callback_query.message:
+ text = (f"Received callback query [ID:{callback_query.id}] "
+ f"from user [ID:{callback_query.from_user.id}] "
+ f"for message [ID:{callback_query.message.message_id}] "
+ f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}]")
+
if callback_query.message.from_user:
- self.logger.info(
- f"Received callback query [ID:{callback_query.id}] "
- f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}] "
- f"from user [ID:{callback_query.message.from_user.id}]"
- )
- else:
- self.logger.info(
- f"Received callback query [ID:{callback_query.id}] "
- f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}]"
- )
+ text += f" originally posted by user [ID:{callback_query.message.from_user.id}]"
+
+ self.logger.info(text)
+
else:
- self.logger.info(
- f"Received callback query [ID:{callback_query.id}] "
- f"from inline message [ID:{callback_query.inline_message_id}] "
- f"from user [ID:{callback_query.from_user.id}]"
- )
+ self.logger.info(f"Received callback query [ID:{callback_query.id}] "
+ f"from user [ID:{callback_query.from_user.id}] "
+ f"for inline message [ID:{callback_query.inline_message_id}] ")
async def on_post_process_callback_query(self, callback_query, results, data: dict):
if callback_query.message:
+ text = (f"{HANDLED_STR[bool(len(results))]} "
+ f"callback query [ID:{callback_query.id}] "
+ f"from user [ID:{callback_query.from_user.id}] "
+ f"for message [ID:{callback_query.message.message_id}] "
+ f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}]")
+
if callback_query.message.from_user:
- self.logger.debug(
- f"{HANDLED_STR[bool(len(results))]} "
- f"callback query [ID:{callback_query.id}] "
- f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}] "
- f"from user [ID:{callback_query.message.from_user.id}]"
- )
- else:
- self.logger.debug(
- f"{HANDLED_STR[bool(len(results))]} "
- f"callback query [ID:{callback_query.id}] "
- f"in chat [{callback_query.message.chat.type}:{callback_query.message.chat.id}]"
- )
+ text += f" originally posted by user [ID:{callback_query.message.from_user.id}]"
+
+ self.logger.info(text)
+
else:
- self.logger.debug(
- f"{HANDLED_STR[bool(len(results))]} "
- f"callback query [ID:{callback_query.id}] "
- f"from inline message [ID:{callback_query.inline_message_id}] "
- f"from user [ID:{callback_query.from_user.id}]"
- )
+ self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
+ f"callback query [ID:{callback_query.id}] "
+ f"from user [ID:{callback_query.from_user.id}]"
+ f"from inline message [ID:{callback_query.inline_message_id}]")
async def on_pre_process_shipping_query(self, shipping_query: types.ShippingQuery, data: dict):
- self.logger.info(
- f"Received shipping query [ID:{shipping_query.id}] "
- f"from user [ID:{shipping_query.from_user.id}]"
- )
+ self.logger.info(f"Received shipping query [ID:{shipping_query.id}] "
+ f"from user [ID:{shipping_query.from_user.id}]")
async def on_post_process_shipping_query(self, shipping_query, results, data: dict):
- self.logger.debug(
- f"{HANDLED_STR[bool(len(results))]} "
- f"shipping query [ID:{shipping_query.id}] "
- f"from user [ID:{shipping_query.from_user.id}]"
- )
+ self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
+ f"shipping query [ID:{shipping_query.id}] "
+ f"from user [ID:{shipping_query.from_user.id}]")
- async def on_pre_process_pre_checkout_query(
- self, pre_checkout_query: types.PreCheckoutQuery, data: dict
- ):
- self.logger.info(
- f"Received pre-checkout query [ID:{pre_checkout_query.id}] "
- f"from user [ID:{pre_checkout_query.from_user.id}]"
- )
+ async def on_pre_process_pre_checkout_query(self, pre_checkout_query: types.PreCheckoutQuery, data: dict):
+ self.logger.info(f"Received pre-checkout query [ID:{pre_checkout_query.id}] "
+ f"from user [ID:{pre_checkout_query.from_user.id}]")
async def on_post_process_pre_checkout_query(self, pre_checkout_query, results, data: dict):
- self.logger.debug(
- f"{HANDLED_STR[bool(len(results))]} "
- f"pre-checkout query [ID:{pre_checkout_query.id}] "
- f"from user [ID:{pre_checkout_query.from_user.id}]"
- )
+ self.logger.debug(f"{HANDLED_STR[bool(len(results))]} "
+ f"pre-checkout query [ID:{pre_checkout_query.id}] "
+ f"from user [ID:{pre_checkout_query.from_user.id}]")
async def on_pre_process_error(self, update, error, data: dict):
timeout = self.check_timeout(update)
@@ -226,7 +173,7 @@ class LoggingFilter(logging.Filter):
"""
- def __init__(self, name="", prefix="tg", include_content=False):
+ def __init__(self, name='', prefix='tg', include_content=False):
"""
:param name:
:param prefix: prefix for all records
@@ -258,34 +205,34 @@ class LoggingFilter(logging.Filter):
:param update:
:return:
"""
- yield "update_id", update.update_id
+ yield 'update_id', update.update_id
if update.message:
- yield "update_type", "message"
+ yield 'update_type', 'message'
yield from self.process_message(update.message)
if update.edited_message:
- yield "update_type", "edited_message"
+ yield 'update_type', 'edited_message'
yield from self.process_message(update.edited_message)
if update.channel_post:
- yield "update_type", "channel_post"
+ yield 'update_type', 'channel_post'
yield from self.process_message(update.channel_post)
if update.edited_channel_post:
- yield "update_type", "edited_channel_post"
+ yield 'update_type', 'edited_channel_post'
yield from self.process_message(update.edited_channel_post)
if update.inline_query:
- yield "update_type", "inline_query"
+ yield 'update_type', 'inline_query'
yield from self.process_inline_query(update.inline_query)
if update.chosen_inline_result:
- yield "update_type", "chosen_inline_result"
+ yield 'update_type', 'chosen_inline_result'
yield from self.process_chosen_inline_result(update.chosen_inline_result)
if update.callback_query:
- yield "update_type", "callback_query"
+ yield 'update_type', 'callback_query'
yield from self.process_callback_query(update.callback_query)
if update.shipping_query:
- yield "update_type", "shipping_query"
+ yield 'update_type', 'shipping_query'
yield from self.process_shipping_query(update.shipping_query)
if update.pre_checkout_query:
- yield "update_type", "pre_checkout_query"
+ yield 'update_type', 'pre_checkout_query'
yield from self.process_pre_checkout_query(update.pre_checkout_query)
def make_prefix(self, prefix, iterable):
@@ -312,11 +259,11 @@ class LoggingFilter(logging.Filter):
if not user:
return
- yield "user_id", user.id
+ yield 'user_id', user.id
if self.include_content:
- yield "user_full_name", user.full_name
+ yield 'user_full_name', user.full_name
if user.username:
- yield "user_name", f"@{user.username}"
+ yield 'user_name', f"@{user.username}"
def process_chat(self, chat: types.Chat):
"""
@@ -328,15 +275,15 @@ class LoggingFilter(logging.Filter):
if not chat:
return
- yield "chat_id", chat.id
- yield "chat_type", chat.type
+ yield 'chat_id', chat.id
+ yield 'chat_type', chat.type
if self.include_content:
- yield "chat_title", chat.full_name
+ yield 'chat_title', chat.full_name
if chat.username:
- yield "chat_name", f"@{chat.username}"
+ yield 'chat_name', f"@{chat.username}"
def process_message(self, message: types.Message):
- yield "message_content_type", message.content_type
+ yield 'message_content_type', message.content_type
yield from self.process_user(message.from_user)
yield from self.process_chat(message.chat)
@@ -344,84 +291,82 @@ class LoggingFilter(logging.Filter):
return
if message.reply_to_message:
- yield from self.make_prefix("reply_to", self.process_message(message.reply_to_message))
+ yield from self.make_prefix('reply_to', self.process_message(message.reply_to_message))
if message.forward_from:
- yield from self.make_prefix("forward_from", self.process_user(message.forward_from))
+ yield from self.make_prefix('forward_from', self.process_user(message.forward_from))
if message.forward_from_chat:
- yield from self.make_prefix(
- "forward_from_chat", self.process_chat(message.forward_from_chat)
- )
+ yield from self.make_prefix('forward_from_chat', self.process_chat(message.forward_from_chat))
if message.forward_from_message_id:
- yield "message_forward_from_message_id", message.forward_from_message_id
+ yield 'message_forward_from_message_id', message.forward_from_message_id
if message.forward_date:
- yield "message_forward_date", message.forward_date
+ yield 'message_forward_date', message.forward_date
if message.edit_date:
- yield "message_edit_date", message.edit_date
+ yield 'message_edit_date', message.edit_date
if message.media_group_id:
- yield "message_media_group_id", message.media_group_id
+ yield 'message_media_group_id', message.media_group_id
if message.author_signature:
- yield "message_author_signature", message.author_signature
+ yield 'message_author_signature', message.author_signature
if message.text:
- yield "text", message.text or message.caption
- yield "html_text", message.html_text
+ yield 'text', message.text or message.caption
+ yield 'html_text', message.html_text
elif message.audio:
- yield "audio", message.audio.file_id
+ yield 'audio', message.audio.file_id
elif message.animation:
- yield "animation", message.animation.file_id
+ yield 'animation', message.animation.file_id
elif message.document:
- yield "document", message.document.file_id
+ yield 'document', message.document.file_id
elif message.game:
- yield "game", message.game.title
+ yield 'game', message.game.title
elif message.photo:
- yield "photo", message.photo[-1].file_id
+ yield 'photo', message.photo[-1].file_id
elif message.sticker:
- yield "sticker", message.sticker.file_id
+ yield 'sticker', message.sticker.file_id
elif message.video:
- yield "video", message.video.file_id
+ yield 'video', message.video.file_id
elif message.video_note:
- yield "video_note", message.video_note.file_id
+ yield 'video_note', message.video_note.file_id
elif message.voice:
- yield "voice", message.voice.file_id
+ yield 'voice', message.voice.file_id
elif message.contact:
- yield "contact_full_name", message.contact.full_name
- yield "contact_phone_number", message.contact.phone_number
+ yield 'contact_full_name', message.contact.full_name
+ yield 'contact_phone_number', message.contact.phone_number
elif message.venue:
- yield "venue_address", message.venue.address
- yield "location_latitude", message.venue.location.latitude
- yield "location_longitude", message.venue.location.longitude
+ yield 'venue_address', message.venue.address
+ yield 'location_latitude', message.venue.location.latitude
+ yield 'location_longitude', message.venue.location.longitude
elif message.location:
- yield "location_latitude", message.location.latitude
- yield "location_longitude", message.location.longitude
+ yield 'location_latitude', message.location.latitude
+ yield 'location_longitude', message.location.longitude
elif message.new_chat_members:
- yield "new_chat_members", [user.id for user in message.new_chat_members]
+ yield 'new_chat_members', [user.id for user in message.new_chat_members]
elif message.left_chat_member:
- yield "left_chat_member", [user.id for user in message.new_chat_members]
+ yield 'left_chat_member', [user.id for user in message.new_chat_members]
elif message.invoice:
- yield "invoice_title", message.invoice.title
- yield "invoice_description", message.invoice.description
- yield "invoice_start_parameter", message.invoice.start_parameter
- yield "invoice_currency", message.invoice.currency
- yield "invoice_total_amount", message.invoice.total_amount
+ yield 'invoice_title', message.invoice.title
+ yield 'invoice_description', message.invoice.description
+ yield 'invoice_start_parameter', message.invoice.start_parameter
+ yield 'invoice_currency', message.invoice.currency
+ yield 'invoice_total_amount', message.invoice.total_amount
elif message.successful_payment:
- yield "successful_payment_currency", message.successful_payment.currency
- yield "successful_payment_total_amount", message.successful_payment.total_amount
- yield "successful_payment_invoice_payload", message.successful_payment.invoice_payload
- yield "successful_payment_shipping_option_id", message.successful_payment.shipping_option_id
- yield "successful_payment_telegram_payment_charge_id", message.successful_payment.telegram_payment_charge_id
- yield "successful_payment_provider_payment_charge_id", message.successful_payment.provider_payment_charge_id
+ yield 'successful_payment_currency', message.successful_payment.currency
+ yield 'successful_payment_total_amount', message.successful_payment.total_amount
+ yield 'successful_payment_invoice_payload', message.successful_payment.invoice_payload
+ yield 'successful_payment_shipping_option_id', message.successful_payment.shipping_option_id
+ yield 'successful_payment_telegram_payment_charge_id', message.successful_payment.telegram_payment_charge_id
+ yield 'successful_payment_provider_payment_charge_id', message.successful_payment.provider_payment_charge_id
elif message.connected_website:
- yield "connected_website", message.connected_website
+ yield 'connected_website', message.connected_website
elif message.migrate_from_chat_id:
- yield "migrate_from_chat_id", message.migrate_from_chat_id
+ yield 'migrate_from_chat_id', message.migrate_from_chat_id
elif message.migrate_to_chat_id:
- yield "migrate_to_chat_id", message.migrate_to_chat_id
+ yield 'migrate_to_chat_id', message.migrate_to_chat_id
elif message.pinned_message:
- yield from self.make_prefix("pinned_message", message.pinned_message)
+ yield from self.make_prefix('pinned_message', message.pinned_message)
elif message.new_chat_title:
- yield "new_chat_title", message.new_chat_title
+ yield 'new_chat_title', message.new_chat_title
elif message.new_chat_photo:
- yield "new_chat_photo", message.new_chat_photo[-1].file_id
+ yield 'new_chat_photo', message.new_chat_photo[-1].file_id
# elif message.delete_chat_photo:
# yield 'delete_chat_photo', message.delete_chat_photo
# elif message.group_chat_created:
@@ -430,55 +375,53 @@ class LoggingFilter(logging.Filter):
# yield 'passport_data', message.passport_data
def process_inline_query(self, inline_query: types.InlineQuery):
- yield "inline_query_id", inline_query.id
+ yield 'inline_query_id', inline_query.id
yield from self.process_user(inline_query.from_user)
if self.include_content:
- yield "inline_query_text", inline_query.query
+ yield 'inline_query_text', inline_query.query
if inline_query.location:
- yield "location_latitude", inline_query.location.latitude
- yield "location_longitude", inline_query.location.longitude
+ yield 'location_latitude', inline_query.location.latitude
+ yield 'location_longitude', inline_query.location.longitude
if inline_query.offset:
- yield "inline_query_offset", inline_query.offset
+ yield 'inline_query_offset', inline_query.offset
def process_chosen_inline_result(self, chosen_inline_result: types.ChosenInlineResult):
- yield "chosen_inline_result_id", chosen_inline_result.result_id
+ yield 'chosen_inline_result_id', chosen_inline_result.result_id
yield from self.process_user(chosen_inline_result.from_user)
if self.include_content:
- yield "inline_query_text", chosen_inline_result.query
+ yield 'inline_query_text', chosen_inline_result.query
if chosen_inline_result.location:
- yield "location_latitude", chosen_inline_result.location.latitude
- yield "location_longitude", chosen_inline_result.location.longitude
+ yield 'location_latitude', chosen_inline_result.location.latitude
+ yield 'location_longitude', chosen_inline_result.location.longitude
def process_callback_query(self, callback_query: types.CallbackQuery):
yield from self.process_user(callback_query.from_user)
- yield "callback_query_data", callback_query.data
+ yield 'callback_query_data', callback_query.data
if callback_query.message:
- yield from self.make_prefix(
- "callback_query_message", self.process_message(callback_query.message)
- )
+ yield from self.make_prefix('callback_query_message', self.process_message(callback_query.message))
if callback_query.inline_message_id:
- yield "callback_query_inline_message_id", callback_query.inline_message_id
+ yield 'callback_query_inline_message_id', callback_query.inline_message_id
if callback_query.chat_instance:
- yield "callback_query_chat_instance", callback_query.chat_instance
+ yield 'callback_query_chat_instance', callback_query.chat_instance
if callback_query.game_short_name:
- yield "callback_query_game_short_name", callback_query.game_short_name
+ yield 'callback_query_game_short_name', callback_query.game_short_name
def process_shipping_query(self, shipping_query: types.ShippingQuery):
- yield "shipping_query_id", shipping_query.id
+ yield 'shipping_query_id', shipping_query.id
yield from self.process_user(shipping_query.from_user)
if self.include_content:
- yield "shipping_query_invoice_payload", shipping_query.invoice_payload
+ yield 'shipping_query_invoice_payload', shipping_query.invoice_payload
def process_pre_checkout_query(self, pre_checkout_query: types.PreCheckoutQuery):
- yield "pre_checkout_query_id", pre_checkout_query.id
+ yield 'pre_checkout_query_id', pre_checkout_query.id
yield from self.process_user(pre_checkout_query.from_user)
if self.include_content:
- yield "pre_checkout_query_currency", pre_checkout_query.currency
- yield "pre_checkout_query_total_amount", pre_checkout_query.total_amount
- yield "pre_checkout_query_invoice_payload", pre_checkout_query.invoice_payload
- yield "pre_checkout_query_shipping_option_id", pre_checkout_query.shipping_option_id
+ yield 'pre_checkout_query_currency', pre_checkout_query.currency
+ yield 'pre_checkout_query_total_amount', pre_checkout_query.total_amount
+ yield 'pre_checkout_query_invoice_payload', pre_checkout_query.invoice_payload
+ yield 'pre_checkout_query_shipping_option_id', pre_checkout_query.shipping_option_id
diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py
index 65ddfed3..600e25ba 100644
--- a/aiogram/dispatcher/dispatcher.py
+++ b/aiogram/dispatcher/dispatcher.py
@@ -8,29 +8,13 @@ import typing
import aiohttp
from aiohttp.helpers import sentinel
-from .filters import (
- Command,
- ContentTypeFilter,
- ExceptionsFilter,
- FiltersFactory,
- HashTag,
- Regexp,
- RegexpCommandsFilter,
- StateFilter,
- Text,
-)
+from aiogram.utils.deprecated import renamed_argument
+from .filters import Command, ContentTypeFilter, ExceptionsFilter, FiltersFactory, HashTag, Regexp, \
+ RegexpCommandsFilter, StateFilter, Text, IDFilter, AdminFilter, IsReplyFilter
from .handler import Handler
from .middlewares import MiddlewareManager
-from .storage import (
- BaseStorage,
- DELTA,
- DisabledStorage,
- EXCEEDED_COUNT,
- FSMContext,
- LAST_CALL,
- RATE_LIMIT,
- RESULT,
-)
+from .storage import BaseStorage, DELTA, DisabledStorage, EXCEEDED_COUNT, FSMContext, \
+ LAST_CALL, RATE_LIMIT, RESULT
from .webhook import BaseResponse
from .. import types
from ..bot import Bot
@@ -39,7 +23,7 @@ from ..utils.mixins import ContextInstanceMixin, DataMixin
log = logging.getLogger(__name__)
-DEFAULT_RATE_LIMIT = 0.1
+DEFAULT_RATE_LIMIT = .1
class Dispatcher(DataMixin, ContextInstanceMixin):
@@ -50,21 +34,13 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
inline queries, chosen inline results, callback queries, shipping queries, pre-checkout queries.
"""
- def __init__(
- self,
- bot,
- loop=None,
- storage: typing.Optional[BaseStorage] = None,
- run_tasks_by_default: bool = False,
- throttling_rate_limit=DEFAULT_RATE_LIMIT,
- no_throttle_error=False,
- filters_factory=None,
- ):
+ def __init__(self, bot, loop=None, storage: typing.Optional[BaseStorage] = None,
+ run_tasks_by_default: bool = False,
+ throttling_rate_limit=DEFAULT_RATE_LIMIT, no_throttle_error=False,
+ filters_factory=None):
if not isinstance(bot, Bot):
- raise TypeError(
- f"Argument 'bot' must be an instance of Bot, not '{type(bot).__name__}'"
- )
+ raise TypeError(f"Argument 'bot' must be an instance of Bot, not '{type(bot).__name__}'")
if loop is None:
loop = bot.loop
@@ -82,18 +58,18 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
self.no_throttle_error = no_throttle_error
self.filters_factory: FiltersFactory = filters_factory
- self.updates_handler = Handler(self, middleware_key="update")
- self.message_handlers = Handler(self, middleware_key="message")
- self.edited_message_handlers = Handler(self, middleware_key="edited_message")
- self.channel_post_handlers = Handler(self, middleware_key="channel_post")
- self.edited_channel_post_handlers = Handler(self, middleware_key="edited_channel_post")
- self.inline_query_handlers = Handler(self, middleware_key="inline_query")
- self.chosen_inline_result_handlers = Handler(self, middleware_key="chosen_inline_result")
- self.callback_query_handlers = Handler(self, middleware_key="callback_query")
- self.shipping_query_handlers = Handler(self, middleware_key="shipping_query")
- self.pre_checkout_query_handlers = Handler(self, middleware_key="pre_checkout_query")
- self.poll_handlers = Handler(self, middleware_key="poll")
- self.errors_handlers = Handler(self, once=False, middleware_key="error")
+ self.updates_handler = Handler(self, middleware_key='update')
+ self.message_handlers = Handler(self, middleware_key='message')
+ self.edited_message_handlers = Handler(self, middleware_key='edited_message')
+ self.channel_post_handlers = Handler(self, middleware_key='channel_post')
+ self.edited_channel_post_handlers = Handler(self, middleware_key='edited_channel_post')
+ self.inline_query_handlers = Handler(self, middleware_key='inline_query')
+ self.chosen_inline_result_handlers = Handler(self, middleware_key='chosen_inline_result')
+ self.callback_query_handlers = Handler(self, middleware_key='callback_query')
+ self.shipping_query_handlers = Handler(self, middleware_key='shipping_query')
+ self.pre_checkout_query_handlers = Handler(self, middleware_key='pre_checkout_query')
+ self.poll_handlers = Handler(self, middleware_key='poll')
+ self.errors_handlers = Handler(self, once=False, middleware_key='error')
self.middleware = MiddlewareManager(self)
@@ -108,57 +84,73 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
def _setup_filters(self):
filters_factory = self.filters_factory
- filters_factory.bind(
- StateFilter, exclude_event_handlers=[self.errors_handlers, self.poll_handlers]
- )
- filters_factory.bind(
- ContentTypeFilter,
- event_handlers=[
- self.message_handlers,
- self.edited_message_handlers,
- self.channel_post_handlers,
- self.edited_channel_post_handlers,
- ],
- ),
- filters_factory.bind(
- Command, event_handlers=[self.message_handlers, self.edited_message_handlers]
- )
- filters_factory.bind(
- Text,
- event_handlers=[
- self.message_handlers,
- self.edited_message_handlers,
- self.channel_post_handlers,
- self.edited_channel_post_handlers,
- self.callback_query_handlers,
- self.poll_handlers,
- ],
- )
- filters_factory.bind(
- HashTag,
- event_handlers=[
- self.message_handlers,
- self.edited_message_handlers,
- self.channel_post_handlers,
- self.edited_channel_post_handlers,
- ],
- )
- filters_factory.bind(
- Regexp,
- event_handlers=[
- self.message_handlers,
- self.edited_message_handlers,
- self.channel_post_handlers,
- self.edited_channel_post_handlers,
- self.callback_query_handlers,
- self.poll_handlers,
- ],
- )
- filters_factory.bind(
- RegexpCommandsFilter,
- event_handlers=[self.message_handlers, self.edited_message_handlers],
- )
- filters_factory.bind(ExceptionsFilter, event_handlers=[self.errors_handlers])
+ filters_factory.bind(StateFilter, exclude_event_handlers=[
+ self.errors_handlers,
+ self.poll_handlers,
+ ])
+ filters_factory.bind(ContentTypeFilter, event_handlers=[
+ self.message_handlers,
+ self.edited_message_handlers,
+ self.channel_post_handlers,
+ self.edited_channel_post_handlers,
+ ]),
+ filters_factory.bind(Command, event_handlers=[
+ self.message_handlers,
+ self.edited_message_handlers
+ ])
+ filters_factory.bind(Text, event_handlers=[
+ self.message_handlers,
+ self.edited_message_handlers,
+ self.channel_post_handlers,
+ self.edited_channel_post_handlers,
+ self.callback_query_handlers,
+ self.poll_handlers,
+ self.inline_query_handlers,
+ ])
+ filters_factory.bind(HashTag, event_handlers=[
+ self.message_handlers,
+ self.edited_message_handlers,
+ self.channel_post_handlers,
+ self.edited_channel_post_handlers,
+ ])
+ filters_factory.bind(Regexp, event_handlers=[
+ self.message_handlers,
+ self.edited_message_handlers,
+ self.channel_post_handlers,
+ self.edited_channel_post_handlers,
+ self.callback_query_handlers,
+ self.poll_handlers,
+ self.inline_query_handlers,
+ ])
+ filters_factory.bind(RegexpCommandsFilter, event_handlers=[
+ self.message_handlers,
+ self.edited_message_handlers,
+ ])
+ filters_factory.bind(ExceptionsFilter, event_handlers=[
+ self.errors_handlers,
+ ])
+ filters_factory.bind(AdminFilter, event_handlers=[
+ self.message_handlers,
+ self.edited_message_handlers,
+ self.channel_post_handlers,
+ self.edited_channel_post_handlers,
+ self.callback_query_handlers,
+ self.inline_query_handlers,
+ ])
+ filters_factory.bind(IDFilter, event_handlers=[
+ self.message_handlers,
+ self.edited_message_handlers,
+ self.channel_post_handlers,
+ self.edited_channel_post_handlers,
+ self.callback_query_handlers,
+ self.inline_query_handlers,
+ ])
+ filters_factory.bind(IsReplyFilter, event_handlers=[
+ self.message_handlers,
+ self.edited_message_handlers,
+ self.channel_post_handlers,
+ self.edited_channel_post_handlers,
+ ])
def __del__(self):
self.stop_polling()
@@ -254,15 +246,13 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
return await self.bot.delete_webhook()
- async def start_polling(
- self,
- timeout=20,
- relax=0.1,
- limit=None,
- reset_webhook=None,
- fast: typing.Optional[bool] = True,
- error_sleep: int = 5,
- ):
+ async def start_polling(self,
+ timeout=20,
+ relax=0.1,
+ limit=None,
+ reset_webhook=None,
+ fast: typing.Optional[bool] = True,
+ error_sleep: int = 5):
"""
Start long-polling
@@ -274,9 +264,9 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
:return:
"""
if self._polling:
- raise RuntimeError("Polling already started")
+ raise RuntimeError('Polling already started')
- log.info("Start polling.")
+ log.info('Start polling.')
# context.set_value(MODE, LONG_POLLING)
Dispatcher.set_current(self)
@@ -292,20 +282,18 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
try:
current_request_timeout = self.bot.timeout
if current_request_timeout is not sentinel and timeout is not None:
- request_timeout = aiohttp.ClientTimeout(
- total=current_request_timeout.total + timeout or 1
- )
+ request_timeout = aiohttp.ClientTimeout(total=current_request_timeout.total + timeout or 1)
else:
request_timeout = None
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)
+ except asyncio.CancelledError:
+ break
except:
- log.exception("Cause exception while getting updates.")
+ log.exception('Cause exception while getting updates.')
await asyncio.sleep(error_sleep)
continue
@@ -319,8 +307,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
await asyncio.sleep(relax)
finally:
- self._close_waiter._set_result(None)
- log.warning("Polling is stopped.")
+ self._close_waiter.set_result(None)
+ log.warning('Polling is stopped.')
async def _process_polling_updates(self, updates, fast: typing.Optional[bool] = True):
"""
@@ -339,7 +327,7 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
try:
asyncio.gather(*need_to_call)
except TelegramAPIError:
- log.exception("Cause exception while processing updates.")
+ log.exception('Cause exception while processing updates.')
def stop_polling(self):
"""
@@ -347,8 +335,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
:return:
"""
- if hasattr(self, "_polling") and self._polling:
- log.info("Stop polling...")
+ if hasattr(self, '_polling') and self._polling:
+ log.info('Stop polling...')
self._polling = False
async def wait_closed(self):
@@ -367,17 +355,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
"""
return self._polling
- def register_message_handler(
- self,
- callback,
- *custom_filters,
- commands=None,
- regexp=None,
- content_types=None,
- state=None,
- run_task=None,
- **kwargs,
- ):
+ def register_message_handler(self, callback, *custom_filters, commands=None, regexp=None, content_types=None,
+ state=None, run_task=None, **kwargs):
"""
Register handler for message
@@ -403,27 +382,17 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
:param state:
:return: decorated function
"""
- filters_set = self.filters_factory.resolve(
- self.message_handlers,
- *custom_filters,
- commands=commands,
- regexp=regexp,
- content_types=content_types,
- state=state,
- **kwargs,
- )
+ filters_set = self.filters_factory.resolve(self.message_handlers,
+ *custom_filters,
+ commands=commands,
+ regexp=regexp,
+ content_types=content_types,
+ state=state,
+ **kwargs)
self.message_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
- def message_handler(
- self,
- *custom_filters,
- commands=None,
- regexp=None,
- content_types=None,
- state=None,
- run_task=None,
- **kwargs,
- ):
+ def message_handler(self, *custom_filters, commands=None, regexp=None, content_types=None, state=None,
+ run_task=None, **kwargs):
"""
Decorator for message handler
@@ -494,31 +463,15 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
"""
def decorator(callback):
- self.register_message_handler(
- callback,
- *custom_filters,
- commands=commands,
- regexp=regexp,
- content_types=content_types,
- state=state,
- run_task=run_task,
- **kwargs,
- )
+ self.register_message_handler(callback, *custom_filters,
+ commands=commands, regexp=regexp, content_types=content_types,
+ state=state, run_task=run_task, **kwargs)
return callback
return decorator
- def register_edited_message_handler(
- self,
- callback,
- *custom_filters,
- commands=None,
- regexp=None,
- content_types=None,
- state=None,
- run_task=None,
- **kwargs,
- ):
+ def register_edited_message_handler(self, callback, *custom_filters, commands=None, regexp=None, content_types=None,
+ state=None, run_task=None, **kwargs):
"""
Register handler for edited message
@@ -532,29 +485,17 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
:param kwargs:
:return: decorated function
"""
- filters_set = self.filters_factory.resolve(
- self.edited_message_handlers,
- *custom_filters,
- commands=commands,
- regexp=regexp,
- content_types=content_types,
- state=state,
- **kwargs,
- )
- self.edited_message_handlers.register(
- self._wrap_async_task(callback, run_task), filters_set
- )
+ filters_set = self.filters_factory.resolve(self.edited_message_handlers,
+ *custom_filters,
+ commands=commands,
+ regexp=regexp,
+ content_types=content_types,
+ state=state,
+ **kwargs)
+ self.edited_message_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
- def edited_message_handler(
- self,
- *custom_filters,
- commands=None,
- regexp=None,
- content_types=None,
- state=None,
- run_task=None,
- **kwargs,
- ):
+ def edited_message_handler(self, *custom_filters, commands=None, regexp=None, content_types=None,
+ state=None, run_task=None, **kwargs):
"""
Decorator for edited message handler
@@ -577,31 +518,14 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
"""
def decorator(callback):
- self.register_edited_message_handler(
- callback,
- *custom_filters,
- commands=commands,
- regexp=regexp,
- content_types=content_types,
- state=state,
- run_task=run_task,
- **kwargs,
- )
+ self.register_edited_message_handler(callback, *custom_filters, commands=commands, regexp=regexp,
+ content_types=content_types, state=state, run_task=run_task, **kwargs)
return callback
return decorator
- def register_channel_post_handler(
- self,
- callback,
- *custom_filters,
- commands=None,
- regexp=None,
- content_types=None,
- state=None,
- run_task=None,
- **kwargs,
- ):
+ def register_channel_post_handler(self, callback, *custom_filters, commands=None, regexp=None, content_types=None,
+ state=None, run_task=None, **kwargs):
"""
Register handler for channel post
@@ -615,27 +539,17 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
:param kwargs:
:return: decorated function
"""
- filters_set = self.filters_factory.resolve(
- self.channel_post_handlers,
- *custom_filters,
- commands=commands,
- regexp=regexp,
- content_types=content_types,
- state=state,
- **kwargs,
- )
+ filters_set = self.filters_factory.resolve(self.channel_post_handlers,
+ *custom_filters,
+ commands=commands,
+ regexp=regexp,
+ content_types=content_types,
+ state=state,
+ **kwargs)
self.channel_post_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
- def channel_post_handler(
- self,
- *custom_filters,
- commands=None,
- regexp=None,
- content_types=None,
- state=None,
- run_task=None,
- **kwargs,
- ):
+ def channel_post_handler(self, *custom_filters, commands=None, regexp=None, content_types=None,
+ state=None, run_task=None, **kwargs):
"""
Decorator for channel post handler
@@ -650,31 +564,14 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
"""
def decorator(callback):
- self.register_channel_post_handler(
- callback,
- *custom_filters,
- commands=commands,
- regexp=regexp,
- content_types=content_types,
- state=state,
- run_task=run_task,
- **kwargs,
- )
+ self.register_channel_post_handler(callback, *custom_filters, commands=commands, regexp=regexp,
+ content_types=content_types, state=state, run_task=run_task, **kwargs)
return callback
return decorator
- def register_edited_channel_post_handler(
- self,
- callback,
- *custom_filters,
- commands=None,
- regexp=None,
- content_types=None,
- state=None,
- run_task=None,
- **kwargs,
- ):
+ def register_edited_channel_post_handler(self, callback, *custom_filters, commands=None, regexp=None,
+ content_types=None, state=None, run_task=None, **kwargs):
"""
Register handler for edited channel post
@@ -688,29 +585,17 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
:param kwargs:
:return: decorated function
"""
- filters_set = self.filters_factory.resolve(
- self.edited_message_handlers,
- *custom_filters,
- commands=commands,
- regexp=regexp,
- content_types=content_types,
- state=state,
- **kwargs,
- )
- self.edited_channel_post_handlers.register(
- self._wrap_async_task(callback, run_task), filters_set
- )
+ filters_set = self.filters_factory.resolve(self.edited_message_handlers,
+ *custom_filters,
+ commands=commands,
+ regexp=regexp,
+ content_types=content_types,
+ state=state,
+ **kwargs)
+ self.edited_channel_post_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
- def edited_channel_post_handler(
- self,
- *custom_filters,
- commands=None,
- regexp=None,
- content_types=None,
- state=None,
- run_task=None,
- **kwargs,
- ):
+ def edited_channel_post_handler(self, *custom_filters, commands=None, regexp=None, content_types=None,
+ state=None, run_task=None, **kwargs):
"""
Decorator for edited channel post handler
@@ -725,23 +610,14 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
"""
def decorator(callback):
- self.register_edited_channel_post_handler(
- callback,
- *custom_filters,
- commands=commands,
- regexp=regexp,
- content_types=content_types,
- state=state,
- run_task=run_task,
- **kwargs,
- )
+ self.register_edited_channel_post_handler(callback, *custom_filters, commands=commands, regexp=regexp,
+ content_types=content_types, state=state, run_task=run_task,
+ **kwargs)
return callback
return decorator
- def register_inline_handler(
- self, callback, *custom_filters, state=None, run_task=None, **kwargs
- ):
+ def register_inline_handler(self, callback, *custom_filters, state=None, run_task=None, **kwargs):
"""
Register handler for inline query
@@ -760,9 +636,10 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
"""
if custom_filters is None:
custom_filters = []
- filters_set = self.filters_factory.resolve(
- self.inline_query_handlers, *custom_filters, state=state, **kwargs
- )
+ filters_set = self.filters_factory.resolve(self.inline_query_handlers,
+ *custom_filters,
+ state=state,
+ **kwargs)
self.inline_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
def inline_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
@@ -784,16 +661,12 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
"""
def decorator(callback):
- self.register_inline_handler(
- callback, *custom_filters, state=state, run_task=run_task, **kwargs
- )
+ self.register_inline_handler(callback, *custom_filters, state=state, run_task=run_task, **kwargs)
return callback
return decorator
- def register_chosen_inline_handler(
- self, callback, *custom_filters, state=None, run_task=None, **kwargs
- ):
+ def register_chosen_inline_handler(self, callback, *custom_filters, state=None, run_task=None, **kwargs):
"""
Register handler for chosen inline query
@@ -812,12 +685,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
"""
if custom_filters is None:
custom_filters = []
- filters_set = self.filters_factory.resolve(
- self.chosen_inline_result_handlers, *custom_filters, state=state, **kwargs
- )
- self.chosen_inline_result_handlers.register(
- self._wrap_async_task(callback, run_task), filters_set
- )
+ filters_set = self.filters_factory.resolve(self.chosen_inline_result_handlers,
+ *custom_filters,
+ state=state,
+ **kwargs)
+ self.chosen_inline_result_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
def chosen_inline_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
"""
@@ -838,16 +710,12 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
"""
def decorator(callback):
- self.register_chosen_inline_handler(
- callback, *custom_filters, state=state, run_task=run_task, **kwargs
- )
+ self.register_chosen_inline_handler(callback, *custom_filters, state=state, run_task=run_task, **kwargs)
return callback
return decorator
- def register_callback_query_handler(
- self, callback, *custom_filters, state=None, run_task=None, **kwargs
- ):
+ def register_callback_query_handler(self, callback, *custom_filters, state=None, run_task=None, **kwargs):
"""
Register handler for callback query
@@ -863,12 +731,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
:param run_task: run callback in task (no wait results)
:param kwargs:
"""
- filters_set = self.filters_factory.resolve(
- self.callback_query_handlers, *custom_filters, state=state, **kwargs
- )
- self.callback_query_handlers.register(
- self._wrap_async_task(callback, run_task), filters_set
- )
+ filters_set = self.filters_factory.resolve(self.callback_query_handlers,
+ *custom_filters,
+ state=state,
+ **kwargs)
+ self.callback_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
def callback_query_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
"""
@@ -888,16 +755,13 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
"""
def decorator(callback):
- self.register_callback_query_handler(
- callback, *custom_filters, state=state, run_task=run_task, **kwargs
- )
+ self.register_callback_query_handler(callback, *custom_filters, state=state, run_task=run_task, **kwargs)
return callback
return decorator
- def register_shipping_query_handler(
- self, callback, *custom_filters, state=None, run_task=None, **kwargs
- ):
+ def register_shipping_query_handler(self, callback, *custom_filters, state=None, run_task=None,
+ **kwargs):
"""
Register handler for shipping query
@@ -913,12 +777,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
:param run_task: run callback in task (no wait results)
:param kwargs:
"""
- filters_set = self.filters_factory.resolve(
- self.shipping_query_handlers, *custom_filters, state=state, **kwargs
- )
- self.shipping_query_handlers.register(
- self._wrap_async_task(callback, run_task), filters_set
- )
+ filters_set = self.filters_factory.resolve(self.shipping_query_handlers,
+ *custom_filters,
+ state=state,
+ **kwargs)
+ self.shipping_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
def shipping_query_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
"""
@@ -938,16 +801,12 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
"""
def decorator(callback):
- self.register_shipping_query_handler(
- callback, *custom_filters, state=state, run_task=run_task, **kwargs
- )
+ self.register_shipping_query_handler(callback, *custom_filters, state=state, run_task=run_task, **kwargs)
return callback
return decorator
- def register_pre_checkout_query_handler(
- self, callback, *custom_filters, state=None, run_task=None, **kwargs
- ):
+ def register_pre_checkout_query_handler(self, callback, *custom_filters, state=None, run_task=None, **kwargs):
"""
Register handler for pre-checkout query
@@ -963,12 +822,11 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
:param run_task: run callback in task (no wait results)
:param kwargs:
"""
- filters_set = self.filters_factory.resolve(
- self.pre_checkout_query_handlers, *custom_filters, state=state, **kwargs
- )
- self.pre_checkout_query_handlers.register(
- self._wrap_async_task(callback, run_task), filters_set
- )
+ filters_set = self.filters_factory.resolve(self.pre_checkout_query_handlers,
+ *custom_filters,
+ state=state,
+ **kwargs)
+ self.pre_checkout_query_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
def pre_checkout_query_handler(self, *custom_filters, state=None, run_task=None, **kwargs):
"""
@@ -988,27 +846,27 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
"""
def decorator(callback):
- self.register_pre_checkout_query_handler(
- callback, *custom_filters, state=state, run_task=run_task, **kwargs
- )
+ self.register_pre_checkout_query_handler(callback, *custom_filters, state=state, run_task=run_task,
+ **kwargs)
return callback
return decorator
def register_poll_handler(self, callback, *custom_filters, run_task=None, **kwargs):
- filters_set = self.filters_factory.resolve(self.poll_handlers, *custom_filters, **kwargs)
+ filters_set = self.filters_factory.resolve(self.poll_handlers,
+ *custom_filters,
+ **kwargs)
self.poll_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
def poll_handler(self, *custom_filters, run_task=None, **kwargs):
def decorator(callback):
- self.register_poll_handler(callback, *custom_filters, run_task=run_task, **kwargs)
+ self.register_poll_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
- ):
+ def register_errors_handler(self, callback, *custom_filters, exception=None, run_task=None, **kwargs):
"""
Register handler for errors
@@ -1016,9 +874,10 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
:param exception: you can make handler for specific errors type
:param run_task: run callback in task (no wait results)
"""
- filters_set = self.filters_factory.resolve(
- self.errors_handlers, *custom_filters, exception=exception, **kwargs
- )
+ filters_set = self.filters_factory.resolve(self.errors_handlers,
+ *custom_filters,
+ exception=exception,
+ **kwargs)
self.errors_handlers.register(self._wrap_async_task(callback, run_task), filters_set)
def errors_handler(self, *custom_filters, exception=None, run_task=None, **kwargs):
@@ -1031,22 +890,15 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
"""
def decorator(callback):
- self.register_errors_handler(
- self._wrap_async_task(callback, run_task),
- *custom_filters,
- exception=exception,
- **kwargs,
- )
+ self.register_errors_handler(self._wrap_async_task(callback, run_task),
+ *custom_filters, exception=exception, **kwargs)
return callback
return decorator
- def current_state(
- self,
- *,
- chat: typing.Union[str, int, None] = None,
- user: typing.Union[str, int, None] = None,
- ) -> FSMContext:
+ def current_state(self, *,
+ chat: typing.Union[str, int, None] = None,
+ user: typing.Union[str, int, None] = None) -> FSMContext:
"""
Get current state for user in chat as context
@@ -1071,33 +923,35 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
return FSMContext(storage=self.storage, chat=chat, user=user)
- async def throttle(self, key, *, rate=None, user=None, chat=None, no_error=None) -> bool:
+ @renamed_argument(old_name='user', new_name='user_id', until_version='3.0', stacklevel=3)
+ @renamed_argument(old_name='chat', new_name='chat_id', until_version='3.0', stacklevel=4)
+ async def throttle(self, key, *, rate=None, user_id=None, chat_id=None, no_error=None) -> bool:
"""
Execute throttling manager.
Returns True if limit has not exceeded otherwise raises ThrottleError or returns False
:param key: key in storage
:param rate: limit (by default is equal to default rate limit)
- :param user: user id
- :param chat: chat id
+ :param user_id: user id
+ :param chat_id: chat id
:param no_error: return boolean value instead of raising error
:return: bool
"""
if not self.storage.has_bucket():
- raise RuntimeError("This storage does not provide Leaky Bucket")
+ raise RuntimeError('This storage does not provide Leaky Bucket')
if no_error is None:
no_error = self.no_throttle_error
if rate is None:
rate = self.throttling_rate_limit
- if user is None and chat is None:
- user = types.User.get_current()
- chat = types.Chat.get_current()
+ if user_id is None and chat_id is None:
+ user_id = types.User.get_current().id
+ chat_id = types.Chat.get_current().id
# Detect current time
now = time.time()
- bucket = await self.storage.get_bucket(chat=chat, user=user)
+ bucket = await self.storage.get_bucket(chat=chat_id, user=user_id)
# Fix bucket
if bucket is None:
@@ -1121,53 +975,57 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
else:
data[EXCEEDED_COUNT] = 1
bucket[key].update(data)
- await self.storage.set_bucket(chat=chat, user=user, bucket=bucket)
+ await self.storage.set_bucket(chat=chat_id, user=user_id, bucket=bucket)
if not result and not no_error:
# Raise if it is allowed
- raise Throttled(key=key, chat=chat, user=user, **data)
+ raise Throttled(key=key, chat=chat_id, user=user_id, **data)
return result
- async def check_key(self, key, chat=None, user=None):
+ @renamed_argument(old_name='user', new_name='user_id', until_version='3.0', stacklevel=3)
+ @renamed_argument(old_name='chat', new_name='chat_id', until_version='3.0', stacklevel=4)
+ async def check_key(self, key, chat_id=None, user_id=None):
"""
Get information about key in bucket
:param key:
- :param chat:
- :param user:
+ :param chat_id:
+ :param user_id:
:return:
"""
if not self.storage.has_bucket():
- raise RuntimeError("This storage does not provide Leaky Bucket")
+ raise RuntimeError('This storage does not provide Leaky Bucket')
- if user is None and chat is None:
- user = types.User.get_current()
- chat = types.Chat.get_current()
+ if user_id is None and chat_id is None:
+ user_id = types.User.get_current()
+ chat_id = types.Chat.get_current()
- bucket = await self.storage.get_bucket(chat=chat, user=user)
+ bucket = await self.storage.get_bucket(chat=chat_id, user=user_id)
data = bucket.get(key, {})
- return Throttled(key=key, chat=chat, user=user, **data)
+ return Throttled(key=key, chat=chat_id, user=user_id, **data)
- async def release_key(self, key, chat=None, user=None):
+ @renamed_argument(old_name='user', new_name='user_id', until_version='3.0', stacklevel=3)
+ @renamed_argument(old_name='chat', new_name='chat_id', until_version='3.0', stacklevel=4)
+ async def release_key(self, key, chat_id=None, user_id=None):
"""
Release blocked key
:param key:
- :param chat:
- :param user:
+ :param chat_id:
+ :param user_id:
:return:
"""
if not self.storage.has_bucket():
- raise RuntimeError("This storage does not provide Leaky Bucket")
+ raise RuntimeError('This storage does not provide Leaky Bucket')
- if user is None and chat is None:
- user = types.User.get_current()
- chat = types.Chat.get_current()
+ if user_id is None and chat_id is None:
+ user_id = types.User.get_current()
+ chat_id = types.Chat.get_current()
- bucket = await self.storage.get_bucket(chat=chat, user=user)
+ bucket = await self.storage.get_bucket(chat=chat_id, user=user_id)
if bucket and key in bucket:
- del bucket["key"]
- await self.storage.set_bucket(chat=chat, user=user, bucket=bucket)
+ del bucket['key']
+ await self.storage.set_bucket(chat=chat_id, user=user_id, bucket=bucket)
return True
return False
@@ -1192,7 +1050,8 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
try:
response = task.result()
except Exception as e:
- self.loop.create_task(self.errors_handlers.notify(types.Update.get_current(), e))
+ 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))
@@ -1211,3 +1070,64 @@ class Dispatcher(DataMixin, ContextInstanceMixin):
if run_task:
return self.async_task(callback)
return callback
+
+ def throttled(self, on_throttled: typing.Optional[typing.Callable] = None,
+ key=None, rate=None,
+ user_id=None, chat_id=None):
+ """
+ Meta-decorator for throttling.
+ Invokes on_throttled if the handler was throttled.
+
+ Example:
+
+ .. code-block:: python3
+
+ async def handler_throttled(message: types.Message, **kwargs):
+ await message.answer("Throttled!")
+
+ @dp.throttled(handler_throttled)
+ async def some_handler(message: types.Message):
+ await message.answer("Didn't throttled!")
+
+ :param on_throttled: the callable object that should be either a function or return a coroutine
+ :param key: key in storage
+ :param rate: limit (by default is equal to default rate limit)
+ :param user_id: user id
+ :param chat_id: chat id
+ :return: decorator
+ """
+ def decorator(func):
+ @functools.wraps(func)
+ async def wrapped(*args, **kwargs):
+ is_not_throttled = await self.throttle(key if key is not None else func.__name__,
+ rate=rate,
+ user_id=user_id, chat_id=chat_id,
+ 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
+
+ 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 4752f56a..67c13872 100644
--- a/aiogram/dispatcher/filters/__init__.py
+++ b/aiogram/dispatcher/filters/__init__.py
@@ -1,51 +1,34 @@
-from .builtin import (
- Command,
- CommandHelp,
- CommandPrivacy,
- CommandSettings,
- CommandStart,
- ContentTypeFilter,
- ExceptionsFilter,
- HashTag,
- Regexp,
- RegexpCommandsFilter,
- StateFilter,
- Text,
-)
+from .builtin import Command, CommandHelp, CommandPrivacy, CommandSettings, CommandStart, ContentTypeFilter, \
+ ExceptionsFilter, HashTag, Regexp, RegexpCommandsFilter, StateFilter, \
+ Text, IDFilter, AdminFilter, IsReplyFilter
from .factory import FiltersFactory
-from .filters import (
- AbstractFilter,
- BoundFilter,
- Filter,
- FilterNotPassed,
- FilterRecord,
- execute_filter,
- check_filters,
- get_filter_spec,
- get_filters_spec,
-)
+from .filters import AbstractFilter, BoundFilter, Filter, FilterNotPassed, FilterRecord, execute_filter, \
+ check_filters, get_filter_spec, get_filters_spec
__all__ = [
- "AbstractFilter",
- "BoundFilter",
- "Command",
- "CommandStart",
- "CommandHelp",
- "CommandPrivacy",
- "CommandSettings",
- "ContentTypeFilter",
- "ExceptionsFilter",
- "HashTag",
- "Filter",
- "FilterNotPassed",
- "FilterRecord",
- "FiltersFactory",
- "RegexpCommandsFilter",
- "Regexp",
- "StateFilter",
- "Text",
- "get_filter_spec",
- "get_filters_spec",
- "execute_filter",
- "check_filters",
+ 'AbstractFilter',
+ 'BoundFilter',
+ 'Command',
+ 'CommandStart',
+ 'CommandHelp',
+ 'CommandPrivacy',
+ 'CommandSettings',
+ 'ContentTypeFilter',
+ 'ExceptionsFilter',
+ 'HashTag',
+ 'Filter',
+ 'FilterNotPassed',
+ 'FilterRecord',
+ 'FiltersFactory',
+ 'RegexpCommandsFilter',
+ 'Regexp',
+ 'StateFilter',
+ 'Text',
+ 'IDFilter',
+ 'IsReplyFilter',
+ 'AdminFilter',
+ 'get_filter_spec',
+ 'get_filters_spec',
+ 'execute_filter',
+ 'check_filters',
]
diff --git a/aiogram/dispatcher/filters/builtin.py b/aiogram/dispatcher/filters/builtin.py
index c7526b7b..55ed63e5 100644
--- a/aiogram/dispatcher/filters/builtin.py
+++ b/aiogram/dispatcher/filters/builtin.py
@@ -9,7 +9,7 @@ from babel.support import LazyProxy
from aiogram import types
from aiogram.dispatcher.filters.filters import BoundFilter, Filter
-from aiogram.types import CallbackQuery, Message, InlineQuery, Poll
+from aiogram.types import CallbackQuery, Message, InlineQuery, Poll, ChatType
class Command(Filter):
@@ -21,13 +21,10 @@ class Command(Filter):
By default this filter is registered for messages and edited messages handlers.
"""
- def __init__(
- self,
- commands: Union[Iterable, str],
- prefixes: Union[Iterable, str] = "/",
- ignore_case: bool = True,
- ignore_mention: bool = False,
- ):
+ def __init__(self, commands: Union[Iterable, str],
+ prefixes: Union[Iterable, str] = '/',
+ ignore_case: bool = True,
+ ignore_mention: bool = False):
"""
Filter can be initialized from filters factory or by simply creating instance of this class.
@@ -69,38 +66,33 @@ class Command(Filter):
:return: config or empty dict
"""
config = {}
- if "commands" in full_config:
- config["commands"] = full_config.pop("commands")
- if config and "commands_prefix" in full_config:
- config["prefixes"] = full_config.pop("commands_prefix")
- if config and "commands_ignore_mention" in full_config:
- config["ignore_mention"] = full_config.pop("commands_ignore_mention")
+ if 'commands' in full_config:
+ config['commands'] = full_config.pop('commands')
+ if config and 'commands_prefix' in full_config:
+ config['prefixes'] = full_config.pop('commands_prefix')
+ if config and 'commands_ignore_mention' in full_config:
+ config['ignore_mention'] = full_config.pop('commands_ignore_mention')
return config
async def check(self, message: types.Message):
- return await self.check_command(
- message, self.commands, self.prefixes, self.ignore_case, self.ignore_mention
- )
+ return await self.check_command(message, self.commands, self.prefixes, self.ignore_case, self.ignore_mention)
@staticmethod
- async def check_command(
- message: types.Message, commands, prefixes, ignore_case=True, ignore_mention=False
- ):
+ async def check_command(message: types.Message, commands, prefixes, ignore_case=True, ignore_mention=False):
+ if not message.text: # Prevent to use with non-text content types
+ return False
+
full_command = message.text.split()[0]
- prefix, (command, _, mention) = (full_command[0], full_command[1:].partition("@"))
+ 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()
- ):
+ if not ignore_mention and mention and (await message.bot.me).username.lower() != mention.lower():
return False
- elif prefix not in prefixes:
+ if prefix not in prefixes:
return False
- elif (command.lower() if ignore_case else command) not in commands:
+ if (command.lower() if ignore_case else command) not in commands:
return False
- return {"command": Command.CommandObj(command=command, prefix=prefix, mention=mention)}
+ return {'command': Command.CommandObj(command=command, prefix=prefix, mention=mention)}
@dataclass
class CommandObj:
@@ -111,9 +103,9 @@ class Command(Filter):
"""
"""Command prefix"""
- prefix: str = "/"
+ prefix: str = '/'
"""Command without prefix and mention"""
- command: str = ""
+ command: str = ''
"""Mention (if available)"""
mention: str = None
"""Command argument"""
@@ -137,9 +129,9 @@ class Command(Filter):
"""
line = self.prefix + self.command
if self.mentioned:
- line += "@" + self.mention
+ line += '@' + self.mention
if self.args:
- line += " " + self.args
+ line += ' ' + self.args
return line
@@ -160,7 +152,7 @@ class CommandStart(Command):
:param deep_link: string or compiled regular expression (by ``re.compile(...)``).
"""
- super(CommandStart, self).__init__(["start"])
+ super().__init__(['start'])
self.deep_link = deep_link
async def check(self, message: types.Message):
@@ -170,7 +162,7 @@ class CommandStart(Command):
:param message:
:return:
"""
- check = await super(CommandStart, self).check(message)
+ check = await super().check(message)
if check and self.deep_link is not None:
if not isinstance(self.deep_link, re.Pattern):
@@ -178,7 +170,7 @@ class CommandStart(Command):
match = self.deep_link.match(message.get_args())
if match:
- return {"deep_link": match}
+ return {'deep_link': match}
return False
return check
@@ -190,7 +182,7 @@ class CommandHelp(Command):
"""
def __init__(self):
- super(CommandHelp, self).__init__(["help"])
+ super().__init__(['help'])
class CommandSettings(Command):
@@ -199,7 +191,7 @@ class CommandSettings(Command):
"""
def __init__(self):
- super(CommandSettings, self).__init__(["settings"])
+ super().__init__(['settings'])
class CommandPrivacy(Command):
@@ -208,7 +200,7 @@ class CommandPrivacy(Command):
"""
def __init__(self):
- super(CommandPrivacy, self).__init__(["privacy"])
+ super().__init__(['privacy'])
class Text(Filter):
@@ -216,42 +208,44 @@ class Text(Filter):
Simple text filter
"""
- def __init__(
- self,
- equals: Optional[Union[str, LazyProxy]] = None,
- contains: Optional[Union[str, LazyProxy]] = None,
- startswith: Optional[Union[str, LazyProxy]] = None,
- endswith: Optional[Union[str, LazyProxy]] = None,
- ignore_case=False,
- ):
+ _default_params = (
+ ('text', 'equals'),
+ ('text_contains', 'contains'),
+ ('text_startswith', 'startswith'),
+ ('text_endswith', 'endswith'),
+ )
+
+ def __init__(self,
+ equals: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
+ contains: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
+ startswith: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
+ endswith: Optional[Union[str, LazyProxy, Iterable[Union[str, LazyProxy]]]] = None,
+ ignore_case=False):
"""
Check text for one of pattern. Only one mode can be used in one filter.
+ In every pattern, a single string is treated as a list with 1 element.
- :param equals:
- :param contains:
- :param startswith:
- :param endswith:
+ :param equals: True if object's text in the list
+ :param contains: True if object's text contains all strings from the list
+ :param startswith: True if object's text starts with any of strings from the list
+ :param endswith: True if object's text ends with any of strings from the list
:param ignore_case: case insensitive
"""
# Only one mode can be used. check it.
- check = sum(map(bool, (equals, contains, startswith, endswith)))
+ check = sum(map(lambda s: s is not None, (equals, contains, startswith, endswith)))
if check > 1:
- args = "' and '".join(
- [
- arg[0]
- for arg in [
- ("equals", equals),
- ("contains", contains),
- ("startswith", startswith),
- ("endswith", endswith),
- ]
- if arg[1]
- ]
- )
+ args = "' and '".join([arg[0] for arg in [('equals', equals),
+ ('contains', contains),
+ ('startswith', startswith),
+ ('endswith', endswith)
+ ] if arg[1] is not None])
raise ValueError(f"Arguments '{args}' cannot be used together.")
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))
self.equals = equals
self.contains = contains
self.endswith = endswith
@@ -260,18 +254,13 @@ class Text(Filter):
@classmethod
def validate(cls, full_config: Dict[str, Any]):
- if "text" in full_config:
- return {"equals": full_config.pop("text")}
- elif "text_contains" in full_config:
- return {"contains": full_config.pop("text_contains")}
- elif "text_startswith" in full_config:
- return {"startswith": full_config.pop("text_startswith")}
- elif "text_endswith" in full_config:
- return {"endswith": full_config.pop("text_endswith")}
+ for param, key in cls._default_params:
+ if param in full_config:
+ return {key: full_config.pop(param)}
- async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]):
+ async def check(self, obj: Union[Message, CallbackQuery, InlineQuery, Poll]):
if isinstance(obj, Message):
- text = obj.text or obj.caption or ""
+ text = obj.text or obj.caption or ''
if not text and obj.poll:
text = obj.poll.question
elif isinstance(obj, CallbackQuery):
@@ -285,15 +274,26 @@ class Text(Filter):
if self.ignore_case:
text = text.lower()
+ _pre_process_func = lambda s: str(s).lower()
+ else:
+ _pre_process_func = str
- if self.equals:
- return text == str(self.equals)
- elif self.contains:
- return str(self.contains) in text
- elif self.startswith:
- return text.startswith(str(self.startswith))
- elif self.endswith:
- return text.endswith(str(self.endswith))
+ # now check
+ if self.equals is not None:
+ equals = list(map(_pre_process_func, self.equals))
+ return text in equals
+
+ if self.contains is not None:
+ contains = list(map(_pre_process_func, self.contains))
+ return all(map(text.__contains__, contains))
+
+ if self.startswith is not None:
+ startswith = list(map(_pre_process_func, self.startswith))
+ return any(map(text.startswith, startswith))
+
+ if self.endswith is not None:
+ endswith = list(map(_pre_process_func, self.endswith))
+ return any(map(text.endswith, endswith))
return False
@@ -307,7 +307,7 @@ class HashTag(Filter):
def __init__(self, hashtags=None, cashtags=None):
if not hashtags and not cashtags:
- raise ValueError("No one hashtag or cashtag is specified!")
+ raise ValueError('No one hashtag or cashtag is specified!')
if hashtags is None:
hashtags = []
@@ -327,10 +327,10 @@ class HashTag(Filter):
@classmethod
def validate(cls, full_config: Dict[str, Any]):
config = {}
- if "hashtags" in full_config:
- config["hashtags"] = full_config.pop("hashtags")
- if "cashtags" in full_config:
- config["cashtags"] = full_config.pop("cashtags")
+ if 'hashtags' in full_config:
+ config['hashtags'] = full_config.pop('hashtags')
+ if 'cashtags' in full_config:
+ config['cashtags'] = full_config.pop('cashtags')
return config
async def check(self, message: types.Message):
@@ -344,13 +344,9 @@ class HashTag(Filter):
return False
hashtags, cashtags = self._get_tags(text, entities)
- if (
- self.hashtags
- and set(hashtags) & set(self.hashtags)
- or self.cashtags
- and set(cashtags) & set(self.cashtags)
- ):
- return {"hashtags": hashtags, "cashtags": cashtags}
+ if self.hashtags and set(hashtags) & set(self.hashtags) \
+ or self.cashtags and set(cashtags) & set(self.cashtags):
+ return {'hashtags': hashtags, 'cashtags': cashtags}
def _get_tags(self, text, entities):
hashtags = []
@@ -358,11 +354,11 @@ class HashTag(Filter):
for entity in entities:
if entity.type == types.MessageEntityType.HASHTAG:
- value = entity.get_text(text).lstrip("#")
+ value = entity.get_text(text).lstrip('#')
hashtags.append(value)
elif entity.type == types.MessageEntityType.CASHTAG:
- value = entity.get_text(text).lstrip("$")
+ value = entity.get_text(text).lstrip('$')
cashtags.append(value)
return hashtags, cashtags
@@ -380,23 +376,27 @@ class Regexp(Filter):
@classmethod
def validate(cls, full_config: Dict[str, Any]):
- if "regexp" in full_config:
- return {"regexp": full_config.pop("regexp")}
+ if 'regexp' in full_config:
+ return {'regexp': full_config.pop('regexp')}
- async def check(self, obj: Union[Message, CallbackQuery]):
+ async def check(self, obj: Union[Message, CallbackQuery, InlineQuery, Poll]):
if isinstance(obj, Message):
- content = obj.text or obj.caption or ""
+ content = obj.text or obj.caption or ''
if not content and obj.poll:
content = obj.poll.question
elif isinstance(obj, CallbackQuery) and obj.data:
content = obj.data
+ elif isinstance(obj, InlineQuery):
+ content = obj.query
+ elif isinstance(obj, Poll):
+ content = obj.question
else:
return False
match = self.regexp.search(content)
if match:
- return {"regexp": match}
+ return {'regexp': match}
return False
@@ -405,19 +405,17 @@ class RegexpCommandsFilter(BoundFilter):
Check commands by regexp in message
"""
- key = "regexp_commands"
+ key = 'regexp_commands'
def __init__(self, regexp_commands):
- self.regexp_commands = [
- re.compile(command, flags=re.IGNORECASE | re.MULTILINE) for command in regexp_commands
- ]
+ self.regexp_commands = [re.compile(command, flags=re.IGNORECASE | re.MULTILINE) for command in regexp_commands]
async def check(self, message):
if not message.is_command():
return False
command = message.text.split()[0][1:]
- command, _, mention = command.partition("@")
+ command, _, mention = command.partition('@')
if mention and mention != (await message.bot.me).username:
return False
@@ -425,7 +423,7 @@ class RegexpCommandsFilter(BoundFilter):
for command in self.regexp_commands:
search = command.search(message.text)
if search:
- return {"regexp_command": search}
+ return {'regexp_command': search}
return False
@@ -434,7 +432,7 @@ class ContentTypeFilter(BoundFilter):
Check message content type
"""
- key = "content_types"
+ key = 'content_types'
required = True
default = types.ContentTypes.TEXT
@@ -442,21 +440,18 @@ class ContentTypeFilter(BoundFilter):
self.content_types = content_types
async def check(self, message):
- return (
- types.ContentType.ANY in self.content_types
- or message.content_type in self.content_types
- )
+ return types.ContentType.ANY in self.content_types or \
+ message.content_type in self.content_types
class StateFilter(BoundFilter):
"""
Check user state
"""
-
- key = "state"
+ key = 'state'
required = True
- ctx_state = ContextVar("user_state")
+ ctx_state = ContextVar('user_state')
def __init__(self, dispatcher, state):
from aiogram.dispatcher.filters.state import State, StatesGroup
@@ -464,7 +459,7 @@ class StateFilter(BoundFilter):
self.dispatcher = dispatcher
states = []
if not isinstance(state, (list, set, tuple, frozenset)) or state is None:
- state = [state]
+ state = [state, ]
for item in state:
if isinstance(item, State):
states.append(item.state)
@@ -475,14 +470,11 @@ class StateFilter(BoundFilter):
self.states = states
def get_target(self, obj):
- return (
- getattr(getattr(obj, "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):
- if "*" in self.states:
- return {"state": self.dispatcher.current_state()}
+ if '*' in self.states:
+ return {'state': self.dispatcher.current_state()}
try:
state = self.ctx_state.get()
@@ -493,11 +485,11 @@ class StateFilter(BoundFilter):
state = await self.dispatcher.storage.get_state(chat=chat, user=user)
self.ctx_state.set(state)
if state in self.states:
- return {"state": self.dispatcher.current_state(), "raw_state": state}
+ return {'state': self.dispatcher.current_state(), 'raw_state': state}
else:
if state in self.states:
- return {"state": self.dispatcher.current_state(), "raw_state": state}
+ return {'state': self.dispatcher.current_state(), 'raw_state': state}
return False
@@ -507,7 +499,7 @@ class ExceptionsFilter(BoundFilter):
Filter for exceptions
"""
- key = "exception"
+ key = 'exception'
def __init__(self, exception):
self.exception = exception
@@ -519,3 +511,136 @@ class ExceptionsFilter(BoundFilter):
return True
except:
return False
+
+
+class IDFilter(Filter):
+
+ def __init__(self,
+ user_id: Optional[Union[Iterable[Union[int, str]], str, int]] = None,
+ chat_id: Optional[Union[Iterable[Union[int, str]], str, int]] = None,
+ ):
+ """
+ :param user_id:
+ :param chat_id:
+ """
+ if user_id is None and chat_id is None:
+ raise ValueError("Both user_id and chat_id can't be None")
+
+ self.user_id = None
+ self.chat_id = None
+ if user_id:
+ if isinstance(user_id, Iterable):
+ self.user_id = list(map(int, user_id))
+ else:
+ self.user_id = [int(user_id), ]
+ if chat_id:
+ if isinstance(chat_id, Iterable):
+ self.chat_id = list(map(int, chat_id))
+ else:
+ self.chat_id = [int(chat_id), ]
+
+ @classmethod
+ def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]:
+ result = {}
+ if 'user_id' in full_config:
+ result['user_id'] = full_config.pop('user_id')
+
+ if 'chat_id' in full_config:
+ result['chat_id'] = full_config.pop('chat_id')
+
+ return result
+
+ async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]):
+ if isinstance(obj, Message):
+ user_id = obj.from_user.id
+ chat_id = obj.chat.id
+ elif isinstance(obj, CallbackQuery):
+ user_id = obj.from_user.id
+ chat_id = None
+ if obj.message is not None:
+ # if the button was sent with message
+ chat_id = obj.message.chat.id
+ elif isinstance(obj, InlineQuery):
+ user_id = obj.from_user.id
+ chat_id = None
+ else:
+ return False
+
+ if self.user_id and self.chat_id:
+ return user_id in self.user_id and chat_id in self.chat_id
+ if self.user_id:
+ return user_id in self.user_id
+ if self.chat_id:
+ return chat_id in self.chat_id
+
+ return False
+
+
+class AdminFilter(Filter):
+ """
+ Checks if user is admin in a chat.
+ If is_chat_admin is not set, the filter will check in the current chat (correct only for messages).
+ is_chat_admin is required for InlineQuery.
+ """
+
+ def __init__(self, is_chat_admin: Optional[Union[Iterable[Union[int, str]], str, int, bool]] = None):
+ self._check_current = False
+ self._chat_ids = None
+
+ if is_chat_admin is False:
+ raise ValueError("is_chat_admin cannot be False")
+
+ if is_chat_admin:
+ if isinstance(is_chat_admin, bool):
+ self._check_current = is_chat_admin
+ if isinstance(is_chat_admin, Iterable):
+ self._chat_ids = list(is_chat_admin)
+ else:
+ self._chat_ids = [is_chat_admin]
+ else:
+ self._check_current = True
+
+ @classmethod
+ def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]:
+ result = {}
+
+ if "is_chat_admin" in full_config:
+ result["is_chat_admin"] = full_config.pop("is_chat_admin")
+
+ return result
+
+ async def check(self, obj: Union[Message, CallbackQuery, InlineQuery]) -> bool:
+ user_id = obj.from_user.id
+
+ if self._check_current:
+ if isinstance(obj, Message):
+ message = obj
+ elif isinstance(obj, CallbackQuery) and obj.message:
+ message = obj.message
+ else:
+ return False
+ if ChatType.is_private(message): # there is no admin in private chats
+ return False
+ chat_ids = [message.chat.id]
+ else:
+ chat_ids = self._chat_ids
+
+ admins = [member.user.id for chat_id in chat_ids for member in await obj.bot.get_chat_administrators(chat_id)]
+
+ return user_id in admins
+
+
+class IsReplyFilter(BoundFilter):
+ """
+ Check if message is replied and send reply message to handler
+ """
+ key = 'is_reply'
+
+ def __init__(self, is_reply):
+ self.is_reply = is_reply
+
+ async def check(self, msg: Message):
+ if msg.reply_to_message and self.is_reply:
+ return {'reply': msg.reply_to_message}
+ elif not msg.reply_to_message and not self.is_reply:
+ return True
diff --git a/aiogram/dispatcher/filters/filters.py b/aiogram/dispatcher/filters/filters.py
index 04f30c3a..220ef96c 100644
--- a/aiogram/dispatcher/filters/filters.py
+++ b/aiogram/dispatcher/filters/filters.py
@@ -13,11 +13,9 @@ def wrap_async(func):
async def async_wrapper(*args, **kwargs):
return func(*args, **kwargs)
- if (
- inspect.isawaitable(func)
- or inspect.iscoroutinefunction(func)
- or isinstance(func, AbstractFilter)
- ):
+ if inspect.isawaitable(func) \
+ or inspect.iscoroutinefunction(func) \
+ or isinstance(func, AbstractFilter):
return func
return async_wrapper
@@ -25,16 +23,14 @@ def wrap_async(func):
def get_filter_spec(dispatcher, filter_: callable):
kwargs = {}
if not callable(filter_):
- raise TypeError("Filter must be callable and/or awaitable!")
+ raise TypeError('Filter must be callable and/or awaitable!')
spec = inspect.getfullargspec(filter_)
- if "dispatcher" in spec:
- kwargs["dispatcher"] = dispatcher
- if (
- inspect.isawaitable(filter_)
- or inspect.iscoroutinefunction(filter_)
- or isinstance(filter_, AbstractFilter)
- ):
+ if 'dispatcher' in spec:
+ kwargs['dispatcher'] = dispatcher
+ if inspect.isawaitable(filter_) \
+ or inspect.iscoroutinefunction(filter_) \
+ or isinstance(filter_, AbstractFilter):
return FilterObj(filter=filter_, kwargs=kwargs, is_async=True)
else:
return FilterObj(filter=filter_, kwargs=kwargs, is_async=False)
@@ -86,17 +82,12 @@ class FilterRecord:
Filters record for factory
"""
- def __init__(
- self,
- callback: typing.Callable,
- validator: typing.Optional[typing.Callable] = None,
- event_handlers: typing.Optional[typing.Iterable[Handler]] = None,
- exclude_event_handlers: typing.Optional[typing.Iterable[Handler]] = None,
- ):
+ def __init__(self, callback: typing.Union[typing.Callable, 'AbstractFilter'],
+ validator: typing.Optional[typing.Callable] = None,
+ event_handlers: typing.Optional[typing.Iterable[Handler]] = None,
+ exclude_event_handlers: typing.Optional[typing.Iterable[Handler]] = None):
if event_handlers and exclude_event_handlers:
- raise ValueError(
- "'event_handlers' and 'exclude_event_handlers' arguments cannot be used together."
- )
+ raise ValueError("'event_handlers' and 'exclude_event_handlers' arguments cannot be used together.")
self.callback = callback
self.event_handlers = event_handlers
@@ -109,17 +100,17 @@ class FilterRecord:
elif issubclass(callback, AbstractFilter):
self.resolver = callback.validate
else:
- raise RuntimeError("validator is required!")
+ raise RuntimeError('validator is required!')
def resolve(self, dispatcher, event_handler, full_config):
if not self._check_event_handler(event_handler):
return
config = self.resolver(full_config)
if config:
- if "dispatcher" not in config:
+ if 'dispatcher' not in config:
spec = inspect.getfullargspec(self.callback)
- if "dispatcher" in spec.args:
- config["dispatcher"] = dispatcher
+ if 'dispatcher' in spec.args:
+ config['dispatcher'] = dispatcher
for key in config:
if key in full_config:
@@ -142,9 +133,7 @@ class AbstractFilter(abc.ABC):
@classmethod
@abc.abstractmethod
- def validate(
- cls, full_config: typing.Dict[str, typing.Any]
- ) -> typing.Optional[typing.Dict[str, typing.Any]]:
+ def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]:
"""
Validate and parse config.
@@ -195,9 +184,7 @@ class Filter(AbstractFilter):
"""
@classmethod
- def validate(
- cls, full_config: typing.Dict[str, typing.Any]
- ) -> typing.Optional[typing.Dict[str, typing.Any]]:
+ def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Optional[typing.Dict[str, typing.Any]]:
"""
Here method ``validate`` is optional.
If you need to use filter from filters factory you need to override this method.
@@ -215,14 +202,14 @@ class BoundFilter(Filter):
You need to implement ``__init__`` method with single argument related with key attribute
and ``check`` method where you need to implement filter logic.
"""
-
- """Unique name of the filter argument. You need to override this attribute."""
+
key = None
- """If :obj:`True` this filter will be added to the all of the registered handlers"""
+ """Unique name of the filter argument. You need to override this attribute."""
required = False
- """Default value for configure required filters"""
+ """If :obj:`True` this filter will be added to the all of the registered handlers"""
default = None
-
+ """Default value for configure required filters"""
+
@classmethod
def validate(cls, full_config: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
"""
@@ -241,7 +228,7 @@ class BoundFilter(Filter):
class _LogicFilter(Filter):
@classmethod
def validate(cls, full_config: typing.Dict[str, typing.Any]):
- raise ValueError("That filter can't be used in filters factory!")
+ raise ValueError('That filter can\'t be used in filters factory!')
class NotFilter(_LogicFilter):
@@ -253,6 +240,7 @@ class NotFilter(_LogicFilter):
class AndFilter(_LogicFilter):
+
def __init__(self, *targets):
self.targets = list(wrap_async(target) for target in targets)
diff --git a/aiogram/dispatcher/filters/state.py b/aiogram/dispatcher/filters/state.py
index 495f9fde..16937e1c 100644
--- a/aiogram/dispatcher/filters/state.py
+++ b/aiogram/dispatcher/filters/state.py
@@ -17,7 +17,7 @@ class State:
@property
def group(self):
if not self._group:
- raise RuntimeError("This state is not in any group.")
+ raise RuntimeError('This state is not in any group.')
return self._group
def get_root(self):
@@ -25,21 +25,21 @@ class State:
@property
def state(self):
- if self._state is None:
- return None
- elif self._state == "*":
+ if self._state is None or self._state == '*':
return self._state
- elif self._group_name is None and self._group:
+
+ if self._group_name is None and self._group:
group = self._group.__full_group_name__
elif self._group_name:
group = self._group_name
else:
- group = "@"
- return f"{group}:{self._state}"
+ group = '@'
+
+ return f'{group}:{self._state}'
def set_parent(self, group):
if not issubclass(group, StatesGroup):
- raise ValueError("Group must be subclass of StatesGroup")
+ raise ValueError('Group must be subclass of StatesGroup')
self._group = group
def __set_name__(self, owner, name):
@@ -73,7 +73,6 @@ class StatesGroupMeta(type):
elif inspect.isclass(prop) and issubclass(prop, StatesGroup):
childs.append(prop)
prop._parent = cls
- # continue
cls._parent = None
cls._childs = tuple(childs)
@@ -83,13 +82,13 @@ class StatesGroupMeta(type):
return cls
@property
- def __group_name__(cls):
+ def __group_name__(cls) -> str:
return cls._group_name
@property
- def __full_group_name__(cls):
+ def __full_group_name__(cls) -> str:
if cls._parent:
- return cls._parent.__full_group_name__ + "." + cls._group_name
+ return '.'.join((cls._parent.__full_group_name__, cls._group_name))
return cls._group_name
@property
@@ -97,7 +96,7 @@ class StatesGroupMeta(type):
return cls._states
@property
- def childs(cls):
+ def childs(cls) -> tuple:
return cls._childs
@property
@@ -130,9 +129,9 @@ class StatesGroupMeta(type):
def __contains__(cls, item):
if isinstance(item, str):
return item in cls.all_states_names
- elif isinstance(item, State):
+ if isinstance(item, State):
return item in cls.all_states
- elif isinstance(item, StatesGroup):
+ if isinstance(item, StatesGroup):
return item in cls.all_childs
return False
@@ -195,4 +194,4 @@ class StatesGroup(metaclass=StatesGroupMeta):
default_state = State()
-any_state = State(state="*")
+any_state = State(state='*')
diff --git a/aiogram/dispatcher/handler.py b/aiogram/dispatcher/handler.py
index 58f7fa80..cd5e9b50 100644
--- a/aiogram/dispatcher/handler.py
+++ b/aiogram/dispatcher/handler.py
@@ -1,10 +1,10 @@
import inspect
from contextvars import ContextVar
from dataclasses import dataclass
-from typing import Optional, Iterable
+from typing import Optional, Iterable, List
-ctx_data = ContextVar("ctx_handler_data")
-current_handler = ContextVar("current_handler")
+ctx_data = ContextVar('ctx_handler_data')
+current_handler = ContextVar('current_handler')
@dataclass
@@ -23,11 +23,10 @@ class CancelHandler(Exception):
def _get_spec(func: callable):
- while hasattr(func, "__wrapped__"): # Try to resolve decorated callbacks
+ while hasattr(func, '__wrapped__'): # Try to resolve decorated callbacks
func = func.__wrapped__
-
spec = inspect.getfullargspec(func)
- return spec, func
+ return spec
def _check_spec(spec: inspect.FullArgSpec, kwargs: dict):
@@ -42,12 +41,10 @@ class Handler:
self.dispatcher = dispatcher
self.once = once
- self.handlers = []
+ self.handlers: List[Handler.HandlerObj] = []
self.middleware_key = middleware_key
def register(self, handler, filters=None, index=None):
- from .filters import get_filters_spec
-
"""
Register callback
@@ -57,7 +54,9 @@ class Handler:
:param filters: list of filters
:param index: you can reorder handlers
"""
- spec, handler = _get_spec(handler)
+ from .filters import get_filters_spec
+
+ spec = _get_spec(handler)
if filters and not isinstance(filters, (list, tuple, set)):
filters = [filters]
@@ -81,7 +80,7 @@ class Handler:
if handler is registered:
self.handlers.remove(handler_obj)
return True
- raise ValueError("This handler is not registered!")
+ raise ValueError('This handler is not registered!')
async def notify(self, *args):
"""
@@ -99,9 +98,7 @@ class Handler:
if self.middleware_key:
try:
- await self.dispatcher.middleware.trigger(
- f"pre_process_{self.middleware_key}", args + (data,)
- )
+ await self.dispatcher.middleware.trigger(f"pre_process_{self.middleware_key}", args + (data,))
except CancelHandler: # Allow to cancel current event
return results
@@ -115,9 +112,7 @@ class Handler:
ctx_token = current_handler.set(handler_obj.handler)
try:
if self.middleware_key:
- await self.dispatcher.middleware.trigger(
- f"process_{self.middleware_key}", args + (data,)
- )
+ await self.dispatcher.middleware.trigger(f"process_{self.middleware_key}", args + (data,))
partial_data = _check_spec(handler_obj.spec, data)
response = await handler_obj.handler(*args, **partial_data)
if response is not None:
@@ -132,9 +127,8 @@ class Handler:
current_handler.reset(ctx_token)
finally:
if self.middleware_key:
- await self.dispatcher.middleware.trigger(
- f"post_process_{self.middleware_key}", args + (results, data)
- )
+ await self.dispatcher.middleware.trigger(f"post_process_{self.middleware_key}",
+ args + (results, data,))
return results
diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py
index b298c660..135fe21e 100644
--- a/aiogram/dispatcher/webhook.py
+++ b/aiogram/dispatcher/webhook.py
@@ -5,6 +5,7 @@ import functools
import ipaddress
import itertools
import typing
+import logging
from typing import Dict, List, Optional, Union
from aiohttp import web
@@ -20,21 +21,23 @@ from ..utils.deprecated import warn_deprecated as warn
from ..utils.exceptions import TimeoutWarning
from ..utils.payload import prepare_arg
-DEFAULT_WEB_PATH = "/webhook"
-DEFAULT_ROUTE_NAME = "webhook_handler"
-BOT_DISPATCHER_KEY = "BOT_DISPATCHER"
+DEFAULT_WEB_PATH = '/webhook'
+DEFAULT_ROUTE_NAME = 'webhook_handler'
+BOT_DISPATCHER_KEY = 'BOT_DISPATCHER'
RESPONSE_TIMEOUT = 55
-WEBHOOK = "webhook"
-WEBHOOK_CONNECTION = "WEBHOOK_CONNECTION"
-WEBHOOK_REQUEST = "WEBHOOK_REQUEST"
+WEBHOOK = 'webhook'
+WEBHOOK_CONNECTION = 'WEBHOOK_CONNECTION'
+WEBHOOK_REQUEST = 'WEBHOOK_REQUEST'
-TELEGRAM_SUBNET_1 = ipaddress.IPv4Network("149.154.160.0/20")
-TELEGRAM_SUBNET_2 = ipaddress.IPv4Network("91.108.4.0/22")
+TELEGRAM_SUBNET_1 = ipaddress.IPv4Network('149.154.160.0/20')
+TELEGRAM_SUBNET_2 = ipaddress.IPv4Network('91.108.4.0/22')
allowed_ips = set()
+log = logging.getLogger(__name__)
+
def _check_ip(ip: str) -> bool:
"""
@@ -77,7 +80,7 @@ class WebhookRequestHandler(web.View):
.. code-block:: python3
- app.router.add_route('*', '/your/webhook/path', WebhookRequestHadler, name='webhook_handler')
+ app.router.add_route('*', '/your/webhook/path', WebhookRequestHandler, name='webhook_handler')
But first you need to configure application for getting Dispatcher instance from request handler!
It must always be with key 'BOT_DISPATCHER'
@@ -99,7 +102,6 @@ class WebhookRequestHandler(web.View):
dp = self.request.app[BOT_DISPATCHER_KEY]
try:
from aiogram import Bot, Dispatcher
-
Dispatcher.set_current(dp)
Bot.set_current(dp.bot)
except RuntimeError:
@@ -141,20 +143,20 @@ class WebhookRequestHandler(web.View):
if response:
web_response = response.get_web_response()
else:
- web_response = web.Response(text="ok")
+ web_response = web.Response(text='ok')
- if self.request.app.get("RETRY_AFTER", None):
- web_response.headers["Retry-After"] = self.request.app["RETRY_AFTER"]
+ if self.request.app.get('RETRY_AFTER', None):
+ web_response.headers['Retry-After'] = self.request.app['RETRY_AFTER']
return web_response
async def get(self):
self.validate_ip()
- return web.Response(text="")
+ return web.Response(text='')
async def head(self):
self.validate_ip()
- return web.Response(text="")
+ return web.Response(text='')
async def process_update(self, update):
"""
@@ -201,12 +203,10 @@ class WebhookRequestHandler(web.View):
:param task:
:return:
"""
- warn(
- f"Detected slow response into webhook. "
- f"(Greater than {RESPONSE_TIMEOUT} seconds)\n"
- f"Recommended to use 'async_task' decorator from Dispatcher for handler with long timeouts.",
- TimeoutWarning,
- )
+ warn(f"Detected slow response into webhook. "
+ f"(Greater than {RESPONSE_TIMEOUT} seconds)\n"
+ f"Recommended to use 'async_task' decorator from Dispatcher for handler with long timeouts.",
+ TimeoutWarning)
dispatcher = self.get_dispatcher()
loop = dispatcher.loop
@@ -215,8 +215,7 @@ class WebhookRequestHandler(web.View):
results = task.result()
except Exception as e:
loop.create_task(
- dispatcher.errors_handlers.notify(dispatcher, types.Update.get_current(), e)
- )
+ dispatcher.errors_handlers.notify(dispatcher, types.Update.get_current(), e))
else:
response = self.get_response(results)
if response is not None:
@@ -242,12 +241,12 @@ class WebhookRequestHandler(web.View):
:return:
"""
# For reverse proxy (nginx)
- forwarded_for = self.request.headers.get("X-Forwarded-For", None)
+ forwarded_for = self.request.headers.get('X-Forwarded-For', None)
if forwarded_for:
return forwarded_for, _check_ip(forwarded_for)
# For default method
- peer_name = self.request.transport.get_extra_info("peername")
+ peer_name = self.request.transport.get_extra_info('peername')
if peer_name is not None:
host, _ = peer_name
return host, _check_ip(host)
@@ -259,10 +258,12 @@ class WebhookRequestHandler(web.View):
"""
Check ip if that is needed. Raise web.HTTPUnauthorized for not allowed hosts.
"""
- if self.request.app.get("_check_ip", False):
+ if self.request.app.get('_check_ip', False):
ip_address, accept = self.check_ip()
if not accept:
+ log.warning(f"Blocking request from an unauthorized IP: {ip_address}")
raise web.HTTPUnauthorized()
+
# context.set_value('TELEGRAM_IP', ip_address)
@@ -279,9 +280,7 @@ class GoneRequestHandler(web.View):
raise HTTPGone()
-def configure_app(
- dispatcher, app: web.Application, path=DEFAULT_WEB_PATH, route_name=DEFAULT_ROUTE_NAME
-):
+def configure_app(dispatcher, app: web.Application, path=DEFAULT_WEB_PATH, route_name=DEFAULT_ROUTE_NAME):
"""
You can prepare web.Application for working with webhook handler.
@@ -291,7 +290,7 @@ def configure_app(
:param route_name: Name of webhook handler route
:return:
"""
- app.router.add_route("*", path, WebhookRequestHandler, name=route_name)
+ app.router.add_route('*', path, WebhookRequestHandler, name=route_name)
app[BOT_DISPATCHER_KEY] = dispatcher
@@ -344,7 +343,7 @@ class BaseResponse:
:return:
"""
- return {"method": self.method, **self.cleanup()}
+ return {'method': self.method, **self.cleanup()}
def get_web_response(self):
"""
@@ -370,7 +369,6 @@ class BaseResponse:
async def __call__(self, bot=None):
if bot is None:
from aiogram import Bot
-
bot = Bot.get_current()
return await self.execute_response(bot)
@@ -393,17 +391,10 @@ class ReplyToMixin:
:param message: :obj:`int` or :obj:`types.Message`
:return: self
"""
- setattr(
- self,
- "reply_to_message_id",
- message.message_id if isinstance(message, types.Message) else message,
- )
+ setattr(self, 'reply_to_message_id', message.message_id if isinstance(message, types.Message) else message)
return self
- def to(
- self,
- target: typing.Union[types.Message, types.Chat, types.base.Integer, types.base.String],
- ):
+ def to(self, target: typing.Union[types.Message, types.Chat, types.base.Integer, types.base.String]):
"""
Send to chat
@@ -419,7 +410,7 @@ class ReplyToMixin:
else:
raise TypeError(f"Bad type of target. ({type(target)})")
- setattr(self, "chat_id", chat_id)
+ setattr(self, 'chat_id', chat_id)
return self
@@ -430,7 +421,7 @@ class DisableNotificationMixin:
:return:
"""
- setattr(self, "disable_notification", True)
+ setattr(self, 'disable_notification', True)
return self
@@ -441,7 +432,7 @@ class DisableWebPagePreviewMixin:
:return:
"""
- setattr(self, "disable_web_page_preview", True)
+ setattr(self, 'disable_web_page_preview', True)
return self
@@ -452,7 +443,7 @@ class ParseModeMixin:
:return:
"""
- setattr(self, "parse_mode", ParseMode.HTML)
+ setattr(self, 'parse_mode', ParseMode.HTML)
return self
def as_markdown(self):
@@ -461,7 +452,7 @@ class ParseModeMixin:
:return:
"""
- setattr(self, "parse_mode", ParseMode.MARKDOWN)
+ setattr(self, 'parse_mode', ParseMode.MARKDOWN)
return self
@staticmethod
@@ -472,48 +463,31 @@ class ParseModeMixin:
:return:
"""
from aiogram import Bot
-
bot = Bot.get_current()
if bot is not None:
return bot.parse_mode
-class SendMessage(
- BaseResponse,
- ReplyToMixin,
- ParseModeMixin,
- DisableNotificationMixin,
- DisableWebPagePreviewMixin,
-):
+class SendMessage(BaseResponse, ReplyToMixin, ParseModeMixin, DisableNotificationMixin, DisableWebPagePreviewMixin):
"""
You can send message with webhook by using this instance of this object.
All arguments is equal with Bot.send_message method.
"""
- __slots__ = (
- "chat_id",
- "text",
- "parse_mode",
- "disable_web_page_preview",
- "disable_notification",
- "reply_to_message_id",
- "reply_markup",
- )
+ __slots__ = ('chat_id', 'text', 'parse_mode',
+ 'disable_web_page_preview', 'disable_notification',
+ 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_MESSAGE
- def __init__(
- self,
- chat_id: Union[Integer, String] = None,
- text: String = None,
- parse_mode: Optional[String] = None,
- disable_web_page_preview: Optional[Boolean] = None,
- disable_notification: Optional[Boolean] = None,
- reply_to_message_id: Optional[Integer] = None,
- reply_markup: Optional[
- Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]
- ] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String] = None,
+ text: String = None,
+ parse_mode: Optional[String] = None,
+ disable_web_page_preview: Optional[Boolean] = None,
+ disable_notification: Optional[Boolean] = None,
+ reply_to_message_id: Optional[Integer] = None,
+ reply_markup: Optional[Union[
+ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target chat or username
of the target channel (in the format @channelusername)
@@ -529,7 +503,7 @@ class SendMessage(
custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user.
"""
if text is None:
- text = ""
+ text = ''
if parse_mode is None:
parse_mode = self._global_parse_mode()
@@ -543,16 +517,16 @@ class SendMessage(
def prepare(self) -> dict:
return {
- "chat_id": self.chat_id,
- "text": self.text,
- "parse_mode": self.parse_mode,
- "disable_web_page_preview": self.disable_web_page_preview,
- "disable_notification": self.disable_notification,
- "reply_to_message_id": self.reply_to_message_id,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'text': self.text,
+ 'parse_mode': self.parse_mode,
+ 'disable_web_page_preview': self.disable_web_page_preview,
+ 'disable_notification': self.disable_notification,
+ 'reply_to_message_id': self.reply_to_message_id,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
- def write(self, *text, sep=" "):
+ def write(self, *text, sep=' '):
"""
Write text to response
@@ -563,7 +537,7 @@ class SendMessage(
self.text += markdown.text(*text, sep)
return self
- def write_ln(self, *text, sep=" "):
+ def write_ln(self, *text, sep=' '):
"""
Write line
@@ -571,9 +545,9 @@ class SendMessage(
:param sep:
:return:
"""
- if self.text and self.text[-1] != "\n":
- self.text += "\n"
- self.text += markdown.text(*text, sep) + "\n"
+ if self.text and self.text[-1] != '\n':
+ self.text += '\n'
+ self.text += markdown.text(*text, sep) + '\n'
return self
@@ -581,18 +555,14 @@ class ForwardMessage(BaseResponse, ReplyToMixin, DisableNotificationMixin):
"""
Use that response type for forward messages of any kind on to webhook.
"""
-
- __slots__ = ("chat_id", "from_chat_id", "message_id", "disable_notification")
+ __slots__ = ('chat_id', 'from_chat_id', 'message_id', 'disable_notification')
method = api.Methods.FORWARD_MESSAGE
- def __init__(
- self,
- chat_id: Union[Integer, String] = None,
- from_chat_id: Union[Integer, String] = None,
- message_id: Integer = None,
- disable_notification: Optional[Boolean] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String] = None,
+ from_chat_id: Union[Integer, String] = None,
+ message_id: Integer = None,
+ disable_notification: Optional[Boolean] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target chat or username of the
target channel (in the format @channelusername)
@@ -614,16 +584,16 @@ class ForwardMessage(BaseResponse, ReplyToMixin, DisableNotificationMixin):
:param message:
:return:
"""
- setattr(self, "from_chat_id", message.chat.id)
- setattr(self, "message_id", message.message_id)
+ setattr(self, 'from_chat_id', message.chat.id)
+ setattr(self, 'message_id', message.message_id)
return self
def prepare(self) -> dict:
return {
- "chat_id": self.chat_id,
- "from_chat_id": self.from_chat_id,
- "message_id": self.message_id,
- "disable_notification": self.disable_notification,
+ 'chat_id': self.chat_id,
+ 'from_chat_id': self.from_chat_id,
+ 'message_id': self.message_id,
+ 'disable_notification': self.disable_notification
}
@@ -632,28 +602,17 @@ class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin):
Use that response type for send photo on to webhook.
"""
- __slots__ = (
- "chat_id",
- "photo",
- "caption",
- "disable_notification",
- "reply_to_message_id",
- "reply_markup",
- )
+ __slots__ = ('chat_id', 'photo', 'caption', 'disable_notification', 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_PHOTO
- def __init__(
- self,
- chat_id: Union[Integer, String],
- photo: String,
- caption: Optional[String] = None,
- disable_notification: Optional[Boolean] = None,
- reply_to_message_id: Optional[Integer] = None,
- reply_markup: Optional[
- Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]
- ] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String],
+ photo: String,
+ caption: Optional[String] = None,
+ disable_notification: Optional[Boolean] = None,
+ reply_to_message_id: Optional[Integer] = None,
+ reply_markup: Optional[Union[
+ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target chat or username of
the target channel (in the format @channelusername)
@@ -678,12 +637,12 @@ class SendPhoto(BaseResponse, ReplyToMixin, DisableNotificationMixin):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "photo": self.photo,
- "caption": self.caption,
- "disable_notification": self.disable_notification,
- "reply_to_message_id": self.reply_to_message_id,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'photo': self.photo,
+ 'caption': self.caption,
+ 'disable_notification': self.disable_notification,
+ 'reply_to_message_id': self.reply_to_message_id,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
@@ -692,34 +651,21 @@ class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin):
Use that response type for send audio on to webhook.
"""
- __slots__ = (
- "chat_id",
- "audio",
- "caption",
- "duration",
- "performer",
- "title",
- "disable_notification",
- "reply_to_message_id",
- "reply_markup",
- )
+ __slots__ = ('chat_id', 'audio', 'caption', 'duration', 'performer', 'title',
+ 'disable_notification', 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_AUDIO
- def __init__(
- self,
- chat_id: Union[Integer, String],
- audio: String,
- caption: Optional[String] = None,
- duration: Optional[Integer] = None,
- performer: Optional[String] = None,
- title: Optional[String] = None,
- disable_notification: Optional[Boolean] = None,
- reply_to_message_id: Optional[Integer] = None,
- reply_markup: Optional[
- Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]
- ] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String],
+ audio: String,
+ caption: Optional[String] = None,
+ duration: Optional[Integer] = None,
+ performer: Optional[String] = None,
+ title: Optional[String] = None,
+ disable_notification: Optional[Boolean] = None,
+ reply_to_message_id: Optional[Integer] = None,
+ reply_markup: Optional[Union[
+ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target chat or username
of the target channel (in the format @channelusername)
@@ -750,15 +696,15 @@ class SendAudio(BaseResponse, ReplyToMixin, DisableNotificationMixin):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "audio": self.audio,
- "caption": self.caption,
- "duration": self.duration,
- "performer": self.performer,
- "title": self.title,
- "disable_notification": self.disable_notification,
- "reply_to_message_id": self.reply_to_message_id,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'audio': self.audio,
+ 'caption': self.caption,
+ 'duration': self.duration,
+ 'performer': self.performer,
+ 'title': self.title,
+ 'disable_notification': self.disable_notification,
+ 'reply_to_message_id': self.reply_to_message_id,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
@@ -767,28 +713,17 @@ class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin):
Use that response type for send document on to webhook.
"""
- __slots__ = (
- "chat_id",
- "document",
- "caption",
- "disable_notification",
- "reply_to_message_id",
- "reply_markup",
- )
+ __slots__ = ('chat_id', 'document', 'caption', 'disable_notification', 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_DOCUMENT
- def __init__(
- self,
- chat_id: Union[Integer, String],
- document: String,
- caption: Optional[String] = None,
- disable_notification: Optional[Boolean] = None,
- reply_to_message_id: Optional[Integer] = None,
- reply_markup: Optional[
- Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]
- ] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String],
+ document: String,
+ caption: Optional[String] = None,
+ disable_notification: Optional[Boolean] = None,
+ reply_to_message_id: Optional[Integer] = None,
+ reply_markup: Optional[Union[
+ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target chat or username
of the target channel (in the format @channelusername)
@@ -814,12 +749,12 @@ class SendDocument(BaseResponse, ReplyToMixin, DisableNotificationMixin):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "document": self.document,
- "caption": self.caption,
- "disable_notification": self.disable_notification,
- "reply_to_message_id": self.reply_to_message_id,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'document': self.document,
+ 'caption': self.caption,
+ 'disable_notification': self.disable_notification,
+ 'reply_to_message_id': self.reply_to_message_id,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
@@ -828,34 +763,21 @@ class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin):
Use that response type for send video on to webhook.
"""
- __slots__ = (
- "chat_id",
- "video",
- "duration",
- "width",
- "height",
- "caption",
- "disable_notification",
- "reply_to_message_id",
- "reply_markup",
- )
+ __slots__ = ('chat_id', 'video', 'duration', 'width', 'height', 'caption', 'disable_notification',
+ 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_VIDEO
- def __init__(
- self,
- chat_id: Union[Integer, String],
- video: String,
- duration: Optional[Integer] = None,
- width: Optional[Integer] = None,
- height: Optional[Integer] = None,
- caption: Optional[String] = None,
- disable_notification: Optional[Boolean] = None,
- reply_to_message_id: Optional[Integer] = None,
- reply_markup: Optional[
- Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]
- ] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String],
+ video: String,
+ duration: Optional[Integer] = None,
+ width: Optional[Integer] = None,
+ height: Optional[Integer] = None,
+ caption: Optional[String] = None,
+ disable_notification: Optional[Boolean] = None,
+ reply_to_message_id: Optional[Integer] = None,
+ reply_markup: Optional[Union[
+ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target chat or username
of the target channel (in the format @channelusername)
@@ -887,15 +809,15 @@ class SendVideo(BaseResponse, ReplyToMixin, DisableNotificationMixin):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "video": self.video,
- "duration": self.duration,
- "width": self.width,
- "height": self.height,
- "caption": self.caption,
- "disable_notification": self.disable_notification,
- "reply_to_message_id": self.reply_to_message_id,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'video': self.video,
+ 'duration': self.duration,
+ 'width': self.width,
+ 'height': self.height,
+ 'caption': self.caption,
+ 'disable_notification': self.disable_notification,
+ 'reply_to_message_id': self.reply_to_message_id,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
@@ -904,30 +826,19 @@ class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
Use that response type for send voice on to webhook.
"""
- __slots__ = (
- "chat_id",
- "voice",
- "caption",
- "duration",
- "disable_notification",
- "reply_to_message_id",
- "reply_markup",
- )
+ __slots__ = ('chat_id', 'voice', 'caption', 'duration', 'disable_notification',
+ 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_VOICE
- def __init__(
- self,
- chat_id: Union[Integer, String],
- voice: String,
- caption: Optional[String] = None,
- duration: Optional[Integer] = None,
- disable_notification: Optional[Boolean] = None,
- reply_to_message_id: Optional[Integer] = None,
- reply_markup: Optional[
- Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]
- ] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String],
+ voice: String,
+ caption: Optional[String] = None,
+ duration: Optional[Integer] = None,
+ disable_notification: Optional[Boolean] = None,
+ reply_to_message_id: Optional[Integer] = None,
+ reply_markup: Optional[Union[
+ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target chat or username
of the target channel (in the format @channelusername)
@@ -954,13 +865,13 @@ class SendVoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "voice": self.voice,
- "caption": self.caption,
- "duration": self.duration,
- "disable_notification": self.disable_notification,
- "reply_to_message_id": self.reply_to_message_id,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'voice': self.voice,
+ 'caption': self.caption,
+ 'duration': self.duration,
+ 'disable_notification': self.disable_notification,
+ 'reply_to_message_id': self.reply_to_message_id,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
@@ -969,30 +880,19 @@ class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin):
Use that response type for send video note on to webhook.
"""
- __slots__ = (
- "chat_id",
- "video_note",
- "duration",
- "length",
- "disable_notification",
- "reply_to_message_id",
- "reply_markup",
- )
+ __slots__ = ('chat_id', 'video_note', 'duration', 'length', 'disable_notification',
+ 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_VIDEO_NOTE
- def __init__(
- self,
- chat_id: Union[Integer, String],
- video_note: String,
- duration: Optional[Integer] = None,
- length: Optional[Integer] = None,
- disable_notification: Optional[Boolean] = None,
- reply_to_message_id: Optional[Integer] = None,
- reply_markup: Optional[
- Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]
- ] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String],
+ video_note: String,
+ duration: Optional[Integer] = None,
+ length: Optional[Integer] = None,
+ disable_notification: Optional[Boolean] = None,
+ reply_to_message_id: Optional[Integer] = None,
+ reply_markup: Optional[Union[
+ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target chat or username
of the target channel (in the format @channelusername)
@@ -1018,13 +918,13 @@ class SendVideoNote(BaseResponse, ReplyToMixin, DisableNotificationMixin):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "video_note": self.video_note,
- "duration": self.duration,
- "length": self.length,
- "disable_notification": self.disable_notification,
- "reply_to_message_id": self.reply_to_message_id,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'video_note': self.video_note,
+ 'duration': self.duration,
+ 'length': self.length,
+ 'disable_notification': self.disable_notification,
+ 'reply_to_message_id': self.reply_to_message_id,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
@@ -1033,17 +933,14 @@ class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin):
Use this method to send a group of photos or videos as an album.
"""
- __slots__ = ("chat_id", "media", "disable_notification", "reply_to_message_id")
+ __slots__ = ('chat_id', 'media', 'disable_notification', 'reply_to_message_id')
method = api.Methods.SEND_MEDIA_GROUP
- 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,
- ):
+ 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):
"""
Use this method to send a group of photos or videos as an album.
@@ -1074,15 +971,15 @@ class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin):
def prepare(self):
files = dict(self.media.get_files())
if files:
- raise TypeError("Allowed only file ID or URL's")
+ raise TypeError('Allowed only file ID or URL\'s')
media = prepare_arg(self.media)
return {
- "chat_id": self.chat_id,
- "media": media,
- "disable_notifications": self.disable_notifications,
- "reply_to_message_id": self.reply_to_message_id,
+ 'chat_id': self.chat_id,
+ 'media': media,
+ 'disable_notifications': self.disable_notifications,
+ 'reply_to_message_id': self.reply_to_message_id
}
def attach_photo(self, photo: String, caption: String = None):
@@ -1096,14 +993,8 @@ class SendMediaGroup(BaseResponse, ReplyToMixin, DisableNotificationMixin):
self.media.attach_photo(photo, caption)
return self
- def attach_video(
- self,
- video: String,
- caption: String = None,
- width: Integer = None,
- height: Integer = None,
- duration: Integer = None,
- ):
+ def attach_video(self, video: String, caption: String = None, width: Integer = None,
+ height: Integer = None, duration: Integer = None):
"""
Attach video
@@ -1123,28 +1014,16 @@ class SendLocation(BaseResponse, ReplyToMixin, DisableNotificationMixin):
Use that response type for send location on to webhook.
"""
- __slots__ = (
- "chat_id",
- "latitude",
- "longitude",
- "disable_notification",
- "reply_to_message_id",
- "reply_markup",
- )
+ __slots__ = ('chat_id', 'latitude', 'longitude', 'disable_notification', 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_LOCATION
- def __init__(
- self,
- chat_id: Union[Integer, String],
- latitude: Float,
- longitude: Float,
- disable_notification: Optional[Boolean] = None,
- reply_to_message_id: Optional[Integer] = None,
- reply_markup: Optional[
- Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]
- ] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String],
+ latitude: Float, longitude: Float,
+ disable_notification: Optional[Boolean] = None,
+ reply_to_message_id: Optional[Integer] = None,
+ reply_markup: Optional[Union[
+ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target chat or username
of the target channel (in the format @channelusername)
@@ -1166,12 +1045,12 @@ class SendLocation(BaseResponse, ReplyToMixin, DisableNotificationMixin):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "latitude": self.latitude,
- "longitude": self.longitude,
- "disable_notification": self.disable_notification,
- "reply_to_message_id": self.reply_to_message_id,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'latitude': self.latitude,
+ 'longitude': self.longitude,
+ 'disable_notification': self.disable_notification,
+ 'reply_to_message_id': self.reply_to_message_id,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
@@ -1180,34 +1059,21 @@ class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin):
Use that response type for send venue on to webhook.
"""
- __slots__ = (
- "chat_id",
- "latitude",
- "longitude",
- "title",
- "address",
- "foursquare_id",
- "disable_notification",
- "reply_to_message_id",
- "reply_markup",
- )
+ __slots__ = ('chat_id', 'latitude', 'longitude', 'title', 'address', 'foursquare_id',
+ 'disable_notification', 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_VENUE
- def __init__(
- self,
- chat_id: Union[Integer, String],
- latitude: Float,
- longitude: Float,
- title: String,
- address: String,
- foursquare_id: Optional[String] = None,
- disable_notification: Optional[Boolean] = None,
- reply_to_message_id: Optional[Integer] = None,
- reply_markup: Optional[
- Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]
- ] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String],
+ latitude: Float,
+ longitude: Float,
+ title: String,
+ address: String,
+ foursquare_id: Optional[String] = None,
+ disable_notification: Optional[Boolean] = None,
+ reply_to_message_id: Optional[Integer] = None,
+ reply_markup: Optional[Union[
+ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target chat or username
of the target channel (in the format @channelusername)
@@ -1235,15 +1101,15 @@ class SendVenue(BaseResponse, ReplyToMixin, DisableNotificationMixin):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "latitude": self.latitude,
- "longitude": self.longitude,
- "title": self.title,
- "address": self.address,
- "foursquare_id": self.foursquare_id,
- "disable_notification": self.disable_notification,
- "reply_to_message_id": self.reply_to_message_id,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'latitude': self.latitude,
+ 'longitude': self.longitude,
+ 'title': self.title,
+ 'address': self.address,
+ 'foursquare_id': self.foursquare_id,
+ 'disable_notification': self.disable_notification,
+ 'reply_to_message_id': self.reply_to_message_id,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
@@ -1252,30 +1118,19 @@ class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin):
Use that response type for send contact on to webhook.
"""
- __slots__ = (
- "chat_id",
- "phone_number",
- "first_name",
- "last_name",
- "disable_notification",
- "reply_to_message_id",
- "reply_markup",
- )
+ __slots__ = ('chat_id', 'phone_number', 'first_name', 'last_name', 'disable_notification',
+ 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_CONTACT
- def __init__(
- self,
- chat_id: Union[Integer, String],
- phone_number: String,
- first_name: String,
- last_name: Optional[String] = None,
- disable_notification: Optional[Boolean] = None,
- reply_to_message_id: Optional[Integer] = None,
- reply_markup: Optional[
- Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]
- ] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String],
+ phone_number: String,
+ first_name: String,
+ last_name: Optional[String] = None,
+ disable_notification: Optional[Boolean] = None,
+ reply_to_message_id: Optional[Integer] = None,
+ reply_markup: Optional[Union[
+ types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target chat or
username of the target channel (in the format @channelusername)
@@ -1299,13 +1154,13 @@ class SendContact(BaseResponse, ReplyToMixin, DisableNotificationMixin):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "phone_number": self.phone_number,
- "first_name": self.first_name,
- "last_name": self.last_name,
- "disable_notification": self.disable_notification,
- "reply_to_message_id": self.reply_to_message_id,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'phone_number': self.phone_number,
+ 'first_name': self.first_name,
+ 'last_name': self.last_name,
+ 'disable_notification': self.disable_notification,
+ 'reply_to_message_id': self.reply_to_message_id,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
@@ -1314,7 +1169,7 @@ class SendChatAction(BaseResponse):
Use that response type for send chat action on to webhook.
"""
- __slots__ = ("chat_id", "action")
+ __slots__ = ('chat_id', 'action')
method = api.Methods.SEND_CHAT_ACTION
@@ -1331,7 +1186,10 @@ class SendChatAction(BaseResponse):
self.action = action
def prepare(self):
- return {"chat_id": self.chat_id, "action": self.action}
+ return {
+ 'chat_id': self.chat_id,
+ 'action': self.action
+ }
class KickChatMember(BaseResponse):
@@ -1339,16 +1197,14 @@ class KickChatMember(BaseResponse):
Use that response type for kick chat member on to webhook.
"""
- __slots__ = ("chat_id", "user_id", "until_date")
+ __slots__ = ('chat_id', 'user_id', 'until_date')
method = api.Methods.KICK_CHAT_MEMBER
- def __init__(
- self,
- chat_id: Union[Integer, String],
- user_id: Integer,
- until_date: Optional[Union[Integer, datetime.datetime, datetime.timedelta]] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String],
+ user_id: Integer,
+ until_date: Optional[
+ Union[Integer, datetime.datetime, datetime.timedelta]] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target group or username
of the target supergroup or channel (in the format @channelusername)
@@ -1362,9 +1218,9 @@ class KickChatMember(BaseResponse):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "user_id": self.user_id,
- "until_date": prepare_arg(self.until_date),
+ 'chat_id': self.chat_id,
+ 'user_id': self.user_id,
+ 'until_date': prepare_arg(self.until_date),
}
@@ -1373,7 +1229,7 @@ class UnbanChatMember(BaseResponse):
Use that response type for unban chat member on to webhook.
"""
- __slots__ = ("chat_id", "user_id")
+ __slots__ = ('chat_id', 'user_id')
method = api.Methods.UNBAN_CHAT_MEMBER
@@ -1387,7 +1243,10 @@ class UnbanChatMember(BaseResponse):
self.user_id = user_id
def prepare(self):
- return {"chat_id": self.chat_id, "user_id": self.user_id}
+ return {
+ 'chat_id': self.chat_id,
+ 'user_id': self.user_id
+ }
class RestrictChatMember(BaseResponse):
@@ -1395,28 +1254,18 @@ class RestrictChatMember(BaseResponse):
Use that response type for restrict chat member on to webhook.
"""
- __slots__ = (
- "chat_id",
- "user_id",
- "until_date",
- "can_send_messages",
- "can_send_media_messages",
- "can_send_other_messages",
- "can_add_web_page_previews",
- )
+ __slots__ = ('chat_id', 'user_id', 'until_date', 'can_send_messages', 'can_send_media_messages',
+ 'can_send_other_messages', 'can_add_web_page_previews')
method = api.Methods.RESTRICT_CHAT_MEMBER
- def __init__(
- self,
- chat_id: Union[Integer, String],
- user_id: Integer,
- until_date: Optional[Union[Integer, datetime.datetime, datetime.timedelta]] = None,
- can_send_messages: Optional[Boolean] = None,
- can_send_media_messages: Optional[Boolean] = None,
- can_send_other_messages: Optional[Boolean] = None,
- can_add_web_page_previews: Optional[Boolean] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String],
+ user_id: Integer,
+ until_date: Optional[Union[Integer, datetime.datetime, datetime.timedelta]] = None,
+ can_send_messages: Optional[Boolean] = None,
+ can_send_media_messages: Optional[Boolean] = None,
+ can_send_other_messages: Optional[Boolean] = None,
+ can_add_web_page_previews: Optional[Boolean] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target chat
or username of the target supergroup (in the format @supergroupusername)
@@ -1443,13 +1292,13 @@ class RestrictChatMember(BaseResponse):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "user_id": self.user_id,
- "until_date": prepare_arg(self.until_date),
- "can_send_messages": self.can_send_messages,
- "can_send_media_messages": self.can_send_media_messages,
- "can_send_other_messages": self.can_send_other_messages,
- "can_add_web_page_previews": self.can_add_web_page_previews,
+ 'chat_id': self.chat_id,
+ 'user_id': self.user_id,
+ 'until_date': prepare_arg(self.until_date),
+ 'can_send_messages': self.can_send_messages,
+ 'can_send_media_messages': self.can_send_media_messages,
+ 'can_send_other_messages': self.can_send_other_messages,
+ 'can_add_web_page_previews': self.can_add_web_page_previews
}
@@ -1458,34 +1307,22 @@ class PromoteChatMember(BaseResponse):
Use that response type for promote chat member on to webhook.
"""
- __slots__ = (
- "chat_id",
- "user_id",
- "can_change_info",
- "can_post_messages",
- "can_edit_messages",
- "can_delete_messages",
- "can_invite_users",
- "can_restrict_members",
- "can_pin_messages",
- "can_promote_members",
- )
+ __slots__ = ('chat_id', 'user_id', 'can_change_info', 'can_post_messages', 'can_edit_messages',
+ 'can_delete_messages', 'can_invite_users', 'can_restrict_members', 'can_pin_messages',
+ 'can_promote_members')
method = api.Methods.PROMOTE_CHAT_MEMBER
- def __init__(
- self,
- chat_id: Union[Integer, String],
- user_id: Integer,
- can_change_info: Optional[Boolean] = None,
- can_post_messages: Optional[Boolean] = None,
- can_edit_messages: Optional[Boolean] = None,
- can_delete_messages: Optional[Boolean] = None,
- can_invite_users: Optional[Boolean] = None,
- can_restrict_members: Optional[Boolean] = None,
- can_pin_messages: Optional[Boolean] = None,
- can_promote_members: Optional[Boolean] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String],
+ user_id: Integer,
+ can_change_info: Optional[Boolean] = None,
+ can_post_messages: Optional[Boolean] = None,
+ can_edit_messages: Optional[Boolean] = None,
+ can_delete_messages: Optional[Boolean] = None,
+ can_invite_users: Optional[Boolean] = None,
+ can_restrict_members: Optional[Boolean] = None,
+ can_pin_messages: Optional[Boolean] = None,
+ can_promote_members: Optional[Boolean] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target chat
or username of the target channel (in the format @channelusername)
@@ -1516,16 +1353,16 @@ class PromoteChatMember(BaseResponse):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "user_id": self.user_id,
- "can_change_info": self.can_change_info,
- "can_post_messages": self.can_post_messages,
- "can_edit_messages": self.can_edit_messages,
- "can_delete_messages": self.can_delete_messages,
- "can_invite_users": self.can_invite_users,
- "can_restrict_members": self.can_restrict_members,
- "can_pin_messages": self.can_pin_messages,
- "can_promote_members": self.can_promote_members,
+ 'chat_id': self.chat_id,
+ 'user_id': self.user_id,
+ 'can_change_info': self.can_change_info,
+ 'can_post_messages': self.can_post_messages,
+ 'can_edit_messages': self.can_edit_messages,
+ 'can_delete_messages': self.can_delete_messages,
+ 'can_invite_users': self.can_invite_users,
+ 'can_restrict_members': self.can_restrict_members,
+ 'can_pin_messages': self.can_pin_messages,
+ 'can_promote_members': self.can_promote_members
}
@@ -1534,7 +1371,7 @@ class DeleteChatPhoto(BaseResponse):
Use that response type for delete chat photo on to webhook.
"""
- __slots__ = ("chat_id",)
+ __slots__ = ('chat_id',)
method = api.Methods.DELETE_CHAT_PHOTO
@@ -1546,7 +1383,9 @@ class DeleteChatPhoto(BaseResponse):
self.chat_id = chat_id
def prepare(self):
- return {"chat_id": self.chat_id}
+ return {
+ 'chat_id': self.chat_id
+ }
class SetChatTitle(BaseResponse):
@@ -1554,7 +1393,7 @@ class SetChatTitle(BaseResponse):
Use that response type for set chat title on to webhook.
"""
- __slots__ = ("chat_id", "title")
+ __slots__ = ('chat_id', 'title')
method = api.Methods.SET_CHAT_TITLE
@@ -1568,7 +1407,10 @@ class SetChatTitle(BaseResponse):
self.title = title
def prepare(self):
- return {"chat_id": self.chat_id, "title": self.title}
+ return {
+ 'chat_id': self.chat_id,
+ 'title': self.title
+ }
class SetChatDescription(BaseResponse):
@@ -1576,7 +1418,7 @@ class SetChatDescription(BaseResponse):
Use that response type for set chat description on to webhook.
"""
- __slots__ = ("chat_id", "description")
+ __slots__ = ('chat_id', 'description')
method = api.Methods.SET_CHAT_DESCRIPTION
@@ -1590,7 +1432,10 @@ class SetChatDescription(BaseResponse):
self.description = description
def prepare(self):
- return {"chat_id": self.chat_id, "description": self.description}
+ return {
+ 'chat_id': self.chat_id,
+ 'description': self.description
+ }
class PinChatMessage(BaseResponse, DisableNotificationMixin):
@@ -1598,16 +1443,12 @@ class PinChatMessage(BaseResponse, DisableNotificationMixin):
Use that response type for pin chat message on to webhook.
"""
- __slots__ = ("chat_id", "message_id", "disable_notification")
+ __slots__ = ('chat_id', 'message_id', 'disable_notification')
method = api.Methods.PIN_CHAT_MESSAGE
- def __init__(
- self,
- chat_id: Union[Integer, String],
- message_id: Integer,
- disable_notification: Optional[Boolean] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String], message_id: Integer,
+ disable_notification: Optional[Boolean] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target chat
or username of the target supergroup (in the format @supergroupusername)
@@ -1621,9 +1462,9 @@ class PinChatMessage(BaseResponse, DisableNotificationMixin):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "message_id": self.message_id,
- "disable_notification": self.disable_notification,
+ 'chat_id': self.chat_id,
+ 'message_id': self.message_id,
+ 'disable_notification': self.disable_notification,
}
@@ -1632,7 +1473,7 @@ class UnpinChatMessage(BaseResponse):
Use that response type for unpin chat message on to webhook.
"""
- __slots__ = ("chat_id",)
+ __slots__ = ('chat_id',)
method = api.Methods.UNPIN_CHAT_MESSAGE
@@ -1644,7 +1485,9 @@ class UnpinChatMessage(BaseResponse):
self.chat_id = chat_id
def prepare(self):
- return {"chat_id": self.chat_id}
+ return {
+ 'chat_id': self.chat_id
+ }
class LeaveChat(BaseResponse):
@@ -1652,7 +1495,7 @@ class LeaveChat(BaseResponse):
Use that response type for leave chat on to webhook.
"""
- __slots__ = ("chat_id",)
+ __slots__ = ('chat_id',)
method = api.Methods.LEAVE_CHAT
@@ -1664,7 +1507,9 @@ class LeaveChat(BaseResponse):
self.chat_id = chat_id
def prepare(self):
- return {"chat_id": self.chat_id}
+ return {
+ 'chat_id': self.chat_id
+ }
class AnswerCallbackQuery(BaseResponse):
@@ -1672,18 +1517,15 @@ class AnswerCallbackQuery(BaseResponse):
Use that response type for answer callback query on to webhook.
"""
- __slots__ = ("callback_query_id", "text", "show_alert", "url", "cache_time")
+ __slots__ = ('callback_query_id', 'text', 'show_alert', 'url', 'cache_time')
method = api.Methods.ANSWER_CALLBACK_QUERY
- def __init__(
- self,
- callback_query_id: String,
- text: Optional[String] = None,
- show_alert: Optional[Boolean] = None,
- url: Optional[String] = None,
- cache_time: Optional[Integer] = None,
- ):
+ def __init__(self, callback_query_id: String,
+ text: Optional[String] = None,
+ show_alert: Optional[Boolean] = None,
+ url: Optional[String] = None,
+ cache_time: Optional[Integer] = None):
"""
:param callback_query_id: String - Unique identifier for the query to be answered
:param text: String (Optional) - Text of the notification. If not specified, nothing will be shown to the user,
@@ -1707,11 +1549,11 @@ class AnswerCallbackQuery(BaseResponse):
def prepare(self):
return {
- "callback_query_id": self.callback_query_id,
- "text": self.text,
- "show_alert": self.show_alert,
- "url": self.url,
- "cache_time": self.cache_time,
+ 'callback_query_id': self.callback_query_id,
+ 'text': self.text,
+ 'show_alert': self.show_alert,
+ 'url': self.url,
+ 'cache_time': self.cache_time
}
@@ -1720,28 +1562,18 @@ class EditMessageText(BaseResponse, ParseModeMixin, DisableWebPagePreviewMixin):
Use that response type for edit message text on to webhook.
"""
- __slots__ = (
- "chat_id",
- "message_id",
- "inline_message_id",
- "text",
- "parse_mode",
- "disable_web_page_preview",
- "reply_markup",
- )
+ __slots__ = ('chat_id', 'message_id', 'inline_message_id', 'text', 'parse_mode',
+ 'disable_web_page_preview', 'reply_markup')
method = api.Methods.EDIT_MESSAGE_TEXT
- def __init__(
- self,
- text: String,
- chat_id: Optional[Union[Integer, String]] = None,
- message_id: Optional[Integer] = None,
- inline_message_id: Optional[String] = None,
- parse_mode: Optional[String] = None,
- disable_web_page_preview: Optional[Boolean] = None,
- reply_markup: Optional[types.InlineKeyboardMarkup] = None,
- ):
+ def __init__(self, text: String,
+ chat_id: Optional[Union[Integer, String]] = None,
+ message_id: Optional[Integer] = None,
+ inline_message_id: Optional[String] = None,
+ parse_mode: Optional[String] = None,
+ disable_web_page_preview: Optional[Boolean] = None,
+ reply_markup: Optional[types.InlineKeyboardMarkup] = None):
"""
:param chat_id: Union[Integer, String] (Optional) - Required if inline_message_id
is not specified. Unique identifier for the target chat or username of the target channel
@@ -1770,13 +1602,13 @@ class EditMessageText(BaseResponse, ParseModeMixin, DisableWebPagePreviewMixin):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "message_id": self.message_id,
- "inline_message_id": self.inline_message_id,
- "text": self.text,
- "parse_mode": self.parse_mode,
- "disable_web_page_preview": self.disable_web_page_preview,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'message_id': self.message_id,
+ 'inline_message_id': self.inline_message_id,
+ 'text': self.text,
+ 'parse_mode': self.parse_mode,
+ 'disable_web_page_preview': self.disable_web_page_preview,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
@@ -1785,18 +1617,15 @@ class EditMessageCaption(BaseResponse):
Use that response type for edit message caption on to webhook.
"""
- __slots__ = ("chat_id", "message_id", "inline_message_id", "caption", "reply_markup")
+ __slots__ = ('chat_id', 'message_id', 'inline_message_id', 'caption', 'reply_markup')
method = api.Methods.EDIT_MESSAGE_CAPTION
- def __init__(
- self,
- chat_id: Optional[Union[Integer, String]] = None,
- message_id: Optional[Integer] = None,
- inline_message_id: Optional[String] = None,
- caption: Optional[String] = None,
- reply_markup: Optional[types.InlineKeyboardMarkup] = None,
- ):
+ def __init__(self, chat_id: Optional[Union[Integer, String]] = None,
+ message_id: Optional[Integer] = None,
+ inline_message_id: Optional[String] = None,
+ caption: Optional[String] = None,
+ reply_markup: Optional[types.InlineKeyboardMarkup] = None):
"""
:param chat_id: Union[Integer, String] (Optional) - Required if inline_message_id
is not specified. Unique identifier for the target chat or username of the target channel
@@ -1816,11 +1645,11 @@ class EditMessageCaption(BaseResponse):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "message_id": self.message_id,
- "inline_message_id": self.inline_message_id,
- "caption": self.caption,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'message_id': self.message_id,
+ 'inline_message_id': self.inline_message_id,
+ 'caption': self.caption,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
@@ -1829,17 +1658,14 @@ class EditMessageReplyMarkup(BaseResponse):
Use that response type for edit message reply markup on to webhook.
"""
- __slots__ = ("chat_id", "message_id", "inline_message_id", "reply_markup")
+ __slots__ = ('chat_id', 'message_id', 'inline_message_id', 'reply_markup')
method = api.Methods.EDIT_MESSAGE_REPLY_MARKUP
- def __init__(
- self,
- chat_id: Optional[Union[Integer, String]] = None,
- message_id: Optional[Integer] = None,
- inline_message_id: Optional[String] = None,
- reply_markup: Optional[types.InlineKeyboardMarkup] = None,
- ):
+ def __init__(self, chat_id: Optional[Union[Integer, String]] = None,
+ message_id: Optional[Integer] = None,
+ inline_message_id: Optional[String] = None,
+ reply_markup: Optional[types.InlineKeyboardMarkup] = None):
"""
:param chat_id: Union[Integer, String] (Optional) - Required if inline_message_id is not specified.
Unique identifier for the target chat or username of the target channel (in the format @channelusername)
@@ -1856,10 +1682,10 @@ class EditMessageReplyMarkup(BaseResponse):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "message_id": self.message_id,
- "inline_message_id": self.inline_message_id,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'message_id': self.message_id,
+ 'inline_message_id': self.inline_message_id,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
@@ -1868,7 +1694,7 @@ class DeleteMessage(BaseResponse):
Use that response type for delete message on to webhook.
"""
- __slots__ = ("chat_id", "message_id")
+ __slots__ = ('chat_id', 'message_id')
method = api.Methods.DELETE_MESSAGE
@@ -1882,7 +1708,10 @@ class DeleteMessage(BaseResponse):
self.message_id = message_id
def prepare(self):
- return {"chat_id": self.chat_id, "message_id": self.message_id}
+ return {
+ 'chat_id': self.chat_id,
+ 'message_id': self.message_id
+ }
class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin):
@@ -1890,26 +1719,17 @@ class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin):
Use that response type for send sticker on to webhook.
"""
- __slots__ = (
- "chat_id",
- "sticker",
- "disable_notification",
- "reply_to_message_id",
- "reply_markup",
- )
+ __slots__ = ('chat_id', 'sticker', 'disable_notification', 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_STICKER
- def __init__(
- self,
- chat_id: Union[Integer, String],
- sticker: String,
- disable_notification: Optional[Boolean] = None,
- reply_to_message_id: Optional[Integer] = None,
- reply_markup: Optional[
- Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String]
- ] = None,
- ):
+ def __init__(self, chat_id: Union[Integer, String],
+ sticker: String,
+ disable_notification: Optional[Boolean] = None,
+ reply_to_message_id: Optional[Integer] = None,
+ reply_markup: Optional[
+ Union[types.InlineKeyboardMarkup,
+ types.ReplyKeyboardMarkup, Dict, String]] = None):
"""
:param chat_id: Union[Integer, String] - Unique identifier for the target chat or username
of the target channel (in the format @channelusername)
@@ -1932,11 +1752,11 @@ class SendSticker(BaseResponse, ReplyToMixin, DisableNotificationMixin):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "sticker": self.sticker,
- "disable_notification": self.disable_notification,
- "reply_to_message_id": self.reply_to_message_id,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'sticker': self.sticker,
+ 'disable_notification': self.disable_notification,
+ 'reply_to_message_id': self.reply_to_message_id,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
@@ -1945,28 +1765,16 @@ class CreateNewStickerSet(BaseResponse):
Use that response type for create new sticker set on to webhook.
"""
- __slots__ = (
- "user_id",
- "name",
- "title",
- "png_sticker",
- "emojis",
- "contains_masks",
- "mask_position",
- )
+ __slots__ = ('user_id', 'name', 'title', 'png_sticker', 'emojis', 'contains_masks', 'mask_position')
method = api.Methods.CREATE_NEW_STICKER_SET
- def __init__(
- self,
- user_id: Integer,
- name: String,
- title: String,
- png_sticker: String,
- emojis: String,
- contains_masks: Optional[Boolean] = None,
- mask_position: Optional[types.MaskPosition] = None,
- ):
+ def __init__(self, user_id: Integer,
+ name: String, title: String,
+ png_sticker: String,
+ emojis: String,
+ contains_masks: Optional[Boolean] = None,
+ mask_position: Optional[types.MaskPosition] = None):
"""
:param user_id: Integer - User identifier of created sticker set owner
:param name: String - Short name of sticker set, to be used in t.me/addstickers/ URLs (e.g., animals).
@@ -1993,13 +1801,13 @@ class CreateNewStickerSet(BaseResponse):
def prepare(self):
return {
- "user_id": self.user_id,
- "name": self.name,
- "title": self.title,
- "png_sticker": self.png_sticker,
- "emojis": self.emojis,
- "contains_masks": self.contains_masks,
- "mask_position": self.mask_position,
+ 'user_id': self.user_id,
+ 'name': self.name,
+ 'title': self.title,
+ 'png_sticker': self.png_sticker,
+ 'emojis': self.emojis,
+ 'contains_masks': self.contains_masks,
+ 'mask_position': self.mask_position
}
@@ -2008,18 +1816,15 @@ class AddStickerToSet(BaseResponse):
Use that response type for add sticker to set on to webhook.
"""
- __slots__ = ("user_id", "name", "png_sticker", "emojis", "mask_position")
+ __slots__ = ('user_id', 'name', 'png_sticker', 'emojis', 'mask_position')
method = api.Methods.ADD_STICKER_TO_SET
- def __init__(
- self,
- user_id: Integer,
- name: String,
- png_sticker: String,
- emojis: String,
- mask_position: Optional[types.MaskPosition] = None,
- ):
+ def __init__(self, user_id: Integer,
+ name: String,
+ png_sticker: String,
+ emojis: String,
+ mask_position: Optional[types.MaskPosition] = None):
"""
:param user_id: Integer - User identifier of sticker set owner
:param name: String - Sticker set name
@@ -2039,11 +1844,11 @@ class AddStickerToSet(BaseResponse):
def prepare(self):
return {
- "user_id": self.user_id,
- "name": self.name,
- "png_sticker": self.png_sticker,
- "emojis": self.emojis,
- "mask_position": prepare_arg(self.mask_position),
+ 'user_id': self.user_id,
+ 'name': self.name,
+ 'png_sticker': self.png_sticker,
+ 'emojis': self.emojis,
+ 'mask_position': prepare_arg(self.mask_position),
}
@@ -2052,7 +1857,7 @@ class SetStickerPositionInSet(BaseResponse):
Use that response type for set sticker position in set on to webhook.
"""
- __slots__ = ("sticker", "position")
+ __slots__ = ('sticker', 'position')
method = api.Methods.SET_STICKER_POSITION_IN_SET
@@ -2065,7 +1870,10 @@ class SetStickerPositionInSet(BaseResponse):
self.position = position
def prepare(self):
- return {"sticker": self.sticker, "position": self.position}
+ return {
+ 'sticker': self.sticker,
+ 'position': self.position
+ }
class DeleteStickerFromSet(BaseResponse):
@@ -2073,7 +1881,7 @@ class DeleteStickerFromSet(BaseResponse):
Use that response type for delete sticker from set on to webhook.
"""
- __slots__ = ("sticker",)
+ __slots__ = ('sticker',)
method = api.Methods.DELETE_STICKER_FROM_SET
@@ -2084,7 +1892,9 @@ class DeleteStickerFromSet(BaseResponse):
self.sticker = sticker
def prepare(self):
- return {"sticker": self.sticker}
+ return {
+ 'sticker': self.sticker
+ }
class AnswerInlineQuery(BaseResponse):
@@ -2092,28 +1902,18 @@ class AnswerInlineQuery(BaseResponse):
Use that response type for answer inline query on to webhook.
"""
- __slots__ = (
- "inline_query_id",
- "results",
- "cache_time",
- "is_personal",
- "next_offset",
- "switch_pm_text",
- "switch_pm_parameter",
- )
+ __slots__ = ('inline_query_id', 'results', 'cache_time', 'is_personal', 'next_offset',
+ 'switch_pm_text', 'switch_pm_parameter')
method = api.Methods.ANSWER_INLINE_QUERY
- def __init__(
- self,
- inline_query_id: String,
- results: [types.InlineQueryResult],
- cache_time: Optional[Integer] = None,
- is_personal: Optional[Boolean] = None,
- next_offset: Optional[String] = None,
- switch_pm_text: Optional[String] = None,
- switch_pm_parameter: Optional[String] = None,
- ):
+ def __init__(self, inline_query_id: String,
+ results: [types.InlineQueryResult],
+ cache_time: Optional[Integer] = None,
+ is_personal: Optional[Boolean] = None,
+ next_offset: Optional[String] = None,
+ switch_pm_text: Optional[String] = None,
+ switch_pm_parameter: Optional[String] = None):
"""
:param inline_query_id: String - Unique identifier for the answered query
:param results: [types.InlineQueryResult] - A JSON-serialized array of results for the inline query
@@ -2150,13 +1950,13 @@ class AnswerInlineQuery(BaseResponse):
def prepare(self):
return {
- "inline_query_id": self.inline_query_id,
- "results": prepare_arg(self.results),
- "cache_time": self.cache_time,
- "is_personal": self.is_personal,
- "next_offset": self.next_offset,
- "switch_pm_text": self.switch_pm_text,
- "switch_pm_parameter": self.switch_pm_parameter,
+ 'inline_query_id': self.inline_query_id,
+ 'results': prepare_arg(self.results),
+ 'cache_time': self.cache_time,
+ 'is_personal': self.is_personal,
+ 'next_offset': self.next_offset,
+ 'switch_pm_text': self.switch_pm_text,
+ 'switch_pm_parameter': self.switch_pm_parameter,
}
@@ -2165,54 +1965,33 @@ class SendInvoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
Use that response type for send invoice on to webhook.
"""
- __slots__ = (
- "chat_id",
- "title",
- "description",
- "payload",
- "provider_token",
- "start_parameter",
- "currency",
- "prices",
- "photo_url",
- "photo_size",
- "photo_width",
- "photo_height",
- "need_name",
- "need_phone_number",
- "need_email",
- "need_shipping_address",
- "is_flexible",
- "disable_notification",
- "reply_to_message_id",
- "reply_markup",
- )
+ __slots__ = ('chat_id', 'title', 'description', 'payload', 'provider_token', 'start_parameter',
+ 'currency', 'prices', 'photo_url', 'photo_size', 'photo_width', 'photo_height',
+ 'need_name', 'need_phone_number', 'need_email', 'need_shipping_address', 'is_flexible',
+ 'disable_notification', 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_INVOICE
- def __init__(
- self,
- chat_id: Integer,
- title: String,
- description: String,
- payload: String,
- provider_token: String,
- start_parameter: String,
- currency: String,
- prices: [types.LabeledPrice],
- photo_url: Optional[String] = None,
- photo_size: Optional[Integer] = None,
- photo_width: Optional[Integer] = None,
- photo_height: Optional[Integer] = None,
- need_name: Optional[Boolean] = None,
- need_phone_number: Optional[Boolean] = None,
- need_email: Optional[Boolean] = None,
- need_shipping_address: Optional[Boolean] = None,
- is_flexible: Optional[Boolean] = None,
- disable_notification: Optional[Boolean] = None,
- reply_to_message_id: Optional[Integer] = None,
- reply_markup: Optional[types.InlineKeyboardMarkup] = None,
- ):
+ def __init__(self, chat_id: Integer,
+ title: String,
+ description: String,
+ payload: String,
+ provider_token: String,
+ start_parameter: String,
+ currency: String,
+ prices: [types.LabeledPrice],
+ photo_url: Optional[String] = None,
+ photo_size: Optional[Integer] = None,
+ photo_width: Optional[Integer] = None,
+ photo_height: Optional[Integer] = None,
+ need_name: Optional[Boolean] = None,
+ need_phone_number: Optional[Boolean] = None,
+ need_email: Optional[Boolean] = None,
+ need_shipping_address: Optional[Boolean] = None,
+ is_flexible: Optional[Boolean] = None,
+ disable_notification: Optional[Boolean] = None,
+ reply_to_message_id: Optional[Integer] = None,
+ reply_markup: Optional[types.InlineKeyboardMarkup] = None):
"""
:param chat_id: Integer - Unique identifier for the target private chat
:param title: String - Product name, 1-32 characters
@@ -2267,26 +2046,26 @@ class SendInvoice(BaseResponse, ReplyToMixin, DisableNotificationMixin):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "title": self.title,
- "description": self.description,
- "payload": self.payload,
- "provider_token": self.provider_token,
- "start_parameter": self.start_parameter,
- "currency": self.currency,
- "prices": prepare_arg(self.prices),
- "photo_url": self.photo_url,
- "photo_size": self.photo_size,
- "photo_width": self.photo_width,
- "photo_height": self.photo_height,
- "need_name": self.need_name,
- "need_phone_number": self.need_phone_number,
- "need_email": self.need_email,
- "need_shipping_address": self.need_shipping_address,
- "is_flexible": self.is_flexible,
- "disable_notification": self.disable_notification,
- "reply_to_message_id": self.reply_to_message_id,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'title': self.title,
+ 'description': self.description,
+ 'payload': self.payload,
+ 'provider_token': self.provider_token,
+ 'start_parameter': self.start_parameter,
+ 'currency': self.currency,
+ 'prices': prepare_arg(self.prices),
+ 'photo_url': self.photo_url,
+ 'photo_size': self.photo_size,
+ 'photo_width': self.photo_width,
+ 'photo_height': self.photo_height,
+ 'need_name': self.need_name,
+ 'need_phone_number': self.need_phone_number,
+ 'need_email': self.need_email,
+ 'need_shipping_address': self.need_shipping_address,
+ 'is_flexible': self.is_flexible,
+ 'disable_notification': self.disable_notification,
+ 'reply_to_message_id': self.reply_to_message_id,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
@@ -2295,17 +2074,14 @@ class AnswerShippingQuery(BaseResponse):
Use that response type for answer shipping query on to webhook.
"""
- __slots__ = ("shipping_query_id", "ok", "shipping_options", "error_message")
+ __slots__ = ('shipping_query_id', 'ok', 'shipping_options', 'error_message')
method = api.Methods.ANSWER_SHIPPING_QUERY
- def __init__(
- self,
- shipping_query_id: String,
- ok: Boolean,
- shipping_options: Optional[typing.List[types.ShippingOption]] = None,
- error_message: Optional[String] = None,
- ):
+ def __init__(self, shipping_query_id: String,
+ ok: Boolean,
+ shipping_options: Optional[typing.List[types.ShippingOption]] = None,
+ error_message: Optional[String] = None):
"""
:param shipping_query_id: String - Unique identifier for the query to be answered
:param ok: Boolean - Specify True if delivery to the specified address is possible and
@@ -2324,10 +2100,10 @@ class AnswerShippingQuery(BaseResponse):
def prepare(self):
return {
- "shipping_query_id": self.shipping_query_id,
- "ok": self.ok,
- "shipping_options": prepare_arg(self.shipping_options),
- "error_message": self.error_message,
+ 'shipping_query_id': self.shipping_query_id,
+ 'ok': self.ok,
+ 'shipping_options': prepare_arg(self.shipping_options),
+ 'error_message': self.error_message
}
@@ -2336,13 +2112,13 @@ class AnswerPreCheckoutQuery(BaseResponse):
Use that response type for answer pre checkout query on to webhook.
"""
- __slots__ = ("pre_checkout_query_id", "ok", "error_message")
+ __slots__ = ('pre_checkout_query_id', 'ok', 'error_message')
method = api.Methods.ANSWER_PRE_CHECKOUT_QUERY
- def __init__(
- self, pre_checkout_query_id: String, ok: Boolean, error_message: Optional[String] = None
- ):
+ def __init__(self, pre_checkout_query_id: String,
+ ok: Boolean,
+ error_message: Optional[String] = None):
"""
:param pre_checkout_query_id: String - Unique identifier for the query to be answered
:param ok: Boolean - Specify True if everything is alright (goods are available, etc.)
@@ -2359,9 +2135,9 @@ class AnswerPreCheckoutQuery(BaseResponse):
def prepare(self):
return {
- "pre_checkout_query_id": self.pre_checkout_query_id,
- "ok": self.ok,
- "error_message": self.error_message,
+ 'pre_checkout_query_id': self.pre_checkout_query_id,
+ 'ok': self.ok,
+ 'error_message': self.error_message
}
@@ -2370,24 +2146,15 @@ class SendGame(BaseResponse, ReplyToMixin, DisableNotificationMixin):
Use that response type for send game on to webhook.
"""
- __slots__ = (
- "chat_id",
- "game_short_name",
- "disable_notification",
- "reply_to_message_id",
- "reply_markup",
- )
+ __slots__ = ('chat_id', 'game_short_name', 'disable_notification', 'reply_to_message_id', 'reply_markup')
method = api.Methods.SEND_GAME
- def __init__(
- self,
- chat_id: Integer,
- game_short_name: String,
- disable_notification: Optional[Boolean] = None,
- reply_to_message_id: Optional[Integer] = None,
- reply_markup: Optional[types.InlineKeyboardMarkup] = None,
- ):
+ def __init__(self, chat_id: Integer,
+ game_short_name: String,
+ disable_notification: Optional[Boolean] = None,
+ reply_to_message_id: Optional[Integer] = None,
+ reply_markup: Optional[types.InlineKeyboardMarkup] = None):
"""
:param chat_id: Integer - Unique identifier for the target chat
:param game_short_name: String - Short name of the game, serves as the unique identifier for the game.
@@ -2406,9 +2173,9 @@ class SendGame(BaseResponse, ReplyToMixin, DisableNotificationMixin):
def prepare(self):
return {
- "chat_id": self.chat_id,
- "game_short_name": self.game_short_name,
- "disable_notification": self.disable_notification,
- "reply_to_message_id": self.reply_to_message_id,
- "reply_markup": prepare_arg(self.reply_markup),
+ 'chat_id': self.chat_id,
+ 'game_short_name': self.game_short_name,
+ 'disable_notification': self.disable_notification,
+ 'reply_to_message_id': self.reply_to_message_id,
+ 'reply_markup': prepare_arg(self.reply_markup),
}
diff --git a/aiogram/types/__init__.py b/aiogram/types/__init__.py
index 2cecc82c..0da1fe38 100644
--- a/aiogram/types/__init__.py
+++ b/aiogram/types/__init__.py
@@ -7,6 +7,7 @@ from .callback_game import CallbackGame
from .callback_query import CallbackQuery
from .chat import Chat, ChatActions, ChatType
from .chat_member import ChatMember, ChatMemberStatus
+from .chat_permissions import ChatPermissions
from .chat_photo import ChatPhoto
from .chosen_inline_result import ChosenInlineResult
from .contact import Contact
diff --git a/aiogram/types/base.py b/aiogram/types/base.py
index b4a76964..2fd9129d 100644
--- a/aiogram/types/base.py
+++ b/aiogram/types/base.py
@@ -9,37 +9,31 @@ from babel.support import LazyProxy
from .fields import BaseField
from ..utils import json
from ..utils.mixins import ContextInstanceMixin
+if typing.TYPE_CHECKING:
+ from ..bot.bot import Bot
-__all__ = (
- "MetaTelegramObject",
- "TelegramObject",
- "InputFile",
- "String",
- "Integer",
- "Float",
- "Boolean",
-)
+__all__ = ('MetaTelegramObject', 'TelegramObject', 'InputFile', 'String', 'Integer', 'Float', 'Boolean')
-PROPS_ATTR_NAME = "_props"
-VALUES_ATTR_NAME = "_values"
-ALIASES_ATTR_NAME = "_aliases"
+PROPS_ATTR_NAME = '_props'
+VALUES_ATTR_NAME = '_values'
+ALIASES_ATTR_NAME = '_aliases'
# Binding of builtin types
-InputFile = TypeVar("InputFile", "InputFile", io.BytesIO, io.FileIO, str)
-String = TypeVar("String", bound=str)
-Integer = TypeVar("Integer", bound=int)
-Float = TypeVar("Float", bound=float)
-Boolean = TypeVar("Boolean", bound=bool)
+InputFile = TypeVar('InputFile', 'InputFile', io.BytesIO, io.FileIO, str)
+String = TypeVar('String', bound=str)
+Integer = TypeVar('Integer', bound=int)
+Float = TypeVar('Float', bound=float)
+Boolean = TypeVar('Boolean', bound=bool)
+T = TypeVar('T')
class MetaTelegramObject(type):
"""
Metaclass for telegram objects
"""
-
_objects = {}
- def __new__(mcs, name, bases, namespace, **kwargs):
+ def __new__(mcs: typing.Type[T], name: str, bases: typing.Tuple[typing.Type], namespace: typing.Dict[str, typing.Any], **kwargs: typing.Any) -> T:
cls = super(MetaTelegramObject, mcs).__new__(mcs, name, bases, namespace)
props = {}
@@ -55,9 +49,7 @@ class MetaTelegramObject(type):
aliases.update(getattr(base, ALIASES_ATTR_NAME))
# Scan current object for props
- for name, prop in (
- (name, prop) for name, prop in namespace.items() if isinstance(prop, BaseField)
- ):
+ for name, prop in ((name, prop) for name, prop in namespace.items() if isinstance(prop, BaseField)):
props[prop.alias] = prop
if prop.default is not None:
values[prop.alias] = prop.default
@@ -82,7 +74,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
Abstract class for telegram objects
"""
- def __init__(self, conf=None, **kwargs):
+ def __init__(self, conf: typing.Dict[str, typing.Any]=None, **kwargs: typing.Any) -> None:
"""
Deserialize object
@@ -128,7 +120,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
return getattr(self, ALIASES_ATTR_NAME, {})
@property
- def values(self):
+ def values(self) -> typing.Tuple[str]:
"""
Get values
@@ -139,11 +131,11 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
return getattr(self, VALUES_ATTR_NAME)
@property
- def telegram_types(self):
+ def telegram_types(self) -> typing.List[TelegramObject]:
return type(self).telegram_types
@classmethod
- def to_object(cls, data):
+ def to_object(cls: typing.Type[T], data: typing.Dict[str, typing.Any]) -> T:
"""
Deserialize object
@@ -153,19 +145,17 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
return cls(**data)
@property
- def bot(self):
+ def bot(self) -> Bot:
from ..bot.bot import Bot
bot = Bot.get_current()
if bot is None:
- raise RuntimeError(
- "Can't get bot instance from context. "
- "You can fix it with setting current instance: "
- "'Bot.set_current(bot_instance)'"
- )
+ raise RuntimeError("Can't get bot instance from context. "
+ "You can fix it with setting current instance: "
+ "'Bot.set_current(bot_instance)'")
return bot
- def to_python(self) -> typing.Dict:
+ def to_python(self) -> typing.Dict[str, typing.Any]:
"""
Get object as JSON serializable
@@ -183,7 +173,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
result[self.props_aliases.get(name, name)] = value
return result
- def clean(self):
+ def clean(self) -> None:
"""
Remove empty values
"""
@@ -201,7 +191,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
return json.dumps(self.to_python())
@classmethod
- def create(cls, *args, **kwargs):
+ def create(cls: Type[T], *args: typing.Any, **kwargs: typing.Any) -> T:
raise NotImplemented
def __str__(self) -> str:
@@ -212,7 +202,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
"""
return self.as_json()
- def __getitem__(self, item):
+ def __getitem__(self, item: typing.Union[str, int]) -> typing.Any:
"""
Item getter (by key)
@@ -223,7 +213,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
return self.props[item].get_value(self)
raise KeyError(item)
- def __setitem__(self, key, value):
+ def __setitem__(self, key: str, value: typing.Any) -> None:
"""
Item setter (by key)
@@ -232,10 +222,10 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
:return:
"""
if key in self.props:
- return self.props[key].set_value(self, value, self.conf.get("parent", None))
+ return self.props[key].set_value(self, value, self.conf.get('parent', None))
raise KeyError(key)
- def __contains__(self, item):
+ def __contains__(self, item: typing.Dict[str, typing.Any]) -> bool:
"""
Check key contains in that object
@@ -245,7 +235,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
self.clean()
return item in self.values
- def __iter__(self):
+ def __iter__(self) -> typing.Iterator[str]:
"""
Iterate over items
@@ -254,7 +244,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
for item in self.to_python().items():
yield item
- def iter_keys(self):
+ def iter_keys(self) -> typing.Generator[typing.Any, None, None]:
"""
Iterate over keys
@@ -263,7 +253,7 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
for key, _ in self:
yield key
- def iter_values(self):
+ def iter_values(self) -> typing.Generator[typing.Any, None, None]:
"""
Iterate over values
@@ -272,9 +262,9 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
for _, value in self:
yield value
- def __hash__(self):
- def _hash(obj):
- buf = 0
+ def __hash__(self) -> int:
+ def _hash(obj)-> int:
+ buf: int = 0
if isinstance(obj, list):
for item in obj:
buf += _hash(item)
@@ -294,5 +284,5 @@ class TelegramObject(ContextInstanceMixin, metaclass=MetaTelegramObject):
return result
- def __eq__(self, other):
+ def __eq__(self, other: TelegramObject) -> bool:
return isinstance(other, self.__class__) and hash(other) == hash(self)
diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py
index d854c074..66b8fe4d 100644
--- a/aiogram/types/chat.py
+++ b/aiogram/types/chat.py
@@ -1,10 +1,12 @@
from __future__ import annotations
import asyncio
+import datetime
import typing
from . import base
from . import fields
+from .chat_permissions import ChatPermissions
from .chat_photo import ChatPhoto
from ..utils import helper
from ..utils import markdown
@@ -16,7 +18,6 @@ class Chat(base.TelegramObject):
https://core.telegram.org/bots/api#chat
"""
-
id: base.Integer = fields.Field()
type: base.String = fields.Field()
title: base.String = fields.Field()
@@ -27,7 +28,8 @@ class Chat(base.TelegramObject):
photo: ChatPhoto = fields.Field(base=ChatPhoto)
description: base.String = fields.Field()
invite_link: base.String = fields.Field()
- pinned_message: "Message" = fields.Field(base="Message")
+ pinned_message: 'Message' = fields.Field(base='Message')
+ permissions: ChatPermissions = fields.Field(base=ChatPermissions)
sticker_set_name: base.String = fields.Field()
can_set_sticker_set: base.Boolean = fields.Field()
@@ -39,7 +41,7 @@ class Chat(base.TelegramObject):
if self.type == ChatType.PRIVATE:
full_name = self.first_name
if self.last_name:
- full_name += " " + self.last_name
+ full_name += ' ' + self.last_name
return full_name
return self.title
@@ -49,7 +51,7 @@ class Chat(base.TelegramObject):
Get mention if a Chat has a username, or get full name if this is a Private Chat, otherwise None is returned
"""
if self.username:
- return "@" + self.username
+ return '@' + self.username
if self.type == ChatType.PRIVATE:
return self.full_name
return None
@@ -57,7 +59,7 @@ class Chat(base.TelegramObject):
@property
def user_url(self):
if self.type != ChatType.PRIVATE:
- raise TypeError("`user_url` property is only available in private chats!")
+ raise TypeError('`user_url` property is only available in private chats!')
return f"tg://user?id={self.id}"
@@ -80,7 +82,7 @@ class Chat(base.TelegramObject):
return f"tg://user?id={self.id}"
if self.username:
- return f"https://t.me/{self.username}"
+ return f'https://t.me/{self.username}'
if self.invite_link:
return self.invite_link
@@ -162,9 +164,8 @@ class Chat(base.TelegramObject):
"""
return await self.bot.delete_chat_description(self.id, description)
- async def kick(
- self, user_id: base.Integer, until_date: typing.Union[base.Integer, None] = None
- ):
+ async def kick(self, user_id: base.Integer,
+ until_date: typing.Union[base.Integer, datetime.datetime, datetime.timedelta, None] = None):
"""
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
@@ -203,15 +204,13 @@ class Chat(base.TelegramObject):
"""
return await self.bot.unban_chat_member(self.id, user_id=user_id)
- async def restrict(
- self,
- user_id: base.Integer,
- until_date: typing.Union[base.Integer, 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:
+ 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:
"""
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.
@@ -221,6 +220,8 @@ class Chat(base.TelegramObject):
:param user_id: Unique identifier of the target user
:type user_id: :obj:`base.Integer`
+ :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]`
:param can_send_messages: Pass True, if the user can send text messages, contacts, locations and venues
@@ -237,28 +238,23 @@ class Chat(base.TelegramObject):
:return: Returns True on success.
:rtype: :obj:`base.Boolean`
"""
- return await self.bot.restrict_chat_member(
- self.id,
- user_id=user_id,
- until_date=until_date,
- can_send_messages=can_send_messages,
- can_send_media_messages=can_send_media_messages,
- can_send_other_messages=can_send_other_messages,
- can_add_web_page_previews=can_add_web_page_previews,
- )
+ return await self.bot.restrict_chat_member(self.id, user_id=user_id,
+ permissions=permissions,
+ until_date=until_date,
+ can_send_messages=can_send_messages,
+ can_send_media_messages=can_send_media_messages,
+ 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,
+ 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:
"""
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.
@@ -289,18 +285,16 @@ class Chat(base.TelegramObject):
:return: Returns True on success.
:rtype: :obj:`base.Boolean`
"""
- return await self.bot.promote_chat_member(
- self.id,
- user_id=user_id,
- can_change_info=can_change_info,
- can_post_messages=can_post_messages,
- can_edit_messages=can_edit_messages,
- can_delete_messages=can_delete_messages,
- can_invite_users=can_invite_users,
- can_restrict_members=can_restrict_members,
- can_pin_messages=can_pin_messages,
- can_promote_members=can_promote_members,
- )
+ return await self.bot.promote_chat_member(self.id,
+ user_id=user_id,
+ can_change_info=can_change_info,
+ can_post_messages=can_post_messages,
+ can_edit_messages=can_edit_messages,
+ can_delete_messages=can_delete_messages,
+ can_invite_users=can_invite_users,
+ can_restrict_members=can_restrict_members,
+ can_pin_messages=can_pin_messages,
+ can_promote_members=can_promote_members)
async def pin_message(self, message_id: int, disable_notification: bool = False):
"""
@@ -436,9 +430,9 @@ class ChatType(helper.Helper):
@staticmethod
def _check(obj, chat_types) -> bool:
- if hasattr(obj, "chat"):
+ if hasattr(obj, 'chat'):
obj = obj.chat
- if not hasattr(obj, "type"):
+ if not hasattr(obj, 'type'):
return False
return obj.type in chat_types
@@ -525,13 +519,12 @@ class ChatActions(helper.Helper):
@classmethod
async def _do(cls, action: str, sleep=None):
from aiogram import Bot
-
await Bot.get_current().send_chat_action(Chat.get_current().id, action)
if sleep:
await asyncio.sleep(sleep)
@classmethod
- def calc_timeout(cls, text, timeout=0.8):
+ def calc_timeout(cls, text, timeout=.8):
"""
Calculate timeout for text
diff --git a/aiogram/types/chat_member.py b/aiogram/types/chat_member.py
index c2f714d9..7e05a33f 100644
--- a/aiogram/types/chat_member.py
+++ b/aiogram/types/chat_member.py
@@ -1,5 +1,6 @@
import datetime
import warnings
+from typing import Optional
from . import base
from . import fields
@@ -13,7 +14,6 @@ class ChatMember(base.TelegramObject):
https://core.telegram.org/bots/api#chatmember
"""
-
user: User = fields.Field(base=User)
status: base.String = fields.Field()
until_date: datetime.datetime = fields.DateTimeField()
@@ -29,25 +29,17 @@ class ChatMember(base.TelegramObject):
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_admin(self):
- warnings.warn(
- "`is_admin` method deprecated due to updates in Bot API 4.2. "
- "This method renamed to `is_chat_admin` and will be available until aiogram 2.3",
- DeprecationWarning,
- stacklevel=2,
- )
- return self.is_chat_admin()
+ def is_chat_admin(self) -> bool:
+ return ChatMemberStatus.is_chat_admin(self.status)
- def is_chat_admin(self):
- return ChatMemberStatus.is_admin(self.status)
+ def is_chat_member(self) -> bool:
+ return ChatMemberStatus.is_chat_member(self.status)
- def is_chat_member(self):
- return ChatMemberStatus.is_member(self.status)
-
- def __int__(self):
+ def __int__(self) -> int:
return self.user.id
@@ -55,39 +47,19 @@ class ChatMemberStatus(helper.Helper):
"""
Chat member status
"""
-
mode = helper.HelperMode.lowercase
CREATOR = helper.Item() # creator
ADMINISTRATOR = helper.Item() # administrator
MEMBER = helper.Item() # member
+ RESTRICTED = helper.Item() # restricted
LEFT = helper.Item() # left
KICKED = helper.Item() # kicked
@classmethod
- def is_admin(cls, role):
- warnings.warn(
- "`is_admin` method deprecated due to updates in Bot API 4.2. "
- "This method renamed to `is_chat_admin` and will be available until aiogram 2.3",
- DeprecationWarning,
- stacklevel=2,
- )
- return cls.is_chat_admin(role)
-
- @classmethod
- def is_member(cls, role):
- warnings.warn(
- "`is_member` method deprecated due to updates in Bot API 4.2. "
- "This method renamed to `is_chat_member` and will be available until aiogram 2.3",
- DeprecationWarning,
- stacklevel=2,
- )
- return cls.is_chat_member(role)
-
- @classmethod
- def is_chat_admin(cls, role):
+ def is_chat_admin(cls, role: str) -> bool:
return role in [cls.ADMINISTRATOR, cls.CREATOR]
@classmethod
- def is_chat_member(cls, role):
- return role in [cls.MEMBER, cls.ADMINISTRATOR, cls.CREATOR]
+ def is_chat_member(cls, role: str) -> bool:
+ return role in [cls.MEMBER, cls.ADMINISTRATOR, cls.CREATOR, cls.RESTRICTED]
diff --git a/aiogram/types/chat_permissions.py b/aiogram/types/chat_permissions.py
new file mode 100644
index 00000000..9d44653e
--- /dev/null
+++ b/aiogram/types/chat_permissions.py
@@ -0,0 +1,39 @@
+from . import base
+from . import fields
+
+
+class ChatPermissions(base.TelegramObject):
+ """
+ Describes actions that a non-administrator user is allowed to take in a chat.
+
+ https://core.telegram.org/bots/api#chatpermissions
+ """
+ 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()
+ can_change_info: base.Boolean = fields.Field()
+ can_invite_users: base.Boolean = fields.Field()
+ can_pin_messages: base.Boolean = fields.Field()
+
+ def __init__(self,
+ can_send_messages: base.Boolean = None,
+ can_send_media_messages: base.Boolean = None,
+ can_send_polls: base.Boolean = None,
+ can_send_other_messages: base.Boolean = None,
+ can_add_web_page_previews: base.Boolean = None,
+ can_change_info: base.Boolean = None,
+ can_invite_users: base.Boolean = None,
+ can_pin_messages: base.Boolean = None,
+ **kwargs):
+ super(ChatPermissions, self).__init__(
+ can_send_messages=can_send_messages,
+ can_send_media_messages=can_send_media_messages,
+ can_send_polls=can_send_polls,
+ can_send_other_messages=can_send_other_messages,
+ can_add_web_page_previews=can_add_web_page_previews,
+ can_change_info=can_change_info,
+ can_invite_users=can_invite_users,
+ can_pin_messages=can_pin_messages,
+ )
diff --git a/aiogram/types/input_media.py b/aiogram/types/input_media.py
index 9517a797..95ca75ae 100644
--- a/aiogram/types/input_media.py
+++ b/aiogram/types/input_media.py
@@ -6,7 +6,7 @@ from . import base
from . import fields
from .input_file import InputFile
-ATTACHMENT_PREFIX = "attach://"
+ATTACHMENT_PREFIX = 'attach://'
class InputMedia(base.TelegramObject):
@@ -22,26 +22,23 @@ class InputMedia(base.TelegramObject):
https://core.telegram.org/bots/api#inputmedia
"""
-
- type: base.String = fields.Field(default="photo")
- media: base.String = fields.Field(alias="media", on_change="_media_changed")
- thumb: typing.Union[base.InputFile, base.String] = fields.Field(
- alias="thumb", on_change="_thumb_changed"
- )
+ type: base.String = fields.Field(default='photo')
+ media: base.String = fields.Field(alias='media', on_change='_media_changed')
+ thumb: typing.Union[base.InputFile, base.String] = fields.Field(alias='thumb', on_change='_thumb_changed')
caption: base.String = fields.Field()
- parse_mode: base.Boolean = fields.Field()
+ parse_mode: base.String = fields.Field()
def __init__(self, *args, **kwargs):
self._thumb_file = None
self._media_file = None
- media = kwargs.pop("media", None)
+ media = kwargs.pop('media', None)
if isinstance(media, (io.IOBase, InputFile)):
self.file = media
elif media is not None:
self.media = media
- thumb = kwargs.pop("thumb", None)
+ thumb = kwargs.pop('thumb', None)
if isinstance(thumb, (io.IOBase, InputFile)):
self.thumb_file = thumb
elif thumb is not None:
@@ -61,7 +58,7 @@ class InputMedia(base.TelegramObject):
@file.setter
def file(self, file: io.IOBase):
- self.media = "attach://" + secrets.token_urlsafe(16)
+ self.media = 'attach://' + secrets.token_urlsafe(16)
self._media_file = file
@file.deleter
@@ -70,7 +67,7 @@ class InputMedia(base.TelegramObject):
self._media_file = None
def _media_changed(self, value):
- if value is None or isinstance(value, str) and not value.startswith("attach://"):
+ if value is None or isinstance(value, str) and not value.startswith('attach://'):
self._media_file = None
@property
@@ -79,7 +76,7 @@ class InputMedia(base.TelegramObject):
@thumb_file.setter
def thumb_file(self, file: io.IOBase):
- self.thumb = "attach://" + secrets.token_urlsafe(16)
+ self.thumb = 'attach://' + secrets.token_urlsafe(16)
self._thumb_file = file
@thumb_file.deleter
@@ -88,7 +85,7 @@ class InputMedia(base.TelegramObject):
self._thumb_file = None
def _thumb_changed(self, value):
- if value is None or isinstance(value, str) and not value.startswith("attach://"):
+ if value is None or isinstance(value, str) and not value.startswith('attach://'):
self._thumb_file = None
def get_files(self):
@@ -109,28 +106,14 @@ 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.Boolean = 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, **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)
class InputMediaDocument(InputMedia):
@@ -140,22 +123,11 @@ class InputMediaDocument(InputMedia):
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.Boolean = 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,
+ 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)
class InputMediaAudio(InputMedia):
@@ -171,32 +143,18 @@ 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,
- width: base.Integer = None,
- height: base.Integer = None,
- duration: base.Integer = None,
- performer: base.String = None,
- title: base.String = None,
- parse_mode: base.Boolean = None,
- **kwargs,
- ):
- super(InputMediaAudio, self).__init__(
- type="audio",
- media=media,
- thumb=thumb,
- caption=caption,
- width=width,
- height=height,
- 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,
+ 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, **kwargs):
+ super(InputMediaAudio, self).__init__(type='audio', media=media, thumb=thumb, caption=caption,
+ width=width, height=height, duration=duration,
+ performer=performer, title=title,
+ parse_mode=parse_mode, conf=kwargs)
class InputMediaPhoto(InputMedia):
@@ -206,22 +164,11 @@ 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.Boolean = 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, 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)
class InputMediaVideo(InputMedia):
@@ -230,36 +177,21 @@ class InputMediaVideo(InputMedia):
https://core.telegram.org/bots/api#inputmediavideo
"""
-
width: base.Integer = fields.Field()
height: base.Integer = fields.Field()
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.Boolean = 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,
+ 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)
class MediaGroup(base.TelegramObject):
@@ -267,9 +199,7 @@ class MediaGroup(base.TelegramObject):
Helper for sending media group
"""
- def __init__(
- self, medias: typing.Optional[typing.List[typing.Union[InputMedia, typing.Dict]]] = None
- ):
+ def __init__(self, medias: typing.Optional[typing.List[typing.Union[InputMedia, typing.Dict]]] = None):
super(MediaGroup, self).__init__()
self.media = []
@@ -292,13 +222,13 @@ class MediaGroup(base.TelegramObject):
:param media:
"""
if isinstance(media, dict):
- if "type" not in media:
+ if 'type' not in media:
raise ValueError(f"Invalid media!")
- media_type = media["type"]
- if media_type == "photo":
+ media_type = media['type']
+ if media_type == 'photo':
media = InputMediaPhoto(**media)
- elif media_type == "video":
+ elif media_type == 'video':
media = InputMediaVideo(**media)
# elif media_type == 'document':
# media = InputMediaDocument(**media)
@@ -310,11 +240,9 @@ class MediaGroup(base.TelegramObject):
raise TypeError(f"Invalid media type '{media_type}'!")
elif not isinstance(media, InputMedia):
- raise TypeError(
- f"Media must be an instance of InputMedia or dict, not {type(media).__name__}"
- )
+ raise TypeError(f"Media must be an instance of InputMedia or dict, not {type(media).__name__}")
- elif media.type in ["document", "audio", "animation"]:
+ elif media.type in ['document', 'audio', 'animation']:
raise ValueError(f"This type of media is not supported by media groups!")
self.media.append(media)
@@ -349,7 +277,7 @@ class MediaGroup(base.TelegramObject):
duration: base.Integer = None,
performer: base.String = None,
title: base.String = None,
- parse_mode: base.Boolean = None):
+ parse_mode: base.String = None):
"""
Attach animation
@@ -371,7 +299,7 @@ class MediaGroup(base.TelegramObject):
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.Boolean = None):
+ caption: base.String = None, parse_mode: base.String = None):
"""
Attach document
@@ -385,9 +313,8 @@ class MediaGroup(base.TelegramObject):
self.attach(document)
'''
- def attach_photo(
- self, photo: typing.Union[InputMediaPhoto, base.InputFile], caption: base.String = None
- ):
+ def attach_photo(self, photo: typing.Union[InputMediaPhoto, base.InputFile],
+ caption: base.String = None):
"""
Attach photo
@@ -398,15 +325,10 @@ class MediaGroup(base.TelegramObject):
photo = InputMediaPhoto(media=photo, caption=caption)
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,
- ):
+ 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):
"""
Attach video
@@ -417,14 +339,8 @@ class MediaGroup(base.TelegramObject):
:param duration:
"""
if not isinstance(video, InputMedia):
- video = InputMediaVideo(
- media=video,
- thumb=thumb,
- caption=caption,
- width=width,
- height=height,
- duration=duration,
- )
+ video = InputMediaVideo(media=video, thumb=thumb, caption=caption,
+ width=width, height=height, duration=duration)
self.attach(video)
def to_python(self) -> typing.List:
diff --git a/aiogram/types/message.py b/aiogram/types/message.py
index 87d95d90..8fdece2b 100644
--- a/aiogram/types/message.py
+++ b/aiogram/types/message.py
@@ -32,7 +32,6 @@ from .video_note import VideoNote
from .voice import Voice
from ..utils import helper
from ..utils import markdown as md
-from ..utils.deprecated import warn_deprecated
class Message(base.TelegramObject):
@@ -41,9 +40,8 @@ class Message(base.TelegramObject):
https://core.telegram.org/bots/api#message
"""
-
message_id: base.Integer = fields.Field()
- from_user: User = fields.Field(alias="from", base=User)
+ from_user: User = fields.Field(alias='from', base=User)
date: datetime.datetime = fields.DateTimeField()
chat: Chat = fields.Field(base=Chat)
forward_from: User = fields.Field(base=User)
@@ -51,7 +49,7 @@ class Message(base.TelegramObject):
forward_from_message_id: base.Integer = fields.Field()
forward_signature: base.String = fields.Field()
forward_date: datetime.datetime = fields.DateTimeField()
- reply_to_message: Message = fields.Field(base="Message")
+ reply_to_message: Message = fields.Field(base='Message')
edit_date: datetime.datetime = fields.DateTimeField()
media_group_id: base.String = fields.Field()
author_signature: base.String = fields.Field()
@@ -82,7 +80,7 @@ class Message(base.TelegramObject):
channel_chat_created: base.Boolean = fields.Field()
migrate_to_chat_id: base.Integer = fields.Field()
migrate_from_chat_id: base.Integer = fields.Field()
- pinned_message: Message = fields.Field(base="Message")
+ pinned_message: Message = fields.Field(base='Message')
invoice: Invoice = fields.Field(base=Invoice)
successful_payment: SuccessfulPayment = fields.Field(base=SuccessfulPayment)
connected_website: base.String = fields.Field()
@@ -95,60 +93,60 @@ class Message(base.TelegramObject):
def content_type(self):
if self.text:
return ContentType.TEXT
- elif self.audio:
+ if self.audio:
return ContentType.AUDIO
- elif self.animation:
+ if self.animation:
return ContentType.ANIMATION
- elif self.document:
+ if self.document:
return ContentType.DOCUMENT
- elif self.game:
+ if self.game:
return ContentType.GAME
- elif self.photo:
+ if self.photo:
return ContentType.PHOTO
- elif self.sticker:
+ if self.sticker:
return ContentType.STICKER
- elif self.video:
+ if self.video:
return ContentType.VIDEO
- elif self.video_note:
+ if self.video_note:
return ContentType.VIDEO_NOTE
- elif self.voice:
+ if self.voice:
return ContentType.VOICE
- elif self.contact:
+ if self.contact:
return ContentType.CONTACT
- elif self.venue:
+ if self.venue:
return ContentType.VENUE
- elif self.location:
+ if self.location:
return ContentType.LOCATION
- elif self.new_chat_members:
+ if self.new_chat_members:
return ContentType.NEW_CHAT_MEMBERS
- elif self.left_chat_member:
+ if self.left_chat_member:
return ContentType.LEFT_CHAT_MEMBER
- elif self.invoice:
+ if self.invoice:
return ContentType.INVOICE
- elif self.successful_payment:
+ if self.successful_payment:
return ContentType.SUCCESSFUL_PAYMENT
- elif self.connected_website:
+ if self.connected_website:
return ContentType.CONNECTED_WEBSITE
- elif self.migrate_from_chat_id:
+ if self.migrate_from_chat_id:
return ContentType.MIGRATE_FROM_CHAT_ID
- elif self.migrate_to_chat_id:
+ if self.migrate_to_chat_id:
return ContentType.MIGRATE_TO_CHAT_ID
- elif self.pinned_message:
+ if self.pinned_message:
return ContentType.PINNED_MESSAGE
- elif self.new_chat_title:
+ if self.new_chat_title:
return ContentType.NEW_CHAT_TITLE
- elif self.new_chat_photo:
+ if self.new_chat_photo:
return ContentType.NEW_CHAT_PHOTO
- elif self.delete_chat_photo:
+ if self.delete_chat_photo:
return ContentType.DELETE_CHAT_PHOTO
- elif self.group_chat_created:
+ if self.group_chat_created:
return ContentType.GROUP_CHAT_CREATED
- elif self.passport_data:
+ if self.passport_data:
return ContentType.PASSPORT_DATA
- elif self.poll:
+ if self.poll:
return ContentType.POLL
- else:
- return ContentType.UNKNOWN
+
+ return ContentType.UNKNOWN
def is_command(self):
"""
@@ -156,7 +154,7 @@ class Message(base.TelegramObject):
:return: bool
"""
- return self.text and self.text.startswith("/")
+ return self.text and self.text.startswith('/')
def get_full_command(self):
"""
@@ -165,7 +163,7 @@ class Message(base.TelegramObject):
:return: tuple of (command, args)
"""
if self.is_command():
- command, _, args = self.text.partition(" ")
+ command, _, args = self.text.partition(' ')
return command, args
def get_command(self, pure=False):
@@ -178,7 +176,7 @@ class Message(base.TelegramObject):
if command:
command = command[0]
if pure:
- command, _, _ = command[1:].partition("@")
+ command, _, _ = command[1:].partition('@')
return command
def get_args(self):
@@ -208,30 +206,30 @@ class Message(base.TelegramObject):
if not entities:
return quote_fn(text)
- if not sys.maxunicode == 0xFFFF:
- text = text.encode("utf-16-le")
+ if not sys.maxunicode == 0xffff:
+ text = text.encode('utf-16-le')
- result = ""
+ result = ''
offset = 0
for entity in sorted(entities, key=lambda item: item.offset):
entity_text = entity.parse(text, as_html=as_html)
- if sys.maxunicode == 0xFFFF:
- part = text[offset : entity.offset]
+ if sys.maxunicode == 0xffff:
+ part = text[offset:entity.offset]
result += quote_fn(part) + entity_text
else:
- part = text[offset * 2 : entity.offset * 2]
- result += quote_fn(part.decode("utf-16-le")) + entity_text
+ part = text[offset * 2:entity.offset * 2]
+ result += quote_fn(part.decode('utf-16-le')) + entity_text
offset = entity.offset + entity.length
- if sys.maxunicode == 0xFFFF:
+ if sys.maxunicode == 0xffff:
part = text[offset:]
result += quote_fn(part)
else:
- part = text[offset * 2 :]
- result += quote_fn(part.decode("utf-16-le"))
+ part = text[offset * 2:]
+ result += quote_fn(part.decode('utf-16-le'))
return result
@@ -261,9 +259,9 @@ class Message(base.TelegramObject):
:return: str
"""
if self.chat.type not in [ChatType.SUPER_GROUP, ChatType.CHANNEL]:
- raise TypeError("Invalid chat type!")
+ raise TypeError('Invalid chat type!')
elif not self.chat.username:
- raise TypeError("This chat does not have @username")
+ raise TypeError('This chat does not have @username')
return f"https://t.me/{self.chat.username}/{self.message_id}"
@@ -286,17 +284,15 @@ class Message(base.TelegramObject):
return md.hlink(text, url)
return md.link(text, url)
- 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,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = False,
- ) -> Message:
+ 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,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = False) -> Message:
"""
Answer to this message
@@ -318,27 +314,23 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_message(
- chat_id=self.chat.id,
- text=text,
- parse_mode=parse_mode,
- disable_web_page_preview=disable_web_page_preview,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_message(chat_id=self.chat.id,
+ text=text,
+ parse_mode=parse_mode,
+ disable_web_page_preview=disable_web_page_preview,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ 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,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = False,
- ) -> Message:
+ 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,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = False) -> Message:
"""
Use this method to send photos.
@@ -362,30 +354,26 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_photo(
- chat_id=self.chat.id,
- photo=photo,
- caption=caption,
- parse_mode=parse_mode,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_photo(chat_id=self.chat.id,
+ photo=photo,
+ caption=caption,
+ parse_mode=parse_mode,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ 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,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = False,
- ) -> Message:
+ 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,
+ disable_notification: typing.Union[base.Boolean, None] = None,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = False) -> 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.
@@ -417,34 +405,30 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_audio(
- chat_id=self.chat.id,
- audio=audio,
- caption=caption,
- parse_mode=parse_mode,
- duration=duration,
- performer=performer,
- title=title,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_audio(chat_id=self.chat.id,
+ audio=audio,
+ caption=caption,
+ parse_mode=parse_mode,
+ duration=duration,
+ performer=performer,
+ title=title,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ 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,
- 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_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = False,
- ) -> Message:
+ 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,
+ 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_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = False) -> Message:
"""
Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound).
@@ -481,31 +465,27 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_animation(
- self.chat.id,
- animation=animation,
- duration=duration,
- width=width,
- height=height,
- thumb=thumb,
- caption=caption,
- parse_mode=parse_mode,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_animation(self.chat.id,
+ animation=animation,
+ duration=duration,
+ width=width,
+ height=height,
+ thumb=thumb,
+ caption=caption,
+ parse_mode=parse_mode,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ reply_markup=reply_markup)
- async def answer_document(
- self,
- document: 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_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = False,
- ) -> Message:
+ async def answer_document(self, document: 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_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = False) -> Message:
"""
Use this method to send general files.
@@ -530,30 +510,26 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_document(
- chat_id=self.chat.id,
- document=document,
- caption=caption,
- parse_mode=parse_mode,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_document(chat_id=self.chat.id,
+ document=document,
+ caption=caption,
+ parse_mode=parse_mode,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ 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,
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = False,
- ) -> Message:
+ 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,
+ caption: typing.Union[base.String, None] = None,
+ parse_mode: typing.Union[base.String, None] = None,
+ disable_notification: typing.Union[base.Boolean, None] = None,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = False) -> Message:
"""
Use this method to send video files, Telegram clients support mp4 videos
(other formats may be sent as Document).
@@ -583,31 +559,27 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_video(
- chat_id=self.chat.id,
- video=video,
- duration=duration,
- width=width,
- height=height,
- caption=caption,
- parse_mode=parse_mode,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_video(chat_id=self.chat.id,
+ video=video,
+ duration=duration,
+ width=width,
+ height=height,
+ caption=caption,
+ parse_mode=parse_mode,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ 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,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = False,
- ) -> Message:
+ 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,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = False) -> Message:
"""
Use this method to send audio files, if you want Telegram clients to display the file
as a playable voice message.
@@ -636,28 +608,24 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_voice(
- chat_id=self.chat.id,
- voice=voice,
- caption=caption,
- parse_mode=parse_mode,
- duration=duration,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_voice(chat_id=self.chat.id,
+ voice=voice,
+ caption=caption,
+ parse_mode=parse_mode,
+ duration=duration,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ 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,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = False,
- ) -> Message:
+ 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,
+ disable_notification: typing.Union[base.Boolean, None] = None,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = False) -> Message:
"""
As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1 minute long.
Use this method to send video messages.
@@ -680,22 +648,17 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_video_note(
- chat_id=self.chat.id,
- video_note=video_note,
- duration=duration,
- length=length,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_video_note(chat_id=self.chat.id,
+ video_note=video_note,
+ duration=duration,
+ length=length,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ reply_markup=reply_markup)
- async def answer_media_group(
- self,
- media: typing.Union[MediaGroup, typing.List],
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply: base.Boolean = False,
- ) -> typing.List[Message]:
+ async def answer_media_group(self, media: typing.Union[MediaGroup, typing.List],
+ disable_notification: typing.Union[base.Boolean, None] = None,
+ reply: base.Boolean = False) -> typing.List[Message]:
"""
Use this method to send a group of photos or videos as an album.
@@ -709,24 +672,20 @@ class Message(base.TelegramObject):
:return: On success, an array of the sent Messages is returned.
:rtype: typing.List[types.Message]
"""
- return await self.bot.send_media_group(
- self.chat.id,
- media=media,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- )
+ return await self.bot.send_media_group(self.chat.id,
+ media=media,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None)
- 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,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = False,
- ) -> Message:
+ 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,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = False) -> Message:
"""
Use this method to send point on the map.
@@ -748,29 +707,24 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_location(
- chat_id=self.chat.id,
- latitude=latitude,
- longitude=longitude,
- live_period=live_period,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_location(chat_id=self.chat.id,
+ latitude=latitude,
+ longitude=longitude,
+ live_period=live_period,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ reply_markup=reply_markup)
- async def answer_venue(
- self,
- latitude: base.Float,
- 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,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = False,
- ) -> Message:
+ async def answer_venue(self,
+ latitude: base.Float, 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,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = False) -> Message:
"""
Use this method to send information about a venue.
@@ -796,29 +750,24 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_venue(
- chat_id=self.chat.id,
- latitude=latitude,
- longitude=longitude,
- title=title,
- address=address,
- foursquare_id=foursquare_id,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_venue(chat_id=self.chat.id,
+ latitude=latitude,
+ longitude=longitude,
+ title=title,
+ address=address,
+ foursquare_id=foursquare_id,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ reply_markup=reply_markup)
- async def answer_contact(
- 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,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = False,
- ) -> Message:
+ async def answer_contact(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,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = False) -> Message:
"""
Use this method to send phone contacts.
@@ -840,25 +789,20 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_contact(
- chat_id=self.chat.id,
- phone_number=phone_number,
- first_name=first_name,
- last_name=last_name,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_contact(chat_id=self.chat.id,
+ phone_number=phone_number,
+ first_name=first_name, last_name=last_name,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ reply_markup=reply_markup)
- async def answer_sticker(
- self,
- sticker: typing.Union[base.InputFile, base.String],
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = False,
- ) -> Message:
+ async def answer_sticker(self, sticker: typing.Union[base.InputFile, base.String],
+ disable_notification: typing.Union[base.Boolean, None] = None,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = False) -> Message:
"""
Use this method to send .webp stickers.
@@ -876,25 +820,21 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_sticker(
- chat_id=self.chat.id,
- sticker=sticker,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_sticker(chat_id=self.chat.id,
+ sticker=sticker,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ reply_markup=reply_markup)
- 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,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = True,
- ) -> Message:
+ 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,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = True) -> Message:
"""
Reply to this message
@@ -916,27 +856,23 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_message(
- chat_id=self.chat.id,
- text=text,
- parse_mode=parse_mode,
- disable_web_page_preview=disable_web_page_preview,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_message(chat_id=self.chat.id,
+ text=text,
+ parse_mode=parse_mode,
+ disable_web_page_preview=disable_web_page_preview,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ 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,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = True,
- ) -> Message:
+ 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,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = True) -> Message:
"""
Use this method to send photos.
@@ -960,30 +896,26 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_photo(
- chat_id=self.chat.id,
- photo=photo,
- caption=caption,
- parse_mode=parse_mode,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_photo(chat_id=self.chat.id,
+ photo=photo,
+ caption=caption,
+ parse_mode=parse_mode,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ 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,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = True,
- ) -> Message:
+ 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,
+ disable_notification: typing.Union[base.Boolean, None] = None,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = True) -> 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.
@@ -1015,34 +947,30 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_audio(
- chat_id=self.chat.id,
- audio=audio,
- caption=caption,
- parse_mode=parse_mode,
- duration=duration,
- performer=performer,
- title=title,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_audio(chat_id=self.chat.id,
+ audio=audio,
+ caption=caption,
+ parse_mode=parse_mode,
+ duration=duration,
+ performer=performer,
+ title=title,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ reply_markup=reply_markup)
- async def send_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,
- 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_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = True,
- ) -> Message:
+ 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,
+ 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_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = True) -> Message:
"""
Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound).
@@ -1079,102 +1007,27 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
- warn_deprecated(
- '"Message.send_animation" method will be removed in 2.3 version.\n'
- 'Use "Message.reply_animation" instead.',
- stacklevel=8,
- )
+ return await self.bot.send_animation(self.chat.id,
+ animation=animation,
+ duration=duration,
+ width=width,
+ height=height,
+ thumb=thumb,
+ caption=caption,
+ parse_mode=parse_mode,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ reply_markup=reply_markup)
- return await self.bot.send_animation(
- self.chat.id,
- animation=animation,
- duration=duration,
- width=width,
- height=height,
- thumb=thumb,
- caption=caption,
- parse_mode=parse_mode,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- 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,
- 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_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = True,
- ) -> Message:
- """
- Use this method to send animation files (GIF or H.264/MPEG-4 AVC video without sound).
-
- On success, the sent Message is returned.
- Bots can currently send animation files of up to 50 MB in size, this limit may be changed in the future.
-
- Source https://core.telegram.org/bots/api#sendanimation
-
- :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]`
- :param width: Animation width
- :type width: :obj:`typing.Union[base.Integer, None]`
- :param height: Animation height
- :type height: :obj:`typing.Union[base.Integer, None]`
- :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 90.
- :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]`
- :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
- :type reply_markup: :obj:`typing.Union[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
- :rtype: :obj:`types.Message`
- """
- return await self.bot.send_animation(
- self.chat.id,
- animation=animation,
- duration=duration,
- width=width,
- height=height,
- thumb=thumb,
- caption=caption,
- parse_mode=parse_mode,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
-
- async def reply_document(
- self,
- document: 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_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = True,
- ) -> Message:
+ async def reply_document(self, document: 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_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = True) -> Message:
"""
Use this method to send general files.
@@ -1199,30 +1052,26 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_document(
- chat_id=self.chat.id,
- document=document,
- caption=caption,
- parse_mode=parse_mode,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_document(chat_id=self.chat.id,
+ document=document,
+ caption=caption,
+ parse_mode=parse_mode,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ 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,
- caption: typing.Union[base.String, None] = None,
- parse_mode: typing.Union[base.String, None] = None,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = True,
- ) -> Message:
+ 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,
+ caption: typing.Union[base.String, None] = None,
+ parse_mode: typing.Union[base.String, None] = None,
+ disable_notification: typing.Union[base.Boolean, None] = None,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = True) -> Message:
"""
Use this method to send video files, Telegram clients support mp4 videos
(other formats may be sent as Document).
@@ -1252,31 +1101,27 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_video(
- chat_id=self.chat.id,
- video=video,
- duration=duration,
- width=width,
- height=height,
- caption=caption,
- parse_mode=parse_mode,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_video(chat_id=self.chat.id,
+ video=video,
+ duration=duration,
+ width=width,
+ height=height,
+ caption=caption,
+ parse_mode=parse_mode,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ 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,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = True,
- ) -> Message:
+ 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,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = True) -> Message:
"""
Use this method to send audio files, if you want Telegram clients to display the file
as a playable voice message.
@@ -1305,28 +1150,24 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_voice(
- chat_id=self.chat.id,
- voice=voice,
- caption=caption,
- parse_mode=parse_mode,
- duration=duration,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_voice(chat_id=self.chat.id,
+ voice=voice,
+ caption=caption,
+ parse_mode=parse_mode,
+ duration=duration,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ 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,
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = True,
- ) -> Message:
+ 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,
+ disable_notification: typing.Union[base.Boolean, None] = None,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = True) -> Message:
"""
As of v.4.0, Telegram clients support rounded square mp4 videos of up to 1 minute long.
Use this method to send video messages.
@@ -1349,22 +1190,17 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_video_note(
- chat_id=self.chat.id,
- video_note=video_note,
- duration=duration,
- length=length,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_video_note(chat_id=self.chat.id,
+ video_note=video_note,
+ duration=duration,
+ length=length,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ reply_markup=reply_markup)
- async def reply_media_group(
- self,
- media: typing.Union[MediaGroup, typing.List],
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply: base.Boolean = True,
- ) -> typing.List[Message]:
+ async def reply_media_group(self, media: typing.Union[MediaGroup, typing.List],
+ disable_notification: typing.Union[base.Boolean, None] = None,
+ reply: base.Boolean = True) -> typing.List[Message]:
"""
Use this method to send a group of photos or videos as an album.
@@ -1378,24 +1214,20 @@ class Message(base.TelegramObject):
:return: On success, an array of the sent Messages is returned.
:rtype: typing.List[types.Message]
"""
- return await self.bot.send_media_group(
- self.chat.id,
- media=media,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- )
+ return await self.bot.send_media_group(self.chat.id,
+ media=media,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None)
- 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,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = True,
- ) -> Message:
+ 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,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = True) -> Message:
"""
Use this method to send point on the map.
@@ -1417,29 +1249,24 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_location(
- chat_id=self.chat.id,
- latitude=latitude,
- longitude=longitude,
- live_period=live_period,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_location(chat_id=self.chat.id,
+ latitude=latitude,
+ longitude=longitude,
+ live_period=live_period,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ reply_markup=reply_markup)
- async def send_venue(
- self,
- latitude: base.Float,
- 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,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = True,
- ) -> Message:
+ async def reply_venue(self,
+ latitude: base.Float, 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,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = True) -> Message:
"""
Use this method to send information about a venue.
@@ -1465,85 +1292,24 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- warn_deprecated(
- '"Message.send_venue" method will be removed in 2.3 version.\n'
- 'Use "Message.reply_venue" instead.',
- stacklevel=8,
- )
+ return await self.bot.send_venue(chat_id=self.chat.id,
+ latitude=latitude,
+ longitude=longitude,
+ title=title,
+ address=address,
+ foursquare_id=foursquare_id,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ reply_markup=reply_markup)
- return await self.bot.send_venue(
- chat_id=self.chat.id,
- latitude=latitude,
- longitude=longitude,
- title=title,
- address=address,
- foursquare_id=foursquare_id,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
-
- async def reply_venue(
- self,
- latitude: base.Float,
- 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,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = True,
- ) -> Message:
- """
- Use this method to send information about a venue.
-
- Source: https://core.telegram.org/bots/api#sendvenue
-
- :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 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.
- :rtype: :obj:`types.Message`
- """
- return await self.bot.send_venue(
- chat_id=self.chat.id,
- latitude=latitude,
- longitude=longitude,
- title=title,
- address=address,
- foursquare_id=foursquare_id,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
-
- async def send_contact(
- 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,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = True,
- ) -> Message:
+ async def reply_contact(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,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = True) -> Message:
"""
Use this method to send phone contacts.
@@ -1565,73 +1331,20 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- warn_deprecated(
- '"Message.send_contact" method will be removed in 2.3 version.\n'
- 'Use "Message.reply_contact" instead.',
- stacklevel=8,
- )
+ return await self.bot.send_contact(chat_id=self.chat.id,
+ phone_number=phone_number,
+ first_name=first_name, last_name=last_name,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ reply_markup=reply_markup)
- return await self.bot.send_contact(
- chat_id=self.chat.id,
- phone_number=phone_number,
- first_name=first_name,
- last_name=last_name,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
-
- async def reply_contact(
- 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,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = True,
- ) -> Message:
- """
- Use this method to send phone contacts.
-
- Source: https://core.telegram.org/bots/api#sendcontact
-
- :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]`
- :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 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.
- :rtype: :obj:`types.Message`
- """
- return await self.bot.send_contact(
- chat_id=self.chat.id,
- phone_number=phone_number,
- first_name=first_name,
- last_name=last_name,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
-
- async def reply_sticker(
- self,
- sticker: typing.Union[base.InputFile, base.String],
- disable_notification: typing.Union[base.Boolean, None] = None,
- reply_markup: typing.Union[
- InlineKeyboardMarkup, ReplyKeyboardMarkup, ReplyKeyboardRemove, ForceReply, None
- ] = None,
- reply: base.Boolean = True,
- ) -> Message:
+ async def reply_sticker(self, sticker: typing.Union[base.InputFile, base.String],
+ disable_notification: typing.Union[base.Boolean, None] = None,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ ReplyKeyboardMarkup,
+ ReplyKeyboardRemove,
+ ForceReply, None] = None,
+ reply: base.Boolean = True) -> Message:
"""
Use this method to send .webp stickers.
@@ -1649,19 +1362,14 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned.
:rtype: :obj:`types.Message`
"""
- return await self.bot.send_sticker(
- chat_id=self.chat.id,
- sticker=sticker,
- disable_notification=disable_notification,
- reply_to_message_id=self.message_id if reply else None,
- reply_markup=reply_markup,
- )
+ return await self.bot.send_sticker(chat_id=self.chat.id,
+ sticker=sticker,
+ disable_notification=disable_notification,
+ reply_to_message_id=self.message_id if reply else None,
+ reply_markup=reply_markup)
- async def forward(
- self,
- chat_id: typing.Union[base.Integer, base.String],
- disable_notification: typing.Union[base.Boolean, None] = None,
- ) -> Message:
+ async def forward(self, chat_id: typing.Union[base.Integer, base.String],
+ disable_notification: typing.Union[base.Boolean, None] = None) -> Message:
"""
Forward this message
@@ -1674,17 +1382,13 @@ class Message(base.TelegramObject):
:return: On success, the sent Message is returned
:rtype: :obj:`types.Message`
"""
- return await self.bot.forward_message(
- chat_id, self.chat.id, self.message_id, disable_notification
- )
+ return await self.bot.forward_message(chat_id, self.chat.id, self.message_id, disable_notification)
- 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,
- ) -> typing.Union[Message, base.Boolean]:
+ 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) -> 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).
@@ -1703,21 +1407,16 @@ class Message(base.TelegramObject):
the edited Message is returned, otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
"""
- return await self.bot.edit_message_text(
- text=text,
- chat_id=self.chat.id,
- message_id=self.message_id,
- parse_mode=parse_mode,
- disable_web_page_preview=disable_web_page_preview,
- reply_markup=reply_markup,
- )
+ return await self.bot.edit_message_text(text=text,
+ chat_id=self.chat.id, message_id=self.message_id,
+ parse_mode=parse_mode,
+ disable_web_page_preview=disable_web_page_preview,
+ reply_markup=reply_markup)
- async def edit_caption(
- self,
- caption: base.String,
- parse_mode: typing.Union[base.String, None] = None,
- reply_markup: typing.Union[InlineKeyboardMarkup, None] = None,
- ) -> typing.Union[Message, base.Boolean]:
+ async def edit_caption(self, caption: base.String,
+ parse_mode: typing.Union[base.String, None] = None,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ None] = 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).
@@ -1734,17 +1433,12 @@ class Message(base.TelegramObject):
otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
"""
- return await self.bot.edit_message_caption(
- chat_id=self.chat.id,
- message_id=self.message_id,
- caption=caption,
- parse_mode=parse_mode,
- reply_markup=reply_markup,
- )
+ return await self.bot.edit_message_caption(chat_id=self.chat.id, message_id=self.message_id, caption=caption,
+ parse_mode=parse_mode, reply_markup=reply_markup)
- async def edit_media(
- self, media: InputMedia, reply_markup: typing.Union[InlineKeyboardMarkup, None] = None
- ) -> typing.Union[Message, base.Boolean]:
+ async def edit_media(self, media: InputMedia,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ None] = None) -> typing.Union[Message, base.Boolean]:
"""
Use this method to edit audio, document, photo, or video messages.
If a message is a part of a message album, then it can be edited only to a photo or a video.
@@ -1765,16 +1459,12 @@ class Message(base.TelegramObject):
otherwise True is returned
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
"""
- return await self.bot.edit_message_media(
- media=media,
- chat_id=self.chat.id,
- message_id=self.message_id,
- reply_markup=reply_markup,
- )
+ return await self.bot.edit_message_media(media=media, chat_id=self.chat.id, message_id=self.message_id,
+ reply_markup=reply_markup)
- async def edit_reply_markup(
- self, reply_markup: typing.Union[InlineKeyboardMarkup, None] = None
- ) -> typing.Union[Message, base.Boolean]:
+ async def edit_reply_markup(self,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ None] = 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).
@@ -1786,9 +1476,8 @@ class Message(base.TelegramObject):
otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
"""
- return await self.bot.edit_message_reply_markup(
- chat_id=self.chat.id, message_id=self.message_id, reply_markup=reply_markup
- )
+ return await self.bot.edit_message_reply_markup(chat_id=self.chat.id, message_id=self.message_id,
+ reply_markup=reply_markup)
async def delete_reply_markup(self) -> typing.Union[Message, base.Boolean]:
"""
@@ -1798,16 +1487,12 @@ class Message(base.TelegramObject):
otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
"""
- return await self.bot.edit_message_reply_markup(
- chat_id=self.chat.id, message_id=self.message_id
- )
+ return await self.bot.edit_message_reply_markup(chat_id=self.chat.id, message_id=self.message_id)
- async def edit_live_location(
- self,
- latitude: base.Float,
- longitude: base.Float,
- reply_markup: typing.Union[InlineKeyboardMarkup, None] = None,
- ) -> typing.Union[Message, base.Boolean]:
+ async def edit_live_location(self, latitude: base.Float,
+ longitude: base.Float,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ None] = 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).
A location can be edited until its live_period expires or editing is explicitly disabled by a call
@@ -1825,17 +1510,13 @@ class Message(base.TelegramObject):
otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
"""
- return await self.bot.edit_message_live_location(
- latitude=latitude,
- longitude=longitude,
- chat_id=self.chat.id,
- message_id=self.message_id,
- reply_markup=reply_markup,
- )
+ return await self.bot.edit_message_live_location(latitude=latitude, longitude=longitude,
+ chat_id=self.chat.id, message_id=self.message_id,
+ reply_markup=reply_markup)
- async def stop_live_location(
- self, reply_markup: typing.Union[InlineKeyboardMarkup, None] = None
- ) -> typing.Union[Message, base.Boolean]:
+ async def stop_live_location(self,
+ reply_markup: typing.Union[InlineKeyboardMarkup,
+ None] = None) -> typing.Union[Message, base.Boolean]:
"""
Use this method to stop updating a live location message sent by the bot or via the bot
(for inline bots) before live_period expires.
@@ -1848,9 +1529,8 @@ class Message(base.TelegramObject):
otherwise True is returned.
:rtype: :obj:`typing.Union[types.Message, base.Boolean]`
"""
- return await self.bot.stop_message_live_location(
- chat_id=self.chat.id, message_id=self.message_id, reply_markup=reply_markup
- )
+ return await self.bot.stop_message_live_location(chat_id=self.chat.id, message_id=self.message_id,
+ reply_markup=reply_markup)
async def delete(self) -> base.Boolean:
"""
@@ -1869,9 +1549,7 @@ 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
- ) -> base.Boolean:
+ async def pin(self, disable_notification: typing.Union[base.Boolean, None] = 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.
@@ -1886,6 +1564,101 @@ class Message(base.TelegramObject):
"""
return await self.chat.pin_message(self.message_id, disable_notification)
+ async def send_copy(
+ self: Message,
+ chat_id: typing.Union[str, int],
+ disable_notification: typing.Optional[bool] = None,
+ reply_to_message_id: typing.Optional[int] = None,
+ reply_markup: typing.Union[InlineKeyboardMarkup, ReplyKeyboardMarkup, None] = None,
+ ) -> Message:
+ """
+ Send copy of current message
+
+ :param chat_id:
+ :param disable_notification:
+ :param reply_to_message_id:
+ :param reply_markup:
+ :return:
+ """
+ kwargs = {
+ "chat_id": chat_id,
+ "reply_markup": reply_markup or self.reply_markup,
+ "parse_mode": ParseMode.HTML,
+ "disable_notification": disable_notification,
+ "reply_to_message_id": reply_to_message_id,
+ }
+ text = self.html_text if (self.text or self.caption) else None
+
+ if self.text:
+ return await self.bot.send_message(text=text, **kwargs)
+ elif self.audio:
+ return await self.bot.send_audio(
+ audio=self.audio.file_id,
+ caption=text,
+ title=self.audio.title,
+ performer=self.audio.performer,
+ duration=self.audio.duration,
+ **kwargs
+ )
+ elif self.animation:
+ return await self.bot.send_animation(
+ animation=self.animation.file_id, caption=text, **kwargs
+ )
+ elif self.document:
+ return await self.bot.send_document(
+ document=self.document.file_id, caption=text, **kwargs
+ )
+ elif self.photo:
+ return await self.bot.send_photo(
+ photo=self.photo[-1].file_id, caption=text, **kwargs
+ )
+ elif self.sticker:
+ kwargs.pop("parse_mode")
+ return await self.bot.send_sticker(sticker=self.sticker.file_id, **kwargs)
+ elif self.video:
+ return await self.bot.send_video(
+ video=self.video.file_id, caption=text, **kwargs
+ )
+ elif self.video_note:
+ kwargs.pop("parse_mode")
+ return await self.bot.send_video_note(
+ video_note=self.video_note.file_id, **kwargs
+ )
+ elif self.voice:
+ return await self.bot.send_voice(voice=self.voice.file_id, **kwargs)
+ elif self.contact:
+ kwargs.pop("parse_mode")
+ return await self.bot.send_contact(
+ phone_number=self.contact.phone_number,
+ first_name=self.contact.first_name,
+ last_name=self.contact.last_name,
+ vcard=self.contact.vcard,
+ **kwargs
+ )
+ elif self.venue:
+ kwargs.pop("parse_mode")
+ return await self.bot.send_venue(
+ latitude=self.venue.location.latitude,
+ longitude=self.venue.location.longitude,
+ title=self.venue.title,
+ address=self.venue.address,
+ foursquare_id=self.venue.foursquare_id,
+ foursquare_type=self.venue.foursquare_type,
+ **kwargs
+ )
+ elif self.location:
+ kwargs.pop("parse_mode")
+ return await self.bot.send_location(
+ latitude=self.location.latitude, longitude=self.location.longitude, **kwargs
+ )
+ elif self.poll:
+ kwargs.pop("parse_mode")
+ return await self.bot.send_poll(
+ question=self.poll.question, options=self.poll.options, **kwargs
+ )
+ else:
+ raise TypeError("This type of message can't be copied.")
+
def __int__(self):
return self.message_id
@@ -1918,7 +1691,6 @@ class ContentType(helper.Helper):
:key: UNKNOWN
:key: ANY
"""
-
mode = helper.HelperMode.snake_case
TEXT = helper.Item() # text
@@ -1981,7 +1753,6 @@ class ContentTypes(helper.Helper):
:key: UNKNOWN
:key: ANY
"""
-
mode = helper.HelperMode.snake_case
TEXT = helper.ListItem() # text
diff --git a/aiogram/types/message_entity.py b/aiogram/types/message_entity.py
index 2c0712e5..ad53da65 100644
--- a/aiogram/types/message_entity.py
+++ b/aiogram/types/message_entity.py
@@ -50,31 +50,26 @@ class MessageEntity(base.TelegramObject):
entity_text = self.get_text(text)
if self.type == MessageEntityType.BOLD:
- if as_html:
- return markdown.hbold(entity_text)
- return markdown.bold(entity_text)
- elif self.type == MessageEntityType.ITALIC:
- if as_html:
- return markdown.hitalic(entity_text)
- return markdown.italic(entity_text)
- elif self.type == MessageEntityType.PRE:
- if as_html:
- return markdown.hpre(entity_text)
- return markdown.pre(entity_text)
- elif self.type == MessageEntityType.CODE:
- if as_html:
- return markdown.hcode(entity_text)
- return markdown.code(entity_text)
- elif self.type == MessageEntityType.URL:
- if as_html:
- return markdown.hlink(entity_text, entity_text)
- return markdown.link(entity_text, entity_text)
- elif self.type == MessageEntityType.TEXT_LINK:
- if as_html:
- return markdown.hlink(entity_text, self.url)
- return markdown.link(entity_text, self.url)
- elif self.type == MessageEntityType.TEXT_MENTION and self.user:
- return self.user.get_mention(entity_text)
+ method = markdown.hbold if as_html else markdown.bold
+ return method(entity_text)
+ if self.type == MessageEntityType.ITALIC:
+ method = markdown.hitalic if as_html else markdown.italic
+ return method(entity_text)
+ if self.type == MessageEntityType.PRE:
+ method = markdown.hpre if as_html else markdown.pre
+ return method(entity_text)
+ if self.type == MessageEntityType.CODE:
+ method = markdown.hcode if as_html else markdown.code
+ return method(entity_text)
+ if self.type == MessageEntityType.URL:
+ method = markdown.hlink if as_html else markdown.link
+ return method(entity_text, entity_text)
+ if self.type == MessageEntityType.TEXT_LINK:
+ method = markdown.hlink if as_html else markdown.link
+ return method(entity_text, self.url)
+ if self.type == MessageEntityType.TEXT_MENTION and self.user:
+ return self.user.get_mention(entity_text, as_html=as_html)
+
return entity_text
diff --git a/aiogram/types/mixins.py b/aiogram/types/mixins.py
index 40c1093d..024cad72 100644
--- a/aiogram/types/mixins.py
+++ b/aiogram/types/mixins.py
@@ -26,7 +26,7 @@ class Downloadable:
if destination is None:
destination = file.file_path
elif isinstance(destination, (str, pathlib.Path)) and os.path.isdir(destination):
- os.path.join(destination, file.file_path)
+ destination = os.path.join(destination, file.file_path)
else:
is_path = False
diff --git a/aiogram/types/sticker.py b/aiogram/types/sticker.py
index f3c4591c..a3f8b80c 100644
--- a/aiogram/types/sticker.py
+++ b/aiogram/types/sticker.py
@@ -15,6 +15,7 @@ class Sticker(base.TelegramObject, mixins.Downloadable):
file_id: base.String = fields.Field()
width: base.Integer = fields.Field()
height: base.Integer = fields.Field()
+ is_animated: base.Boolean = fields.Field()
thumb: PhotoSize = fields.Field(base=PhotoSize)
emoji: base.String = fields.Field()
set_name: base.String = fields.Field()
diff --git a/aiogram/types/sticker_set.py b/aiogram/types/sticker_set.py
index eed073ab..05d2cd98 100644
--- a/aiogram/types/sticker_set.py
+++ b/aiogram/types/sticker_set.py
@@ -14,5 +14,6 @@ class StickerSet(base.TelegramObject):
name: base.String = fields.Field()
title: base.String = fields.Field()
+ is_animated: base.Boolean = fields.Field()
contains_masks: base.Boolean = fields.Field()
stickers: typing.List[Sticker] = fields.ListField(base=Sticker)
diff --git a/aiogram/types/user.py b/aiogram/types/user.py
index ca260626..05ff9401 100644
--- a/aiogram/types/user.py
+++ b/aiogram/types/user.py
@@ -1,5 +1,7 @@
from __future__ import annotations
+from typing import Optional
+
import babel
from . import base
@@ -46,7 +48,7 @@ class User(base.TelegramObject):
return self.full_name
@property
- def locale(self) -> babel.core.Locale or None:
+ def locale(self) -> Optional[babel.core.Locale]:
"""
Get user's locale
diff --git a/aiogram/utils/auth_widget.py b/aiogram/utils/auth_widget.py
index 24a18698..e612c2f1 100644
--- a/aiogram/utils/auth_widget.py
+++ b/aiogram/utils/auth_widget.py
@@ -8,7 +8,10 @@ import collections
import hashlib
import hmac
+from aiogram.utils.deprecated import deprecated
+
+@deprecated('`generate_hash` is outdated, please use `check_signature` or `check_integrity`', stacklevel=3)
def generate_hash(data: dict, token: str) -> str:
"""
Generate secret hash
@@ -24,6 +27,7 @@ def generate_hash(data: dict, token: str) -> str:
return hmac.new(secret.digest(), msg.encode("utf-8"), digestmod=hashlib.sha256).hexdigest()
+@deprecated('`check_token` helper was renamed to `check_integrity`', stacklevel=3)
def check_token(data: dict, token: str) -> bool:
"""
Validate auth token
@@ -34,3 +38,32 @@ def check_token(data: dict, token: str) -> bool:
"""
param_hash = data.get("hash", "") or ""
return param_hash == generate_hash(data, token)
+
+
+def check_signature(token: str, hash: str, **kwargs) -> bool:
+ """
+ Generate hexadecimal representation
+ of the HMAC-SHA-256 signature of the data-check-string
+ with the SHA256 hash of the bot's token used as a secret key
+
+ :param token:
+ :param hash:
+ :param kwargs: all params received on auth
+ :return:
+ """
+ secret = hashlib.sha256(token.encode('utf-8'))
+ check_string = '\n'.join(map(lambda k: f'{k}={kwargs[k]}', sorted(kwargs)))
+ hmac_string = hmac.new(secret.digest(), check_string.encode('utf-8'), digestmod=hashlib.sha256).hexdigest()
+ return hmac_string == hash
+
+
+def check_integrity(token: str, data: dict) -> bool:
+ """
+ Verify the authentication and the integrity
+ of the data received on user's auth
+
+ :param token: Bot's token
+ :param data: all data that came on auth
+ :return:
+ """
+ return check_signature(token, **data)
diff --git a/aiogram/utils/callback_data.py b/aiogram/utils/callback_data.py
index 81ef1cc7..b0162a7e 100644
--- a/aiogram/utils/callback_data.py
+++ b/aiogram/utils/callback_data.py
@@ -26,15 +26,15 @@ class CallbackData:
Callback data factory
"""
- def __init__(self, prefix, *parts, sep=":"):
+ def __init__(self, prefix, *parts, sep=':'):
if not isinstance(prefix, str):
- raise TypeError(f"Prefix must be instance of str not {type(prefix).__name__}")
- elif not prefix:
+ raise TypeError(f'Prefix must be instance of str not {type(prefix).__name__}')
+ if not prefix:
raise ValueError("Prefix can't be empty")
- elif sep in prefix:
- raise ValueError(f"Separator '{sep}' can't be used in prefix")
- elif not parts:
- raise TypeError("Parts is not passed!")
+ if sep in prefix:
+ raise ValueError(f"Separator {sep!r} can't be used in prefix")
+ if not parts:
+ raise TypeError('Parts were not passed!')
self.prefix = prefix
self.sep = sep
@@ -59,24 +59,24 @@ class CallbackData:
if args:
value = args.pop(0)
else:
- raise ValueError(f"Value for '{part}' is not passed!")
+ raise ValueError(f'Value for {part!r} was not passed!')
if value is not None and not isinstance(value, str):
value = str(value)
if not value:
- raise ValueError(f"Value for part {part} can't be empty!'")
- elif self.sep in value:
- raise ValueError(f"Symbol defined as separator can't be used in values of parts")
+ raise ValueError(f"Value for part {part!r} can't be empty!'")
+ if self.sep in value:
+ raise ValueError(f"Symbol {self.sep!r} is defined as the separator and can't be used in parts' values")
data.append(value)
if args or kwargs:
- raise TypeError("Too many arguments is passed!")
+ raise TypeError('Too many arguments were passed!')
callback_data = self.sep.join(data)
if len(callback_data) > 64:
- raise ValueError("Resulted callback data is too long!")
+ raise ValueError('Resulted callback data is too long!')
return callback_data
@@ -91,9 +91,9 @@ class CallbackData:
if prefix != self.prefix:
raise ValueError("Passed callback data can't be parsed with that prefix.")
elif len(parts) != len(self._part_names):
- raise ValueError("Invalid parts count!")
+ raise ValueError('Invalid parts count!')
- result = {"@": prefix}
+ result = {'@': prefix}
result.update(zip(self._part_names, parts))
return result
@@ -106,11 +106,12 @@ class CallbackData:
"""
for key in config.keys():
if key not in self._part_names:
- raise ValueError(f"Invalid field name '{key}'")
+ raise ValueError(f'Invalid field name {key!r}')
return CallbackDataFilter(self, config)
class CallbackDataFilter(Filter):
+
def __init__(self, factory: CallbackData, config: typing.Dict[str, str]):
self.config = config
self.factory = factory
@@ -124,12 +125,12 @@ class CallbackDataFilter(Filter):
data = self.factory.parse(query.data)
except ValueError:
return False
- else:
- for key, value in self.config.items():
- if isinstance(value, (list, tuple, set)):
- if data.get(key) not in value:
- return False
- else:
- if value != data.get(key):
- return False
- return {"callback_data": data}
+
+ for key, value in self.config.items():
+ if isinstance(value, (list, tuple, set, frozenset)):
+ if data.get(key) not in value:
+ return False
+ else:
+ if data.get(key) != value:
+ return False
+ return {'callback_data': data}
diff --git a/aiogram/utils/deprecated.py b/aiogram/utils/deprecated.py
index 65b62a22..cb22c506 100644
--- a/aiogram/utils/deprecated.py
+++ b/aiogram/utils/deprecated.py
@@ -1,17 +1,17 @@
-"""
-Source: https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically
-"""
-
-import functools
+import asyncio
import inspect
import warnings
+import functools
+from typing import Callable
-def deprecated(reason):
+def deprecated(reason, stacklevel=2) -> Callable:
"""
This is a decorator which can be used to mark functions
as deprecated. It will result in a warning being emitted
when the function is used.
+
+ Source: https://stackoverflow.com/questions/2536307/decorators-in-the-python-standard-lib-deprecated-specifically
"""
if isinstance(reason, str):
@@ -33,15 +33,15 @@ def deprecated(reason):
@functools.wraps(func)
def wrapper(*args, **kwargs):
- warn_deprecated(msg.format(name=func.__name__, reason=reason))
- warnings.simplefilter("default", DeprecationWarning)
+ warn_deprecated(msg.format(name=func.__name__, reason=reason), stacklevel=stacklevel)
+ warnings.simplefilter('default', DeprecationWarning)
return func(*args, **kwargs)
return wrapper
return decorator
- elif inspect.isclass(reason) or inspect.isfunction(reason):
+ if inspect.isclass(reason) or inspect.isfunction(reason):
# The @deprecated is used without any 'reason'.
#
@@ -60,16 +60,76 @@ def deprecated(reason):
@functools.wraps(func1)
def wrapper1(*args, **kwargs):
- warn_deprecated(msg1.format(name=func1.__name__))
+ warn_deprecated(msg1.format(name=func1.__name__), stacklevel=stacklevel)
return func1(*args, **kwargs)
return wrapper1
- else:
- raise TypeError(repr(type(reason)))
+ raise TypeError(repr(type(reason)))
def warn_deprecated(message, warning=DeprecationWarning, stacklevel=2):
- warnings.simplefilter("always", warning)
+ warnings.simplefilter('always', warning)
warnings.warn(message, category=warning, stacklevel=stacklevel)
- warnings.simplefilter("default", warning)
+ warnings.simplefilter('default', warning)
+
+
+def renamed_argument(old_name: str, new_name: str, until_version: str, stacklevel: int = 3):
+ """
+ A meta-decorator to mark an argument as deprecated.
+
+ .. code-block:: python3
+
+ @renamed_argument("chat", "chat_id", "3.0") # stacklevel=3 by default
+ @renamed_argument("user", "user_id", "3.0", stacklevel=4)
+ def some_function(user_id, chat_id=None):
+ print(f"user_id={user_id}, chat_id={chat_id}")
+
+ some_function(user=123) # prints 'user_id=123, chat_id=None' with warning
+ some_function(123) # prints 'user_id=123, chat_id=None' without warning
+ some_function(user_id=123) # prints 'user_id=123, chat_id=None' without warning
+
+
+ :param old_name:
+ :param new_name:
+ :param until_version: the version in which the argument is scheduled to be removed
+ :param stacklevel: leave it to default if it's the first decorator used.
+ Increment with any new decorator used.
+ :return: decorator
+ """
+
+ def decorator(func):
+ if asyncio.iscoroutinefunction(func):
+ @functools.wraps(func)
+ async def wrapped(*args, **kwargs):
+ if old_name in kwargs:
+ warn_deprecated(f"In coroutine '{func.__name__}' argument '{old_name}' "
+ f"is renamed to '{new_name}' "
+ f"and will be removed in aiogram {until_version}",
+ stacklevel=stacklevel)
+ kwargs.update(
+ {
+ new_name: kwargs[old_name],
+ }
+ )
+ kwargs.pop(old_name)
+ return await func(*args, **kwargs)
+ else:
+ @functools.wraps(func)
+ def wrapped(*args, **kwargs):
+ if old_name in kwargs:
+ warn_deprecated(f"In function `{func.__name__}` argument `{old_name}` "
+ f"is renamed to `{new_name}` "
+ f"and will be removed in aiogram {until_version}",
+ stacklevel=stacklevel)
+ kwargs.update(
+ {
+ new_name: kwargs[old_name],
+ }
+ )
+ kwargs.pop(old_name)
+ return func(*args, **kwargs)
+
+ return wrapped
+
+ return decorator
diff --git a/aiogram/utils/exceptions.py b/aiogram/utils/exceptions.py
index 83352521..f77fe257 100644
--- a/aiogram/utils/exceptions.py
+++ b/aiogram/utils/exceptions.py
@@ -1,102 +1,100 @@
"""
-TelegramAPIError
- ValidationError
- Throttled
- BadRequest
- MessageError
- MessageNotModified
- MessageToForwardNotFound
- MessageToDeleteNotFound
- MessageIdentifierNotSpecified
- MessageTextIsEmpty
- MessageCantBeEdited
- MessageCantBeDeleted
- MessageToEditNotFound
- MessageToReplyNotFound
- ToMuchMessages
- PollError
- PollCantBeStopped
- PollHasAlreadyClosed
- PollsCantBeSentToPrivateChats
- PollSizeError
- PollMustHaveMoreOptions
- PollCantHaveMoreOptions
- PollsOptionsLengthTooLong
- PollOptionsMustBeNonEmpty
- PollQuestionMustBeNonEmpty
- MessageWithPollNotFound (with MessageError)
- MessageIsNotAPoll (with MessageError)
- ObjectExpectedAsReplyMarkup
- InlineKeyboardExpected
- ChatNotFound
- ChatDescriptionIsNotModified
- InvalidQueryID
- InvalidPeerID
- InvalidHTTPUrlContent
- ButtonURLInvalid
- URLHostIsEmpty
- StartParamInvalid
- ButtonDataInvalid
- WrongFileIdentifier
- GroupDeactivated
- BadWebhook
- WebhookRequireHTTPS
- BadWebhookPort
- BadWebhookAddrInfo
- BadWebhookNoAddressAssociatedWithHostname
- NotFound
- MethodNotKnown
- PhotoAsInputFileRequired
- InvalidStickersSet
- NoStickerInRequest
- ChatAdminRequired
- NeedAdministratorRightsInTheChannel
- MethodNotAvailableInPrivateChats
- CantDemoteChatCreator
- CantRestrictSelf
- NotEnoughRightsToRestrict
- PhotoDimensions
- UnavailableMembers
- TypeOfFileMismatch
- WrongRemoteFileIdSpecified
- PaymentProviderInvalid
- CurrencyTotalAmountInvalid
- CantParseUrl
- UnsupportedUrlProtocol
- CantParseEntities
- ResultIdDuplicate
- ConflictError
- TerminatedByOtherGetUpdates
- CantGetUpdates
- Unauthorized
- BotKicked
- BotBlocked
- UserDeactivated
- CantInitiateConversation
- CantTalkWithBots
- NetworkError
- RetryAfter
- MigrateToChat
- RestartingTelegram
+- TelegramAPIError
+ - ValidationError
+ - Throttled
+ - BadRequest
+ - MessageError
+ - MessageNotModified
+ - MessageToForwardNotFound
+ - MessageToDeleteNotFound
+ - MessageIdentifierNotSpecified
+ - MessageTextIsEmpty
+ - MessageCantBeEdited
+ - MessageCantBeDeleted
+ - MessageToEditNotFound
+ - MessageToReplyNotFound
+ - ToMuchMessages
+ - PollError
+ - PollCantBeStopped
+ - PollHasAlreadyClosed
+ - PollsCantBeSentToPrivateChats
+ - PollSizeError
+ - PollMustHaveMoreOptions
+ - PollCantHaveMoreOptions
+ - PollsOptionsLengthTooLong
+ - PollOptionsMustBeNonEmpty
+ - PollQuestionMustBeNonEmpty
+ - MessageWithPollNotFound (with MessageError)
+ - MessageIsNotAPoll (with MessageError)
+ - ObjectExpectedAsReplyMarkup
+ - InlineKeyboardExpected
+ - ChatNotFound
+ - ChatDescriptionIsNotModified
+ - InvalidQueryID
+ - InvalidPeerID
+ - InvalidHTTPUrlContent
+ - ButtonURLInvalid
+ - URLHostIsEmpty
+ - StartParamInvalid
+ - ButtonDataInvalid
+ - WrongFileIdentifier
+ - GroupDeactivated
+ - BadWebhook
+ - WebhookRequireHTTPS
+ - BadWebhookPort
+ - BadWebhookAddrInfo
+ - BadWebhookNoAddressAssociatedWithHostname
+ - NotFound
+ - MethodNotKnown
+ - PhotoAsInputFileRequired
+ - InvalidStickersSet
+ - NoStickerInRequest
+ - ChatAdminRequired
+ - NeedAdministratorRightsInTheChannel
+ - MethodNotAvailableInPrivateChats
+ - CantDemoteChatCreator
+ - CantRestrictSelf
+ - NotEnoughRightsToRestrict
+ - PhotoDimensions
+ - UnavailableMembers
+ - TypeOfFileMismatch
+ - WrongRemoteFileIdSpecified
+ - PaymentProviderInvalid
+ - CurrencyTotalAmountInvalid
+ - CantParseUrl
+ - UnsupportedUrlProtocol
+ - CantParseEntities
+ - ResultIdDuplicate
+ - ConflictError
+ - TerminatedByOtherGetUpdates
+ - CantGetUpdates
+ - Unauthorized
+ - BotKicked
+ - BotBlocked
+ - UserDeactivated
+ - CantInitiateConversation
+ - CantTalkWithBots
+ - NetworkError
+ - RetryAfter
+ - MigrateToChat
+ - RestartingTelegram
-
-TODO: aiogram.utils.exceptions.BadRequest: Bad request: can't parse entities: unsupported start tag "function" at byte offset 0
-TODO: aiogram.utils.exceptions.TelegramAPIError: Gateway Timeout
-
-AIOGramWarning
- TimeoutWarning
+- AIOGramWarning
+ - TimeoutWarning
"""
import time
# TODO: Use exceptions detector from `aiograph`.
+# TODO: aiogram.utils.exceptions.BadRequest: Bad request: can't parse entities: unsupported start tag "function" at byte offset 0
+# TODO: aiogram.utils.exceptions.TelegramAPIError: Gateway Timeout
-_PREFIXES = ["error: ", "[error]: ", "bad request: ", "conflict: ", "not found: "]
+_PREFIXES = ['error: ', '[error]: ', 'bad request: ', 'conflict: ', 'not found: ']
def _clean_message(text):
for prefix in _PREFIXES:
if text.startswith(prefix):
- text = text[len(prefix) :]
+ text = text[len(prefix):]
return (text[0].upper() + text[1:]).strip()
@@ -106,7 +104,7 @@ class TelegramAPIError(Exception):
class _MatchErrorMixin:
- match = ""
+ match = ''
text = None
__subclasses = []
@@ -166,72 +164,67 @@ class MessageNotModified(MessageError):
"""
Will be raised when you try to set new text is equals to current text.
"""
-
- match = "message is not modified"
+ match = 'message is not modified'
class MessageToForwardNotFound(MessageError):
"""
Will be raised when you try to forward very old or deleted or unknown message.
"""
-
- match = "message to forward not found"
+ match = 'message to forward not found'
class MessageToDeleteNotFound(MessageError):
"""
Will be raised when you try to delete very old or deleted or unknown message.
"""
-
- match = "message to delete not found"
+ match = 'message to delete 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 = 'message to reply not found'
class MessageIdentifierNotSpecified(MessageError):
- match = "message identifier is not specified"
+ match = 'message identifier is not specified'
class MessageTextIsEmpty(MessageError):
- match = "Message text is empty"
+ match = 'Message text is empty'
class MessageCantBeEdited(MessageError):
- match = "message can't be edited"
+ match = 'message can\'t be edited'
class MessageCantBeDeleted(MessageError):
- match = "message can't be deleted"
+ match = 'message can\'t be deleted'
class MessageToEditNotFound(MessageError):
- match = "message to edit not found"
+ match = 'message to edit not found'
class MessageIsTooLong(MessageError):
- match = "message is too long"
+ match = 'message is too long'
class ToMuchMessages(MessageError):
"""
Will be raised when you try to send media group with more than 10 items.
"""
-
- match = "Too much messages to send as an album"
+ match = 'Too much messages to send as an album'
class ObjectExpectedAsReplyMarkup(BadRequest):
- match = "object expected as reply markup"
+ match = 'object expected as reply markup'
class InlineKeyboardExpected(BadRequest):
- match = "inline keyboard expected"
+ match = 'inline keyboard expected'
class PollError(BadRequest):
@@ -243,7 +236,7 @@ class PollCantBeStopped(PollError):
class PollHasAlreadyBeenClosed(PollError):
- match = "poll has already been closed"
+ match = 'poll has already been closed'
class PollsCantBeSentToPrivateChats(PollError):
@@ -282,112 +275,109 @@ class MessageWithPollNotFound(PollError, MessageError):
"""
Will be raised when you try to stop poll with message without poll
"""
-
- match = "message with poll to stop not found"
+ match = 'message with poll to stop not found'
class MessageIsNotAPoll(PollError, MessageError):
"""
Will be raised when you try to stop poll with message without poll
"""
-
- match = "message is not a poll"
+ match = 'message is not a poll'
class ChatNotFound(BadRequest):
- match = "chat not found"
+ match = 'chat not found'
class ChatIdIsEmpty(BadRequest):
- match = "chat_id is empty"
+ match = 'chat_id is empty'
class InvalidUserId(BadRequest):
- match = "user_id_invalid"
- text = "Invalid user id"
+ match = 'user_id_invalid'
+ text = 'Invalid user id'
class ChatDescriptionIsNotModified(BadRequest):
- match = "chat description is not modified"
+ match = 'chat description is not modified'
class InvalidQueryID(BadRequest):
- match = "query is too old and response timeout expired or query id is invalid"
+ match = 'query is too old and response timeout expired or query id is invalid'
class InvalidPeerID(BadRequest):
- match = "PEER_ID_INVALID"
- text = "Invalid peer ID"
+ match = 'PEER_ID_INVALID'
+ text = 'Invalid peer ID'
class InvalidHTTPUrlContent(BadRequest):
- match = "Failed to get HTTP URL content"
+ match = 'Failed to get HTTP URL content'
class ButtonURLInvalid(BadRequest):
- match = "BUTTON_URL_INVALID"
- text = "Button URL invalid"
+ match = 'BUTTON_URL_INVALID'
+ text = 'Button URL invalid'
class URLHostIsEmpty(BadRequest):
- match = "URL host is empty"
+ match = 'URL host is empty'
class StartParamInvalid(BadRequest):
- match = "START_PARAM_INVALID"
- text = "Start param invalid"
+ match = 'START_PARAM_INVALID'
+ text = 'Start param invalid'
class ButtonDataInvalid(BadRequest):
- match = "BUTTON_DATA_INVALID"
- text = "Button data invalid"
+ match = 'BUTTON_DATA_INVALID'
+ text = 'Button data invalid'
class WrongFileIdentifier(BadRequest):
- match = "wrong file identifier/HTTP URL specified"
+ match = 'wrong file identifier/HTTP URL specified'
class GroupDeactivated(BadRequest):
- match = "group is deactivated"
+ match = 'group is deactivated'
class PhotoAsInputFileRequired(BadRequest):
"""
Will be raised when you try to set chat photo from file ID.
"""
-
- match = "Photo should be uploaded as an InputFile"
+ match = 'Photo should be uploaded as an InputFile'
class InvalidStickersSet(BadRequest):
- match = "STICKERSET_INVALID"
- text = "Stickers set is invalid"
+ match = 'STICKERSET_INVALID'
+ text = 'Stickers set is invalid'
class NoStickerInRequest(BadRequest):
- match = "there is no sticker in the request"
+ match = 'there is no sticker in the request'
class ChatAdminRequired(BadRequest):
- match = "CHAT_ADMIN_REQUIRED"
- text = "Admin permissions is required!"
+ match = 'CHAT_ADMIN_REQUIRED'
+ text = 'Admin permissions is required!'
class NeedAdministratorRightsInTheChannel(BadRequest):
- match = "need administrator rights in the channel chat"
- text = "Admin permissions is required!"
+ match = 'need administrator rights in the channel chat'
+ text = 'Admin permissions is required!'
class NotEnoughRightsToPinMessage(BadRequest):
- match = "not enough rights to pin a message"
+ match = 'not enough rights to pin a message'
class MethodNotAvailableInPrivateChats(BadRequest):
- match = "method is available only for supergroups and channel"
+ match = 'method is available only for supergroups and channel'
class CantDemoteChatCreator(BadRequest):
- match = "can't demote chat creator"
+ match = 'can\'t demote chat creator'
class CantRestrictSelf(BadRequest):
@@ -396,34 +386,34 @@ class CantRestrictSelf(BadRequest):
class NotEnoughRightsToRestrict(BadRequest):
- match = "not enough rights to restrict/unrestrict chat member"
+ match = 'not enough rights to restrict/unrestrict chat member'
class PhotoDimensions(BadRequest):
- match = "PHOTO_INVALID_DIMENSIONS"
- text = "Invalid photo dimensions"
+ match = 'PHOTO_INVALID_DIMENSIONS'
+ text = 'Invalid photo dimensions'
class UnavailableMembers(BadRequest):
- match = "supergroup members are unavailable"
+ match = 'supergroup members are unavailable'
class TypeOfFileMismatch(BadRequest):
- match = "type of file mismatch"
+ match = 'type of file mismatch'
class WrongRemoteFileIdSpecified(BadRequest):
- match = "wrong remote file id specified"
+ match = 'wrong remote file id specified'
class PaymentProviderInvalid(BadRequest):
- match = "PAYMENT_PROVIDER_INVALID"
- text = "payment provider invalid"
+ match = 'PAYMENT_PROVIDER_INVALID'
+ text = 'payment provider invalid'
class CurrencyTotalAmountInvalid(BadRequest):
- match = "currency_total_amount_invalid"
- text = "currency total amount invalid"
+ match = 'currency_total_amount_invalid'
+ text = 'currency total amount invalid'
class BadWebhook(BadRequest):
@@ -431,44 +421,44 @@ class BadWebhook(BadRequest):
class WebhookRequireHTTPS(BadWebhook):
- match = "HTTPS url must be provided for webhook"
- text = "bad webhook: " + match
+ match = 'HTTPS url must be provided for webhook'
+ text = 'bad webhook: ' + match
class BadWebhookPort(BadWebhook):
- match = "Webhook can be set up only on ports 80, 88, 443 or 8443"
- text = "bad webhook: " + match
+ match = 'Webhook can be set up only on ports 80, 88, 443 or 8443'
+ text = 'bad webhook: ' + match
class BadWebhookAddrInfo(BadWebhook):
- match = "getaddrinfo: Temporary failure in name resolution"
- text = "bad webhook: " + match
+ match = 'getaddrinfo: Temporary failure in name resolution'
+ text = 'bad webhook: ' + match
class BadWebhookNoAddressAssociatedWithHostname(BadWebhook):
- match = "failed to resolve host: no address associated with hostname"
+ match = 'failed to resolve host: no address associated with hostname'
class CantParseUrl(BadRequest):
- match = "can't parse URL"
+ match = 'can\'t parse URL'
class UnsupportedUrlProtocol(BadRequest):
- match = "unsupported URL protocol"
+ match = 'unsupported URL protocol'
class CantParseEntities(BadRequest):
- match = "can't parse entities"
+ match = 'can\'t parse entities'
class ResultIdDuplicate(BadRequest):
- match = "result_id_duplicate"
- text = "Result ID duplicate"
+ match = 'result_id_duplicate'
+ text = 'Result ID duplicate'
class BotDomainInvalid(BadRequest):
- match = "bot_domain_invalid"
- text = "Invalid bot domain"
+ match = 'bot_domain_invalid'
+ text = 'Invalid bot domain'
class NotFound(TelegramAPIError, _MatchErrorMixin):
@@ -476,7 +466,7 @@ class NotFound(TelegramAPIError, _MatchErrorMixin):
class MethodNotKnown(NotFound):
- match = "method not found"
+ match = 'method not found'
class ConflictError(TelegramAPIError, _MatchErrorMixin):
@@ -484,15 +474,13 @@ class ConflictError(TelegramAPIError, _MatchErrorMixin):
class TerminatedByOtherGetUpdates(ConflictError):
- match = "terminated by other getUpdates request"
- text = (
- "Terminated by other getUpdates request; "
- "Make sure that only one bot instance is running"
- )
+ match = 'terminated by other getUpdates request'
+ text = 'Terminated by other getUpdates request; ' \
+ 'Make sure that only one bot instance is running'
class CantGetUpdates(ConflictError):
- match = "can't use getUpdates method while webhook is active"
+ match = 'can\'t use getUpdates method while webhook is active'
class Unauthorized(TelegramAPIError, _MatchErrorMixin):
@@ -500,23 +488,23 @@ class Unauthorized(TelegramAPIError, _MatchErrorMixin):
class BotKicked(Unauthorized):
- match = "Bot was kicked from a chat"
+ match = 'bot was kicked from a chat'
class BotBlocked(Unauthorized):
- match = "bot was blocked by the user"
+ match = 'bot was blocked by the user'
class UserDeactivated(Unauthorized):
- match = "user is deactivated"
+ match = 'user is deactivated'
class CantInitiateConversation(Unauthorized):
- match = "bot can't initiate conversation with a user"
+ match = 'bot can\'t initiate conversation with a user'
class CantTalkWithBots(Unauthorized):
- match = "bot can't send messages to bots"
+ match = 'bot can\'t send messages to bots'
class NetworkError(TelegramAPIError):
@@ -525,43 +513,34 @@ class NetworkError(TelegramAPIError):
class RestartingTelegram(TelegramAPIError):
def __init__(self):
- super(RestartingTelegram, self).__init__(
- "The Telegram Bot API service is restarting. Wait few second."
- )
+ super(RestartingTelegram, self).__init__('The Telegram Bot API service is restarting. Wait few second.')
class RetryAfter(TelegramAPIError):
def __init__(self, retry_after):
- super(RetryAfter, self).__init__(
- f"Flood control exceeded. Retry in {retry_after} seconds."
- )
+ super(RetryAfter, self).__init__(f"Flood control exceeded. Retry in {retry_after} seconds.")
self.timeout = retry_after
class MigrateToChat(TelegramAPIError):
def __init__(self, chat_id):
- super(MigrateToChat, self).__init__(
- f"The group has been migrated to a supergroup. New id: {chat_id}."
- )
+ super(MigrateToChat, self).__init__(f"The group has been migrated to a supergroup. New id: {chat_id}.")
self.migrate_to_chat_id = chat_id
class Throttled(TelegramAPIError):
def __init__(self, **kwargs):
from ..dispatcher.storage import DELTA, EXCEEDED_COUNT, KEY, LAST_CALL, RATE_LIMIT, RESULT
-
- self.key = kwargs.pop(KEY, "")
+ self.key = kwargs.pop(KEY, '')
self.called_at = kwargs.pop(LAST_CALL, time.time())
self.rate = kwargs.pop(RATE_LIMIT, None)
self.result = kwargs.pop(RESULT, False)
self.exceeded_count = kwargs.pop(EXCEEDED_COUNT, 0)
self.delta = kwargs.pop(DELTA, 0)
- self.user = kwargs.pop("user", None)
- self.chat = kwargs.pop("chat", None)
+ self.user = kwargs.pop('user', None)
+ self.chat = kwargs.pop('chat', None)
def __str__(self):
- return (
- f"Rate limit exceeded! (Limit: {self.rate} s, "
- f"exceeded: {self.exceeded_count}, "
+ return f"Rate limit exceeded! (Limit: {self.rate} s, " \
+ f"exceeded: {self.exceeded_count}, " \
f"time delta: {round(self.delta, 3)} s)"
- )
diff --git a/aiogram/utils/executor.py b/aiogram/utils/executor.py
index f4834901..fe3483f6 100644
--- a/aiogram/utils/executor.py
+++ b/aiogram/utils/executor.py
@@ -12,27 +12,18 @@ from ..bot.api import log
from ..dispatcher.dispatcher import Dispatcher
from ..dispatcher.webhook import BOT_DISPATCHER_KEY, DEFAULT_ROUTE_NAME, WebhookRequestHandler
-APP_EXECUTOR_KEY = "APP_EXECUTOR"
+APP_EXECUTOR_KEY = 'APP_EXECUTOR'
-def _setup_callbacks(executor, on_startup=None, on_shutdown=None):
+def _setup_callbacks(executor: 'Executor', on_startup=None, on_shutdown=None):
if on_startup is not None:
executor.on_startup(on_startup)
if on_shutdown is not None:
executor.on_shutdown(on_shutdown)
-def start_polling(
- dispatcher,
- *,
- loop=None,
- skip_updates=False,
- reset_webhook=True,
- on_startup=None,
- on_shutdown=None,
- timeout=20,
- fast=True,
-):
+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):
"""
Start bot in long-polling mode
@@ -47,22 +38,14 @@ def start_polling(
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, fast=fast)
+ executor.start_polling(reset_webhook=reset_webhook, timeout=timeout, relax=relax, fast=fast)
-def set_webhook(
- dispatcher: Dispatcher,
- webhook_path: str,
- *,
- loop: Optional[asyncio.AbstractEventLoop] = None,
- skip_updates: bool = None,
- on_startup: Optional[Callable] = None,
- on_shutdown: Optional[Callable] = None,
- check_ip: bool = False,
- retry_after: Optional[Union[str, int]] = None,
- route_name: str = DEFAULT_ROUTE_NAME,
- web_app: Optional[Application] = None,
-):
+def set_webhook(dispatcher: Dispatcher, webhook_path: str, *, loop: Optional[asyncio.AbstractEventLoop] = None,
+ skip_updates: bool = None, on_startup: Optional[Callable] = None,
+ on_shutdown: Optional[Callable] = None, check_ip: bool = False,
+ retry_after: Optional[Union[str, int]] = None, route_name: str = DEFAULT_ROUTE_NAME,
+ web_app: Optional[Application] = None):
"""
Set webhook for bot
@@ -78,32 +61,17 @@ def set_webhook(
:param web_app: Optional[Application] (default: None)
:return:
"""
- executor = Executor(
- dispatcher,
- skip_updates=skip_updates,
- check_ip=check_ip,
- retry_after=retry_after,
- loop=loop,
- )
+ executor = Executor(dispatcher, skip_updates=skip_updates, check_ip=check_ip, retry_after=retry_after,
+ loop=loop)
_setup_callbacks(executor, on_startup, on_shutdown)
executor.set_webhook(webhook_path, route_name=route_name, web_app=web_app)
return executor
-def start_webhook(
- dispatcher,
- webhook_path,
- *,
- loop=None,
- skip_updates=None,
- on_startup=None,
- on_shutdown=None,
- check_ip=False,
- retry_after=None,
- route_name=DEFAULT_ROUTE_NAME,
- **kwargs,
-):
+def start_webhook(dispatcher, webhook_path, *, loop=None, skip_updates=None,
+ on_startup=None, on_shutdown=None, check_ip=False, retry_after=None, route_name=DEFAULT_ROUTE_NAME,
+ **kwargs):
"""
Start bot in webhook mode
@@ -118,21 +86,20 @@ def start_webhook(
:param kwargs:
:return:
"""
- executor = set_webhook(
- dispatcher=dispatcher,
- webhook_path=webhook_path,
- loop=loop,
- skip_updates=skip_updates,
- on_startup=on_startup,
- on_shutdown=on_shutdown,
- check_ip=check_ip,
- retry_after=retry_after,
- route_name=route_name,
- )
+ executor = set_webhook(dispatcher=dispatcher,
+ webhook_path=webhook_path,
+ loop=loop,
+ skip_updates=skip_updates,
+ on_startup=on_startup,
+ on_shutdown=on_shutdown,
+ check_ip=check_ip,
+ retry_after=retry_after,
+ route_name=route_name)
executor.run_app(**kwargs)
-def start(dispatcher, future, *, loop=None, skip_updates=None, on_startup=None, on_shutdown=None):
+def start(dispatcher, future, *, loop=None, skip_updates=None,
+ on_startup=None, on_shutdown=None):
"""
Execute Future.
@@ -175,7 +142,6 @@ class Executor:
self._freeze = False
from aiogram import Bot, Dispatcher
-
Bot.set_current(dispatcher.bot)
Dispatcher.set_current(dispatcher)
@@ -194,7 +160,7 @@ class Executor:
@property
def web_app(self) -> web.Application:
if self._web_app is None:
- raise RuntimeError("web.Application() is not configured!")
+ raise RuntimeError('web.Application() is not configured!')
return self._web_app
def on_startup(self, callback: callable, polling=True, webhook=True):
@@ -207,7 +173,7 @@ class Executor:
"""
self._check_frozen()
if not webhook and not polling:
- warn("This action has no effect!", UserWarning)
+ warn('This action has no effect!', UserWarning)
return
if isinstance(callback, (list, tuple, set)):
@@ -230,7 +196,7 @@ class Executor:
"""
self._check_frozen()
if not webhook and not polling:
- warn("This action has no effect!", UserWarning)
+ warn('This action has no effect!', UserWarning)
return
if isinstance(callback, (list, tuple, set)):
@@ -245,7 +211,7 @@ class Executor:
def _check_frozen(self):
if self.frozen:
- raise RuntimeError("Executor is frozen!")
+ raise RuntimeError('Executor is frozen!')
def _prepare_polling(self):
self._check_frozen()
@@ -253,9 +219,7 @@ class Executor:
# self.loop.set_task_factory(context.task_factory)
- def _prepare_webhook(
- self, path=None, handler=WebhookRequestHandler, route_name=DEFAULT_ROUTE_NAME, app=None
- ):
+ def _prepare_webhook(self, path=None, handler=WebhookRequestHandler, route_name=DEFAULT_ROUTE_NAME, app=None):
self._check_frozen()
self._freeze = True
@@ -269,14 +233,14 @@ class Executor:
raise RuntimeError("web.Application() is already configured!")
if self.retry_after:
- app["RETRY_AFTER"] = self.retry_after
+ app['RETRY_AFTER'] = self.retry_after
if self._identity == app.get(self._identity):
# App is already configured
return
if path is not None:
- app.router.add_route("*", path, handler, name=route_name)
+ app.router.add_route('*', path, handler, name=route_name)
async def _wrap_callback(cb, _):
return await cb(self.dispatcher)
@@ -294,15 +258,10 @@ class Executor:
app[APP_EXECUTOR_KEY] = self
app[BOT_DISPATCHER_KEY] = self.dispatcher
app[self._identity] = datetime.datetime.now()
- app["_check_ip"] = self.check_ip
+ app['_check_ip'] = self.check_ip
- def set_webhook(
- self,
- webhook_path: Optional[str] = None,
- request_handler: Any = WebhookRequestHandler,
- route_name: str = DEFAULT_ROUTE_NAME,
- web_app: Optional[Application] = None,
- ):
+ def set_webhook(self, webhook_path: Optional[str] = None, request_handler: Any = WebhookRequestHandler,
+ route_name: str = DEFAULT_ROUTE_NAME, web_app: Optional[Application] = None):
"""
Set webhook for bot
@@ -318,13 +277,8 @@ class Executor:
def run_app(self, **kwargs):
web.run_app(self._web_app, **kwargs)
- def start_webhook(
- self,
- webhook_path=None,
- request_handler=WebhookRequestHandler,
- route_name=DEFAULT_ROUTE_NAME,
- **kwargs,
- ):
+ def start_webhook(self, webhook_path=None, request_handler=WebhookRequestHandler, route_name=DEFAULT_ROUTE_NAME,
+ **kwargs):
"""
Start bot in webhook mode
@@ -334,12 +288,10 @@ class Executor:
:param kwargs:
:return:
"""
- self.set_webhook(
- webhook_path=webhook_path, request_handler=request_handler, route_name=route_name
- )
+ 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, fast=True):
+ def start_polling(self, reset_webhook=None, timeout=20, relax=0.1, fast=True):
"""
Start bot in long-polling mode
@@ -351,11 +303,8 @@ class Executor:
try:
loop.run_until_complete(self._startup_polling())
- loop.create_task(
- self.dispatcher.start_polling(
- reset_webhook=reset_webhook, timeout=timeout, fast=fast
- )
- )
+ loop.create_task(self.dispatcher.start_polling(reset_webhook=reset_webhook, timeout=timeout,
+ relax=relax, fast=fast))
loop.run_forever()
except (KeyboardInterrupt, SystemExit):
# loop.stop()
@@ -391,7 +340,7 @@ class Executor:
async def _skip_updates(self):
await self.dispatcher.reset_webhook(True)
await self.dispatcher.skip_updates()
- log.warning(f"Updates are skipped successfully.")
+ log.warning(f'Updates were skipped successfully.')
async def _welcome(self):
user = await self.dispatcher.bot.me
@@ -412,11 +361,11 @@ class Executor:
await callback(self.dispatcher)
async def _shutdown_polling(self, wait_closed=False):
- await self._shutdown()
-
for callback in self._on_shutdown_polling:
await callback(self.dispatcher)
+ await self._shutdown()
+
if wait_closed:
await self.dispatcher.wait_closed()
diff --git a/aiogram/utils/helper.py b/aiogram/utils/helper.py
index 2ee14260..735afe5d 100644
--- a/aiogram/utils/helper.py
+++ b/aiogram/utils/helper.py
@@ -13,10 +13,13 @@ Example:
>>> print(MyHelper.all())
<<< ['barItem', 'bazItem', 'fooItem', 'lorem']
"""
+from typing import List
+
+PROPS_KEYS_ATTR_NAME = '_props_keys'
class Helper:
- mode = ""
+ mode = ''
@classmethod
def all(cls):
@@ -37,13 +40,13 @@ class Helper:
class HelperMode(Helper):
- mode = "original"
+ mode = 'original'
- SCREAMING_SNAKE_CASE = "SCREAMING_SNAKE_CASE"
- lowerCamelCase = "lowerCamelCase"
- CamelCase = "CamelCase"
- snake_case = "snake_case"
- lowercase = "lowercase"
+ SCREAMING_SNAKE_CASE = 'SCREAMING_SNAKE_CASE'
+ lowerCamelCase = 'lowerCamelCase'
+ CamelCase = 'CamelCase'
+ snake_case = 'snake_case'
+ lowercase = 'lowercase'
@classmethod
def all(cls):
@@ -65,10 +68,10 @@ class HelperMode(Helper):
"""
if text.isupper():
return text
- result = ""
+ result = ''
for pos, symbol in enumerate(text):
if symbol.isupper() and pos > 0:
- result += "_" + symbol
+ result += '_' + symbol
else:
result += symbol.upper()
return result
@@ -94,10 +97,10 @@ class HelperMode(Helper):
:param first_upper: first symbol must be upper?
:return:
"""
- result = ""
+ result = ''
need_upper = False
for pos, symbol in enumerate(text):
- if symbol == "_" and pos > 0:
+ if symbol == '_' and pos > 0:
need_upper = True
else:
if need_upper:
@@ -120,15 +123,15 @@ class HelperMode(Helper):
"""
if mode == cls.SCREAMING_SNAKE_CASE:
return cls._screaming_snake_case(text)
- elif mode == cls.snake_case:
+ if mode == cls.snake_case:
return cls._snake_case(text)
- elif mode == cls.lowercase:
- return cls._snake_case(text).replace("_", "")
- elif mode == cls.lowerCamelCase:
+ if mode == cls.lowercase:
+ return cls._snake_case(text).replace('_', '')
+ if mode == cls.lowerCamelCase:
return cls._camel_case(text)
- elif mode == cls.CamelCase:
+ if mode == cls.CamelCase:
return cls._camel_case(text, True)
- elif callable(mode):
+ if callable(mode):
return mode(text)
return text
@@ -149,10 +152,10 @@ class Item:
def __set_name__(self, owner, name):
if not name.isupper():
- raise NameError("Name for helper item must be in uppercase!")
+ raise NameError('Name for helper item must be in uppercase!')
if not self._value:
- if hasattr(owner, "mode"):
- self._value = HelperMode.apply(name, getattr(owner, "mode"))
+ if hasattr(owner, 'mode'):
+ self._value = HelperMode.apply(name, getattr(owner, 'mode'))
class ListItem(Item):
@@ -191,3 +194,36 @@ class ItemsList(list):
return self
__iadd__ = __add__ = __rand__ = __and__ = __ror__ = __or__ = add
+
+
+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)
+
+ setattr(cls, PROPS_KEYS_ATTR_NAME, props_keys)
+
+ return cls
+
+
+class OrderedHelper(metaclass=OrderedHelperMeta):
+ mode = ''
+
+ @classmethod
+ def all(cls) -> List[str]:
+ """
+ Get all Items values
+ """
+ result = []
+ for name in getattr(cls, PROPS_KEYS_ATTR_NAME, []):
+ value = getattr(cls, name)
+ if isinstance(value, ItemsList):
+ result.append(value[0])
+ else:
+ result.append(value)
+ return result
diff --git a/aiogram/utils/json.py b/aiogram/utils/json.py
index 42fdb8f3..56f122e4 100644
--- a/aiogram/utils/json.py
+++ b/aiogram/utils/json.py
@@ -1,14 +1,14 @@
import importlib
import os
-JSON = "json"
-RAPIDJSON = "rapidjson"
-UJSON = "ujson"
+JSON = 'json'
+RAPIDJSON = 'rapidjson'
+UJSON = 'ujson'
# Detect mode
mode = JSON
for json_lib in (RAPIDJSON, UJSON):
- if "DISABLE_" + json_lib.upper() in os.environ:
+ if 'DISABLE_' + json_lib.upper() in os.environ:
continue
try:
@@ -20,35 +20,28 @@ for json_lib in (RAPIDJSON, UJSON):
break
if mode == RAPIDJSON:
-
- def dumps(data):
- return json.dumps(
- data,
- ensure_ascii=False,
- number_mode=json.NM_NATIVE,
- datetime_mode=json.DM_ISO8601 | json.DM_NAIVE_IS_UTC,
- )
-
- def loads(data):
- return json.loads(
- data, number_mode=json.NM_NATIVE, datetime_mode=json.DM_ISO8601 | json.DM_NAIVE_IS_UTC
- )
-
-
-elif mode == UJSON:
-
- def loads(data):
- return json.loads(data)
-
def dumps(data):
return json.dumps(data, ensure_ascii=False)
+ def loads(data):
+ return json.loads(data, number_mode=json.NM_NATIVE)
+
+elif mode == UJSON:
+ def loads(data):
+ return json.loads(data)
+
+
+ def dumps(data):
+ return json.dumps(data, ensure_ascii=False)
+
else:
import json
+
def dumps(data):
return json.dumps(data, ensure_ascii=False)
+
def loads(data):
return json.loads(data)
diff --git a/aiogram/utils/mixins.py b/aiogram/utils/mixins.py
index a2c872a2..e6857263 100644
--- a/aiogram/utils/mixins.py
+++ b/aiogram/utils/mixins.py
@@ -1,16 +1,16 @@
import contextvars
from typing import TypeVar, Type
-__all__ = ("DataMixin", "ContextInstanceMixin")
+__all__ = ('DataMixin', 'ContextInstanceMixin')
class DataMixin:
@property
def data(self):
- data = getattr(self, "_data", None)
+ data = getattr(self, '_data', None)
if data is None:
data = {}
- setattr(self, "_data", data)
+ setattr(self, '_data', data)
return data
def __getitem__(self, item):
@@ -26,12 +26,12 @@ class DataMixin:
return self.data.get(key, default)
-T = TypeVar("T")
+T = TypeVar('T')
class ContextInstanceMixin:
def __init_subclass__(cls, **kwargs):
- cls.__context_instance = contextvars.ContextVar("instance_" + cls.__name__)
+ cls.__context_instance = contextvars.ContextVar(f'instance_{cls.__name__}')
return cls
@classmethod
@@ -43,7 +43,5 @@ class ContextInstanceMixin:
@classmethod
def set_current(cls: Type[T], value: T):
if not isinstance(value, cls):
- raise TypeError(
- f"Value should be instance of '{cls.__name__}' not '{type(value).__name__}'"
- )
+ raise TypeError(f'Value should be instance of {cls.__name__!r} not {type(value).__name__!r}')
cls.__context_instance.set(value)
diff --git a/aiogram/utils/payload.py b/aiogram/utils/payload.py
index db86eed0..0c5e8ae9 100644
--- a/aiogram/utils/payload.py
+++ b/aiogram/utils/payload.py
@@ -6,7 +6,7 @@ from babel.support import LazyProxy
from aiogram import types
from . import json
-DEFAULT_FILTER = ["self", "cls"]
+DEFAULT_FILTER = ['self', 'cls']
def generate_payload(exclude=None, **kwargs):
@@ -21,11 +21,10 @@ def generate_payload(exclude=None, **kwargs):
"""
if exclude is None:
exclude = []
- return {
- key: value
- for key, value in kwargs.items()
- if key not in exclude + DEFAULT_FILTER and value is not None and not key.startswith("_")
- }
+ return {key: value for key, value in kwargs.items() if
+ key not in exclude + DEFAULT_FILTER
+ and value is not None
+ and not key.startswith('_')}
def _normalize(obj):
@@ -39,7 +38,7 @@ def _normalize(obj):
return [_normalize(item) for item in obj]
elif isinstance(obj, dict):
return {k: _normalize(v) for k, v in obj.items() if v is not None}
- elif hasattr(obj, "to_python"):
+ elif hasattr(obj, 'to_python'):
return obj.to_python()
return obj
@@ -53,14 +52,14 @@ def prepare_arg(value):
"""
if value is None:
return value
- elif isinstance(value, (list, dict)) or hasattr(value, "to_python"):
+ if isinstance(value, (list, dict)) or hasattr(value, 'to_python'):
return json.dumps(_normalize(value))
- elif isinstance(value, datetime.timedelta):
+ if isinstance(value, datetime.timedelta):
now = datetime.datetime.now()
return int((now + value).timestamp())
- elif isinstance(value, datetime.datetime):
+ if isinstance(value, datetime.datetime):
return round(value.timestamp())
- elif isinstance(value, LazyProxy):
+ if isinstance(value, LazyProxy):
return str(value)
return value
diff --git a/dev_requirements.txt b/dev_requirements.txt
index b746e5fc..06bc3e9c 100644
--- a/dev_requirements.txt
+++ b/dev_requirements.txt
@@ -15,5 +15,4 @@ sphinx-rtd-theme>=0.4.3
sphinxcontrib-programoutput>=0.14
aiohttp-socks>=0.2.2
rethinkdb>=2.4.1
-lxml==4.3.4
-requests==2.22.0
+coverage==4.5.3
diff --git a/docs/source/dispatcher/filters.rst b/docs/source/dispatcher/filters.rst
index d103ac36..b174f1ef 100644
--- a/docs/source/dispatcher/filters.rst
+++ b/docs/source/dispatcher/filters.rst
@@ -111,6 +111,30 @@ ExceptionsFilter
:show-inheritance:
+IDFilter
+----------------
+
+.. autoclass:: aiogram.dispatcher.filters.builtin.IDFilter
+ :members:
+ :show-inheritance:
+
+
+AdminFilter
+----------------
+
+.. autoclass:: aiogram.dispatcher.filters.builtin.AdminFilter
+ :members:
+ :show-inheritance:
+
+
+IsReplyFilter
+-------------
+
+.. autoclass:: aiogram.dispatcher.filters.filters.IsReplyFilter
+ :members:
+ :show-inheritance:
+
+
Making own filters (Custom filters)
===================================
@@ -156,3 +180,4 @@ BoundFilter
dp.filters_factory.bind(ChatIdFilter, event_handlers=[dp.message_handlers])
+
diff --git a/docs/source/examples/adwanced_executor_example.rst b/docs/source/examples/advanced_executor_example.rst
similarity index 74%
rename from docs/source/examples/adwanced_executor_example.rst
rename to docs/source/examples/advanced_executor_example.rst
index 88011e23..9eb5d950 100644
--- a/docs/source/examples/adwanced_executor_example.rst
+++ b/docs/source/examples/advanced_executor_example.rst
@@ -1,28 +1,28 @@
.. Autogenerated file at 2018-10-28 19:31:48.335963
=========================
-Adwanced executor example
+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 adwanced_executor_example.py \
+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 adwanced_executor_example.py --token TOKEN_HERE
+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/adwanced_executor_example.py
- :caption: adwanced_executor_example.py
+.. literalinclude:: ../../../examples/advanced_executor_example.py
+ :caption: advanced_executor_example.py
:language: python
:linenos:
:lines: 25-
diff --git a/docs/source/examples/index.rst b/docs/source/examples/index.rst
index ab417bf4..2bedaa52 100644
--- a/docs/source/examples/index.rst
+++ b/docs/source/examples/index.rst
@@ -6,7 +6,7 @@ Examples
echo_bot
inline_bot
- adwanced_executor_example
+ advanced_executor_example
proxy_and_emojize
finite_state_machine_example
throtling_example
diff --git a/docs/source/index.rst b/docs/source/index.rst
index a39ad5eb..7b6fd231 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -22,18 +22,14 @@ 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.3-blue.svg?style=flat-square&logo=telegram
+ .. image:: https://img.shields.io/badge/Telegram%20Bot%20API-4.4-blue.svg?style=flat-square&logo=telegram
:target: https://core.telegram.org/bots/api
:alt: Telegram Bot API
- .. image:: https://img.shields.io/readthedocs/pip/stable.svg?style=flat-square
- :target: http://aiogram.readthedocs.io/en/latest/?badge=latest?style=flat-square
+ .. image:: https://img.shields.io/readthedocs/aiogram?style=flat-square
+ :target: http://aiogram.readthedocs.io/en/latest/?badge=latest
:alt: Documentation Status
- .. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=flat-square
- :target: https://github.com/python/black
- :alt: Code style: Black
-
.. image:: https://img.shields.io/github/issues/aiogram/aiogram.svg?style=flat-square
:target: https://github.com/aiogram/aiogram/issues
:alt: Github issues
@@ -43,7 +39,7 @@ Welcome to aiogram's documentation!
:alt: MIT License
-**aiogram** is a pretty simple and fully asynchronous library for `Telegram Bot API `_ written in Python 3.7 with `asyncio `_ and `aiohttp `_. It helps you to make your bots faster and simpler.
+**aiogram** is a pretty simple and fully asynchronous framework for `Telegram Bot API `_ written in Python 3.7 with `asyncio `_ and `aiohttp `_. It helps you to make your bots faster and simpler.
Official aiogram resources
diff --git a/docs/source/install.rst b/docs/source/install.rst
index de199af6..b2fd8e38 100644
--- a/docs/source/install.rst
+++ b/docs/source/install.rst
@@ -7,21 +7,34 @@ Using PIP
$ pip install -U aiogram
+Using Pipenv
+------------
+ .. code-block:: bash
+
+ $ pipenv install aiogram
+
+Using AUR
+---------
+*aiogram* is also available in Arch User Repository, so you can install this library on any Arch-based distribution like ArchLinux, Antergos, Manjaro, etc. To do this, use your favorite AUR-helper and install `python-aiogram `_ package.
+
From sources
------------
+
+ Development versions:
+
.. code-block:: bash
$ git clone https://github.com/aiogram/aiogram.git
$ cd aiogram
$ python setup.py install
- or if you want to install development version (maybe unstable):
+ Or if you want to install stable version (The same with version form PyPi):
.. code-block:: bash
$ git clone https://github.com/aiogram/aiogram.git
$ cd aiogram
- $ git checkout dev-2.x
+ $ git checkout master
$ python setup.py install
@@ -49,4 +62,36 @@ You can speedup your bots by following next instructions:
$ pip install ujson
+- Use aiohttp speedups
+
+ - Use `cchardet `_ instead of chardet module.
+
+ *cChardet* is high speed universal character encoding detector.
+
+ **Installation:**
+
+ .. code-block:: bash
+
+ $ pip install cchardet
+
+ - Use `aiodns `_ for speeding up DNS resolving.
+
+ *aiodns* provides a simple way for doing asynchronous DNS resolutions.
+
+ **Installation:**
+
+ .. code-block:: bash
+
+ $ pip install aiodns
+
+ - Installing speedups altogether.
+
+ The following will get you ``aiohttp`` along with ``cchardet``, ``aiodns`` and ``brotlipy`` in one bundle.
+
+ **Installation:**
+
+ .. code-block:: bash
+
+ $ pip install aiohttp[speedups]
+
In addition, you don't need do nothing, *aiogram* is automatically starts using that if is found in your environment.
diff --git a/docs/source/utils/auth_widget.rst b/docs/source/utils/auth_widget.rst
index e3a90ef6..95cb3913 100644
--- a/docs/source/utils/auth_widget.rst
+++ b/docs/source/utils/auth_widget.rst
@@ -1,4 +1,6 @@
===========
Auth Widget
===========
-Coming soon...
+
+.. automodule:: aiogram.utils.auth_widget
+ :members:
diff --git a/docs/source/utils/context.rst b/docs/source/utils/context.rst
deleted file mode 100644
index 7a930a7e..00000000
--- a/docs/source/utils/context.rst
+++ /dev/null
@@ -1,4 +0,0 @@
-=======
-Context
-=======
-Coming soon...
diff --git a/docs/source/utils/deprecated.rst b/docs/source/utils/deprecated.rst
index 0a7b4089..619224f8 100644
--- a/docs/source/utils/deprecated.rst
+++ b/docs/source/utils/deprecated.rst
@@ -1,4 +1,6 @@
==========
Deprecated
==========
-Coming soon...
+
+.. automodule:: aiogram.utils.deprecated
+ :members:
diff --git a/docs/source/utils/emoji.rst b/docs/source/utils/emoji.rst
index 27382dd6..1be210e3 100644
--- a/docs/source/utils/emoji.rst
+++ b/docs/source/utils/emoji.rst
@@ -1,4 +1,6 @@
=====
Emoji
=====
-Coming soon...
+
+.. automodule:: aiogram.utils.emoji
+ :members:
diff --git a/docs/source/utils/exceptions.rst b/docs/source/utils/exceptions.rst
index 199e67aa..b296afd3 100644
--- a/docs/source/utils/exceptions.rst
+++ b/docs/source/utils/exceptions.rst
@@ -1,4 +1,6 @@
==========
Exceptions
==========
-Coming soon...
+
+.. automodule:: aiogram.utils.exceptions
+ :members:
diff --git a/docs/source/utils/executor.rst b/docs/source/utils/executor.rst
index 2cb8eaa1..f88dd8c5 100644
--- a/docs/source/utils/executor.rst
+++ b/docs/source/utils/executor.rst
@@ -1,4 +1,7 @@
========
Executor
========
-Coming soon...
+
+.. automodule:: aiogram.utils.executor
+ :members:
+
diff --git a/docs/source/utils/helper.rst b/docs/source/utils/helper.rst
index 4ffc74ab..ba8bf016 100644
--- a/docs/source/utils/helper.rst
+++ b/docs/source/utils/helper.rst
@@ -1,4 +1,6 @@
======
Helper
======
-Coming soon...
+
+.. automodule:: aiogram.utils.helper
+ :members:
diff --git a/docs/source/utils/index.rst b/docs/source/utils/index.rst
index bc4a52ed..1ac3777c 100644
--- a/docs/source/utils/index.rst
+++ b/docs/source/utils/index.rst
@@ -3,14 +3,13 @@ Utils
.. toctree::
+ auth_widget
executor
exceptions
- context
markdown
helper
- auth_widget
+ deprecated
payload
parts
json
emoji
- deprecated
diff --git a/docs/source/utils/json.rst b/docs/source/utils/json.rst
index 84833031..68577ff4 100644
--- a/docs/source/utils/json.rst
+++ b/docs/source/utils/json.rst
@@ -1,4 +1,6 @@
====
JSON
====
-Coming soon...
+
+.. automodule:: aiogram.utils.json
+ :members:
diff --git a/docs/source/utils/markdown.rst b/docs/source/utils/markdown.rst
index ee32dfd4..bcbe0497 100644
--- a/docs/source/utils/markdown.rst
+++ b/docs/source/utils/markdown.rst
@@ -1,4 +1,6 @@
========
Markdown
========
-Coming soon...
+
+.. automodule:: aiogram.utils.markdown
+ :members:
diff --git a/docs/source/utils/parts.rst b/docs/source/utils/parts.rst
index 845d017e..fd2e91de 100644
--- a/docs/source/utils/parts.rst
+++ b/docs/source/utils/parts.rst
@@ -1,4 +1,6 @@
=====
Parts
=====
-Coming soon...
+
+.. automodule:: aiogram.utils.parts
+ :members:
diff --git a/docs/source/utils/payload.rst b/docs/source/utils/payload.rst
index b3427906..e3e0331a 100644
--- a/docs/source/utils/payload.rst
+++ b/docs/source/utils/payload.rst
@@ -1,4 +1,6 @@
=======
Payload
=======
-Coming soon...
+
+.. automodule:: aiogram.utils.payload
+ :members:
diff --git a/examples/admin_filter_example.py b/examples/admin_filter_example.py
new file mode 100644
index 00000000..ec8746bb
--- /dev/null
+++ b/examples/admin_filter_example.py
@@ -0,0 +1,33 @@
+import logging
+
+from aiogram import Bot, Dispatcher, types, executor
+
+API_TOKEN = 'API_TOKEN_HERE'
+
+
+logging.basicConfig(level=logging.DEBUG)
+
+bot = Bot(token=API_TOKEN)
+dp = Dispatcher(bot=bot)
+
+
+# checks specified chat
+@dp.message_handler(is_chat_admin=-1001241113577)
+async def handle_specified(msg: types.Message):
+ await msg.answer("You are an admin of the specified chat!")
+
+
+# checks multiple chats
+@dp.message_handler(is_chat_admin=[-1001241113577, -320463906])
+async def handle_multiple(msg: types.Message):
+ await msg.answer("You are an admin of multiple chats!")
+
+
+# checks current chat
+@dp.message_handler(is_chat_admin=True)
+async def handler3(msg: types.Message):
+ await msg.answer("You are an admin of the current chat!")
+
+
+if __name__ == '__main__':
+ executor.start_polling(dp)
diff --git a/examples/adwanced_executor_example.py b/examples/advanced_executor_example.py
similarity index 97%
rename from examples/adwanced_executor_example.py
rename to examples/advanced_executor_example.py
index c32bfa2d..6c226ba1 100644
--- a/examples/adwanced_executor_example.py
+++ b/examples/advanced_executor_example.py
@@ -4,7 +4,7 @@
In this example used ArgumentParser for configuring Your bot.
Provided to start bot with webhook:
- python adwanced_executor_example.py \
+ python advanced_executor_example.py \
--token TOKEN_HERE \
--host 0.0.0.0 \
--port 8084 \
@@ -12,7 +12,7 @@ Provided to start bot with webhook:
--webhook-port 443
Or long polling:
- python adwanced_executor_example.py --token TOKEN_HERE
+ python advanced_executor_example.py --token TOKEN_HERE
So... In this example found small trouble:
can't get bot instance in handlers.
diff --git a/examples/broadcast_example.py b/examples/broadcast_example.py
index 1f41e8bc..848739d3 100644
--- a/examples/broadcast_example.py
+++ b/examples/broadcast_example.py
@@ -9,9 +9,8 @@ API_TOKEN = "BOT TOKEN HERE"
logging.basicConfig(level=logging.INFO)
log = logging.getLogger("broadcast")
-loop = asyncio.get_event_loop()
-bot = Bot(token=API_TOKEN, loop=loop, parse_mode=types.ParseMode.HTML)
-dp = Dispatcher(bot, loop=loop)
+bot = Bot(token=API_TOKEN, parse_mode=types.ParseMode.HTML)
+dp = Dispatcher(bot)
def get_users():
diff --git a/examples/callback_data_factory.py b/examples/callback_data_factory.py
index a76d5d21..9a8affe9 100644
--- a/examples/callback_data_factory.py
+++ b/examples/callback_data_factory.py
@@ -1,4 +1,3 @@
-import asyncio
import logging
import random
import uuid
@@ -11,27 +10,26 @@ from aiogram.utils.exceptions import MessageNotModified, Throttled
logging.basicConfig(level=logging.INFO)
-API_TOKEN = "BOT TOKEN HERE"
+API_TOKEN = 'BOT TOKEN HERE'
-loop = asyncio.get_event_loop()
-bot = Bot(token=API_TOKEN, loop=loop, parse_mode=types.ParseMode.HTML)
+
+bot = Bot(token=API_TOKEN, parse_mode=types.ParseMode.HTML)
storage = MemoryStorage()
dp = Dispatcher(bot, storage=storage)
dp.middleware.setup(LoggingMiddleware())
POSTS = {
str(uuid.uuid4()): {
- "title": f"Post {index}",
- "body": "Lorem ipsum dolor sit amet, "
- "consectetur adipiscing elit, "
- "sed do eiusmod tempor incididunt ut "
- "labore et dolore magna aliqua",
- "votes": random.randint(-2, 5),
- }
- for index in range(1, 6)
+ 'title': f'Post {index}',
+ 'body': 'Lorem ipsum dolor sit amet, '
+ 'consectetur adipiscing elit, '
+ 'sed do eiusmod tempor incididunt ut '
+ 'labore et dolore magna aliqua',
+ 'votes': random.randint(-2, 5),
+ } for index in range(1, 6)
}
-posts_cb = CallbackData("post", "id", "action") # post::
+posts_cb = CallbackData('post', 'id', 'action') # post::
def get_keyboard() -> types.InlineKeyboardMarkup:
@@ -42,73 +40,72 @@ def get_keyboard() -> types.InlineKeyboardMarkup:
for post_id, post in POSTS.items():
markup.add(
types.InlineKeyboardButton(
- post["title"], callback_data=posts_cb.new(id=post_id, action="view")
- )
+ post['title'],
+ callback_data=posts_cb.new(id=post_id, action='view')),
)
return markup
def format_post(post_id: str, post: dict) -> (str, types.InlineKeyboardMarkup):
- text = (
- f"{md.hbold(post['title'])}\n"
- f"{md.quote_html(post['body'])}\n"
- f"\n"
- f"Votes: {post['votes']}"
+ text = md.text(
+ md.hbold(post['title']),
+ md.quote_html(post['body']),
+ '', # just new empty line
+ f"Votes: {post['votes']}",
+ sep = '\n',
)
markup = types.InlineKeyboardMarkup()
markup.row(
- types.InlineKeyboardButton("👍", callback_data=posts_cb.new(id=post_id, action="like")),
- types.InlineKeyboardButton("👎", callback_data=posts_cb.new(id=post_id, action="unlike")),
- )
- markup.add(
- types.InlineKeyboardButton("<< Back", callback_data=posts_cb.new(id="-", action="list"))
+ types.InlineKeyboardButton('👍', callback_data=posts_cb.new(id=post_id, action='like')),
+ types.InlineKeyboardButton('👎', callback_data=posts_cb.new(id=post_id, action='dislike')),
)
+ markup.add(types.InlineKeyboardButton('<< Back', callback_data=posts_cb.new(id='-', action='list')))
return text, markup
-@dp.message_handler(commands="start")
+@dp.message_handler(commands='start')
async def cmd_start(message: types.Message):
- await message.reply("Posts", reply_markup=get_keyboard())
+ await message.reply('Posts', reply_markup=get_keyboard())
-@dp.callback_query_handler(posts_cb.filter(action="list"))
+@dp.callback_query_handler(posts_cb.filter(action='list'))
async def query_show_list(query: types.CallbackQuery):
- await query.message.edit_text("Posts", reply_markup=get_keyboard())
+ await query.message.edit_text('Posts', reply_markup=get_keyboard())
-@dp.callback_query_handler(posts_cb.filter(action="view"))
+@dp.callback_query_handler(posts_cb.filter(action='view'))
async def query_view(query: types.CallbackQuery, callback_data: dict):
- post_id = callback_data["id"]
+ post_id = callback_data['id']
post = POSTS.get(post_id, None)
if not post:
- return await query.answer("Unknown post!")
+ return await query.answer('Unknown post!')
text, markup = format_post(post_id, post)
await query.message.edit_text(text, reply_markup=markup)
-@dp.callback_query_handler(posts_cb.filter(action=["like", "unlike"]))
+@dp.callback_query_handler(posts_cb.filter(action=['like', 'dislike']))
async def query_post_vote(query: types.CallbackQuery, callback_data: dict):
try:
- await dp.throttle("vote", rate=1)
+ await dp.throttle('vote', rate=1)
except Throttled:
- return await query.answer("Too many requests.")
+ return await query.answer('Too many requests.')
- post_id = callback_data["id"]
- action = callback_data["action"]
+ post_id = callback_data['id']
+ action = callback_data['action']
post = POSTS.get(post_id, None)
if not post:
- return await query.answer("Unknown post!")
+ return await query.answer('Unknown post!')
- if action == "like":
- post["votes"] += 1
- elif action == "unlike":
- post["votes"] -= 1
+ if action == 'like':
+ post['votes'] += 1
+ elif action == 'dislike':
+ post['votes'] -= 1
- await query.answer("Voted.")
+ await query.answer('Vote accepted')
text, markup = format_post(post_id, post)
await query.message.edit_text(text, reply_markup=markup)
@@ -118,5 +115,5 @@ async def message_not_modified_handler(update, error):
return True
-if __name__ == "__main__":
- executor.start_polling(dp, loop=loop, skip_updates=True)
+if __name__ == '__main__':
+ executor.start_polling(dp, skip_updates=True)
diff --git a/examples/callback_data_factory_simple.py b/examples/callback_data_factory_simple.py
new file mode 100644
index 00000000..5fc9c548
--- /dev/null
+++ b/examples/callback_data_factory_simple.py
@@ -0,0 +1,68 @@
+"""
+This is a simple example of usage of CallbackData factory
+For more comprehensive example see callback_data_factory.py
+"""
+
+import logging
+
+from aiogram import Bot, Dispatcher, executor, types
+from aiogram.contrib.middlewares.logging import LoggingMiddleware
+from aiogram.utils.callback_data import CallbackData
+from aiogram.utils.exceptions import MessageNotModified
+
+logging.basicConfig(level=logging.INFO)
+
+API_TOKEN = 'BOT_TOKEN_HERE'
+
+
+bot = Bot(token=API_TOKEN)
+
+dp = Dispatcher(bot)
+dp.middleware.setup(LoggingMiddleware())
+
+vote_cb = CallbackData('vote', 'action') # vote:
+likes = {} # user_id: amount_of_likes
+
+
+def get_keyboard():
+ return types.InlineKeyboardMarkup().row(
+ types.InlineKeyboardButton('👍', callback_data=vote_cb.new(action='up')),
+ types.InlineKeyboardButton('👎', callback_data=vote_cb.new(action='down')),
+ )
+
+
+@dp.message_handler(commands=['start'])
+async def cmd_start(message: types.Message):
+ amount_of_likes = likes.get(message.from_user.id, 0) # get value if key exists else set to 0
+ await message.reply(f'Vote! You have {amount_of_likes} votes now.', reply_markup=get_keyboard())
+
+
+@dp.callback_query_handler(vote_cb.filter(action=['up', 'down']))
+async def callback_vote_action(query: types.CallbackQuery, callback_data: dict):
+ logging.info('Got this callback data: %r', callback_data) # callback_data contains all info from callback data
+ await query.answer() # don't forget to answer callback query as soon as possible
+ callback_data_action = callback_data['action']
+ likes_count = likes.get(query.from_user.id, 0)
+
+ if callback_data_action == 'up':
+ likes_count += 1
+ else:
+ likes_count -= 1
+
+ likes[query.from_user.id] = likes_count # update amount of likes in storage
+
+ await bot.edit_message_text(
+ f'You voted {callback_data_action}! Now you have {likes_count} vote[s].',
+ query.from_user.id,
+ query.message.message_id,
+ reply_markup=get_keyboard(),
+ )
+
+
+@dp.errors_handler(exception=MessageNotModified) # handle the cases when this exception raises
+async def message_not_modified_handler(update, error):
+ return True
+
+
+if __name__ == '__main__':
+ executor.start_polling(dp, skip_updates=True)
diff --git a/examples/check_user_language.py b/examples/check_user_language.py
index 5bc54f17..98bed8a6 100644
--- a/examples/check_user_language.py
+++ b/examples/check_user_language.py
@@ -2,17 +2,16 @@
Babel is required.
"""
-import asyncio
import logging
from aiogram import Bot, Dispatcher, executor, md, types
-API_TOKEN = "BOT TOKEN HERE"
+API_TOKEN = 'BOT TOKEN HERE'
logging.basicConfig(level=logging.INFO)
-loop = asyncio.get_event_loop()
-bot = Bot(token=API_TOKEN, loop=loop, parse_mode=types.ParseMode.MARKDOWN)
+
+bot = Bot(token=API_TOKEN, parse_mode=types.ParseMode.MARKDOWN)
dp = Dispatcher(bot)
@@ -20,17 +19,15 @@ dp = Dispatcher(bot)
async def check_language(message: types.Message):
locale = message.from_user.locale
- await message.reply(
- md.text(
- md.bold("Info about your language:"),
- md.text(" 🔸", md.bold("Code:"), md.italic(locale.locale)),
- md.text(" 🔸", md.bold("Territory:"), md.italic(locale.territory or "Unknown")),
- md.text(" 🔸", md.bold("Language name:"), md.italic(locale.language_name)),
- md.text(" 🔸", md.bold("English language name:"), md.italic(locale.english_name)),
- sep="\n",
- )
- )
+ await message.reply(md.text(
+ md.bold('Info about your language:'),
+ md.text('🔸', md.bold('Code:'), md.code(locale.language)),
+ md.text('🔸', md.bold('Territory:'), md.code(locale.territory or 'Unknown')),
+ md.text('🔸', md.bold('Language name:'), md.code(locale.language_name)),
+ md.text('🔸', md.bold('English language name:'), md.code(locale.english_name)),
+ sep='\n',
+ ))
-if __name__ == "__main__":
- executor.start_polling(dp, loop=loop, skip_updates=True)
+if __name__ == '__main__':
+ executor.start_polling(dp, skip_updates=True)
diff --git a/examples/echo_bot.py b/examples/echo_bot.py
index 536dc62c..00046f3a 100644
--- a/examples/echo_bot.py
+++ b/examples/echo_bot.py
@@ -7,7 +7,7 @@ import logging
from aiogram import Bot, Dispatcher, executor, types
-API_TOKEN = "BOT TOKEN HERE"
+API_TOKEN = 'BOT TOKEN HERE'
# Configure logging
logging.basicConfig(level=logging.INFO)
@@ -17,29 +17,37 @@ bot = Bot(token=API_TOKEN)
dp = Dispatcher(bot)
-@dp.message_handler(commands=["start", "help"])
+@dp.message_handler(commands=['start', 'help'])
async def send_welcome(message: types.Message):
"""
- This handler will be called when client send `/start` or `/help` commands.
+ This handler will be called when user sends `/start` or `/help` command
"""
await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.")
-@dp.message_handler(regexp="(^cat[s]?$|puss)")
+@dp.message_handler(regexp='(^cat[s]?$|puss)')
async def cats(message: types.Message):
- with open("data/cats.jpg", "rb") as photo:
+ with open('data/cats.jpg', 'rb') as photo:
+ '''
+ # Old fashioned way:
await bot.send_photo(
message.chat.id,
photo,
- caption="Cats is here 😺",
+ caption='Cats are here 😺',
reply_to_message_id=message.message_id,
)
+ '''
+
+ await message.reply_photo(photo, caption='Cats are here 😺')
@dp.message_handler()
async def echo(message: types.Message):
- await bot.send_message(message.chat.id, message.text)
+ # old style:
+ # await bot.send_message(message.chat.id, message.text)
+
+ await message.reply(message.text, reply=False)
-if __name__ == "__main__":
+if __name__ == '__main__':
executor.start_polling(dp, skip_updates=True)
diff --git a/examples/finite_state_machine_example.py b/examples/finite_state_machine_example.py
index c2787ff5..7c0536a7 100644
--- a/examples/finite_state_machine_example.py
+++ b/examples/finite_state_machine_example.py
@@ -1,19 +1,20 @@
-import asyncio
-from typing import Optional
+import logging
import aiogram.utils.markdown as md
from aiogram import Bot, Dispatcher, types
from aiogram.contrib.fsm_storage.memory import MemoryStorage
from aiogram.dispatcher import FSMContext
+from aiogram.dispatcher.filters import Text
from aiogram.dispatcher.filters.state import State, StatesGroup
from aiogram.types import ParseMode
from aiogram.utils import executor
-API_TOKEN = "BOT TOKEN HERE"
+logging.basicConfig(level=logging.INFO)
-loop = asyncio.get_event_loop()
+API_TOKEN = 'BOT TOKEN HERE'
-bot = Bot(token=API_TOKEN, loop=loop)
+
+bot = Bot(token=API_TOKEN)
# For example use simple MemoryStorage for Dispatcher.
storage = MemoryStorage()
@@ -27,7 +28,7 @@ class Form(StatesGroup):
gender = State() # Will be represented in storage as 'Form:gender'
-@dp.message_handler(commands=["start"])
+@dp.message_handler(commands='start')
async def cmd_start(message: types.Message):
"""
Conversation's entry point
@@ -39,21 +40,21 @@ async def cmd_start(message: types.Message):
# You can use state '*' if you need to handle all states
-@dp.message_handler(state="*", commands=["cancel"])
-@dp.message_handler(lambda message: message.text.lower() == "cancel", state="*")
-async def cancel_handler(
- message: types.Message, state: FSMContext, raw_state: Optional[str] = None
-):
+@dp.message_handler(state='*', commands='cancel')
+@dp.message_handler(Text(equals='cancel', ignore_case=True), state='*')
+async def cancel_handler(message: types.Message, state: FSMContext):
"""
Allow user to cancel any action
"""
- if raw_state is None:
+ current_state = await state.get_state()
+ if current_state is None:
return
+ logging.info('Cancelling state %r', current_state)
# Cancel state and inform user about it
await state.finish()
# And remove keyboard (just in case)
- await message.reply("Canceled.", reply_markup=types.ReplyKeyboardRemove())
+ await message.reply('Cancelled.', reply_markup=types.ReplyKeyboardRemove())
@dp.message_handler(state=Form.name)
@@ -62,7 +63,7 @@ async def process_name(message: types.Message, state: FSMContext):
Process user name
"""
async with state.proxy() as data:
- data["name"] = message.text
+ data['name'] = message.text
await Form.next()
await message.reply("How old are you?")
@@ -70,7 +71,7 @@ async def process_name(message: types.Message, state: FSMContext):
# Check age. Age gotta be digit
@dp.message_handler(lambda message: not message.text.isdigit(), state=Form.age)
-async def failed_process_age(message: types.Message):
+async def process_age_invalid(message: types.Message):
"""
If age is invalid
"""
@@ -91,20 +92,18 @@ async def process_age(message: types.Message, state: FSMContext):
await message.reply("What is your gender?", reply_markup=markup)
-@dp.message_handler(
- lambda message: message.text not in ["Male", "Female", "Other"], state=Form.gender
-)
-async def failed_process_gender(message: types.Message):
+@dp.message_handler(lambda message: message.text not in ["Male", "Female", "Other"], state=Form.gender)
+async def process_gender_invalid(message: types.Message):
"""
In this example gender has to be one of: Male, Female, Other.
"""
- return await message.reply("Bad gender name. Choose you gender from keyboard.")
+ return await message.reply("Bad gender name. Choose your gender from the keyboard.")
@dp.message_handler(state=Form.gender)
async def process_gender(message: types.Message, state: FSMContext):
async with state.proxy() as data:
- data["gender"] = message.text
+ data['gender'] = message.text
# Remove keyboard
markup = types.ReplyKeyboardRemove()
@@ -113,18 +112,18 @@ async def process_gender(message: types.Message, state: FSMContext):
await bot.send_message(
message.chat.id,
md.text(
- md.text("Hi! Nice to meet you,", md.bold(data["name"])),
- md.text("Age:", data["age"]),
- md.text("Gender:", data["gender"]),
- sep="\n",
+ md.text('Hi! Nice to meet you,', md.bold(data['name'])),
+ md.text('Age:', md.code(data['age'])),
+ md.text('Gender:', data['gender']),
+ sep='\n',
),
reply_markup=markup,
parse_mode=ParseMode.MARKDOWN,
)
- # Finish conversation
- data.state = None
+ # Finish conversation
+ await state.finish()
-if __name__ == "__main__":
- executor.start_polling(dp, loop=loop, skip_updates=True)
+if __name__ == '__main__':
+ executor.start_polling(dp, skip_updates=True)
diff --git a/examples/i18n_example.py b/examples/i18n_example.py
index 2a353d58..3bb624bd 100644
--- a/examples/i18n_example.py
+++ b/examples/i18n_example.py
@@ -3,6 +3,19 @@ Internalize your bot
Step 1: extract texts
# pybabel extract i18n_example.py -o locales/mybot.pot
+
+ Some useful options:
+ - Extract texts with pluralization support
+ # -k __:1,2
+ - Add comments for translators, you can use another tag if you want (TR)
+ # --add-comments=NOTE
+ - Disable comments with string location in code
+ # --no-location
+ - Set project name
+ # --project=MySuperBot
+ - 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
@@ -24,11 +37,11 @@ from pathlib import Path
from aiogram import Bot, Dispatcher, executor, types
from aiogram.contrib.middlewares.i18n import I18nMiddleware
-TOKEN = "BOT TOKEN HERE"
-I18N_DOMAIN = "mybot"
+TOKEN = 'BOT_TOKEN_HERE'
+I18N_DOMAIN = 'mybot'
BASE_DIR = Path(__file__).parent
-LOCALES_DIR = BASE_DIR / "locales"
+LOCALES_DIR = BASE_DIR / 'locales'
bot = Bot(TOKEN, parse_mode=types.ParseMode.HTML)
dp = Dispatcher(bot)
@@ -41,16 +54,45 @@ dp.middleware.setup(i18n)
_ = i18n.gettext
-@dp.message_handler(commands=["start"])
+@dp.message_handler(commands='start')
async def cmd_start(message: types.Message):
# Simply use `_('message')` instead of `'message'` and never use f-strings for translatable texts.
- await message.reply(_("Hello, {user}!").format(user=message.from_user.full_name))
+ await message.reply(_('Hello, {user}!').format(user=message.from_user.full_name))
-@dp.message_handler(commands=["lang"])
+@dp.message_handler(commands='lang')
async def cmd_lang(message: types.Message, locale):
- await message.reply(_("Your current language: {language}").format(language=locale))
+ # For setting custom lang you have to modify i18n middleware, like this:
+ # https://github.com/aiogram/EventsTrackerBot/blob/master/modules/base/middlewares.py
+ await message.reply(_('Your current language: {language}').format(language=locale))
+
+# If you care about pluralization, here's small handler
+# And also, there's and example of comments for translators. Most translation tools support them.
+
+# Alias for gettext method, parser will understand double underscore as plural (aka ngettext)
+__ = i18n.gettext
-if __name__ == "__main__":
+# some likes manager
+LIKES_STORAGE = {'count': 0}
+
+
+def get_likes() -> int:
+ return LIKES_STORAGE['count']
+
+
+def increase_likes() -> int:
+ LIKES_STORAGE['count'] += 1
+ return get_likes()
+#
+
+
+@dp.message_handler(commands='like')
+async def cmd_like(message: types.Message, locale):
+ likes = increase_likes()
+
+ # 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/id_filter_example.py b/examples/id_filter_example.py
new file mode 100644
index 00000000..343253e3
--- /dev/null
+++ b/examples/id_filter_example.py
@@ -0,0 +1,36 @@
+from aiogram import Bot, Dispatcher, executor, types
+from aiogram.dispatcher.handler import SkipHandler
+
+
+API_TOKEN = 'BOT_TOKEN_HERE'
+bot = Bot(token=API_TOKEN)
+dp = Dispatcher(bot)
+
+user_id_required = None # TODO: Set id here
+chat_id_required = user_id_required # Change for use in groups (user_id == chat_id in pm)
+
+
+@dp.message_handler(user_id=user_id_required)
+async def handler1(msg: types.Message):
+ await bot.send_message(msg.chat.id, "Hello, checking with user_id=")
+ raise SkipHandler # just for demo
+
+
+@dp.message_handler(chat_id=chat_id_required)
+async def handler2(msg: types.Message):
+ await bot.send_message(msg.chat.id, "Hello, checking with chat_id=")
+ raise SkipHandler # just for demo
+
+
+@dp.message_handler(user_id=user_id_required, chat_id=chat_id_required)
+async def handler3(msg: types.Message):
+ await msg.reply("Hello from user= & chat_id=", reply=False)
+
+
+@dp.message_handler(user_id=[user_id_required, 42]) # TODO: You can add any number of ids here
+async def handler4(msg: types.Message):
+ await msg.reply("Checked user_id with list!", reply=False)
+
+
+if __name__ == '__main__':
+ executor.start_polling(dp)
diff --git a/examples/inline_bot.py b/examples/inline_bot.py
index ce3a3c1c..28f83e43 100644
--- a/examples/inline_bot.py
+++ b/examples/inline_bot.py
@@ -1,25 +1,37 @@
-import asyncio
+import hashlib
import logging
-from aiogram import Bot, types, Dispatcher, executor
+from aiogram import Bot, Dispatcher, executor
+from aiogram.types import InlineQuery, \
+ InputTextMessageContent, InlineQueryResultArticle
-API_TOKEN = "BOT TOKEN HERE"
+API_TOKEN = 'BOT_TOKEN_HERE'
logging.basicConfig(level=logging.DEBUG)
-loop = asyncio.get_event_loop()
-bot = Bot(token=API_TOKEN, loop=loop)
+bot = Bot(token=API_TOKEN)
dp = Dispatcher(bot)
@dp.inline_handler()
-async def inline_echo(inline_query: types.InlineQuery):
- input_content = types.InputTextMessageContent(inline_query.query or "echo")
- item = types.InlineQueryResultArticle(
- id="1", title="echo", input_message_content=input_content
+async def inline_echo(inline_query: InlineQuery):
+ # id affects both preview and content,
+ # so it has to be unique for each result
+ # (Unique identifier for this result, 1-64 Bytes)
+ # you can set your unique id's
+ # but for example i'll generate it based on text because I know, that
+ # only text will be passed in this example
+ text = inline_query.query or 'echo'
+ input_content = InputTextMessageContent(text)
+ result_id: str = hashlib.md5(text.encode()).hexdigest()
+ item = InlineQueryResultArticle(
+ id=result_id,
+ title=f'Result {text!r}',
+ input_message_content=input_content,
)
+ # don't forget to set cache_time=1 for testing (default is 300s or 5m)
await bot.answer_inline_query(inline_query.id, results=[item], cache_time=1)
-if __name__ == "__main__":
- executor.start_polling(dp, loop=loop, skip_updates=True)
+if __name__ == '__main__':
+ executor.start_polling(dp, skip_updates=True)
diff --git a/examples/inline_keyboard_example.py b/examples/inline_keyboard_example.py
new file mode 100644
index 00000000..8b950f98
--- /dev/null
+++ b/examples/inline_keyboard_example.py
@@ -0,0 +1,62 @@
+"""
+This bot is created for the demonstration of a usage of inline keyboards.
+"""
+
+import logging
+
+from aiogram import Bot, Dispatcher, executor, types
+
+
+API_TOKEN = 'BOT_TOKEN_HERE'
+
+# Configure logging
+logging.basicConfig(level=logging.INFO)
+
+# Initialize bot and dispatcher
+bot = Bot(token=API_TOKEN)
+dp = Dispatcher(bot)
+
+
+@dp.message_handler(commands='start')
+async def start_cmd_handler(message: types.Message):
+ keyboard_markup = types.InlineKeyboardMarkup(row_width=3)
+ # default row_width is 3, so here we can omit it actually
+ # kept for clearness
+
+ text_and_data = (
+ ('Yes!', 'yes'),
+ ('No!', 'no'),
+ )
+ # in real life for the callback_data the callback data factory should be used
+ # here the raw string is used for the simplicity
+ row_btns = (types.InlineKeyboardButton(text, callback_data=data) for text, data in text_and_data)
+
+ keyboard_markup.row(*row_btns)
+ keyboard_markup.add(
+ # url buttons have no callback data
+ types.InlineKeyboardButton('aiogram source', url='https://github.com/aiogram/aiogram'),
+ )
+
+ await message.reply("Hi!\nDo you love aiogram?", reply_markup=keyboard_markup)
+
+
+# Use multiple registrators. Handler will execute when one of the filters is OK
+@dp.callback_query_handler(text='no') # if cb.data == 'no'
+@dp.callback_query_handler(text='yes') # if cb.data == 'yes'
+async def inline_kb_answer_callback_handler(query: types.CallbackQuery):
+ answer_data = query.data
+ # always answer callback queries, even if you have nothing to say
+ await query.answer(f'You answered with {answer_data!r}')
+
+ if answer_data == 'yes':
+ text = 'Great, me too!'
+ elif answer_data == 'no':
+ text = 'Oh no...Why so?'
+ else:
+ text = f'Unexpected callback data {answer_data!r}!'
+
+ await bot.send_message(query.from_user.id, text)
+
+
+if __name__ == '__main__':
+ executor.start_polling(dp, skip_updates=True)
diff --git a/examples/locales/mybot.pot b/examples/locales/mybot.pot
index 988ed463..62b2d425 100644
--- a/examples/locales/mybot.pot
+++ b/examples/locales/mybot.pot
@@ -1,27 +1,26 @@
# Translations template for PROJECT.
-# Copyright (C) 2018 ORGANIZATION
+# Copyright (C) 2019 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
-# FIRST AUTHOR , 2018.
+# FIRST AUTHOR , 2019.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2018-06-30 03:50+0300\n"
+"POT-Creation-Date: 2019-08-10 17:51+0300\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.6.0\n"
+"Generated-By: Babel 2.7.0\n"
-#: i18n_example.py:48
+#: i18n_example.py:60
msgid "Hello, {user}!"
msgstr ""
-#: i18n_example.py:53
+#: i18n_example.py:67
msgid "Your current language: {language}"
msgstr ""
-
diff --git a/examples/locales/ru/LC_MESSAGES/mybot.po b/examples/locales/ru/LC_MESSAGES/mybot.po
index 73876f30..9064bc0e 100644
--- a/examples/locales/ru/LC_MESSAGES/mybot.po
+++ b/examples/locales/ru/LC_MESSAGES/mybot.po
@@ -1,14 +1,14 @@
# Russian translations for PROJECT.
-# Copyright (C) 2018 ORGANIZATION
+# Copyright (C) 2019 ORGANIZATION
# This file is distributed under the same license as the PROJECT project.
-# FIRST AUTHOR , 2018.
+# FIRST AUTHOR , 2019.
#
msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
-"POT-Creation-Date: 2018-06-30 03:50+0300\n"
-"PO-Revision-Date: 2018-06-30 03:43+0300\n"
+"POT-Creation-Date: 2019-08-10 17:51+0300\n"
+"PO-Revision-Date: 2019-08-10 17:52+0300\n"
"Last-Translator: FULL NAME \n"
"Language: ru\n"
"Language-Team: ru \n"
@@ -17,13 +17,19 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"Generated-By: Babel 2.6.0\n"
+"Generated-By: Babel 2.7.0\n"
-#: i18n_example.py:48
+#: i18n_example.py:60
msgid "Hello, {user}!"
msgstr "Привет, {user}!"
-#: i18n_example.py:53
+#: i18n_example.py:67
msgid "Your current language: {language}"
msgstr "Твой язык: {language}"
+#: i18n_example.py:95
+msgid "Aiogram has {number} like!"
+msgid_plural "Aiogram has {number} likes!"
+msgstr[0] "Aiogram имеет {number} лайк!"
+msgstr[1] "Aiogram имеет {number} лайка!"
+msgstr[2] "Aiogram имеет {number} лайков!"
diff --git a/examples/media_group.py b/examples/media_group.py
index d2a2282a..3d488364 100644
--- a/examples/media_group.py
+++ b/examples/media_group.py
@@ -2,10 +2,10 @@ import asyncio
from aiogram import Bot, Dispatcher, executor, filters, types
-API_TOKEN = "BOT TOKEN HERE"
-loop = asyncio.get_event_loop()
-bot = Bot(token=API_TOKEN, loop=loop)
+API_TOKEN = 'BOT_TOKEN_HERE'
+
+bot = Bot(token=API_TOKEN)
dp = Dispatcher(bot)
@@ -14,23 +14,23 @@ async def send_welcome(message: types.Message):
# So... At first I want to send something like this:
await message.reply("Do you want to see many pussies? Are you ready?")
- # And wait few seconds...
+ # Wait a little...
await asyncio.sleep(1)
- # Good bots should send chat actions. Or not.
+ # Good bots should send chat actions...
await types.ChatActions.upload_photo()
# Create media group
media = types.MediaGroup()
# Attach local file
- media.attach_photo(types.InputFile("data/cat.jpg"), "Cat!")
+ media.attach_photo(types.InputFile('data/cat.jpg'), 'Cat!')
# More local files and more cats!
- media.attach_photo(types.InputFile("data/cats.jpg"), "More cats!")
+ media.attach_photo(types.InputFile('data/cats.jpg'), 'More cats!')
# You can also use URL's
# For example: get random puss:
- media.attach_photo("http://lorempixel.com/400/200/cats/", "Random cat.")
+ media.attach_photo('http://lorempixel.com/400/200/cats/', 'Random cat.')
# And you can also use file ID:
# media.attach_photo('', 'cat-cat-cat.')
@@ -39,5 +39,5 @@ async def send_welcome(message: types.Message):
await message.reply_media_group(media=media)
-if __name__ == "__main__":
- executor.start_polling(dp, loop=loop, skip_updates=True)
+if __name__ == '__main__':
+ executor.start_polling(dp, skip_updates=True)
diff --git a/examples/middleware_and_antiflood.py b/examples/middleware_and_antiflood.py
index c1c96b73..72fabf55 100644
--- a/examples/middleware_and_antiflood.py
+++ b/examples/middleware_and_antiflood.py
@@ -7,14 +7,12 @@ from aiogram.dispatcher.handler import CancelHandler, current_handler
from aiogram.dispatcher.middlewares import BaseMiddleware
from aiogram.utils.exceptions import Throttled
-TOKEN = "BOT TOKEN HERE"
-
-loop = asyncio.get_event_loop()
+TOKEN = 'BOT_TOKEN_HERE'
# In this example Redis storage is used
storage = RedisStorage2(db=5)
-bot = Bot(token=TOKEN, loop=loop)
+bot = Bot(token=TOKEN)
dp = Dispatcher(bot, storage=storage)
@@ -28,9 +26,9 @@ def rate_limit(limit: int, key=None):
"""
def decorator(func):
- setattr(func, "throttling_rate_limit", limit)
+ setattr(func, 'throttling_rate_limit', limit)
if key:
- setattr(func, "throttling_key", key)
+ setattr(func, 'throttling_key', key)
return func
return decorator
@@ -41,7 +39,7 @@ class ThrottlingMiddleware(BaseMiddleware):
Simple middleware
"""
- def __init__(self, limit=DEFAULT_RATE_LIMIT, key_prefix="antiflood_"):
+ def __init__(self, limit=DEFAULT_RATE_LIMIT, key_prefix='antiflood_'):
self.rate_limit = limit
self.prefix = key_prefix
super(ThrottlingMiddleware, self).__init__()
@@ -59,8 +57,8 @@ class ThrottlingMiddleware(BaseMiddleware):
dispatcher = Dispatcher.get_current()
# If handler was configured, get rate limit and key from handler
if handler:
- limit = getattr(handler, "throttling_rate_limit", self.rate_limit)
- key = getattr(handler, "throttling_key", f"{self.prefix}_{handler.__name__}")
+ limit = getattr(handler, 'throttling_rate_limit', self.rate_limit)
+ key = getattr(handler, 'throttling_key', f"{self.prefix}_{handler.__name__}")
else:
limit = self.rate_limit
key = f"{self.prefix}_message"
@@ -85,7 +83,7 @@ class ThrottlingMiddleware(BaseMiddleware):
handler = current_handler.get()
dispatcher = Dispatcher.get_current()
if handler:
- key = getattr(handler, "throttling_key", f"{self.prefix}_{handler.__name__}")
+ key = getattr(handler, 'throttling_key', f"{self.prefix}_{handler.__name__}")
else:
key = f"{self.prefix}_message"
@@ -94,7 +92,7 @@ class ThrottlingMiddleware(BaseMiddleware):
# Prevent flooding
if throttled.exceeded_count <= 2:
- await message.reply("Too many requests! ")
+ await message.reply('Too many requests! ')
# Sleep.
await asyncio.sleep(delta)
@@ -104,21 +102,19 @@ class ThrottlingMiddleware(BaseMiddleware):
# If current message is not last with current key - do not send message
if thr.exceeded_count == throttled.exceeded_count:
- await message.reply("Unlocked.")
+ await message.reply('Unlocked.')
-@dp.message_handler(commands=["start"])
-@rate_limit(
- 5, "start"
-) # this is not required but you can configure throttling manager for current handler using it
+@dp.message_handler(commands=['start'])
+@rate_limit(5, 'start') # this is not required but you can configure throttling manager for current handler using it
async def cmd_test(message: types.Message):
# You can use this command every 5 seconds
- await message.reply("Test passed! You can use this command every 5 seconds.")
+ await message.reply('Test passed! You can use this command every 5 seconds.')
-if __name__ == "__main__":
+if __name__ == '__main__':
# Setup middleware
dp.middleware.setup(ThrottlingMiddleware())
# Start long-polling
- executor.start_polling(dp, loop=loop)
+ executor.start_polling(dp)
diff --git a/examples/payments.py b/examples/payments.py
index 00a4e833..42162578 100644
--- a/examples/payments.py
+++ b/examples/payments.py
@@ -1,121 +1,97 @@
-import asyncio
-
from aiogram import Bot
from aiogram import types
from aiogram.dispatcher import Dispatcher
from aiogram.types.message import ContentTypes
from aiogram.utils import executor
-BOT_TOKEN = "BOT TOKEN HERE"
-PAYMENTS_PROVIDER_TOKEN = "123456789:TEST:1234567890abcdef1234567890abcdef"
-loop = asyncio.get_event_loop()
+BOT_TOKEN = 'BOT_TOKEN_HERE'
+PAYMENTS_PROVIDER_TOKEN = '123456789:TEST:1422'
+
bot = Bot(BOT_TOKEN)
-dp = Dispatcher(bot, loop=loop)
+dp = Dispatcher(bot)
# Setup prices
prices = [
- types.LabeledPrice(label="Working Time Machine", amount=5750),
- types.LabeledPrice(label="Gift wrapping", amount=500),
+ types.LabeledPrice(label='Working Time Machine', amount=5750),
+ types.LabeledPrice(label='Gift wrapping', amount=500),
]
# Setup shipping options
shipping_options = [
- types.ShippingOption(id="instant", title="WorldWide Teleporter").add(
- types.LabeledPrice("Teleporter", 1000)
- ),
- types.ShippingOption(id="pickup", title="Local pickup").add(types.LabeledPrice("Pickup", 300)),
+ types.ShippingOption(id='instant', title='WorldWide Teleporter').add(types.LabeledPrice('Teleporter', 1000)),
+ types.ShippingOption(id='pickup', title='Local pickup').add(types.LabeledPrice('Pickup', 300)),
]
-@dp.message_handler(commands=["start"])
+@dp.message_handler(commands=['start'])
async def cmd_start(message: types.Message):
- await bot.send_message(
- message.chat.id,
- "Hello, I'm the demo merchant bot."
- " I can sell you a Time Machine."
- " Use /buy to order one, /terms for Terms and Conditions",
- )
+ await bot.send_message(message.chat.id,
+ "Hello, I'm the demo merchant bot."
+ " I can sell you a Time Machine."
+ " Use /buy to order one, /terms for Terms and Conditions")
-@dp.message_handler(commands=["terms"])
+@dp.message_handler(commands=['terms'])
async def cmd_terms(message: types.Message):
- await bot.send_message(
- message.chat.id,
- "Thank you for shopping with our demo bot. We hope you like your new time machine!\n"
- "1. If your time machine was not delivered on time, please rethink your concept of time"
- " and try again.\n"
- "2. If you find that your time machine is not working, kindly contact our future service"
- " workshops on Trappist-1e. They will be accessible anywhere between"
- " May 2075 and November 4000 C.E.\n"
- "3. If you would like a refund, kindly apply for one yesterday and we will have sent it"
- " to you immediately.",
- )
+ await bot.send_message(message.chat.id,
+ 'Thank you for shopping with our demo bot. We hope you like your new time machine!\n'
+ '1. If your time machine was not delivered on time, please rethink your concept of time'
+ ' and try again.\n'
+ '2. If you find that your time machine is not working, kindly contact our future service'
+ ' workshops on Trappist-1e. They will be accessible anywhere between'
+ ' May 2075 and November 4000 C.E.\n'
+ '3. If you would like a refund, kindly apply for one yesterday and we will have sent it'
+ ' to you immediately.')
-@dp.message_handler(commands=["buy"])
+@dp.message_handler(commands=['buy'])
async def cmd_buy(message: types.Message):
- await bot.send_message(
- message.chat.id,
- "Real cards won't work with me, no money will be debited from your account."
- " Use this test card number to pay for your Time Machine: `4242 4242 4242 4242`"
- "\n\nThis is your demo invoice:",
- parse_mode="Markdown",
- )
- await bot.send_invoice(
- message.chat.id,
- title="Working Time Machine",
- description="Want to visit your great-great-great-grandparents?"
- " Make a fortune at the races?"
- " Shake hands with Hammurabi and take a stroll in the Hanging Gardens?"
- " Order our Working Time Machine today!",
- provider_token=PAYMENTS_PROVIDER_TOKEN,
- currency="usd",
- photo_url="https://images.fineartamerica.com/images-medium-large/2-the-time-machine-dmitriy-khristenko.jpg",
- photo_height=512, # !=0/None or picture won't be shown
- photo_width=512,
- photo_size=512,
- is_flexible=True, # True If you need to set up Shipping Fee
- prices=prices,
- start_parameter="time-machine-example",
- payload="HAPPY FRIDAYS COUPON",
- )
+ await bot.send_message(message.chat.id,
+ "Real cards won't work with me, no money will be debited from your account."
+ " Use this test card number to pay for your Time Machine: `4242 4242 4242 4242`"
+ "\n\nThis is your demo invoice:", parse_mode='Markdown')
+ await bot.send_invoice(message.chat.id, title='Working Time Machine',
+ description='Want to visit your great-great-great-grandparents?'
+ ' Make a fortune at the races?'
+ ' Shake hands with Hammurabi and take a stroll in the Hanging Gardens?'
+ ' Order our Working Time Machine today!',
+ provider_token=PAYMENTS_PROVIDER_TOKEN,
+ currency='usd',
+ photo_url='https://telegra.ph/file/d08ff863531f10bf2ea4b.jpg',
+ photo_height=512, # !=0/None or picture won't be shown
+ photo_width=512,
+ photo_size=512,
+ is_flexible=True, # True If you need to set up Shipping Fee
+ prices=prices,
+ start_parameter='time-machine-example',
+ payload='HAPPY FRIDAYS COUPON')
-@dp.shipping_query_handler(func=lambda query: True)
+@dp.shipping_query_handler(lambda query: True)
async def shipping(shipping_query: types.ShippingQuery):
- await bot.answer_shipping_query(
- shipping_query.id,
- ok=True,
- shipping_options=shipping_options,
- error_message="Oh, seems like our Dog couriers are having a lunch right now."
- " Try again later!",
- )
+ await bot.answer_shipping_query(shipping_query.id, ok=True, shipping_options=shipping_options,
+ error_message='Oh, seems like our Dog couriers are having a lunch right now.'
+ ' Try again later!')
-@dp.pre_checkout_query_handler(func=lambda query: True)
+@dp.pre_checkout_query_handler(lambda query: True)
async def checkout(pre_checkout_query: types.PreCheckoutQuery):
- await bot.answer_pre_checkout_query(
- pre_checkout_query.id,
- ok=True,
- error_message="Aliens tried to steal your card's CVV,"
- " but we successfully protected your credentials,"
- " try to pay again in a few minutes, we need a small rest.",
- )
+ await bot.answer_pre_checkout_query(pre_checkout_query.id, ok=True,
+ error_message="Aliens tried to steal your card's CVV,"
+ " but we successfully protected your credentials,"
+ " try to pay again in a few minutes, we need a small rest.")
@dp.message_handler(content_types=ContentTypes.SUCCESSFUL_PAYMENT)
async def got_payment(message: types.Message):
- await bot.send_message(
- message.chat.id,
- "Hoooooray! Thanks for payment! We will proceed your order for `{} {}`"
- " as fast as possible! Stay in touch."
- "\n\nUse /buy again to get a Time Machine for your friend!".format(
- message.successful_payment.total_amount / 100, message.successful_payment.currency
- ),
- parse_mode="Markdown",
- )
+ await bot.send_message(message.chat.id,
+ 'Hoooooray! Thanks for payment! We will proceed your order for `{} {}`'
+ ' as fast as possible! Stay in touch.'
+ '\n\nUse /buy again to get a Time Machine for your friend!'.format(
+ message.successful_payment.total_amount / 100, message.successful_payment.currency),
+ parse_mode='Markdown')
-if __name__ == "__main__":
- executor.start_polling(dp, loop=loop)
+if __name__ == '__main__':
+ executor.start_polling(dp, skip_updates=True)
diff --git a/examples/proxy_and_emojize.py b/examples/proxy_and_emojize.py
index 7c78eed9..5ef40608 100644
--- a/examples/proxy_and_emojize.py
+++ b/examples/proxy_and_emojize.py
@@ -1,4 +1,3 @@
-import asyncio
import logging
import aiohttp
@@ -11,49 +10,52 @@ from aiogram.utils.executor import start_polling
from aiogram.utils.markdown import bold, code, italic, text
# Configure bot here
-API_TOKEN = "BOT TOKEN HERE"
-PROXY_URL = "http://PROXY_URL" # Or 'socks5://...'
+API_TOKEN = 'BOT_TOKEN_HERE'
+PROXY_URL = 'http://PROXY_URL' # Or 'socks5://host:port'
-# If authentication is required in your proxy then uncomment next line and change login/password for it
+# NOTE: If authentication is required in your proxy then uncomment next line and change login/password for it
# PROXY_AUTH = aiohttp.BasicAuth(login='login', password='password')
-# And add `proxy_auth=PROXY_AUTH` argument in line 25, like this:
-# >>> bot = Bot(token=API_TOKEN, loop=loop, proxy=PROXY_URL, proxy_auth=PROXY_AUTH)
+# And add `proxy_auth=PROXY_AUTH` argument in line 30, like this:
+# >>> bot = Bot(token=API_TOKEN, proxy=PROXY_URL, proxy_auth=PROXY_AUTH)
# Also you can use Socks5 proxy but you need manually install aiohttp_socks package.
# Get my ip URL
-GET_IP_URL = "http://bot.whatismyipaddress.com/"
+GET_IP_URL = 'http://bot.whatismyipaddress.com/'
logging.basicConfig(level=logging.INFO)
-loop = asyncio.get_event_loop()
-bot = Bot(token=API_TOKEN, loop=loop, proxy=PROXY_URL)
+bot = Bot(token=API_TOKEN, proxy=PROXY_URL)
+
+# If auth is required:
+# bot = Bot(token=API_TOKEN, proxy=PROXY_URL, proxy_auth=PROXY_AUTH)
dp = Dispatcher(bot)
-async def fetch(url, proxy=None, proxy_auth=None):
- async with aiohttp.ClientSession() as session:
- async with session.get(url, proxy=proxy, proxy_auth=proxy_auth) as response:
- return await response.text()
+async def fetch(url, session):
+ async with session.get(url) as response:
+ return await response.text()
-@dp.message_handler(commands=["start"])
+@dp.message_handler(commands=['start'])
async def cmd_start(message: types.Message):
+ # fetching urls will take some time, so notify user that everything is OK
+ await types.ChatActions.typing()
+
content = []
# Make request (without proxy)
- ip = await fetch(GET_IP_URL)
- content.append(text(":globe_showing_Americas:", bold("IP:"), code(ip)))
+ async with aiohttp.ClientSession() as session:
+ ip = await fetch(GET_IP_URL, session)
+ content.append(text(':globe_showing_Americas:', bold('IP:'), code(ip)))
# This line is formatted to '🌎 *IP:* `YOUR IP`'
- # Make request through proxy
- ip = await fetch(GET_IP_URL, bot.proxy, bot.proxy_auth)
- content.append(text(":locked_with_key:", bold("IP:"), code(ip), italic("via proxy")))
+ # Make request through bot's proxy
+ ip = await fetch(GET_IP_URL, bot.session)
+ content.append(text(':locked_with_key:', bold('IP:'), code(ip), italic('via proxy')))
# This line is formatted to '🔐 *IP:* `YOUR IP` _via proxy_'
# Send content
- await bot.send_message(
- message.chat.id, emojize(text(*content, sep="\n")), parse_mode=ParseMode.MARKDOWN
- )
+ await bot.send_message(message.chat.id, emojize(text(*content, sep='\n')), parse_mode=ParseMode.MARKDOWN)
# In this example you can see emoji codes: ":globe_showing_Americas:" and ":locked_with_key:"
# You can find full emoji cheat sheet at https://www.webpagefx.com/tools/emoji-cheat-sheet/
@@ -63,5 +65,5 @@ async def cmd_start(message: types.Message):
# For example emojize('Moon face :new_moon_face:') is transformed to 'Moon face 🌚'
-if __name__ == "__main__":
- start_polling(dp, loop=loop, skip_updates=True)
+if __name__ == '__main__':
+ start_polling(dp, skip_updates=True)
diff --git a/examples/regexp_commands_filter_example.py b/examples/regexp_commands_filter_example.py
index 260a513a..05de9dd8 100644
--- a/examples/regexp_commands_filter_example.py
+++ b/examples/regexp_commands_filter_example.py
@@ -2,16 +2,28 @@ from aiogram import Bot, types
from aiogram.dispatcher import Dispatcher, filters
from aiogram.utils import executor
-bot = Bot(token="TOKEN")
+
+bot = Bot(token='BOT_TOKEN_HERE', parse_mode=types.ParseMode.HTML)
dp = Dispatcher(bot)
-@dp.message_handler(filters.RegexpCommandsFilter(regexp_commands=["item_([0-9]*)"]))
+@dp.message_handler(filters.RegexpCommandsFilter(regexp_commands=['item_([0-9]*)']))
async def send_welcome(message: types.Message, regexp_command):
- await message.reply(
- "You have requested an item with number: {}".format(regexp_command.group(1))
+ await message.reply(f"You have requested an item with id {regexp_command.group(1)}")
+
+
+@dp.message_handler(commands='start')
+async def create_deeplink(message: types.Message):
+ bot_user = await bot.me
+ bot_username = bot_user.username
+ deeplink = f'https://t.me/{bot_username}?start=item_12345'
+ text = (
+ f'Either send a command /item_1234 or follow this link {deeplink} and then click start\n'
+ 'It also can be hidden in a inline button\n\n'
+ 'Or just send /start item_123'
)
+ await message.reply(text, disable_web_page_preview=True)
-if __name__ == "__main__":
- executor.start_polling(dp)
+if __name__ == '__main__':
+ executor.start_polling(dp, skip_updates=True)
diff --git a/examples/regular_keyboard_example.py b/examples/regular_keyboard_example.py
new file mode 100644
index 00000000..d111053c
--- /dev/null
+++ b/examples/regular_keyboard_example.py
@@ -0,0 +1,67 @@
+"""
+This bot is created for the demonstration of a usage of regular keyboards.
+"""
+
+import logging
+
+from aiogram import Bot, Dispatcher, executor, types
+
+
+API_TOKEN = 'BOT_TOKEN_HERE'
+
+# Configure logging
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+logger.setLevel(logging.DEBUG)
+
+# Initialize bot and dispatcher
+bot = Bot(token=API_TOKEN)
+dp = Dispatcher(bot)
+
+
+@dp.message_handler(commands='start')
+async def start_cmd_handler(message: types.Message):
+ keyboard_markup = types.ReplyKeyboardMarkup(row_width=3)
+ # default row_width is 3, so here we can omit it actually
+ # kept for clearness
+
+ btns_text = ('Yes!', 'No!')
+ keyboard_markup.row(*(types.KeyboardButton(text) for text in btns_text))
+ # adds buttons as a new row to the existing keyboard
+ # the behaviour doesn't depend on row_width attribute
+
+ more_btns_text = (
+ "I don't know",
+ "Who am i?",
+ "Where am i?",
+ "Who is there?",
+ )
+ keyboard_markup.add(*(types.KeyboardButton(text) for text in more_btns_text))
+ # adds buttons. New rows are formed according to row_width parameter
+
+ await message.reply("Hi!\nDo you like aiogram?", reply_markup=keyboard_markup)
+
+
+@dp.message_handler()
+async def all_msg_handler(message: types.Message):
+ # pressing of a KeyboardButton is the same as sending the regular message with the same text
+ # so, to handle the responses from the keyboard, we need to use a message_handler
+ # in real bot, it's better to define message_handler(text="...") for each button
+ # but here for the simplicity only one handler is defined
+
+ button_text = message.text
+ logger.debug('The answer is %r', button_text) # print the text we've got
+
+ if button_text == 'Yes!':
+ reply_text = "That's great"
+ elif button_text == 'No!':
+ reply_text = "Oh no! Why?"
+ else:
+ reply_text = "Keep calm...Everything is fine"
+
+ await message.reply(reply_text, reply_markup=types.ReplyKeyboardRemove())
+ # with message, we send types.ReplyKeyboardRemove() to hide the keyboard
+
+
+if __name__ == '__main__':
+ executor.start_polling(dp, skip_updates=True)
diff --git a/examples/text_filter_example.py b/examples/text_filter_example.py
new file mode 100644
index 00000000..60d631e3
--- /dev/null
+++ b/examples/text_filter_example.py
@@ -0,0 +1,53 @@
+"""
+This is a bot to show the usage of the builtin Text filter
+Instead of a list, a single element can be passed to any filter, it will be treated as list with an element
+"""
+
+import logging
+
+from aiogram import Bot, Dispatcher, executor, types
+
+
+API_TOKEN = 'BOT_TOKEN_HERE'
+
+# Configure logging
+logging.basicConfig(level=logging.INFO)
+
+# Initialize bot and dispatcher
+bot = Bot(token=API_TOKEN)
+dp = Dispatcher(bot)
+
+
+# if the text from user 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!")
+
+
+# if the text contains any string
+@dp.message_handler(text_contains='example1')
+@dp.message_handler(text_contains='example2')
+async def text_contains_any_handler(message: types.Message):
+ await message.answer("The message text contains any of strings")
+
+
+# if the text contains all the strings from the list
+@dp.message_handler(text_contains=['str1', 'str2'])
+async def text_contains_all_handler(message: types.Message):
+ await message.answer("The message text contains all strings from the list")
+
+
+# if the text starts with any string from the list
+@dp.message_handler(text_startswith=['prefix1', 'prefix2'])
+async def text_startswith_handler(message: types.Message):
+ await message.answer("The message text starts with any of prefixes")
+
+
+# if the text ends with any string from the list
+@dp.message_handler(text_endswith=['postfix1', 'postfix2'])
+async def text_endswith_handler(message: types.Message):
+ await message.answer("The message text ends with any of postfixes")
+
+
+if __name__ == '__main__':
+ executor.start_polling(dp, skip_updates=True)
diff --git a/examples/throtling_example.py b/examples/throtling_example.py
deleted file mode 100644
index 31e962ec..00000000
--- a/examples/throtling_example.py
+++ /dev/null
@@ -1,43 +0,0 @@
-"""
-Example for throttling manager.
-
-You can use that for flood controlling.
-"""
-
-import asyncio
-import logging
-
-from aiogram import Bot, types
-from aiogram.contrib.fsm_storage.memory import MemoryStorage
-from aiogram.dispatcher import Dispatcher
-from aiogram.utils.exceptions import Throttled
-from aiogram.utils.executor import start_polling
-
-API_TOKEN = "BOT TOKEN HERE"
-
-logging.basicConfig(level=logging.INFO)
-
-loop = asyncio.get_event_loop()
-bot = Bot(token=API_TOKEN, loop=loop)
-
-# Throttling manager does not work without Leaky Bucket.
-# Then need to use storages. For example use simple in-memory storage.
-storage = MemoryStorage()
-dp = Dispatcher(bot, storage=storage)
-
-
-@dp.message_handler(commands=["start", "help"])
-async def send_welcome(message: types.Message):
- try:
- # Execute throttling manager with rate-limit equal to 2 seconds for key "start"
- await dp.throttle("start", rate=2)
- except Throttled:
- # If request is throttled, the `Throttled` exception will be raised
- await message.reply("Too many requests!")
- else:
- # Otherwise do something
- await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.")
-
-
-if __name__ == "__main__":
- start_polling(dp, loop=loop, skip_updates=True)
diff --git a/examples/throttling_example.py b/examples/throttling_example.py
new file mode 100644
index 00000000..f9ad1c67
--- /dev/null
+++ b/examples/throttling_example.py
@@ -0,0 +1,71 @@
+"""
+Example for throttling manager.
+
+You can use that for flood controlling.
+"""
+
+import logging
+
+from aiogram import Bot, types
+from aiogram.contrib.fsm_storage.memory import MemoryStorage
+from aiogram.dispatcher import Dispatcher
+from aiogram.utils.exceptions import Throttled
+from aiogram.utils.executor import start_polling
+
+
+API_TOKEN = 'BOT_TOKEN_HERE'
+
+logging.basicConfig(level=logging.INFO)
+
+bot = Bot(token=API_TOKEN)
+
+# Throttling manager does not work without Leaky Bucket.
+# You need to use a storage. For example use simple in-memory storage.
+storage = MemoryStorage()
+dp = Dispatcher(bot, storage=storage)
+
+
+@dp.message_handler(commands=['start'])
+async def send_welcome(message: types.Message):
+ try:
+ # Execute throttling manager with rate-limit equal to 2 seconds for key "start"
+ await dp.throttle('start', rate=2)
+ except Throttled:
+ # If request is throttled, the `Throttled` exception will be raised
+ await message.reply('Too many requests!')
+ else:
+ # Otherwise do something
+ await message.reply("Hi!\nI'm EchoBot!\nPowered by aiogram.")
+
+
+@dp.message_handler(commands=['hi'])
+@dp.throttled(lambda msg, loop, *args, **kwargs: loop.create_task(bot.send_message(msg.from_user.id, "Throttled")),
+ rate=5)
+# loop is added to the function to run coroutines from it
+async def say_hi(message: types.Message):
+ await message.answer("Hi")
+
+
+# the on_throttled object can be either a regular function or coroutine
+async def hello_throttled(*args, **kwargs):
+ # args will be the same as in the original handler
+ # kwargs will be updated with parameters given to .throttled (rate, key, user_id, chat_id)
+ print(f"hello_throttled was called with args={args} and kwargs={kwargs}")
+ message = args[0] # as message was the first argument in the original handler
+ await message.answer("Throttled")
+
+
+@dp.message_handler(commands=['hello'])
+@dp.throttled(hello_throttled, rate=4)
+async def say_hello(message: types.Message):
+ await message.answer("Hello!")
+
+
+@dp.message_handler(commands=['help'])
+@dp.throttled(rate=5)
+# nothing will happen if the handler will be throttled
+async def help_handler(message: types.Message):
+ await message.answer('Help!')
+
+if __name__ == '__main__':
+ start_polling(dp, skip_updates=True)
diff --git a/examples/webhook_example.py b/examples/webhook_example.py
index db4fbb50..6efa8767 100644
--- a/examples/webhook_example.py
+++ b/examples/webhook_example.py
@@ -1,199 +1,66 @@
-"""
-Example outdated
-"""
+import logging
-import asyncio
-import ssl
-import sys
-
-from aiohttp import web
-
-import aiogram
from aiogram import Bot, types
-from aiogram.contrib.fsm_storage.memory import MemoryStorage
+from aiogram.contrib.middlewares.logging import LoggingMiddleware
from aiogram.dispatcher import Dispatcher
-from aiogram.dispatcher.webhook import get_new_configured_app, SendMessage
-from aiogram.types import ChatType, ParseMode, ContentTypes
-from aiogram.utils.markdown import hbold, bold, text, link
+from aiogram.dispatcher.webhook import SendMessage
+from aiogram.utils.executor import start_webhook
-TOKEN = "BOT TOKEN HERE"
-WEBHOOK_HOST = "example.com" # Domain name or IP addres which your bot is located.
-WEBHOOK_PORT = 443 # Telegram Bot API allows only for usage next ports: 443, 80, 88 or 8443
-WEBHOOK_URL_PATH = "/webhook" # Part of URL
+API_TOKEN = 'BOT_TOKEN_HERE'
-# This options needed if you use self-signed SSL certificate
-# Instructions: https://core.telegram.org/bots/self-signed
-WEBHOOK_SSL_CERT = "./webhook_cert.pem" # Path to the ssl certificate
-WEBHOOK_SSL_PRIV = "./webhook_pkey.pem" # Path to the ssl private key
+# webhook settings
+WEBHOOK_HOST = 'https://your.domain'
+WEBHOOK_PATH = '/path/to/api'
+WEBHOOK_URL = f"{WEBHOOK_HOST}{WEBHOOK_PATH}"
-WEBHOOK_URL = f"https://{WEBHOOK_HOST}:{WEBHOOK_PORT}{WEBHOOK_URL_PATH}"
-
-# Web app settings:
-# Use LAN address to listen webhooks
-# User any available port in range from 1024 to 49151 if you're using proxy, or WEBHOOK_PORT if you're using direct webhook handling
-WEBAPP_HOST = "localhost"
+# webserver settings
+WEBAPP_HOST = 'localhost' # or ip
WEBAPP_PORT = 3001
-BAD_CONTENT = (
- ContentTypes.PHOTO & ContentTypes.DOCUMENT & ContentTypes.STICKER & ContentTypes.AUDIO
-)
+logging.basicConfig(level=logging.INFO)
-loop = asyncio.get_event_loop()
-bot = Bot(TOKEN, loop=loop)
-storage = MemoryStorage()
-dp = Dispatcher(bot, storage=storage)
+bot = Bot(token=API_TOKEN)
+dp = Dispatcher(bot)
+dp.middleware.setup(LoggingMiddleware())
-async def cmd_start(message: types.Message):
- # Yep. aiogram allows to respond into webhook.
- # https://core.telegram.org/bots/api#making-requests-when-getting-updates
- return SendMessage(
- chat_id=message.chat.id, text="Hi from webhook!", reply_to_message_id=message.message_id
- )
+@dp.message_handler()
+async def echo(message: types.Message):
+ # Regular request
+ # await bot.send_message(message.chat.id, message.text)
+
+ # or reply INTO webhook
+ return SendMessage(message.chat.id, message.text)
-async def cmd_about(message: types.Message):
- # In this function markdown utils are userd for formatting message text
- return SendMessage(
- message.chat.id,
- text(
- bold("Hi! I'm just a simple telegram bot."),
- "",
- text("I'm powered by", bold("Python", Version(*sys.version_info[:]))),
- text(
- "With",
- link(text("aiogram", aiogram.VERSION), "https://github.com/aiogram/aiogram"),
- ),
- sep="\n",
- ),
- parse_mode=ParseMode.MARKDOWN,
- )
+async def on_startup(dp):
+ await bot.set_webhook(WEBHOOK_URL)
+ # insert code here to run it after start
-async def cancel(message: types.Message):
- # Get current state context
- state = dp.current_state(chat=message.chat.id, user=message.from_user.id)
+async def on_shutdown(dp):
+ logging.warning('Shutting down..')
- # If current user in any state - cancel it.
- if await state.get_state() is not None:
- await state.set_state(state=None)
- return SendMessage(message.chat.id, "Current action is canceled.")
- # Otherwise do nothing
+ # insert code here to run it before shutdown
-
-async def unknown(message: types.Message):
- """
- Handler for unknown messages.
- """
- return SendMessage(
- message.chat.id,
- f"I don't know what to do with content type `{message.content_type()}`. Sorry :c",
- )
-
-
-async def cmd_id(message: types.Message):
- """
- Return info about user.
- """
- if message.reply_to_message:
- target = message.reply_to_message.from_user
- chat = message.chat
- elif message.forward_from and message.chat.type == ChatType.PRIVATE:
- target = message.forward_from
- chat = message.forward_from or message.chat
- else:
- target = message.from_user
- chat = message.chat
-
- result_msg = [hbold("Info about user:"), f"First name: {target.first_name}"]
- if target.last_name:
- result_msg.append(f"Last name: {target.last_name}")
- if target.username:
- result_msg.append(f"Username: {target.mention}")
- result_msg.append(f"User ID: {target.id}")
-
- result_msg.extend([hbold("Chat:"), f"Type: {chat.type}", f"Chat ID: {chat.id}"])
- if chat.type != ChatType.PRIVATE:
- result_msg.append(f"Title: {chat.title}")
- else:
- result_msg.append(f"Title: {chat.full_name}")
- return SendMessage(
- message.chat.id,
- "\n".join(result_msg),
- reply_to_message_id=message.message_id,
- parse_mode=ParseMode.HTML,
- )
-
-
-async def on_startup(app):
- # Demonstrate one of the available methods for registering handlers
- # This command available only in main state (state=None)
- dp.register_message_handler(cmd_start, commands=["start"])
-
- # This handler is available in all states at any time.
- dp.register_message_handler(cmd_about, commands=["help", "about"], state="*")
- dp.register_message_handler(
- unknown,
- content_types=BAD_CONTENT,
- func=lambda message: message.chat.type == ChatType.PRIVATE,
- )
-
- # You are able to register one function handler for multiple conditions
- dp.register_message_handler(cancel, commands=["cancel"], state="*")
- dp.register_message_handler(
- cancel, func=lambda message: message.text.lower().strip() in ["cancel"], state="*"
- )
-
- dp.register_message_handler(cmd_id, commands=["id"], state="*")
- dp.register_message_handler(
- cmd_id,
- func=lambda message: message.forward_from
- or message.reply_to_message
- and message.chat.type == ChatType.PRIVATE,
- state="*",
- )
-
- # Get current webhook status
- webhook = await bot.get_webhook_info()
-
- # If URL is bad
- if webhook.url != WEBHOOK_URL:
- # If URL doesnt match current - remove webhook
- if not webhook.url:
- await bot.delete_webhook()
-
- # Set new URL for webhook
- await bot.set_webhook(WEBHOOK_URL, certificate=open(WEBHOOK_SSL_CERT, "rb"))
- # If you want to use free certificate signed by LetsEncrypt you need to set only URL without sending certificate.
-
-
-async def on_shutdown(app):
- """
- Graceful shutdown. This method is recommended by aiohttp docs.
- """
- # Remove webhook.
+ # Remove webhook (not acceptable in some cases)
await bot.delete_webhook()
- # Close Redis connection.
+ # Close DB connection (if used)
await dp.storage.close()
await dp.storage.wait_closed()
+ logging.warning('Bye!')
-if __name__ == "__main__":
- # Get instance of :class:`aiohttp.web.Application` with configured router.
- app = get_new_configured_app(dispatcher=dp, path=WEBHOOK_URL_PATH)
- # Setup event handlers.
- app.on_startup.append(on_startup)
- app.on_shutdown.append(on_shutdown)
-
- # Generate SSL context
- context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
- context.load_cert_chain(WEBHOOK_SSL_CERT, WEBHOOK_SSL_PRIV)
-
- # Start web-application.
- web.run_app(app, host=WEBAPP_HOST, port=WEBAPP_PORT, ssl_context=context)
- # Note:
- # If you start your bot using nginx or Apache web server, SSL context is not required.
- # Otherwise you need to set `ssl_context` parameter.
+if __name__ == '__main__':
+ start_webhook(
+ dispatcher=dp,
+ webhook_path=WEBHOOK_PATH,
+ on_startup=on_startup,
+ on_shutdown=on_shutdown,
+ skip_updates=True,
+ host=WEBAPP_HOST,
+ port=WEBAPP_PORT,
+ )
diff --git a/examples/webhook_example_2.py b/examples/webhook_example_2.py
deleted file mode 100644
index 742dedb4..00000000
--- a/examples/webhook_example_2.py
+++ /dev/null
@@ -1,50 +0,0 @@
-import asyncio
-import logging
-
-from aiogram import Bot, types
-from aiogram.dispatcher import Dispatcher
-from aiogram.utils.executor import start_webhook
-
-API_TOKEN = "BOT TOKEN HERE"
-
-# webhook settings
-WEBHOOK_HOST = "https://your.domain"
-WEBHOOK_PATH = "/path/to/api"
-WEBHOOK_URL = f"{WEBHOOK_HOST}{WEBHOOK_PATH}"
-
-# webserver settings
-WEBAPP_HOST = "localhost" # or ip
-WEBAPP_PORT = 3001
-
-logging.basicConfig(level=logging.INFO)
-
-loop = asyncio.get_event_loop()
-bot = Bot(token=API_TOKEN, loop=loop)
-dp = Dispatcher(bot)
-
-
-@dp.message_handler()
-async def echo(message: types.Message):
- await bot.send_message(message.chat.id, message.text)
-
-
-async def on_startup(dp):
- await bot.set_webhook(WEBHOOK_URL)
- # insert code here to run it after start
-
-
-async def on_shutdown(dp):
- # insert code here to run it before shutdown
- pass
-
-
-if __name__ == "__main__":
- start_webhook(
- dispatcher=dp,
- webhook_path=WEBHOOK_PATH,
- on_startup=on_startup,
- on_shutdown=on_shutdown,
- skip_updates=True,
- host=WEBAPP_HOST,
- port=WEBAPP_PORT,
- )
diff --git a/examples/webhook_example_old.py b/examples/webhook_example_old.py
new file mode 100644
index 00000000..0f6ae3cd
--- /dev/null
+++ b/examples/webhook_example_old.py
@@ -0,0 +1,176 @@
+"""
+Example outdated
+"""
+
+import asyncio
+import ssl
+import sys
+
+from aiohttp import web
+
+import aiogram
+from aiogram import Bot, types
+from aiogram.contrib.fsm_storage.memory import MemoryStorage
+from aiogram.dispatcher import Dispatcher
+from aiogram.dispatcher.webhook import get_new_configured_app, SendMessage
+from aiogram.types import ChatType, ParseMode, ContentTypes
+from aiogram.utils.markdown import hbold, bold, text, link
+
+TOKEN = 'BOT TOKEN HERE'
+
+WEBHOOK_HOST = 'example.com' # Domain name or IP addres which your bot is located.
+WEBHOOK_PORT = 443 # Telegram Bot API allows only for usage next ports: 443, 80, 88 or 8443
+WEBHOOK_URL_PATH = '/webhook' # Part of URL
+
+# This options needed if you use self-signed SSL certificate
+# Instructions: https://core.telegram.org/bots/self-signed
+WEBHOOK_SSL_CERT = './webhook_cert.pem' # Path to the ssl certificate
+WEBHOOK_SSL_PRIV = './webhook_pkey.pem' # Path to the ssl private key
+
+WEBHOOK_URL = f"https://{WEBHOOK_HOST}:{WEBHOOK_PORT}{WEBHOOK_URL_PATH}"
+
+# Web app settings:
+# Use LAN address to listen webhooks
+# User any available port in range from 1024 to 49151 if you're using proxy, or WEBHOOK_PORT if you're using direct webhook handling
+WEBAPP_HOST = 'localhost'
+WEBAPP_PORT = 3001
+
+BAD_CONTENT = ContentTypes.PHOTO & ContentTypes.DOCUMENT & ContentTypes.STICKER & ContentTypes.AUDIO
+
+bot = Bot(TOKEN)
+storage = MemoryStorage()
+dp = Dispatcher(bot, storage=storage)
+
+
+async def cmd_start(message: types.Message):
+ # Yep. aiogram allows to respond into webhook.
+ # https://core.telegram.org/bots/api#making-requests-when-getting-updates
+ return SendMessage(chat_id=message.chat.id, text='Hi from webhook!',
+ reply_to_message_id=message.message_id)
+
+
+async def cmd_about(message: types.Message):
+ # In this function markdown utils are userd for formatting message text
+ return SendMessage(message.chat.id, text(
+ bold('Hi! I\'m just a simple telegram bot.'),
+ '',
+ text('I\'m powered by', bold('Python', Version(*sys.version_info[:]))),
+ text('With', link(text('aiogram', aiogram.VERSION), 'https://github.com/aiogram/aiogram')),
+ sep='\n'
+ ), parse_mode=ParseMode.MARKDOWN)
+
+
+async def cancel(message: types.Message):
+ # Get current state context
+ state = dp.current_state(chat=message.chat.id, user=message.from_user.id)
+
+ # If current user in any state - cancel it.
+ if await state.get_state() is not None:
+ await state.set_state(state=None)
+ return SendMessage(message.chat.id, 'Current action is canceled.')
+ # Otherwise do nothing
+
+
+async def unknown(message: types.Message):
+ """
+ Handler for unknown messages.
+ """
+ return SendMessage(message.chat.id,
+ f"I don\'t know what to do with content type `{message.content_type()}`. Sorry :c")
+
+
+async def cmd_id(message: types.Message):
+ """
+ Return info about user.
+ """
+ if message.reply_to_message:
+ target = message.reply_to_message.from_user
+ chat = message.chat
+ elif message.forward_from and message.chat.type == ChatType.PRIVATE:
+ target = message.forward_from
+ chat = message.forward_from or message.chat
+ else:
+ target = message.from_user
+ chat = message.chat
+
+ result_msg = [hbold('Info about user:'),
+ f"First name: {target.first_name}"]
+ if target.last_name:
+ result_msg.append(f"Last name: {target.last_name}")
+ if target.username:
+ result_msg.append(f"Username: {target.mention}")
+ result_msg.append(f"User ID: {target.id}")
+
+ result_msg.extend([hbold('Chat:'),
+ f"Type: {chat.type}",
+ f"Chat ID: {chat.id}"])
+ if chat.type != ChatType.PRIVATE:
+ result_msg.append(f"Title: {chat.title}")
+ else:
+ result_msg.append(f"Title: {chat.full_name}")
+ return SendMessage(message.chat.id, '\n'.join(result_msg), reply_to_message_id=message.message_id,
+ parse_mode=ParseMode.HTML)
+
+
+async def on_startup(app):
+ # Demonstrate one of the available methods for registering handlers
+ # This command available only in main state (state=None)
+ dp.register_message_handler(cmd_start, commands=['start'])
+
+ # This handler is available in all states at any time.
+ dp.register_message_handler(cmd_about, commands=['help', 'about'], state='*')
+ dp.register_message_handler(unknown, content_types=BAD_CONTENT,
+ func=lambda message: message.chat.type == ChatType.PRIVATE)
+
+ # You are able to register one function handler for multiple conditions
+ dp.register_message_handler(cancel, commands=['cancel'], state='*')
+ dp.register_message_handler(cancel, func=lambda message: message.text.lower().strip() in ['cancel'], state='*')
+
+ dp.register_message_handler(cmd_id, commands=['id'], state='*')
+ dp.register_message_handler(cmd_id, func=lambda message: message.forward_from or
+ message.reply_to_message and
+ message.chat.type == ChatType.PRIVATE, state='*')
+
+ # Get current webhook status
+ webhook = await bot.get_webhook_info()
+
+ # If URL is bad
+ if webhook.url != WEBHOOK_URL:
+ # If URL doesnt match current - remove webhook
+ if not webhook.url:
+ await bot.delete_webhook()
+
+ # Set new URL for webhook
+ await bot.set_webhook(WEBHOOK_URL, certificate=open(WEBHOOK_SSL_CERT, 'rb'))
+ # If you want to use free certificate signed by LetsEncrypt you need to set only URL without sending certificate.
+
+
+async def on_shutdown(app):
+ """
+ Graceful shutdown. This method is recommended by aiohttp docs.
+ """
+ # Remove webhook.
+ await bot.delete_webhook()
+
+ # Close Redis connection.
+ await dp.storage.close()
+ await dp.storage.wait_closed()
+
+
+if __name__ == '__main__':
+ # Get instance of :class:`aiohttp.web.Application` with configured router.
+ app = get_new_configured_app(dispatcher=dp, path=WEBHOOK_URL_PATH)
+
+ # Setup event handlers.
+ app.on_startup.append(on_startup)
+ app.on_shutdown.append(on_shutdown)
+
+ # Generate SSL context
+ context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
+ context.load_cert_chain(WEBHOOK_SSL_CERT, WEBHOOK_SSL_PRIV)
+
+ # Start web-application.
+ web.run_app(app, host=WEBAPP_HOST, port=WEBAPP_PORT, ssl_context=context)
+ # Note:
+ # If you start your bot using nginx or Apache web server, SSL context is not required.
+ # Otherwise you need to set `ssl_context` parameter.
diff --git a/requirements.txt b/requirements.txt
index 37d328b3..7f7dc1ac 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,3 +1,3 @@
-aiohttp>=3.5.4
+aiohttp>=3.5.4,<4.0.0
Babel>=2.6.0
certifi>=2019.3.9
diff --git a/setup.py b/setup.py
index 1db97490..a7876f96 100755
--- a/setup.py
+++ b/setup.py
@@ -15,9 +15,7 @@ WORK_DIR = pathlib.Path(__file__).parent
# Check python version
MINIMAL_PY_VERSION = (3, 7)
if sys.version_info < MINIMAL_PY_VERSION:
- raise RuntimeError(
- "aiogram works only with Python {}+".format(".".join(map(str, MINIMAL_PY_VERSION)))
- )
+ raise RuntimeError('aiogram works only with Python {}+'.format('.'.join(map(str, MINIMAL_PY_VERSION))))
def get_version():
@@ -26,11 +24,11 @@ def get_version():
:return: str
"""
- txt = (WORK_DIR / "aiogram" / "__init__.py").read_text("utf-8")
+ txt = (WORK_DIR / 'aiogram' / '__init__.py').read_text('utf-8')
try:
return re.findall(r"^__version__ = '([^']+)'\r?$", txt, re.M)[0]
except IndexError:
- raise RuntimeError("Unable to determine version.")
+ raise RuntimeError('Unable to determine version.')
def get_description():
@@ -40,7 +38,7 @@ def get_description():
:return: description
:rtype: str
"""
- with open("README.rst", "r", encoding="utf-8") as f:
+ with open('README.rst', 'r', encoding='utf-8') as f:
return f.read()
@@ -52,36 +50,36 @@ def get_requirements(filename=None):
:rtype: list
"""
if filename is None:
- filename = "requirements.txt"
+ filename = 'requirements.txt'
file = WORK_DIR / filename
- install_reqs = parse_requirements(str(file), session="hack")
+ install_reqs = parse_requirements(str(file), session='hack')
return [str(ir.req) for ir in install_reqs]
setup(
- name="aiogram",
+ name='aiogram',
version=get_version(),
- packages=find_packages(exclude=("tests", "tests.*", "examples.*", "docs", "generator")),
- url="https://github.com/aiogram/aiogram",
- license="MIT",
- author="Alex Root Junior",
- requires_python=">=3.7",
- author_email="jroot.junior@gmail.com",
- description="Is a pretty simple and fully asynchronous library for Telegram Bot API",
+ packages=find_packages(exclude=('tests', 'tests.*', 'examples.*', 'docs',)),
+ url='https://github.com/aiogram/aiogram',
+ license='MIT',
+ author='Alex Root Junior',
+ requires_python='>=3.7',
+ author_email='jroot.junior@gmail.com',
+ description='Is a pretty simple and fully asynchronous framework for Telegram Bot API',
long_description=get_description(),
classifiers=[
- "Development Status :: 5 - Production/Stable",
- "Environment :: Console",
- "Framework :: AsyncIO",
- "Intended Audience :: Developers",
- "Intended Audience :: System Administrators",
- "License :: OSI Approved :: MIT License",
- "Programming Language :: Python :: 3.7",
- "Topic :: Software Development :: Libraries :: Application Frameworks",
+ 'Development Status :: 5 - Production/Stable',
+ 'Environment :: Console',
+ 'Framework :: AsyncIO',
+ 'Intended Audience :: Developers',
+ 'Intended Audience :: System Administrators',
+ 'License :: OSI Approved :: MIT License',
+ 'Programming Language :: Python :: 3.7',
+ 'Topic :: Software Development :: Libraries :: Application Frameworks',
],
install_requires=get_requirements(),
- package_data={"": ["requirements.txt"]},
+ package_data={'': ['requirements.txt']},
include_package_data=False,
)
diff --git a/tests/test_bot.py b/tests/test_bot.py
index 6f64fc13..3e48ea57 100644
--- a/tests/test_bot.py
+++ b/tests/test_bot.py
@@ -3,7 +3,7 @@ import pytest
from aiogram import Bot, types
-TOKEN = "123456789:AABBCCDDEEFFaabbccddeeff-1234567890"
+TOKEN = '123456789:AABBCCDDEEFFaabbccddeeff-1234567890'
class FakeTelegram(aresponses.ResponsesMockServer):
@@ -13,25 +13,23 @@ class FakeTelegram(aresponses.ResponsesMockServer):
async def __aenter__(self):
await super().__aenter__()
- _response = self.Response(text=self._body, headers=self._headers, status=200, reason="OK")
+ _response = self.Response(text=self._body, headers=self._headers, status=200, reason='OK')
self.add(self.ANY, response=_response)
@staticmethod
def parse_data(message_dict):
import json
- _body = '{"ok":true,"result":' + json.dumps(message_dict) + "}"
- _headers = {
- "Server": "nginx/1.12.2",
- "Date": "Tue, 03 Apr 2018 16:59:54 GMT",
- "Content-Type": "application/json",
- "Content-Length": str(len(_body)),
- "Connection": "keep-alive",
- "Access-Control-Allow-Origin": "*",
- "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
- "Access-Control-Expose-Headers": "Content-Length,Content-Type,Date,Server,Connection",
- "Strict-Transport-Security": "max-age=31536000; includeSubdomains",
- }
+ _body = '{"ok":true,"result":' + json.dumps(message_dict) + '}'
+ _headers = {'Server': 'nginx/1.12.2',
+ 'Date': 'Tue, 03 Apr 2018 16:59:54 GMT',
+ 'Content-Type': 'application/json',
+ 'Content-Length': str(len(_body)),
+ 'Connection': 'keep-alive',
+ 'Access-Control-Allow-Origin': '*',
+ 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
+ 'Access-Control-Expose-Headers': 'Content-Length,Content-Type,Date,Server,Connection',
+ 'Strict-Transport-Security': 'max-age=31536000; includeSubdomains'}
return _body, _headers
@@ -48,7 +46,6 @@ async def bot(event_loop):
async def test_get_me(bot: Bot, event_loop):
""" getMe method test """
from .types.dataset import USER
-
user = types.User(**USER)
async with FakeTelegram(message_dict=USER, loop=event_loop):
@@ -60,7 +57,6 @@ async def test_get_me(bot: Bot, event_loop):
async def test_send_message(bot: Bot, event_loop):
""" sendMessage method test """
from .types.dataset import MESSAGE
-
msg = types.Message(**MESSAGE)
async with FakeTelegram(message_dict=MESSAGE, loop=event_loop):
@@ -72,15 +68,11 @@ async def test_send_message(bot: Bot, event_loop):
async def test_forward_message(bot: Bot, event_loop):
""" forwardMessage method test """
from .types.dataset import FORWARDED_MESSAGE
-
msg = types.Message(**FORWARDED_MESSAGE)
async with FakeTelegram(message_dict=FORWARDED_MESSAGE, loop=event_loop):
- 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,
- )
+ 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
@@ -88,18 +80,12 @@ async def test_forward_message(bot: Bot, event_loop):
async def test_send_photo(bot: Bot, event_loop):
""" 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_dict=MESSAGE_WITH_PHOTO, loop=event_loop):
- result = await bot.send_photo(
- msg.chat.id,
- photo=photo.file_id,
- caption=msg.caption,
- parse_mode=types.ParseMode.HTML,
- disable_notification=False,
- )
+ 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
@@ -107,20 +93,12 @@ async def test_send_photo(bot: Bot, event_loop):
async def test_send_audio(bot: Bot, event_loop):
""" sendAudio method test with file_id """
from .types.dataset import MESSAGE_WITH_AUDIO
-
msg = types.Message(**MESSAGE_WITH_AUDIO)
async with FakeTelegram(message_dict=MESSAGE_WITH_AUDIO, loop=event_loop):
- 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,
- )
+ 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
@@ -128,17 +106,11 @@ async def test_send_audio(bot: Bot, event_loop):
async def test_send_document(bot: Bot, event_loop):
""" sendDocument method test with file_id """
from .types.dataset import MESSAGE_WITH_DOCUMENT
-
msg = types.Message(**MESSAGE_WITH_DOCUMENT)
async with FakeTelegram(message_dict=MESSAGE_WITH_DOCUMENT, loop=event_loop):
- 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,
- )
+ 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
@@ -146,22 +118,14 @@ async def test_send_document(bot: Bot, event_loop):
async def test_send_video(bot: Bot, event_loop):
""" 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_dict=MESSAGE_WITH_VIDEO, loop=event_loop):
- 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,
- disable_notification=False,
- )
+ 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,
+ disable_notification=False)
assert result == msg
@@ -169,19 +133,13 @@ async def test_send_video(bot: Bot, event_loop):
async def test_send_voice(bot: Bot, event_loop):
""" 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_dict=MESSAGE_WITH_VOICE, loop=event_loop):
- 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,
- )
+ 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
@@ -189,18 +147,13 @@ async def test_send_voice(bot: Bot, event_loop):
async def test_send_video_note(bot: Bot, event_loop):
""" 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_dict=MESSAGE_WITH_VIDEO_NOTE, loop=event_loop):
- 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,
- )
+ 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
@@ -208,17 +161,11 @@ async def test_send_video_note(bot: Bot, event_loop):
async def test_send_media_group(bot: Bot, event_loop):
""" 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),
- ]
+ media = [types.InputMediaPhoto(media=photo.file_id), types.InputMediaPhoto(media=photo.file_id)]
- async with FakeTelegram(
- message_dict=[MESSAGE_WITH_MEDIA_GROUP, MESSAGE_WITH_MEDIA_GROUP], loop=event_loop
- ):
+ async with FakeTelegram(message_dict=[MESSAGE_WITH_MEDIA_GROUP, MESSAGE_WITH_MEDIA_GROUP], loop=event_loop):
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
@@ -228,18 +175,12 @@ async def test_send_media_group(bot: Bot, event_loop):
async def test_send_location(bot: Bot, event_loop):
""" 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_dict=MESSAGE_WITH_LOCATION, loop=event_loop):
- result = await bot.send_location(
- msg.chat.id,
- latitude=location.latitude,
- longitude=location.longitude,
- live_period=10,
- disable_notification=False,
- )
+ result = await bot.send_location(msg.chat.id, latitude=location.latitude, longitude=location.longitude,
+ live_period=10, disable_notification=False)
assert result == msg
@@ -247,18 +188,13 @@ async def test_send_location(bot: Bot, event_loop):
async def test_edit_message_live_location_by_bot(bot: Bot, event_loop):
""" 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_dict=MESSAGE_WITH_LOCATION, loop=event_loop):
- result = await bot.edit_message_live_location(
- chat_id=msg.chat.id,
- message_id=msg.message_id,
- latitude=location.latitude,
- longitude=location.longitude,
- )
+ 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
@@ -266,18 +202,13 @@ async def test_edit_message_live_location_by_bot(bot: Bot, event_loop):
async def test_edit_message_live_location_by_user(bot: Bot, event_loop):
""" 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_dict=True, loop=event_loop):
- result = await bot.edit_message_live_location(
- chat_id=msg.chat.id,
- message_id=msg.message_id,
- latitude=location.latitude,
- longitude=location.longitude,
- )
+ 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
@@ -285,14 +216,11 @@ async def test_edit_message_live_location_by_user(bot: Bot, event_loop):
async def test_stop_message_live_location_by_bot(bot: Bot, event_loop):
""" stopMessageLiveLocation method test """
from .types.dataset import MESSAGE_WITH_LOCATION
-
msg = types.Message(**MESSAGE_WITH_LOCATION)
# stopping bot message
async with FakeTelegram(message_dict=MESSAGE_WITH_LOCATION, loop=event_loop):
- result = await bot.stop_message_live_location(
- chat_id=msg.chat.id, message_id=msg.message_id
- )
+ result = await bot.stop_message_live_location(chat_id=msg.chat.id, message_id=msg.message_id)
assert result == msg
@@ -300,14 +228,11 @@ async def test_stop_message_live_location_by_bot(bot: Bot, event_loop):
async def test_stop_message_live_location_by_user(bot: Bot, event_loop):
""" stopMessageLiveLocation method test """
from .types.dataset import MESSAGE_WITH_LOCATION
-
msg = types.Message(**MESSAGE_WITH_LOCATION)
# stopping user's message
async with FakeTelegram(message_dict=True, loop=event_loop):
- result = await bot.stop_message_live_location(
- chat_id=msg.chat.id, message_id=msg.message_id
- )
+ 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
@@ -316,21 +241,14 @@ async def test_stop_message_live_location_by_user(bot: Bot, event_loop):
async def test_send_venue(bot: Bot, event_loop):
""" 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_dict=MESSAGE_WITH_VENUE, loop=event_loop):
- 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,
- )
+ 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
@@ -338,18 +256,12 @@ async def test_send_venue(bot: Bot, event_loop):
async def test_send_contact(bot: Bot, event_loop):
""" 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_dict=MESSAGE_WITH_CONTACT, loop=event_loop):
- 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,
- )
+ 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
@@ -357,7 +269,6 @@ async def test_send_contact(bot: Bot, event_loop):
async def test_send_chat_action(bot: Bot, event_loop):
""" sendChatAction method test """
from .types.dataset import CHAT
-
chat = types.Chat(**CHAT)
async with FakeTelegram(message_dict=True, loop=event_loop):
@@ -370,7 +281,6 @@ async def test_send_chat_action(bot: Bot, event_loop):
async def test_get_user_profile_photo(bot: Bot, event_loop):
""" getUserProfilePhotos method test """
from .types.dataset import USER_PROFILE_PHOTOS, USER
-
user = types.User(**USER)
async with FakeTelegram(message_dict=USER_PROFILE_PHOTOS, loop=event_loop):
@@ -382,7 +292,6 @@ async def test_get_user_profile_photo(bot: Bot, event_loop):
async def test_get_file(bot: Bot, event_loop):
""" getFile method test """
from .types.dataset import FILE
-
file = types.File(**FILE)
async with FakeTelegram(message_dict=FILE, loop=event_loop):
@@ -394,7 +303,6 @@ async def test_get_file(bot: Bot, event_loop):
async def test_kick_chat_member(bot: Bot, event_loop):
""" kickChatMember method test """
from .types.dataset import USER, CHAT
-
user = types.User(**USER)
chat = types.Chat(**CHAT)
@@ -408,7 +316,6 @@ async def test_kick_chat_member(bot: Bot, event_loop):
async def test_unban_chat_member(bot: Bot, event_loop):
""" unbanChatMember method test """
from .types.dataset import USER, CHAT
-
user = types.User(**USER)
chat = types.Chat(**CHAT)
@@ -422,7 +329,6 @@ async def test_unban_chat_member(bot: Bot, event_loop):
async def test_restrict_chat_member(bot: Bot, event_loop):
""" restrictChatMember method test """
from .types.dataset import USER, CHAT
-
user = types.User(**USER)
chat = types.Chat(**CHAT)
@@ -430,12 +336,12 @@ async def test_restrict_chat_member(bot: Bot, event_loop):
result = await bot.restrict_chat_member(
chat_id=chat.id,
user_id=user.id,
- can_add_web_page_previews=False,
- can_send_media_messages=False,
- can_send_messages=False,
- can_send_other_messages=False,
- until_date=123,
- )
+ permissions=types.ChatPermissions(
+ can_add_web_page_previews=False,
+ can_send_media_messages=False,
+ can_send_messages=False,
+ can_send_other_messages=False
+ ), until_date=123)
assert isinstance(result, bool)
assert result is True
@@ -444,23 +350,14 @@ async def test_restrict_chat_member(bot: Bot, event_loop):
async def test_promote_chat_member(bot: Bot, event_loop):
""" promoteChatMember method test """
from .types.dataset import USER, CHAT
-
user = types.User(**USER)
chat = types.Chat(**CHAT)
async with FakeTelegram(message_dict=True, loop=event_loop):
- 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,
- can_promote_members=True,
- can_restrict_members=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,
+ can_promote_members=True, can_restrict_members=True)
assert isinstance(result, bool)
assert result is True
@@ -469,7 +366,6 @@ async def test_promote_chat_member(bot: Bot, event_loop):
async def test_export_chat_invite_link(bot: Bot, event_loop):
""" exportChatInviteLink method test """
from .types.dataset import CHAT, INVITE_LINK
-
chat = types.Chat(**CHAT)
async with FakeTelegram(message_dict=INVITE_LINK, loop=event_loop):
@@ -481,7 +377,6 @@ async def test_export_chat_invite_link(bot: Bot, event_loop):
async def test_delete_chat_photo(bot: Bot, event_loop):
""" deleteChatPhoto method test """
from .types.dataset import CHAT
-
chat = types.Chat(**CHAT)
async with FakeTelegram(message_dict=True, loop=event_loop):
@@ -494,11 +389,10 @@ async def test_delete_chat_photo(bot: Bot, event_loop):
async def test_set_chat_title(bot: Bot, event_loop):
""" setChatTitle method test """
from .types.dataset import CHAT
-
chat = types.Chat(**CHAT)
async with FakeTelegram(message_dict=True, loop=event_loop):
- result = await bot.set_chat_title(chat_id=chat.id, title="Test title")
+ result = await bot.set_chat_title(chat_id=chat.id, title='Test title')
assert isinstance(result, bool)
assert result is True
@@ -507,11 +401,10 @@ async def test_set_chat_title(bot: Bot, event_loop):
async def test_set_chat_description(bot: Bot, event_loop):
""" setChatDescription method test """
from .types.dataset import CHAT
-
chat = types.Chat(**CHAT)
async with FakeTelegram(message_dict=True, loop=event_loop):
- result = await bot.set_chat_description(chat_id=chat.id, description="Test description")
+ result = await bot.set_chat_description(chat_id=chat.id, description='Test description')
assert isinstance(result, bool)
assert result is True
@@ -520,13 +413,11 @@ async def test_set_chat_description(bot: Bot, event_loop):
async def test_pin_chat_message(bot: Bot, event_loop):
""" pinChatMessage method test """
from .types.dataset import MESSAGE
-
message = types.Message(**MESSAGE)
async with FakeTelegram(message_dict=True, loop=event_loop):
- result = await bot.pin_chat_message(
- chat_id=message.chat.id, message_id=message.message_id, disable_notification=False
- )
+ 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
@@ -535,7 +426,6 @@ async def test_pin_chat_message(bot: Bot, event_loop):
async def test_unpin_chat_message(bot: Bot, event_loop):
""" unpinChatMessage method test """
from .types.dataset import CHAT
-
chat = types.Chat(**CHAT)
async with FakeTelegram(message_dict=True, loop=event_loop):
@@ -548,7 +438,6 @@ async def test_unpin_chat_message(bot: Bot, event_loop):
async def test_leave_chat(bot: Bot, event_loop):
""" leaveChat method test """
from .types.dataset import CHAT
-
chat = types.Chat(**CHAT)
async with FakeTelegram(message_dict=True, loop=event_loop):
@@ -561,7 +450,6 @@ async def test_leave_chat(bot: Bot, event_loop):
async def test_get_chat(bot: Bot, event_loop):
""" getChat method test """
from .types.dataset import CHAT
-
chat = types.Chat(**CHAT)
async with FakeTelegram(message_dict=CHAT, loop=event_loop):
@@ -573,7 +461,6 @@ async def test_get_chat(bot: Bot, event_loop):
async def test_get_chat_administrators(bot: Bot, event_loop):
""" getChatAdministrators method test """
from .types.dataset import CHAT, CHAT_MEMBER
-
chat = types.Chat(**CHAT)
member = types.ChatMember(**CHAT_MEMBER)
@@ -587,7 +474,6 @@ async def test_get_chat_administrators(bot: Bot, event_loop):
async def test_get_chat_members_count(bot: Bot, event_loop):
""" getChatMembersCount method test """
from .types.dataset import CHAT
-
chat = types.Chat(**CHAT)
count = 5
@@ -600,7 +486,6 @@ async def test_get_chat_members_count(bot: Bot, event_loop):
async def test_get_chat_member(bot: Bot, event_loop):
""" getChatMember method test """
from .types.dataset import CHAT, CHAT_MEMBER
-
chat = types.Chat(**CHAT)
member = types.ChatMember(**CHAT_MEMBER)
@@ -614,13 +499,10 @@ async def test_get_chat_member(bot: Bot, event_loop):
async def test_set_chat_sticker_set(bot: Bot, event_loop):
""" setChatStickerSet method test """
from .types.dataset import CHAT
-
chat = types.Chat(**CHAT)
async with FakeTelegram(message_dict=True, loop=event_loop):
- result = await bot.set_chat_sticker_set(
- chat_id=chat.id, sticker_set_name="aiogram_stickers"
- )
+ result = await bot.set_chat_sticker_set(chat_id=chat.id, sticker_set_name='aiogram_stickers')
assert isinstance(result, bool)
assert result is True
@@ -629,7 +511,6 @@ async def test_set_chat_sticker_set(bot: Bot, event_loop):
async def test_delete_chat_sticker_set(bot: Bot, event_loop):
""" setChatStickerSet method test """
from .types.dataset import CHAT
-
chat = types.Chat(**CHAT)
async with FakeTelegram(message_dict=True, loop=event_loop):
@@ -643,7 +524,7 @@ async def test_answer_callback_query(bot: Bot, event_loop):
""" answerCallbackQuery method test """
async with FakeTelegram(message_dict=True, loop=event_loop):
- result = await bot.answer_callback_query(callback_query_id="QuERyId", text="Test Answer")
+ result = await bot.answer_callback_query(callback_query_id='QuERyId', text='Test Answer')
assert isinstance(result, bool)
assert result is True
@@ -652,14 +533,11 @@ async def test_answer_callback_query(bot: Bot, event_loop):
async def test_edit_message_text_by_bot(bot: Bot, event_loop):
""" editMessageText method test """
from .types.dataset import EDITED_MESSAGE
-
msg = types.Message(**EDITED_MESSAGE)
# message by bot
async with FakeTelegram(message_dict=EDITED_MESSAGE, loop=event_loop):
- result = await bot.edit_message_text(
- text=msg.text, chat_id=msg.chat.id, message_id=msg.message_id
- )
+ result = await bot.edit_message_text(text=msg.text, chat_id=msg.chat.id, message_id=msg.message_id)
assert result == msg
@@ -667,13 +545,10 @@ async def test_edit_message_text_by_bot(bot: Bot, event_loop):
async def test_edit_message_text_by_user(bot: Bot, event_loop):
""" editMessageText method test """
from .types.dataset import EDITED_MESSAGE
-
msg = types.Message(**EDITED_MESSAGE)
# message by user
async with FakeTelegram(message_dict=True, loop=event_loop):
- result = await bot.edit_message_text(
- text=msg.text, chat_id=msg.chat.id, message_id=msg.message_id
- )
+ 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
diff --git a/tests/test_bot/test_api.py b/tests/test_bot/test_api.py
new file mode 100644
index 00000000..c5193bcc
--- /dev/null
+++ b/tests/test_bot/test_api.py
@@ -0,0 +1,18 @@
+import pytest
+from aiogram.bot.api import check_token
+
+from aiogram.utils.exceptions import ValidationError
+
+
+VALID_TOKEN = '123456789:AABBCCDDEEFFaabbccddeeff-1234567890'
+INVALID_TOKEN = '123456789:AABBCCDDEEFFaabbccddeeff 123456789' # Space in token and wrong length
+
+
+class Test_check_token:
+
+ def test_valid(self):
+ assert check_token(VALID_TOKEN) is True
+
+ def test_invalid_token(self):
+ with pytest.raises(ValidationError):
+ check_token(INVALID_TOKEN)
diff --git a/tests/test_dispatcher/test_filters/test_builtin.py b/tests/test_dispatcher/test_filters/test_builtin.py
new file mode 100644
index 00000000..86344cec
--- /dev/null
+++ b/tests/test_dispatcher/test_filters/test_builtin.py
@@ -0,0 +1,18 @@
+import pytest
+
+from aiogram.dispatcher.filters.builtin import Text
+
+
+class TestText:
+
+ @pytest.mark.parametrize('param, key', [
+ ('text', 'equals'),
+ ('text_contains', 'contains'),
+ ('text_startswith', 'startswith'),
+ ('text_endswith', 'endswith'),
+ ])
+ def test_validate(self, param, key):
+ value = 'spam and eggs'
+ config = {param: value}
+ res = Text.validate(config)
+ assert res == {key: value}
diff --git a/tests/test_dispatcher/test_filters/test_state.py b/tests/test_dispatcher/test_filters/test_state.py
new file mode 100644
index 00000000..b7f5a5fd
--- /dev/null
+++ b/tests/test_dispatcher/test_filters/test_state.py
@@ -0,0 +1,18 @@
+from aiogram.dispatcher.filters.state import StatesGroup
+
+class TestStatesGroup:
+
+ def test_all_childs(self):
+
+ class InnerState1(StatesGroup):
+ pass
+
+ class InnerState2(InnerState1):
+ pass
+
+ class Form(StatesGroup):
+ inner1 = InnerState1
+ inner2 = InnerState2
+
+ form_childs = Form.all_childs
+ assert form_childs == (InnerState1, InnerState2)
diff --git a/tests/test_filters.py b/tests/test_filters.py
new file mode 100644
index 00000000..609db736
--- /dev/null
+++ b/tests/test_filters.py
@@ -0,0 +1,263 @@
+import pytest
+
+from aiogram.dispatcher.filters import Text
+from aiogram.types import Message, CallbackQuery, InlineQuery, Poll
+
+
+def data_sample_1():
+ return [
+ ('', ''),
+ ('', 'exAmple_string'),
+
+ ('example_string', 'example_string'),
+ ('example_string', 'exAmple_string'),
+ ('exAmple_string', 'example_string'),
+
+ ('example_string', 'example_string_dsf'),
+ ('example_string', 'example_striNG_dsf'),
+ ('example_striNG', 'example_string_dsf'),
+
+ ('example_string', 'not_example_string'),
+ ('example_string', 'not_eXample_string'),
+ ('EXample_string', 'not_example_string'),
+ ]
+
+class TestTextFilter:
+
+ async def _run_check(self, check, test_text):
+ assert await check(Message(text=test_text))
+ assert await check(CallbackQuery(data=test_text))
+ assert await check(InlineQuery(query=test_text))
+ assert await check(Poll(question=test_text))
+
+ @pytest.mark.asyncio
+ @pytest.mark.parametrize('ignore_case', (True, False))
+ @pytest.mark.parametrize("test_prefix, test_text", data_sample_1())
+ async def test_startswith(self, test_prefix, test_text, ignore_case):
+ test_filter = Text(startswith=test_prefix, ignore_case=ignore_case)
+
+ async def check(obj):
+ result = await test_filter.check(obj)
+ if ignore_case:
+ _test_prefix = test_prefix.lower()
+ _test_text = test_text.lower()
+ else:
+ _test_prefix = test_prefix
+ _test_text = test_text
+
+ return result is _test_text.startswith(_test_prefix)
+
+ await self._run_check(check, test_text)
+
+ @pytest.mark.asyncio
+ @pytest.mark.parametrize('ignore_case', (True, False))
+ @pytest.mark.parametrize("test_prefix_list, test_text", [
+ (['not_example', ''], ''),
+ (['', 'not_example'], 'exAmple_string'),
+
+ (['not_example', 'example_string'], 'example_string'),
+ (['example_string', 'not_example'], 'exAmple_string'),
+ (['not_example', 'exAmple_string'], 'example_string'),
+
+ (['not_example', 'example_string'], 'example_string_dsf'),
+ (['example_string', 'not_example'], 'example_striNG_dsf'),
+ (['not_example', 'example_striNG'], 'example_string_dsf'),
+
+ (['not_example', 'example_string'], 'not_example_string'),
+ (['example_string', 'not_example'], 'not_eXample_string'),
+ (['not_example', 'EXample_string'], 'not_example_string'),
+ ])
+ async def test_startswith_list(self, test_prefix_list, test_text, ignore_case):
+ test_filter = Text(startswith=test_prefix_list, ignore_case=ignore_case)
+
+ async def check(obj):
+ result = await test_filter.check(obj)
+ if ignore_case:
+ _test_prefix_list = map(str.lower, test_prefix_list)
+ _test_text = test_text.lower()
+ else:
+ _test_prefix_list = test_prefix_list
+ _test_text = test_text
+
+ return result is any(map(_test_text.startswith, _test_prefix_list))
+
+ await self._run_check(check, test_text)
+
+ @pytest.mark.asyncio
+ @pytest.mark.parametrize('ignore_case', (True, False))
+ @pytest.mark.parametrize("test_postfix, test_text", data_sample_1())
+ async def test_endswith(self, test_postfix, test_text, ignore_case):
+ test_filter = Text(endswith=test_postfix, ignore_case=ignore_case)
+
+ async def check(obj):
+ result = await test_filter.check(obj)
+ if ignore_case:
+ _test_postfix = test_postfix.lower()
+ _test_text = test_text.lower()
+ else:
+ _test_postfix = test_postfix
+ _test_text = test_text
+
+ return result is _test_text.endswith(_test_postfix)
+
+ await self._run_check(check, test_text)
+
+ @pytest.mark.asyncio
+ @pytest.mark.parametrize('ignore_case', (True, False))
+ @pytest.mark.parametrize("test_postfix_list, test_text", [
+ (['', 'not_example'], ''),
+ (['not_example', ''], 'exAmple_string'),
+
+ (['example_string', 'not_example'], 'example_string'),
+ (['not_example', 'example_string'], 'exAmple_string'),
+ (['exAmple_string', 'not_example'], 'example_string'),
+
+ (['not_example', 'example_string'], 'example_string_dsf'),
+ (['example_string', 'not_example'], 'example_striNG_dsf'),
+ (['not_example', 'example_striNG'], 'example_string_dsf'),
+
+ (['not_example', 'example_string'], 'not_example_string'),
+ (['example_string', 'not_example'], 'not_eXample_string'),
+ (['not_example', 'EXample_string'], 'not_example_string'),
+ ])
+ async def test_endswith_list(self, test_postfix_list, test_text, ignore_case):
+ test_filter = Text(endswith=test_postfix_list, ignore_case=ignore_case)
+
+ async def check(obj):
+ result = await test_filter.check(obj)
+ if ignore_case:
+ _test_postfix_list = map(str.lower, test_postfix_list)
+ _test_text = test_text.lower()
+ else:
+ _test_postfix_list = test_postfix_list
+ _test_text = test_text
+
+ return result is any(map(_test_text.endswith, _test_postfix_list))
+ await self._run_check(check, test_text)
+
+ @pytest.mark.asyncio
+ @pytest.mark.parametrize('ignore_case', (True, False))
+ @pytest.mark.parametrize("test_string, test_text", [
+ ('', ''),
+ ('', 'exAmple_string'),
+
+ ('example_string', 'example_string'),
+ ('example_string', 'exAmple_string'),
+ ('exAmple_string', 'example_string'),
+
+ ('example_string', 'example_string_dsf'),
+ ('example_string', 'example_striNG_dsf'),
+ ('example_striNG', 'example_string_dsf'),
+
+ ('example_string', 'not_example_strin'),
+ ('example_string', 'not_eXample_strin'),
+ ('EXample_string', 'not_example_strin'),
+ ])
+ async def test_contains(self, test_string, test_text, ignore_case):
+ test_filter = Text(contains=test_string, ignore_case=ignore_case)
+
+ async def check(obj):
+ result = await test_filter.check(obj)
+ if ignore_case:
+ _test_string = test_string.lower()
+ _test_text = test_text.lower()
+ else:
+ _test_string = test_string
+ _test_text = test_text
+
+ return result is (_test_string in _test_text)
+
+ await self._run_check(check, test_text)
+
+ @pytest.mark.asyncio
+ @pytest.mark.parametrize('ignore_case', (True, False))
+ @pytest.mark.parametrize("test_filter_list, test_text", [
+ (['a', 'ab', 'abc'], 'A'),
+ (['a', 'ab', 'abc'], 'ab'),
+ (['a', 'ab', 'abc'], 'aBc'),
+ (['a', 'ab', 'abc'], 'd'),
+ ])
+ async def test_contains_list(self, test_filter_list, test_text, ignore_case):
+ test_filter = Text(contains=test_filter_list, ignore_case=ignore_case)
+
+ async def check(obj):
+ result = await test_filter.check(obj)
+ if ignore_case:
+ _test_filter_list = list(map(str.lower, test_filter_list))
+ _test_text = test_text.lower()
+ else:
+ _test_filter_list = test_filter_list
+ _test_text = test_text
+
+ return result is all(map(_test_text.__contains__, _test_filter_list))
+
+ await self._run_check(check, test_text)
+
+ @pytest.mark.asyncio
+ @pytest.mark.parametrize('ignore_case', (True, False))
+ @pytest.mark.parametrize("test_filter_text, test_text", [
+ ('', ''),
+ ('', 'exAmple_string'),
+
+ ('example_string', 'example_string'),
+ ('example_string', 'exAmple_string'),
+ ('exAmple_string', 'example_string'),
+
+ ('example_string', 'not_example_string'),
+ ('example_string', 'not_eXample_string'),
+ ('EXample_string', 'not_example_string'),
+ ])
+ async def test_equals_string(self, test_filter_text, test_text, ignore_case):
+ test_filter = Text(equals=test_filter_text, ignore_case=ignore_case)
+
+ async def check(obj):
+ result = await test_filter.check(obj)
+ if ignore_case:
+ _test_filter_text = test_filter_text.lower()
+ _test_text = test_text.lower()
+ else:
+ _test_filter_text = test_filter_text
+ _test_text = test_text
+ return result is (_test_text == _test_filter_text)
+
+ await self._run_check(check, test_text)
+
+ @pytest.mark.asyncio
+ @pytest.mark.parametrize('ignore_case', (True, False))
+ @pytest.mark.parametrize("test_filter_list, test_text", [
+ (['new_string', ''], ''),
+ (['', 'new_string'], 'exAmple_string'),
+
+ (['example_string'], 'example_string'),
+ (['example_string'], 'exAmple_string'),
+ (['exAmple_string'], 'example_string'),
+
+ (['example_string'], 'not_example_string'),
+ (['example_string'], 'not_eXample_string'),
+ (['EXample_string'], 'not_example_string'),
+
+ (['example_string', 'new_string'], 'example_string'),
+ (['new_string', 'example_string'], 'exAmple_string'),
+ (['exAmple_string', 'new_string'], 'example_string'),
+
+ (['example_string', 'new_string'], 'not_example_string'),
+ (['new_string', 'example_string'], 'not_eXample_string'),
+ (['EXample_string', 'new_string'], 'not_example_string'),
+ ])
+ async def test_equals_list(self, test_filter_list, test_text, ignore_case):
+ test_filter = Text(equals=test_filter_list, ignore_case=ignore_case)
+
+ async def check(obj):
+ result = await test_filter.check(obj)
+ if ignore_case:
+ _test_filter_list = list(map(str.lower, test_filter_list))
+ _test_text = test_text.lower()
+ else:
+ _test_filter_list = test_filter_list
+ _test_text = test_text
+ assert result is (_test_text in _test_filter_list)
+
+ await check(Message(text=test_text))
+ await check(CallbackQuery(data=test_text))
+ await check(InlineQuery(query=test_text))
+ await check(Poll(question=test_text))
diff --git a/tests/states_group.py b/tests/test_states_group.py
similarity index 100%
rename from tests/states_group.py
rename to tests/test_states_group.py
diff --git a/tests/test_token.py b/tests/test_token.py
deleted file mode 100644
index f11ff9e4..00000000
--- a/tests/test_token.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import pytest
-
-from aiogram.bot import api
-from aiogram.utils import auth_widget, exceptions
-
-VALID_TOKEN = "123456789:AABBCCDDEEFFaabbccddeeff-1234567890"
-INVALID_TOKEN = "123456789:AABBCCDDEEFFaabbccddeeff 123456789" # Space in token and wrong length
-
-VALID_DATA = {
- "date": 1525385236,
- "first_name": "Test",
- "last_name": "User",
- "id": 123456789,
- "username": "username",
- "hash": "69a9871558fbbe4cd0dbaba52fa1cc4f38315d3245b7504381a64139fb024b5b",
-}
-INVALID_DATA = {
- "date": 1525385237,
- "first_name": "Test",
- "last_name": "User",
- "id": 123456789,
- "username": "username",
- "hash": "69a9871558fbbe4cd0dbaba52fa1cc4f38315d3245b7504381a64139fb024b5b",
-}
-
-
-def test_valid_token():
- assert api.check_token(VALID_TOKEN)
-
-
-def test_invalid_token():
- with pytest.raises(exceptions.ValidationError):
- api.check_token(INVALID_TOKEN)
-
-
-def test_widget():
- assert auth_widget.check_token(VALID_DATA, VALID_TOKEN)
-
-
-def test_invalid_widget_data():
- assert not auth_widget.check_token(INVALID_DATA, VALID_TOKEN)
diff --git a/tests/test_utils/test_auth_widget.py b/tests/test_utils/test_auth_widget.py
new file mode 100644
index 00000000..8c6f5941
--- /dev/null
+++ b/tests/test_utils/test_auth_widget.py
@@ -0,0 +1,46 @@
+import pytest
+
+from aiogram.utils.auth_widget import check_integrity, \
+ generate_hash, check_token
+
+TOKEN = '123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11'
+
+
+@pytest.fixture
+def data():
+ return {
+ 'id': '42',
+ 'first_name': 'John',
+ 'last_name': 'Smith',
+ 'username': 'username',
+ 'photo_url': 'https://t.me/i/userpic/320/picname.jpg',
+ 'auth_date': '1565810688',
+ 'hash': 'c303db2b5a06fe41d23a9b14f7c545cfc11dcc7473c07c9c5034ae60062461ce',
+ }
+
+
+def test_generate_hash(data):
+ res = generate_hash(data, TOKEN)
+ assert res == data['hash']
+
+
+class Test_check_token:
+ """
+ This case gonna be deleted
+ """
+ def test_ok(self, data):
+ assert check_token(data, TOKEN) is True
+
+ def test_fail(self, data):
+ data.pop('username')
+ assert check_token(data, TOKEN) is False
+
+
+class Test_check_integrity:
+
+ def test_ok(self, data):
+ assert check_integrity(TOKEN, data) is True
+
+ def test_fail(self, data):
+ data.pop('username')
+ assert check_integrity(TOKEN, data) is False
diff --git a/tests/test_utils/test_helper.py b/tests/test_utils/test_helper.py
new file mode 100644
index 00000000..d202d289
--- /dev/null
+++ b/tests/test_utils/test_helper.py
@@ -0,0 +1,22 @@
+from aiogram.utils.helper import OrderedHelper, Item, ListItem
+
+
+class TestOrderedHelper:
+
+ def test_items_are_ordered(self):
+ class Helper(OrderedHelper):
+ A = Item()
+ D = Item()
+ C = Item()
+ B = Item()
+
+ assert Helper.all() == ['A', 'D', 'C', 'B']
+
+ def test_list_items_are_ordered(self):
+ class Helper(OrderedHelper):
+ A = ListItem()
+ D = ListItem()
+ C = ListItem()
+ B = ListItem()
+
+ assert Helper.all() == ['A', 'D', 'C', 'B']
diff --git a/tests/types/dataset.py b/tests/types/dataset.py
index acfb2a34..18bcbdad 100644
--- a/tests/types/dataset.py
+++ b/tests/types/dataset.py
@@ -47,7 +47,11 @@ CHAT_MEMBER = {
"can_promote_members": False,
}
-CONTACT = {"phone_number": "88005553535", "first_name": "John", "last_name": "Smith"}
+CONTACT = {
+ "phone_number": "88005553535",
+ "first_name": "John",
+ "last_name": "Smith",
+}
DOCUMENT = {
"file_name": "test.docx",
@@ -64,17 +68,42 @@ ANIMATION = {
"file_size": 65837,
}
-ENTITY_BOLD = {"offset": 5, "length": 2, "type": "bold"}
+ENTITY_BOLD = {
+ "offset": 5,
+ "length": 2,
+ "type": "bold",
+}
-ENTITY_ITALIC = {"offset": 8, "length": 1, "type": "italic"}
+ENTITY_ITALIC = {
+ "offset": 8,
+ "length": 1,
+ "type": "italic",
+}
-ENTITY_LINK = {"offset": 10, "length": 6, "type": "text_link", "url": "http://google.com/"}
+ENTITY_LINK = {
+ "offset": 10,
+ "length": 6,
+ "type": "text_link",
+ "url": "http://google.com/",
+}
-ENTITY_CODE = {"offset": 17, "length": 7, "type": "code"}
+ENTITY_CODE = {
+ "offset": 17,
+ "length": 7,
+ "type": "code",
+}
-ENTITY_PRE = {"offset": 30, "length": 4, "type": "pre"}
+ENTITY_PRE = {
+ "offset": 30,
+ "length": 4,
+ "type": "pre",
+}
-ENTITY_MENTION = {"offset": 47, "length": 9, "type": "mention"}
+ENTITY_MENTION = {
+ "offset": 47,
+ "length": 9,
+ "type": "mention",
+}
GAME = {
"title": "Karate Kido",
@@ -86,15 +115,18 @@ GAME = {
INVOICE = {
"title": "Working Time Machine",
"description": "Want to visit your great-great-great-grandparents? "
- "Make a fortune at the races? "
- "Shake hands with Hammurabi and take a stroll in the Hanging Gardens? "
- "Order our Working Time Machine today!",
+ "Make a fortune at the races? "
+ "Shake hands with Hammurabi and take a stroll in the Hanging Gardens? "
+ "Order our Working Time Machine today!",
"start_parameter": "time-machine-example",
"currency": "USD",
"total_amount": 6250,
}
-LOCATION = {"latitude": 50.693416, "longitude": 30.624605}
+LOCATION = {
+ "latitude": 50.693416,
+ "longitude": 30.624605,
+}
VENUE = {
"location": LOCATION,
@@ -121,7 +153,7 @@ STICKER = {
"file_id": "AAbbCCddEEffGGhh1234567890",
"file_size": 1234,
"width": 128,
- "height": 128,
+ "height": 128
},
"file_id": "AAbbCCddEEffGGhh1234567890",
"file_size": 12345,
@@ -186,15 +218,8 @@ FORWARDED_MESSAGE = {
"forward_from_message_id": 123,
"forward_date": 1522749037,
"text": "Forwarded text with entities from public channel ",
- "entities": [
- ENTITY_BOLD,
- ENTITY_CODE,
- ENTITY_ITALIC,
- ENTITY_LINK,
- ENTITY_LINK,
- ENTITY_MENTION,
- ENTITY_PRE,
- ],
+ "entities": [ENTITY_BOLD, ENTITY_CODE, ENTITY_ITALIC, ENTITY_LINK,
+ ENTITY_LINK, ENTITY_MENTION, ENTITY_PRE],
}
INLINE_QUERY = {}
@@ -391,12 +416,27 @@ SHIPPING_QUERY = {
"shipping_address": SHIPPING_ADDRESS,
}
-USER_PROFILE_PHOTOS = {"total_count": 1, "photos": [[PHOTO, PHOTO, PHOTO]]}
+USER_PROFILE_PHOTOS = {
+ "total_count": 1, "photos": [
+ [PHOTO, PHOTO, PHOTO],
+ ],
+}
-FILE = {"file_id": "XXXYYYZZZ", "file_size": 5254, "file_path": "voice/file_8"}
+FILE = {
+ "file_id": "XXXYYYZZZ",
+ "file_size": 5254,
+ "file_path": "voice/file_8",
+}
-INVITE_LINK = "https://t.me/joinchat/AbCdEfjKILDADwdd123"
+INVITE_LINK = 'https://t.me/joinchat/AbCdEfjKILDADwdd123'
-UPDATE = {"update_id": 123456789, "message": MESSAGE}
+UPDATE = {
+ "update_id": 123456789,
+ "message": MESSAGE,
+}
-WEBHOOK_INFO = {"url": "", "has_custom_certificate": False, "pending_update_count": 0}
+WEBHOOK_INFO = {
+ "url": "",
+ "has_custom_certificate": False,
+ "pending_update_count": 0,
+}
diff --git a/tests/types/test_chat_member.py b/tests/types/test_chat_member.py
new file mode 100644
index 00000000..2cea44ce
--- /dev/null
+++ b/tests/types/test_chat_member.py
@@ -0,0 +1,77 @@
+from aiogram import types
+from .dataset import CHAT_MEMBER
+
+chat_member = types.ChatMember(**CHAT_MEMBER)
+
+
+def test_export():
+ exported = chat_member.to_python()
+ assert isinstance(exported, dict)
+ assert exported == CHAT_MEMBER
+
+
+def test_user():
+ assert isinstance(chat_member.user, types.User)
+
+
+def test_status():
+ assert isinstance(chat_member.status, str)
+ assert chat_member.status == CHAT_MEMBER['status']
+
+
+def test_privileges():
+ assert isinstance(chat_member.can_be_edited, bool)
+ assert chat_member.can_be_edited == CHAT_MEMBER['can_be_edited']
+
+ assert isinstance(chat_member.can_change_info, bool)
+ assert chat_member.can_change_info == CHAT_MEMBER['can_change_info']
+
+ assert isinstance(chat_member.can_delete_messages, bool)
+ assert chat_member.can_delete_messages == CHAT_MEMBER['can_delete_messages']
+
+ assert isinstance(chat_member.can_invite_users, bool)
+ assert chat_member.can_invite_users == CHAT_MEMBER['can_invite_users']
+
+ assert isinstance(chat_member.can_restrict_members, bool)
+ assert chat_member.can_restrict_members == CHAT_MEMBER['can_restrict_members']
+
+ assert isinstance(chat_member.can_pin_messages, bool)
+ assert chat_member.can_pin_messages == CHAT_MEMBER['can_pin_messages']
+
+ assert isinstance(chat_member.can_promote_members, bool)
+ assert chat_member.can_promote_members == CHAT_MEMBER['can_promote_members']
+
+
+def test_int():
+ assert int(chat_member) == chat_member.user.id
+ assert isinstance(int(chat_member), int)
+
+
+def test_chat_member_status():
+ assert types.ChatMemberStatus.CREATOR == 'creator'
+ assert types.ChatMemberStatus.ADMINISTRATOR == 'administrator'
+ assert types.ChatMemberStatus.MEMBER == 'member'
+ assert types.ChatMemberStatus.RESTRICTED == 'restricted'
+ assert types.ChatMemberStatus.LEFT == 'left'
+ assert types.ChatMemberStatus.KICKED == 'kicked'
+
+
+def test_chat_member_status_filters():
+ assert types.ChatMemberStatus.is_chat_admin(chat_member.status)
+ assert types.ChatMemberStatus.is_chat_member(chat_member.status)
+
+ assert types.ChatMemberStatus.is_chat_admin(types.ChatMemberStatus.CREATOR)
+ assert types.ChatMemberStatus.is_chat_admin(types.ChatMemberStatus.ADMINISTRATOR)
+
+ assert types.ChatMemberStatus.is_chat_member(types.ChatMemberStatus.CREATOR)
+ assert types.ChatMemberStatus.is_chat_member(types.ChatMemberStatus.ADMINISTRATOR)
+ assert types.ChatMemberStatus.is_chat_member(types.ChatMemberStatus.MEMBER)
+ assert types.ChatMemberStatus.is_chat_member(types.ChatMemberStatus.RESTRICTED)
+
+ assert not types.ChatMemberStatus.is_chat_member(types.ChatMemberStatus.LEFT)
+ assert not types.ChatMemberStatus.is_chat_member(types.ChatMemberStatus.KICKED)
+
+
+def test_chat_member_filters():
+ assert chat_member.is_chat_admin()
+ assert chat_member.is_chat_member()