diff --git a/aiogram/contrib/fsm_storage/redis.py b/aiogram/contrib/fsm_storage/redis.py index 8c1abd90..2bfe1642 100644 --- a/aiogram/contrib/fsm_storage/redis.py +++ b/aiogram/contrib/fsm_storage/redis.py @@ -173,10 +173,10 @@ class RedisStorage(BaseStorage): conn = await self.redis() if full: - conn.execute('FLUSHDB') + await conn.execute('FLUSHDB') else: keys = await conn.execute('KEYS', 'fsm:*') - conn.execute('DEL', *keys) + await conn.execute('DEL', *keys) def has_bucket(self): return True @@ -350,10 +350,10 @@ class RedisStorage2(BaseStorage): conn = await self.redis() if full: - conn.flushdb() + await conn.flushdb() else: keys = await conn.keys(self.generate_key('*')) - conn.delete(*keys) + await conn.delete(*keys) async def get_states_list(self) -> typing.List[typing.Tuple[int]]: """ diff --git a/aiogram/contrib/middlewares/i18n.py b/aiogram/contrib/middlewares/i18n.py index 65cb1400..264bc653 100644 --- a/aiogram/contrib/middlewares/i18n.py +++ b/aiogram/contrib/middlewares/i18n.py @@ -4,6 +4,7 @@ from contextvars import ContextVar from typing import Any, Dict, Tuple from babel import Locale +from babel.support import LazyProxy from ... import types from ...dispatcher.middlewares import BaseMiddleware @@ -106,6 +107,18 @@ class I18nMiddleware(BaseMiddleware): else: return translator.ngettext(singular, plural, n) + def lazy_gettext(self, singular, plural=None, n=1, locale=None) -> LazyProxy: + """ + Lazy get text + + :param singular: + :param plural: + :param n: + :param locale: + :return: + """ + return LazyProxy(self.gettext, singular, plural, n, locale) + # noinspection PyMethodMayBeStatic,PyUnusedLocal async def get_user_locale(self, action: str, args: Tuple[Any]) -> str: """ diff --git a/aiogram/dispatcher/handler.py b/aiogram/dispatcher/handler.py index 2caf80d8..24771e92 100644 --- a/aiogram/dispatcher/handler.py +++ b/aiogram/dispatcher/handler.py @@ -1,5 +1,7 @@ import inspect from contextvars import ContextVar +from dataclasses import dataclass +from typing import Union ctx_data = ContextVar('ctx_handler_data') current_handler = ContextVar('current_handler') @@ -13,11 +15,15 @@ class CancelHandler(Exception): pass -def _check_spec(func: callable, kwargs: dict): +def _get_spec(func: callable): while hasattr(func, '__wrapped__'): # Try to resolve decorated callbacks func = func.__wrapped__ spec = inspect.getfullargspec(func) + return spec, func + + +def _check_spec(spec: inspect.FullArgSpec, kwargs: dict): if spec.varkw: return kwargs @@ -42,9 +48,11 @@ class Handler: :param filters: list of filters :param index: you can reorder handlers """ + spec, handler = _get_spec(handler) + if filters and not isinstance(filters, (list, tuple, set)): filters = [filters] - record = (filters, handler) + record = Handler.HandlerObj(handler=handler, spec=spec, filters=filters) if index is None: self.handlers.append(record) else: @@ -57,10 +65,10 @@ class Handler: :param handler: callback :return: """ - for handler_with_filters in self.handlers: - _, registered = handler_with_filters + for handler_obj in self.handlers: + registered = handler_obj.handler if handler is registered: - self.handlers.remove(handler_with_filters) + self.handlers.remove(handler_obj) return True raise ValueError('This handler is not registered!') @@ -85,18 +93,18 @@ class Handler: return results try: - for filters, handler in self.handlers: + for handler_obj in self.handlers: try: - data.update(await check_filters(self.dispatcher, filters, args)) + data.update(await check_filters(self.dispatcher, handler_obj.filters, args)) except FilterNotPassed: continue else: - ctx_token = current_handler.set(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,)) - partial_data = _check_spec(handler, data) - response = await handler(*args, **partial_data) + partial_data = _check_spec(handler_obj.spec, data) + response = await handler_obj.handler(*args, **partial_data) if response is not None: results.append(response) if self.once: @@ -113,3 +121,9 @@ class Handler: args + (results, data,)) return results + + @dataclass + class HandlerObj: + handler: callable + spec: inspect.FullArgSpec + filters: Union[list, tuple, set, None] = None diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py index c8abdef2..8a5662bf 100644 --- a/aiogram/dispatcher/webhook.py +++ b/aiogram/dispatcher/webhook.py @@ -11,7 +11,6 @@ from typing import Dict, List, Optional, Union from aiohttp import web from aiohttp.web_exceptions import HTTPGone - from .. import types from ..bot import api from ..types import ParseMode diff --git a/aiogram/types/encrypted_passport_element.py b/aiogram/types/encrypted_passport_element.py index bc7b212b..76e02ec1 100644 --- a/aiogram/types/encrypted_passport_element.py +++ b/aiogram/types/encrypted_passport_element.py @@ -1,6 +1,7 @@ +import typing + from . import base from . import fields -import typing from .passport_file import PassportFile diff --git a/aiogram/types/message.py b/aiogram/types/message.py index b163f548..3863de68 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -28,6 +28,7 @@ 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): @@ -192,7 +193,7 @@ class Message(base.TelegramObject): raise TypeError("This message doesn't have any text.") quote_fn = md.quote_html if as_html else md.escape_md - + entities = self.entities or self.caption_entities if not entities: return quote_fn(text) @@ -275,6 +276,454 @@ class Message(base.TelegramObject): return md.hlink(text, url) return md.link(text, url) + async def answer(self, text, parse_mode=None, disable_web_page_preview=None, + disable_notification=None, reply_markup=None, reply=False) -> Message: + """ + Answer to this message + + :param text: str + :param parse_mode: str + :param disable_web_page_preview: bool + :param disable_notification: bool + :param reply_markup: + :param reply: fill 'reply_to_message_id' + :return: :class:`aiogram.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) + + async def answer_photo(self, photo: typing.Union[base.InputFile, base.String], + caption: typing.Union[base.String, None] = None, + disable_notification: typing.Union[base.Boolean, None] = None, + reply_markup=None, reply=False) -> Message: + """ + Use this method to send photos. + + Source: https://core.telegram.org/bots/api#sendphoto + + :param photo: Photo to send. + :type photo: :obj:`typing.Union[base.InputFile, base.String]` + :param caption: Photo caption (may also be used when resending photos by file_id), 0-200 characters + :type caption: :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. + :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_photo(chat_id=self.chat.id, photo=photo, caption=caption, + 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, + 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=None, + reply=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. + + For sending voice messages, use the sendVoice method instead. + + Source: https://core.telegram.org/bots/api#sendaudio + + :param audio: Audio file to send. + :type audio: :obj:`typing.Union[base.InputFile, base.String]` + :param caption: Audio caption, 0-200 characters + :type caption: :obj:`typing.Union[base.String, None]` + :param duration: Duration of the audio in seconds + :type duration: :obj:`typing.Union[base.Integer, None]` + :param performer: Performer + :type performer: :obj:`typing.Union[base.String, None]` + :param title: Track name + :type title: :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. + :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_audio(chat_id=self.chat.id, + audio=audio, + caption=caption, + 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=None, + reply=False) -> 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 answer_document(self, document: typing.Union[base.InputFile, base.String], + caption: typing.Union[base.String, None] = None, + disable_notification: typing.Union[base.Boolean, None] = None, + reply_markup=None, + reply=False) -> Message: + """ + Use this method to send general files. + + Bots can currently send files of any type of up to 50 MB in size, this limit may be changed in the future. + + Source: https://core.telegram.org/bots/api#senddocument + + :param document: File to send. + :type document: :obj:`typing.Union[base.InputFile, base.String]` + :param caption: Document caption (may also be used when resending documents by file_id), 0-200 characters + :type caption: :obj:`typing.Union[base.String, None]` + :param 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. + :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_document(chat_id=self.chat.id, + document=document, + caption=caption, + 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, + disable_notification: typing.Union[base.Boolean, None] = None, + reply_markup=None, + reply=False) -> Message: + """ + Use this method to send video files, Telegram clients support mp4 videos + (other formats may be sent as Document). + + Source: https://core.telegram.org/bots/api#sendvideo + + :param video: Video to send. + :type video: :obj:`typing.Union[base.InputFile, base.String]` + :param duration: Duration of sent video in seconds + :type duration: :obj:`typing.Union[base.Integer, None]` + :param width: Video width + :type width: :obj:`typing.Union[base.Integer, None]` + :param height: Video height + :type height: :obj:`typing.Union[base.Integer, None]` + :param caption: Video caption (may also be used when resending videos by file_id), 0-200 characters + :type caption: :obj:`typing.Union[base.String, None]` + :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. + :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_video(chat_id=self.chat.id, + video=video, + duration=duration, + width=width, + height=height, + caption=caption, + 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, + duration: typing.Union[base.Integer, None] = None, + disable_notification: typing.Union[base.Boolean, None] = None, + reply_markup=None, + reply=False) -> Message: + """ + Use this method to send audio files, if you want Telegram clients to display the file + as a playable voice message. + + For this to work, your audio must be in an .ogg file encoded with OPUS + (other formats may be sent as Audio or Document). + + Source: https://core.telegram.org/bots/api#sendvoice + + :param voice: Audio file to send. + :type voice: :obj:`typing.Union[base.InputFile, base.String]` + :param caption: Voice message caption, 0-200 characters + :type caption: :obj:`typing.Union[base.String, None]` + :param duration: Duration of the voice message in seconds + :type duration: :obj:`typing.Union[base.Integer, 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. + :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_voice(chat_id=self.chat.id, + voice=voice, + caption=caption, + 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=None, + reply=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. + + Source: https://core.telegram.org/bots/api#sendvideonote + + :param video_note: Video note to send. + :type video_note: :obj:`typing.Union[base.InputFile, base.String]` + :param duration: Duration of sent video in seconds + :type duration: :obj:`typing.Union[base.Integer, None]` + :param length: Video width and height + :type length: :obj:`typing.Union[base.Integer, 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. + :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_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=False) -> typing.List[Message]: + """ + Use this method to send a group of photos or videos as an album. + + Source: https://core.telegram.org/bots/api#sendmediagroup + + :param media: A JSON-serialized array describing photos and videos to be sent + :type media: :obj:`typing.Union[types.MediaGroup, typing.List]` + :param disable_notification: Sends the message silently. Users will receive a notification with no sound. + :type disable_notification: :obj:`typing.Union[base.Boolean, None]` + :param reply: fill 'reply_to_message_id' + :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) + + 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=None, + reply=False) -> Message: + """ + Use this method to send point on the map. + + Source: https://core.telegram.org/bots/api#sendlocation + + :param latitude: Latitude of the location + :type latitude: :obj:`base.Float` + :param longitude: Longitude of the location + :type longitude: :obj:`base.Float` + :param live_period: Period in seconds for which the location will be updated + :type live_period: :obj:`typing.Union[base.Integer, 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. + :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_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=None, + reply=False) -> 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. + :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 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=None, + reply=False) -> 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. + :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 answer_sticker(self, sticker: typing.Union[base.InputFile, base.String], + disable_notification: typing.Union[base.Boolean, None] = None, + reply_markup=None, reply=False) -> Message: + """ + Use this method to send .webp stickers. + + Source: https://core.telegram.org/bots/api#sendsticker + + :param sticker: Sticker to send. + :type sticker: :obj:`typing.Union[base.InputFile, base.String]` + :param disable_notification: Sends the message silently. Users will receive a notification with no sound. + :type disable_notification: :obj:`typing.Union[base.Boolean, None]` + :param reply_markup: Additional interface options. + :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_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, parse_mode=None, disable_web_page_preview=None, disable_notification=None, reply_markup=None, reply=True) -> Message: """ @@ -386,6 +835,69 @@ class Message(base.TelegramObject): 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` + """ + warn_deprecated('"Message.send_animation" method will be removed in 2.2 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 + ) + + 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=None, + reply=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 @@ -628,45 +1140,6 @@ class Message(base.TelegramObject): reply_to_message_id=self.message_id if reply else None, reply_markup=reply_markup) - async def edit_live_location(self, latitude: base.Float, longitude: base.Float, - reply_markup=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 - to stopMessageLiveLocation. - - Source: https://core.telegram.org/bots/api#editmessagelivelocation - - :param latitude: Latitude of new location - :type latitude: :obj:`base.Float` - :param longitude: Longitude of new location - :type longitude: :obj:`base.Float` - :param reply_markup: A JSON-serialized object for a new inline keyboard. - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, if the edited message was sent by the bot, the edited Message is returned, - otherwise True is returned. - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - 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=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. - - Source: https://core.telegram.org/bots/api#stopmessagelivelocation - - :param reply_markup: A JSON-serialized object for a new inline keyboard. - :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` - :return: On success, if the message was sent by the bot, the sent Message is returned, - otherwise True is returned. - :rtype: :obj:`typing.Union[types.Message, base.Boolean]` - """ - return await self.bot.stop_message_live_location(chat_id=self.chat.id, message_id=self.message_id, - 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, @@ -677,6 +1150,49 @@ class Message(base.TelegramObject): 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. + :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` + """ + warn_deprecated('"Message.send_venue" method will be removed in 2.2 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) + + 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=None, + reply=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 @@ -731,6 +1247,10 @@ 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.2 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, @@ -738,6 +1258,62 @@ class Message(base.TelegramObject): 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=None, + reply=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. + :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=None, reply=True) -> Message: + """ + Use this method to send .webp stickers. + + Source: https://core.telegram.org/bots/api#sendsticker + + :param sticker: Sticker to send. + :type sticker: :obj:`typing.Union[base.InputFile, base.String]` + :param disable_notification: Sends the message silently. Users will receive a notification with no sound. + :type disable_notification: :obj:`typing.Union[base.Boolean, None]` + :param reply_markup: Additional interface options. + :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_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, disable_notification=None) -> Message: """ Forward this message @@ -837,6 +1413,45 @@ class Message(base.TelegramObject): return await self.bot.edit_message_reply_markup(chat_id=self.chat.id, message_id=self.message_id, reply_markup=reply_markup) + async def edit_live_location(self, latitude: base.Float, longitude: base.Float, + reply_markup=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 + to stopMessageLiveLocation. + + Source: https://core.telegram.org/bots/api#editmessagelivelocation + + :param latitude: Latitude of new location + :type latitude: :obj:`base.Float` + :param longitude: Longitude of new location + :type longitude: :obj:`base.Float` + :param reply_markup: A JSON-serialized object for a new inline keyboard. + :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` + :return: On success, if the edited message was sent by the bot, the edited Message is returned, + otherwise True is returned. + :rtype: :obj:`typing.Union[types.Message, base.Boolean]` + """ + 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=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. + + Source: https://core.telegram.org/bots/api#stopmessagelivelocation + + :param reply_markup: A JSON-serialized object for a new inline keyboard. + :type reply_markup: :obj:`typing.Union[types.InlineKeyboardMarkup, None]` + :return: On success, if the message was sent by the bot, the sent Message is returned, + otherwise True is returned. + :rtype: :obj:`typing.Union[types.Message, base.Boolean]` + """ + 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): """ Delete this message @@ -845,30 +1460,6 @@ class Message(base.TelegramObject): """ return await self.bot.delete_message(self.chat.id, self.message_id) - async def reply_sticker(self, sticker: typing.Union[base.InputFile, base.String], - disable_notification: typing.Union[base.Boolean, None] = None, - reply_markup=None, reply=True) -> Message: - """ - Use this method to send .webp stickers. - - Source: https://core.telegram.org/bots/api#sendsticker - - :param sticker: Sticker to send. - :type sticker: :obj:`typing.Union[base.InputFile, base.String]` - :param disable_notification: Sends the message silently. Users will receive a notification with no sound. - :type disable_notification: :obj:`typing.Union[base.Boolean, None]` - :param reply_markup: Additional interface options. - :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_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 pin(self, disable_notification: bool = False): """ Pin message diff --git a/aiogram/types/passport_data.py b/aiogram/types/passport_data.py index 06cbad1c..2fed9fae 100644 --- a/aiogram/types/passport_data.py +++ b/aiogram/types/passport_data.py @@ -1,8 +1,9 @@ +import typing + from . import base from . import fields -import typing -from .encrypted_passport_element import EncryptedPassportElement from .encrypted_credentials import EncryptedCredentials +from .encrypted_passport_element import EncryptedPassportElement class PassportData(base.TelegramObject): diff --git a/aiogram/types/venue.py b/aiogram/types/venue.py index 1b420d57..f7b2a277 100644 --- a/aiogram/types/venue.py +++ b/aiogram/types/venue.py @@ -14,4 +14,3 @@ class Venue(base.TelegramObject): address: base.String = fields.Field() foursquare_id: base.String = fields.Field() foursquare_type: base.String = fields.Field() - diff --git a/aiogram/utils/auth_widget.py b/aiogram/utils/auth_widget.py index 8b2eacf7..b9084eb1 100644 --- a/aiogram/utils/auth_widget.py +++ b/aiogram/utils/auth_widget.py @@ -4,11 +4,10 @@ for more information https://core.telegram.org/widgets/login#checking-authorizat Source: https://gist.github.com/JrooTJunior/887791de7273c9df5277d2b1ecadc839 """ +import collections import hashlib import hmac -import collections - def generate_hash(data: dict, token: str) -> str: """ diff --git a/aiogram/utils/exceptions.py b/aiogram/utils/exceptions.py index 9e3bb6d2..24afe356 100644 --- a/aiogram/utils/exceptions.py +++ b/aiogram/utils/exceptions.py @@ -174,7 +174,7 @@ class MessageTextIsEmpty(MessageError): class MessageCantBeEdited(MessageError): match = 'message can\'t be edited' - + class MessageCantBeDeleted(MessageError): match = 'message can\'t be deleted' @@ -429,5 +429,5 @@ class Throttled(TelegramAPIError): def __str__(self): return f"Rate limit exceeded! (Limit: {self.rate} s, " \ - f"exceeded: {self.exceeded_count}, " \ - f"time delta: {round(self.delta, 3)} s)" + f"exceeded: {self.exceeded_count}, " \ + f"time delta: {round(self.delta, 3)} s)" diff --git a/aiogram/utils/mixins.py b/aiogram/utils/mixins.py index aa248cd9..776479bd 100644 --- a/aiogram/utils/mixins.py +++ b/aiogram/utils/mixins.py @@ -45,4 +45,3 @@ class ContextInstanceMixin: if not isinstance(value, cls): raise TypeError(f"Value should be instance of '{cls.__name__}' not '{type(value).__name__}'") cls.__context_instance.set(value) - diff --git a/examples/callback_data_factory.py b/examples/callback_data_factory.py index d87ae1a3..3dd7d35e 100644 --- a/examples/callback_data_factory.py +++ b/examples/callback_data_factory.py @@ -49,9 +49,9 @@ def get_keyboard() -> types.InlineKeyboardMarkup: 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']}" + f"{md.quote_html(post['body'])}\n" \ + f"\n" \ + f"Votes: {post['votes']}" markup = types.InlineKeyboardMarkup() markup.row( diff --git a/examples/payments.py b/examples/payments.py index d85e94ab..e8e37011 100644 --- a/examples/payments.py +++ b/examples/payments.py @@ -2,10 +2,9 @@ import asyncio from aiogram import Bot from aiogram import types -from aiogram.utils import executor 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' diff --git a/examples/webhook_example.py b/examples/webhook_example.py index fb0046ef..86520988 100644 --- a/examples/webhook_example.py +++ b/examples/webhook_example.py @@ -76,7 +76,8 @@ 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") + 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): diff --git a/tests/__init__.py b/tests/__init__.py index cb0c6b9d..262c9395 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,4 +1,5 @@ import aresponses + from aiogram import Bot TOKEN = '123456789:AABBCCDDEEFFaabbccddeeff-1234567890' diff --git a/tests/test_dispatcher.py b/tests/test_dispatcher.py index 3dec03e7..6ebaf472 100644 --- a/tests/test_dispatcher.py +++ b/tests/test_dispatcher.py @@ -1,7 +1,7 @@ -from aiogram import Dispatcher, Bot - import pytest +from aiogram import Dispatcher, Bot + pytestmark = pytest.mark.asyncio