From 19ce5c18b6cef30a0fc55c854e25db98712503a3 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 5 Aug 2017 04:13:12 +0300 Subject: [PATCH 01/12] What about response to Webhook? https://core.telegram.org/bots/faq#how-can-i-make-requests-in-response-to-updates --- aiogram/dispatcher/__init__.py | 53 +++++++--- aiogram/dispatcher/handler.py | 8 +- aiogram/dispatcher/webhook.py | 185 +++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 15 deletions(-) create mode 100644 aiogram/dispatcher/webhook.py diff --git a/aiogram/dispatcher/__init__.py b/aiogram/dispatcher/__init__.py index 08f23d2c..28e53014 100644 --- a/aiogram/dispatcher/__init__.py +++ b/aiogram/dispatcher/__init__.py @@ -5,6 +5,7 @@ import typing from .filters import CommandsFilter, RegexpFilter, ContentTypeFilter, generate_default_filters from .handler import Handler from .storage import DisabledStorage, BaseStorage, FSMContext +from .webhook import BaseResponse from ..bot import Bot from ..types.message import ContentType @@ -74,8 +75,10 @@ class Dispatcher: :param updates: :return: """ + tasks = [] for update in updates: - self.loop.create_task(self.updates_handler.notify(update)) + tasks.append(self.loop.create_task(self.updates_handler.notify(update))) + return await asyncio.gather(*tasks) async def process_update(self, update): """ @@ -86,30 +89,31 @@ class Dispatcher: """ self.last_update_id = update.update_id if update.message: - await self.message_handlers.notify(update.message) + return await self.message_handlers.notify(update.message) if update.edited_message: - await self.edited_message_handlers.notify(update.edited_message) + return await self.edited_message_handlers.notify(update.edited_message) if update.channel_post: - await self.channel_post_handlers.notify(update.channel_post) + return await self.channel_post_handlers.notify(update.channel_post) if update.edited_channel_post: - await self.edited_channel_post_handlers.notify(update.edited_channel_post) + return await self.edited_channel_post_handlers.notify(update.edited_channel_post) if update.inline_query: - await self.inline_query_handlers.notify(update.inline_query) + return await self.inline_query_handlers.notify(update.inline_query) if update.chosen_inline_result: - await self.chosen_inline_result_handlers.notify(update.chosen_inline_result) + return await self.chosen_inline_result_handlers.notify(update.chosen_inline_result) if update.callback_query: - await self.callback_query_handlers.notify(update.callback_query) + return await self.callback_query_handlers.notify(update.callback_query) if update.shipping_query: - await self.shipping_query_handlers.notify(update.shipping_query) + return await self.shipping_query_handlers.notify(update.shipping_query) if update.pre_checkout_query: - await self.pre_checkout_query_handlers.notify(update.pre_checkout_query) + return await self.pre_checkout_query_handlers.notify(update.pre_checkout_query) - async def start_pooling(self, timeout=20, relax=0.1): + async def start_pooling(self, timeout=20, relax=0.1, limit=None): """ Start long-pooling :param timeout: :param relax: + :param limit: :return: """ if self._pooling: @@ -120,21 +124,42 @@ class Dispatcher: offset = None while self._pooling: try: - updates = await self.bot.get_updates(offset=offset, timeout=timeout) + updates = await self.bot.get_updates(limit=limit, offset=offset, timeout=timeout) except Exception as e: log.exception('Cause exception while getting updates') - await asyncio.sleep(relax) + if relax: + await asyncio.sleep(relax) continue if updates: log.info("Received {0} updates.".format(len(updates))) offset = updates[-1].update_id + 1 - await self.process_updates(updates) + + self.loop.create_task(self._process_pooling_updates(updates)) await asyncio.sleep(relax) log.warning('Pooling is stopped.') + async def _process_pooling_updates(self, updates): + """ + Process updates received from long-pooling. + + :param updates: list of updates. + """ + need_to_call = [] + for update in await self.process_updates(updates): + for responses in update: + for response in responses: + if not isinstance(response, BaseResponse): + continue + need_to_call.append(response.execute_response(self.bot)) + if need_to_call: + try: + asyncio.gather(*need_to_call) + except Exception as e: + log.exception('Cause exception while processing updates.') + def stop_pooling(self): """ Break long-pooling process. diff --git a/aiogram/dispatcher/handler.py b/aiogram/dispatcher/handler.py index 1cd5bb98..92ef9e7c 100644 --- a/aiogram/dispatcher/handler.py +++ b/aiogram/dispatcher/handler.py @@ -34,13 +34,19 @@ class Handler: raise ValueError('This handler is not registered!') async def notify(self, *args, **kwargs): + results = [] + for filters, handler in self.handlers: if await check_filters(filters, args, kwargs): try: - await handler(*args, **kwargs) + response = await handler(*args, **kwargs) + if results is not None: + results.append(response) if self.once: break except SkipHandler: continue except CancelHandler: break + + return results diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py new file mode 100644 index 00000000..4a7c8505 --- /dev/null +++ b/aiogram/dispatcher/webhook.py @@ -0,0 +1,185 @@ +import typing +from typing import Union, Dict, Optional + +from aiohttp import web + +from aiogram import types +from aiogram.bot import api +from aiogram.bot.base import Integer, String, Boolean +from aiogram.utils.payload import prepare_arg +from ..utils import json + +DEFAULT_WEB_PATH = '/webhook' +BOT_DISPATCHER_KEY = 'BOT_DISPATCHER' + + +class WebhookRequestHandler(web.View): + """ + Simple Wehhook request handler for aiohttp web server. + + You need to register that in app: + + .. code-block:: python3 + + app.router.add_route('*', '/your/webhook/path', WebhookRequestHadler, 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' + + .. code-block:: python3 + + bot = Bot(TOKEN, loop) + dp = Dispatcher(bot) + app['BOT_DISPATCHER'] = dp + + """ + def get_dispatcher(self): + """ + Get Dispatcher instance from environment + + :return: :class:`aiogram.Dispatcher` + """ + return self.request.app[BOT_DISPATCHER_KEY] + + async def parse_update(self, bot): + """ + Read update from stream and deserialize it. + + :param bot: bot instance. You an get it from Dispatcher + :return: :class:`aiogram.types.Update` + """ + data = await self.request.json() + update = types.Update.deserialize(data) + bot.prepare_object(update, parent=bot) + return update + + async def post(self): + """ + Process POST request + + if one of handler returns instance of :class:`aiogram.dispatcher.webhook.BaseResponse` return it to webhook. + Otherwise do nothing (return 'ok') + + :return: :class:`aiohttp.web.Response` + """ + dispatcher = self.get_dispatcher() + update = await self.parse_update(dispatcher.bot) + results = await dispatcher.process_update(update) + + for result in results: + if isinstance(result, BaseResponse): + return result.get_web_response() + return web.Response(text='ok') + + +def configure_app(dispatcher, app: web.Application, path=DEFAULT_WEB_PATH): + """ + You can prepare web.Application for working with webhook handler. + + :param dispatcher: Dispatcher instance + :param app: :class:`aiohttp.web.Application` + :param path: Path to your webhook. + :return: + """ + app.router.add_route('*', path, WebhookRequestHandler, name='webhook_handler') + app[BOT_DISPATCHER_KEY] = dispatcher + + +def get_new_configured_app(dispatcher, path=DEFAULT_WEB_PATH): + """ + Create new :class:`aiohttp.web.Application` and configure it. + + :param dispatcher: Dispatcher instance + :param path: Path to your webhook. + :return: + """ + app = web.Application() + configure_app(dispatcher, app, path) + return app + + +class BaseResponse: + """ + Base class for webhook responses. + """ + method = None + + def prepare(self) -> typing.Dict: + """ + You need to owerwrite this method. + + :return: response parameters dict + """ + raise NotImplementedError + + def cleanup(self) -> typing.Dict: + """ + Cleanup response after preparing. Remove empty fields. + + :return: response parameters dict + """ + return {k: v for k, v in self.prepare().items() if v is not None} + + def get_response(self): + """ + Get response object + + :return: + """ + return {'method': self.method, **self.cleanup()} + + def get_web_response(self): + """ + Get prepared web response with JSON data. + + :return: :class:`aiohttp.web.Response` + """ + return web.json_response(self.get_response(), dumps=json.dumps) + + async def execute_response(self, bot): + """ + Use this method if you want to execute response as simple HTTP request. + + :param bot: Bot instance. + :return: + """ + return await bot.request(self.method, self.cleanup()) + + +class SendMessage(BaseResponse): + """ + You can send message with webhook by using this instance of this object. + All arguments is equal with :method:`Bot.send_message` method. + """ + __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], + text: String, + 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): + self.chat_id = chat_id + self.text = text + self.parse_mode = parse_mode + self.disable_web_page_preview = disable_web_page_preview + self.disable_notification = disable_notification + self.reply_to_message_id = reply_to_message_id + self.reply_markup = reply_markup + + 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) + } From 412684e7b5d6cac1b48c071e444832321b01ec41 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 5 Aug 2017 04:13:42 +0300 Subject: [PATCH 02/12] Optimize parsing arguments without value. --- aiogram/utils/payload.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aiogram/utils/payload.py b/aiogram/utils/payload.py index 5254fa44..dfef3db1 100644 --- a/aiogram/utils/payload.py +++ b/aiogram/utils/payload.py @@ -15,7 +15,9 @@ def generate_payload(exclude=None, **kwargs): def prepare_arg(value): - if isinstance(value, (list, dict)): + if value is None: + return None + elif isinstance(value, (list, dict)): return json.dumps(value) elif hasattr(value, 'to_json'): return json.dumps(value.to_json()) From b2798e39a83cd8085983c7cbe7bc22b2e0ce0e2e Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 5 Aug 2017 04:14:44 +0300 Subject: [PATCH 03/12] Fix send_file method for requests without files. (For e.g. setWebhook) --- aiogram/bot/base.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aiogram/bot/base.py b/aiogram/bot/base.py index dba39d1d..bd70b4d4 100644 --- a/aiogram/bot/base.py +++ b/aiogram/bot/base.py @@ -151,7 +151,9 @@ class BaseBot: :param payload: request payload :return: resonse """ - if isinstance(file, str): + if file is None: + files = {} + elif isinstance(file, str): # You can use file ID or URL in the most of requests payload[file_type] = file files = None From 33f7f0018eb8a34b8208deb8e28b1b638a589995 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 5 Aug 2017 04:15:16 +0300 Subject: [PATCH 04/12] Fix parsing date in Deserializable objects. --- aiogram/types/base.py | 5 +++++ aiogram/types/chat_member.py | 5 ----- aiogram/types/message.py | 4 ---- aiogram/types/webhook_info.py | 4 ---- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/aiogram/types/base.py b/aiogram/types/base.py index d40095a4..cafecab1 100644 --- a/aiogram/types/base.py +++ b/aiogram/types/base.py @@ -59,6 +59,11 @@ class Deserializable: result[name] = attr return result + @classmethod + def _parse_date(cls, unix_time): + if unix_time is not None: + return datetime.datetime.fromtimestamp(unix_time) + @property def bot(self) -> 'Bot': """ diff --git a/aiogram/types/chat_member.py b/aiogram/types/chat_member.py index e471a91d..3ef7311a 100644 --- a/aiogram/types/chat_member.py +++ b/aiogram/types/chat_member.py @@ -35,11 +35,6 @@ class ChatMember(Deserializable): self.can_send_other_messages: bool = can_send_other_messages self.can_add_web_page_previews: bool = can_add_web_page_previews - @classmethod - def _parse_date(cls, unix_time): - if unix_time is not None: - return datetime.datetime.fromtimestamp(unix_time) - @classmethod def de_json(cls, raw_data): user = User.deserialize(raw_data.get('user')) diff --git a/aiogram/types/message.py b/aiogram/types/message.py index 3a4d6a97..7cf95a91 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -74,10 +74,6 @@ class Message(Deserializable): self.content_type = content_type - @classmethod - def _parse_date(cls, unix_time): - return datetime.datetime.fromtimestamp(unix_time) - @classmethod def de_json(cls, raw_data): message_id = raw_data.get('message_id') diff --git a/aiogram/types/webhook_info.py b/aiogram/types/webhook_info.py index c8b2805b..de0f6f4c 100644 --- a/aiogram/types/webhook_info.py +++ b/aiogram/types/webhook_info.py @@ -19,10 +19,6 @@ class WebhookInfo(Deserializable): self.max_connections: int = max_connections self.allowed_updates: [str] = allowed_updates - @classmethod - def _parse_date(cls, unix_time): - return datetime.datetime.fromtimestamp(unix_time) - @classmethod def de_json(cls, raw_data): url = raw_data.get('url') From f3088f2f0a929ad12b840cbad7c398a00d192c4d Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 5 Aug 2017 19:30:08 +0300 Subject: [PATCH 05/12] Typo --- aiogram/dispatcher/webhook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py index 4a7c8505..ba4a23e3 100644 --- a/aiogram/dispatcher/webhook.py +++ b/aiogram/dispatcher/webhook.py @@ -106,7 +106,7 @@ class BaseResponse: def prepare(self) -> typing.Dict: """ - You need to owerwrite this method. + You need to override this method. :return: response parameters dict """ From cf510623780af8e855d26c78844c861f4ca01673 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Sat, 5 Aug 2017 21:33:59 +0300 Subject: [PATCH 06/12] Implement more things for graceful shutdown of storage and more thread-safe Redis storage. --- aiogram/contrib/fsm_storage/memory.py | 6 +++++ aiogram/contrib/fsm_storage/redis.py | 34 ++++++++++++++++++++++----- aiogram/dispatcher/storage.py | 23 ++++++++++++++++++ 3 files changed, 57 insertions(+), 6 deletions(-) diff --git a/aiogram/contrib/fsm_storage/memory.py b/aiogram/contrib/fsm_storage/memory.py index cbd8f243..c0c81c57 100644 --- a/aiogram/contrib/fsm_storage/memory.py +++ b/aiogram/contrib/fsm_storage/memory.py @@ -10,6 +10,12 @@ class MemoryStorage(BaseStorage): This type of storage is not recommended for usage in bots, because you will lost all states after restarting. """ + async def wait_closed(self): + pass + + def close(self): + self.data.clear() + def __init__(self): self.data = {} diff --git a/aiogram/contrib/fsm_storage/redis.py b/aiogram/contrib/fsm_storage/redis.py index d0bab8f3..30c61128 100644 --- a/aiogram/contrib/fsm_storage/redis.py +++ b/aiogram/contrib/fsm_storage/redis.py @@ -2,6 +2,7 @@ This module has redis storage for finite-state machine based on `aioredis `_ driver """ +import asyncio import typing import aioredis @@ -21,6 +22,13 @@ class RedisStorage(BaseStorage): storage = RedisStorage('localhost', 6379, db=5) dp = Dispatcher(bot, storage=storage) + And need to close Redis connection when shutdown + + .. code-block:: python3 + + dp.storage.close() + await dp.storage.wait_closed() + """ def __init__(self, host, port, db=None, password=None, ssl=None, loop=None, **kwargs): @@ -29,10 +37,22 @@ class RedisStorage(BaseStorage): self._db = db self._password = password self._ssl = ssl - self._loop = loop + self._loop = loop or asyncio.get_event_loop() self._kwargs = kwargs self._redis: aioredis.RedisConnection = None + self._connection_lock = asyncio.Lock(loop=self._loop) + + def close(self): + if self._redis and not self._redis.closed: + self._redis.close() + del self._redis + self._redis = None + + async def wait_closed(self): + if self._redis: + return await self._redis.wait_closed() + return True @property async def redis(self) -> aioredis.RedisConnection: @@ -41,11 +61,13 @@ class RedisStorage(BaseStorage): This property is awaitable. """ - 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) + # 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) return self._redis async def get_record(self, *, diff --git a/aiogram/dispatcher/storage.py b/aiogram/dispatcher/storage.py index 4deb3b54..d3c450ae 100644 --- a/aiogram/dispatcher/storage.py +++ b/aiogram/dispatcher/storage.py @@ -6,6 +6,23 @@ class BaseStorage: In states-storage you can save current user state and data for all steps """ + def close(self): + """ + Need override this method and use when application is shutdowns. + You can save data or etc. + + :return: + """ + raise NotImplementedError + + async def wait_closed(self): + """ + You need override this method for all asynchronously storage's like Redis. + + :return: + """ + raise NotImplementedError + @classmethod def check_address(cls, *, chat: typing.Union[str, int, None] = None, @@ -209,6 +226,12 @@ class DisabledStorage(BaseStorage): Empty storage. Use it if you don't want to use Finite-State Machine """ + def close(self): + pass + + async def wait_closed(self): + pass + async def get_state(self, *, chat: typing.Union[str, int, None] = None, user: typing.Union[str, int, None] = None, From 7b61fff7c1bfe6c1e7c688d075e45e640261484f Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Tue, 8 Aug 2017 04:47:41 +0300 Subject: [PATCH 07/12] I think that's time for make commit. --- aiogram/dispatcher/webhook.py | 580 +++++++++++++++++++++++++++++++++- 1 file changed, 576 insertions(+), 4 deletions(-) diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py index ba4a23e3..ac0815b7 100644 --- a/aiogram/dispatcher/webhook.py +++ b/aiogram/dispatcher/webhook.py @@ -3,11 +3,11 @@ from typing import Union, Dict, Optional from aiohttp import web -from aiogram import types -from aiogram.bot import api -from aiogram.bot.base import Integer, String, Boolean -from aiogram.utils.payload import prepare_arg +from .. import types +from ..bot import api +from ..bot.base import Integer, String, Boolean, Float from ..utils import json +from ..utils.payload import prepare_arg DEFAULT_WEB_PATH = '/webhook' BOT_DISPATCHER_KEY = 'BOT_DISPATCHER' @@ -33,6 +33,7 @@ class WebhookRequestHandler(web.View): app['BOT_DISPATCHER'] = dp """ + def get_dispatcher(self): """ Get Dispatcher instance from environment @@ -165,6 +166,20 @@ class SendMessage(BaseResponse): 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) + :param text: String - Text of the message to be sent + :param parse_mode: String (Optional) - Send Markdown or HTML, if you want Telegram apps to show bold, + italic, fixed-width text or inline URLs in your bot's message. + :param disable_web_page_preview: Boolean (Optional) - Disables link previews for links in this message + :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive + a notification with no sound. + :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message + :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) + - 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. + """ self.chat_id = chat_id self.text = text self.parse_mode = parse_mode @@ -183,3 +198,560 @@ class SendMessage(BaseResponse): 'reply_to_message_id': self.reply_to_message_id, 'reply_markup': prepare_arg(self.reply_markup) } + + +class ForwardMessage(BaseResponse): + """ + Forward message from + """ + __slots__ = ('chat_id', 'from_chat_id', 'message_id', 'disable_notification') + + method = api.Methods.FORWARD_MESSAGE + + def __init__(self, chat_id: Union[Integer, String], + from_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 channel (in the format @channelusername) + :param from_chat_id: Union[Integer, String] - Unique identifier for the chat where the original + message was sent (or channel username in the format @channelusername) + :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive a + notification with no sound. + :param message_id: Integer - Message identifier in the chat specified in from_chat_id + """ + self.chat_id = chat_id + self.from_chat_id = from_chat_id + self.message_id = message_id + self.disable_notification = disable_notification + + 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 + } + + +class SendPhoto(BaseResponse): + """ + + """ + __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): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username of + the target channel (in the format @channelusername) + :param photo: String - Photo to send. Pass a file_id as String to send + a photo that exists on the Telegram servers (recommended), pass an HTTP URL as a String for + Telegram to get a photo from the Internet, or upload a new photo using multipart/form-data. + :param caption: String (Optional) - Photo caption (may also be used when resending photos by file_id), + 0-200 characters + :param disable_notification: Boolean (Optional) - Sends the message silently. Users will receive + a notification with no sound. + :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message + :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) + - 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. + """ + self.chat_id = chat_id + self.photo = photo + self.caption = caption + self.disable_notification = disable_notification + self.reply_to_message_id = reply_to_message_id + self.reply_markup = reply_markup + + 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) + } + + +class SendAudio(BaseResponse): + """ + + """ + __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): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username + of the target channel (in the format @channelusername) + :param audio: String - Audio file to send. Pass a file_id as String + to send an audio file that exists on the Telegram servers (recommended), pass an HTTP URL + as a String for Telegram to get an audio file from the Internet, or upload a new one + using multipart/form-data. + :param caption: String (Optional) - Audio caption, 0-200 characters + :param duration: Integer (Optional) - Duration of the audio in seconds + :param performer: String (Optional) - Performer + :param title: String (Optional) - Track name + :param disable_notification: Boolean (Optional) - Sends the message silently. + Users will receive a notification with no sound. + :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message + :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) + - 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. + """ + self.chat_id = chat_id + self.audio = audio + self.caption = caption + self.duration = duration + self.performer = performer + self.title = title + self.disable_notification = disable_notification + self.reply_to_message_id = reply_to_message_id + self.reply_markup = reply_markup + + 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) + } + + +class SendDocument(BaseResponse): + """ + + """ + __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): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username + of the target channel (in the format @channelusername) + :param document: String - File to send. Pass a file_id as String + to send a file that exists on the Telegram servers (recommended), pass an HTTP URL + as a String for Telegram to get a file from the Internet, or upload a new one + using multipart/form-data. + :param caption: String (Optional) - Document caption + (may also be used when resending documents by file_id), 0-200 characters + :param disable_notification: Boolean (Optional) - Sends the message silently. + Users will receive a notification with no sound. + :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message + :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) + - 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. + """ + self.chat_id = chat_id + self.document = document + self.caption = caption + self.disable_notification = disable_notification + self.reply_to_message_id = reply_to_message_id + self.reply_markup = reply_markup + + 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), + } + + +class SendVideo(BaseResponse): + """ + + """ + __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): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username + of the target channel (in the format @channelusername) + :param video: String - Video to send. Pass a file_id as String + to send a video that exists on the Telegram servers (recommended), pass an HTTP URL + as a String for Telegram to get a video from the Internet, or upload a new video + using multipart/form-data. + :param duration: Integer (Optional) - Duration of sent video in seconds + :param width: Integer (Optional) - Video width + :param height: Integer (Optional) - Video height + :param caption: String (Optional) - Video caption (may also be used when resending videos by file_id), + 0-200 characters + :param disable_notification: Boolean (Optional) - Sends the message silently. + Users will receive a notification with no sound. + :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message + :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) + - 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. + """ + self.chat_id = chat_id + self.video = video + self.duration = duration + self.width = width + self.height = height + self.caption = caption + self.disable_notification = disable_notification + self.reply_to_message_id = reply_to_message_id + self.reply_markup = reply_markup + + 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) + } + + +class SendVoice(BaseResponse): + """ + + """ + __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): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username + of the target channel (in the format @channelusername) + :param voice: String - Audio file to send. Pass a file_id as String + to send a file that exists on the Telegram servers (recommended), pass an HTTP URL + as a String for Telegram to get a file from the Internet, or upload a new one + using multipart/form-data. + :param caption: String (Optional) - Voice message caption, 0-200 characters + :param duration: Integer (Optional) - Duration of the voice message in seconds + :param disable_notification: Boolean (Optional) - Sends the message silently. + Users will receive a notification with no sound. + :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message + :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) + - 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. + """ + self.chat_id = chat_id + self.voice = voice + self.caption = caption + self.duration = duration + self.disable_notification = disable_notification + self.reply_to_message_id = reply_to_message_id + self.reply_markup = reply_markup + + 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) + } + + +class SendVideoNote(BaseResponse): + """ + + """ + __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): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username + of the target channel (in the format @channelusername) + :param video_note: Union[io.BytesIO, io.FileIO, String] - Video note to send. Pass a file_id + as String to send a video note that exists on the Telegram servers (recommended) + or upload a new video using multipart/form-data. Sending video notes by a URL is currently unsupported + :param duration: Integer (Optional) - Duration of sent video in seconds + :param length: Integer (Optional) - Video width and height + :param disable_notification: Boolean (Optional) - Sends the message silently. + Users will receive a notification with no sound. + :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message + :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) + - 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. + """ + self.chat_id = chat_id + self.video_note = video_note + self.duration = duration + self.length = length + self.disable_notification = disable_notification + self.reply_to_message_id = reply_to_message_id + self.reply_markup = reply_markup + + 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) + } + + +class SendLocation(BaseResponse): + """ + + """ + __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): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username + of the target channel (in the format @channelusername) + :param latitude: Float - Latitude of location + :param longitude: Float - Longitude of location + :param disable_notification: Boolean (Optional) - Sends the message silently. + Users will receive a notification with no sound. + :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message + :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) + - 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. + """ + self.chat_id = chat_id + self.latitude = latitude + self.longitude = longitude + self.disable_notification = disable_notification + self.reply_to_message_id = reply_to_message_id + self.reply_markup = reply_markup + + 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) + } + + +class SendVenue(BaseResponse): + """ + + """ + __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): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username + of the target channel (in the format @channelusername) + :param latitude: Float - Latitude of the venue + :param longitude: Float - Longitude of the venue + :param title: String - Name of the venue + :param address: String - Address of the venue + :param foursquare_id: String (Optional) - Foursquare identifier of the venue + :param disable_notification: Boolean (Optional) - Sends the message silently. + Users will receive a notification with no sound. + :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message + :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) + - 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. + """ + self.chat_id = chat_id + self.latitude = latitude + self.longitude = longitude + self.title = title + self.address = address + self.foursquare_id = foursquare_id + self.disable_notification = disable_notification + self.reply_to_message_id = reply_to_message_id + self.reply_markup = reply_markup + + 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) + } + + +class SendContact(BaseResponse): + """ + + """ + __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): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat or + username of the target channel (in the format @channelusername) + :param phone_number: String - Contact's phone number + :param first_name: String - Contact's first name + :param last_name: String (Optional) - Contact's last name + :param disable_notification: Boolean (Optional) - Sends the message silently. + Users will receive a notification with no sound. + :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message + :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) + - Additional interface options. A JSON-serialized object for an inline keyboard, + custom reply keyboard, instructions to remove keyboard or to force a reply from the user. + """ + self.chat_id = chat_id + self.phone_number = phone_number + self.first_name = first_name + self.last_name = last_name + self.disable_notification = disable_notification + self.reply_to_message_id = reply_to_message_id + self.reply_markup = reply_markup + + 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) + } + + +class SendChatAction(BaseResponse): + """ + + """ + __slots__ = ('chat_id', 'action') + + method = api.Methods.SEND_CHAT_ACTION + + def __init__(self, chat_id: Union[Integer, String], action: String): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username + of the target channel (in the format @channelusername) + :param action: String - Type of action to broadcast. Choose one, depending on what the user is about to receive: + typing for text messages, upload_photo for photos, record_video or upload_video for videos, + record_audio or upload_audio for audio files, upload_document for general files, + find_location for location data, record_video_note or upload_video_note for video notes. + """ + self.chat_id = chat_id + self.action = action + + def prepare(self): + return { + 'chat_id': self.chat_id, + 'action': self.action + } + + +class Empty(BaseResponse): + """ + + """ + __slots__ = () + + method = api.Methods + + def __init__(self): + """ + + """ + pass + + def prepare(self): + return {} From 9804ec224096b0b5fddda2d700612bdfb1af4f7b Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Tue, 8 Aug 2017 05:35:26 +0300 Subject: [PATCH 08/12] Added more responses. (that's not all) --- aiogram/dispatcher/webhook.py | 355 ++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py index ac0815b7..dd6b3ffe 100644 --- a/aiogram/dispatcher/webhook.py +++ b/aiogram/dispatcher/webhook.py @@ -1,3 +1,4 @@ +import datetime import typing from typing import Union, Dict, Optional @@ -739,6 +740,360 @@ class SendChatAction(BaseResponse): } +class KickChatMember(BaseResponse): + """ + + """ + __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): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target group or username + of the target supergroup or channel (in the format @channelusername) + :param user_id: Integer - Unique identifier of the target user + :param until_date: Integer - Date when the user will be unbanned, unix time. If user is banned for + more than 366 days or less than 30 seconds from the current time they are considered to be banned forever + """ + self.chat_id = chat_id + self.user_id = user_id + self.until_date = until_date + + def prepare(self): + return { + 'chat_id': self.chat_id, + 'user_id': self.user_id, + 'until_date': prepare_arg(self.until_date) + } + + +class UnbanChatMember(BaseResponse): + """ + + """ + __slots__ = ('chat_id', 'user_id') + + method = api.Methods.UNBAN_CHAT_MEMBER + + def __init__(self, chat_id: Union[Integer, String], user_id: Integer): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target group or + username of the target supergroup or channel (in the format @username) + :param user_id: Integer - Unique identifier of the target user + """ + self.chat_id = chat_id + self.user_id = user_id + + def prepare(self): + return { + 'chat_id': self.chat_id, + 'user_id': self.user_id + } + + +class Empty(BaseResponse): + """ + + """ + __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): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat + or username of the target supergroup (in the format @supergroupusername) + :param user_id: Integer - Unique identifier of the target user + :param until_date: Integer - Date when restrictions will be lifted for the user, unix time. + If user is restricted for more than 366 days or less than 30 seconds from the current time, + they are considered to be restricted forever + :param can_send_messages: Boolean - Pass True, if the user can send text messages, contacts, + locations and venues + :param can_send_media_messages: Boolean - Pass True, if the user can send audios, documents, + photos, videos, video notes and voice notes, implies can_send_messages + :param can_send_other_messages: Boolean - Pass True, if the user can send animations, games, + stickers and use inline bots, implies can_send_media_messages + :param can_add_web_page_previews: Boolean - Pass True, if the user may add web page previews + to their messages, implies can_send_media_messages + """ + self.chat_id = chat_id + self.user_id = user_id + self.until_date = until_date + self.can_send_messages = can_send_messages + self.can_send_media_messages = can_send_media_messages + self.can_send_other_messages = can_send_other_messages + self.can_add_web_page_previews = can_add_web_page_previews + + 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 + } + + +class PromoteChatMember(BaseResponse): + """ + + """ + __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): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat + or username of the target channel (in the format @channelusername) + :param user_id: Integer - Unique identifier of the target user + :param can_change_info: Boolean - Pass True, if the administrator can change chat title, + photo and other settings + :param can_post_messages: Boolean - Pass True, if the administrator can create channel posts, channels only + :param can_edit_messages: Boolean - Pass True, if the administrator can edit messages of other users, + channels only + :param can_delete_messages: Boolean - Pass True, if the administrator can delete messages of other users + :param can_invite_users: Boolean - Pass True, if the administrator can invite new users to the chat + :param can_restrict_members: Boolean - Pass True, if the administrator can restrict, ban or unban chat members + :param can_pin_messages: Boolean - Pass True, if the administrator can pin messages, supergroups only + :param can_promote_members: Boolean - Pass True, if the administrator can add new administrators + with a subset of his own privileges or demote administrators that he has promoted, + directly or indirectly (promoted by administrators that were appointed by him) + """ + self.chat_id = chat_id + self.user_id = user_id + self.can_change_info = can_change_info + self.can_post_messages = can_post_messages + self.can_edit_messages = can_edit_messages + self.can_delete_messages = can_delete_messages + self.can_invite_users = can_invite_users + self.can_restrict_members = can_restrict_members + self.can_pin_messages = can_pin_messages + self.can_promote_members = can_promote_members + + 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 + } + + +class DeleteChatPhoto(BaseResponse): + """ + + """ + __slots__ = ('chat_id',) + + method = api.Methods.DELETE_CHAT_PHOTO + + def __init__(self, chat_id: Union[Integer, String]): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat + or username of the target channel (in the format @channelusername) + """ + self.chat_id = chat_id + + def prepare(self): + return { + 'chat_id': self.chat_id + } + + +class SetChatTitle(BaseResponse): + """ + + """ + __slots__ = ('chat_id', 'title') + + method = api.Methods.SET_CHAT_TITLE + + def __init__(self, chat_id: Union[Integer, String], title: String): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username + of the target channel (in the format @channelusername) + :param title: String - New chat title, 1-255 characters + """ + self.chat_id = chat_id + self.title = title + + def prepare(self): + return { + 'chat_id': self.chat_id, + 'title': self.title + } + + +class SetChatDescription(BaseResponse): + """ + + """ + __slots__ = ('chat_id', 'description') + + method = api.Methods.SET_CHAT_DESCRIPTION + + def __init__(self, chat_id: Union[Integer, String], description: String): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat + or username of the target channel (in the format @channelusername) + :param description: String - New chat description, 0-255 characters + """ + self.chat_id = chat_id + self.description = description + + def prepare(self): + return { + 'chat_id': self.chat_id, + 'description': self.description + } + + +class PinChatMessage(BaseResponse): + """ + + """ + __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): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat + or username of the target supergroup (in the format @supergroupusername) + :param message_id: Integer - Identifier of a message to pin + :param disable_notification: Boolean - Pass True, if it is not necessary to send a notification + to all group members about the new pinned message + """ + self.chat_id = chat_id + self.message_id = message_id + self.disable_notification = disable_notification + + def prepare(self): + return { + 'chat_id': self.chat_id, + 'message_id': self.message_id, + 'disable_notification': self.disable_notification, + } + + +class UnpinChatMessage(BaseResponse): + """ + + """ + __slots__ = ('chat_id',) + + method = api.Methods.UNPIN_CHAT_MESSAGE + + def __init__(self, chat_id: Union[Integer, String]): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat or + username of the target supergroup (in the format @supergroupusername) + """ + self.chat_id = chat_id + + def prepare(self): + return { + 'chat_id': self.chat_id + } + + +class LeaveChat(BaseResponse): + """ + + """ + __slots__ = ('chat_id',) + + method = api.Methods.LEAVE_CHAT + + def __init__(self, chat_id: Union[Integer, String]): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat + or username of the target supergroup or channel (in the format @channelusername) + """ + self.chat_id = chat_id + + def prepare(self): + return { + 'chat_id': self.chat_id + } + + +class AnswerCallbackQuery(BaseResponse): + """ + + """ + __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): + """ + :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, + 0-200 characters + :param show_alert: Boolean (Optional) - If true, an alert will be shown by the client instead + of a notification at the top of the chat screen. Defaults to false. + :param url: String (Optional) - URL that will be opened by the user's client. + If you have created a Game and accepted the conditions via @Botfather, + specify the URL that opens your game – note that this will only work + if the query comes from a callback_game button. + Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter. + :param cache_time: Integer (Optional) - The maximum amount of time in seconds that the result + of the callback query may be cached client-side. Telegram apps will support + caching starting in version 3.14. Defaults to 0. + """ + self.callback_query_id = callback_query_id + self.text = text + self.show_alert = show_alert + self.url = url + self.cache_time = cache_time + + 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 + } + + class Empty(BaseResponse): """ From 257b281bb4751511d5ec3a0918c86664f5f65003 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Tue, 8 Aug 2017 05:36:13 +0300 Subject: [PATCH 09/12] Empty line. --- aiogram/bot/base.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aiogram/bot/base.py b/aiogram/bot/base.py index bd70b4d4..c897cb9d 100644 --- a/aiogram/bot/base.py +++ b/aiogram/bot/base.py @@ -1078,8 +1078,7 @@ class BaseBot: If you have created a Game and accepted the conditions via @Botfather, specify the URL that opens your game – note that this will only work if the query comes from a callback_game button. - - Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter. + Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter. :param cache_time: Integer (Optional) - The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0. From 2318bbf46076a99863d316f28009aaf6aa72f27f Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Wed, 9 Aug 2017 04:00:29 +0300 Subject: [PATCH 10/12] Written all types of webhook responses (i think). Skiped all "get" requests. --- aiogram/dispatcher/webhook.py | 603 +++++++++++++++++++++++++++++++++- 1 file changed, 596 insertions(+), 7 deletions(-) diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py index dd6b3ffe..21f32954 100644 --- a/aiogram/dispatcher/webhook.py +++ b/aiogram/dispatcher/webhook.py @@ -529,7 +529,7 @@ class SendVideoNote(BaseResponse): """ :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username of the target channel (in the format @channelusername) - :param video_note: Union[io.BytesIO, io.FileIO, String] - Video note to send. Pass a file_id + :param video_note: String - Video note to send. Pass a file_id as String to send a video note that exists on the Telegram servers (recommended) or upload a new video using multipart/form-data. Sending video notes by a URL is currently unsupported :param duration: Integer (Optional) - Duration of sent video in seconds @@ -1094,19 +1094,608 @@ class AnswerCallbackQuery(BaseResponse): } -class Empty(BaseResponse): +class EditMessageText(BaseResponse): """ """ - __slots__ = () + __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): + """ + :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) + :param message_id: Integer (Optional) - Required if inline_message_id is not specified. + Identifier of the sent message + :param inline_message_id: String (Optional) - Required if chat_id and message_id are not specified. + Identifier of the inline message + :param text: String - New text of the message + :param parse_mode: String (Optional) - Send Markdown or HTML, if you want Telegram apps to show bold, + italic, fixed-width text or inline URLs in your bot's message. + :param disable_web_page_preview: Boolean (Optional) - Disables link previews for links in this message + :param reply_markup: types.InlineKeyboardMarkup (Optional) - A JSON-serialized object for + an inline keyboard. + """ + self.chat_id = chat_id + self.message_id = message_id + self.inline_message_id = inline_message_id + self.text = text + self.parse_mode = parse_mode + self.disable_web_page_preview = disable_web_page_preview + self.reply_markup = reply_markup + + 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) + } + + +class EditMessageCaption(BaseResponse): + """ + + """ + __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): + """ + :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) + :param message_id: Integer (Optional) - Required if inline_message_id is not specified. + Identifier of the sent message + :param inline_message_id: String (Optional) - Required if chat_id and message_id are not specified. + Identifier of the inline message + :param caption: String (Optional) - New caption of the message + :param reply_markup: types.InlineKeyboardMarkup (Optional) - A JSON-serialized object for an inline keyboard. + """ + self.chat_id = chat_id + self.message_id = message_id + self.inline_message_id = inline_message_id + self.caption = caption + self.reply_markup = reply_markup + + 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) + } + + +class EditMessageReplyMarkup(BaseResponse): + """ + + """ + __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): + """ + :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) + :param message_id: Integer (Optional) - Required if inline_message_id is not specified. + Identifier of the sent message + :param inline_message_id: String (Optional) - Required if chat_id and message_id are not specified. + Identifier of the inline message + :param reply_markup: types.InlineKeyboardMarkup (Optional) - A JSON-serialized object for an inline keyboard. + """ + self.chat_id = chat_id + self.message_id = message_id + self.inline_message_id = inline_message_id + self.reply_markup = reply_markup + + 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) + } + + +class DeleteMessage(BaseResponse): + """ + + """ + __slots__ = ('chat_id', 'message_id') + + method = api.Methods.DELETE_MESSAGE + + def __init__(self, chat_id: Union[Integer, String], message_id: Integer): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username + of the target channel (in the format @channelusername) + :param message_id: Integer - Identifier of the message to delete + """ + self.chat_id = chat_id + self.message_id = message_id + + def prepare(self): + return { + 'chat_id': self.chat_id, + 'message_id': self.message_id + } + + +class SendSticker(BaseResponse): + """ + + """ + __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): + """ + :param chat_id: Union[Integer, String] - Unique identifier for the target chat or username + of the target channel (in the format @channelusername) + :param sticker: String - Sticker to send. Pass a file_id + as String to send a file that exists on the Telegram servers (recommended), + pass an HTTP URL as a String for Telegram to get a .webp file from the Internet, + or upload a new one using multipart/form-data. More info on Sending Files » + :param disable_notification: Boolean (Optional) - Sends the message silently. + Users will receive a notification with no sound. + :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message + :param reply_markup: Union[types.InlineKeyboardMarkup, types.ReplyKeyboardMarkup, Dict, String] (Optional) - + 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. + """ + self.chat_id = chat_id + self.sticker = sticker + self.disable_notification = disable_notification + self.reply_to_message_id = reply_to_message_id + self.reply_markup = reply_markup + + 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) + } + + +class CreateNewStickerSet(BaseResponse): + """ + + """ + __slots__ = ('user_id', 'name', 'title', 'png_sticker', 'emojis', 'contains_masks', 'mask_position') method = api.Methods - def __init__(self): + 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). + Can contain only english letters, digits and underscores. Must begin with a letter, + can't contain consecutive underscores and must end in “_by_”. + is case insensitive. 1-64 characters. + :param title: String - Sticker set title, 1-64 characters + :param png_sticker: String - Png image with the sticker, + must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width + or height must be exactly 512px. Pass a file_id as a String to send a file that + already exists on the Telegram servers, pass an HTTP URL + as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. + :param emojis: String - One or more emoji corresponding to the sticker + :param contains_masks: Boolean (Optional) - Pass True, if a set of mask stickers should be created + :param mask_position: types.MaskPosition (Optional) - Position where the mask should be placed on faces """ - pass + self.user_id = user_id + self.name = name + self.title = title + self.png_sticker = png_sticker + self.emojis = emojis + self.contains_masks = contains_masks + self.mask_position = mask_position def prepare(self): - return {} + 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 + } + + +class AddStickerToSet(BaseResponse): + """ + + """ + __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): + """ + :param user_id: Integer - User identifier of sticker set owner + :param name: String - Sticker set name + :param png_sticker: String - Png image with the sticker, + must be up to 512 kilobytes in size, dimensions must not exceed 512px, + and either width or height must be exactly 512px. Pass a file_id as a String + to send a file that already exists on the Telegram servers, pass an HTTP URL + as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. + :param emojis: String - One or more emoji corresponding to the sticker + :param mask_position: types.MaskPosition (Optional) - Position where the mask should be placed on faces + """ + self.user_id = user_id + self.name = name + self.png_sticker = png_sticker + self.emojis = emojis + self.mask_position = mask_position + + 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) + } + + +class SetStickerPositionInSet(BaseResponse): + """ + + """ + __slots__ = ('sticker', 'position') + + method = api.Methods.SET_STICKER_POSITION_IN_SET + + def __init__(self, sticker: String, position: Integer): + """ + :param sticker: String - File identifier of the sticker + :param position: Integer - New sticker position in the set, zero-based + """ + self.sticker = sticker + self.position = position + + def prepare(self): + return { + 'sticker': self.sticker, + 'position': self.position + } + + +class DeleteStickerFromSet(BaseResponse): + """ + + """ + __slots__ = ('sticker',) + + method = api.Methods.DELETE_STICKER_FROM_SET + + def __init__(self, sticker: String): + """ + :param sticker: String - File identifier of the sticker + """ + self.sticker = sticker + + def prepare(self): + return { + 'sticker': self.sticker + } + + +class AnswerInlineQuery(BaseResponse): + """ + + """ + __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): + """ + :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 + :param cache_time: Integer (Optional) - The maximum amount of time in seconds that the result + of the inline query may be cached on the server. Defaults to 300. + :param is_personal: Boolean (Optional) - Pass True, if results may be cached on the server side + only for the user that sent the query. By default, results may be returned + to any user who sends the same query + :param next_offset: String (Optional) - Pass the offset that a client should send in the + next query with the same text to receive more results. + Pass an empty string if there are no more results or if you don‘t support pagination. + Offset length can’t exceed 64 bytes. + :param switch_pm_text: String (Optional) - If passed, clients will display a button with specified text + that switches the user to a private chat with the bot and sends the bot a start + message with the parameter switch_pm_parameter + :param switch_pm_parameter: String (Optional) - Deep-linking parameter for the /start message + sent to the bot when user presses the switch button. 1-64 characters, + only A-Z, a-z, 0-9, _ and - are allowed. + Example: An inline bot that sends YouTube videos can ask the user to connect the bot to their + YouTube account to adapt search results accordingly. To do this, + it displays a ‘Connect your YouTube account’ button above the results, or even before showing any. + The user presses the button, switches to a private chat with the bot and, + in doing so, passes a start parameter that instructs the bot to return an oauth link. + Once done, the bot can offer a switch_inline button so that the user can easily return + to the chat where they wanted to use the bot's inline capabilities. + """ + self.inline_query_id = inline_query_id + self.results = results + self.cache_time = cache_time + self.is_personal = is_personal + self.next_offset = next_offset + self.switch_pm_text = switch_pm_text + self.switch_pm_parameter = switch_pm_parameter + + 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, + } + + +class SendInvoice(BaseResponse): + """ + + """ + __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): + """ + :param chat_id: Integer - Unique identifier for the target private chat + :param title: String - Product name, 1-32 characters + :param description: String - Product description, 1-255 characters + :param payload: String - Bot-defined invoice payload, 1-128 bytes. + This will not be displayed to the user, use for your internal processes. + :param provider_token: String - Payments provider token, obtained via Botfather + :param start_parameter: String - Unique deep-linking parameter that can be used to + generate this invoice when used as a start parameter + :param currency: String - Three-letter ISO 4217 currency code, see more on currencies + :param prices: [types.LabeledPrice] - Price breakdown, a list of components + (e.g. product price, tax, discount, delivery cost, delivery tax, bonus, etc.) + :param photo_url: String (Optional) - URL of the product photo for the invoice. + Can be a photo of the goods or a marketing image for a service. + People like it better when they see what they are paying for. + :param photo_size: Integer (Optional) - Photo size + :param photo_width: Integer (Optional) - Photo width + :param photo_height: Integer (Optional) - Photo height + :param need_name: Boolean (Optional) - Pass True, if you require the user's full name to complete the order + :param need_phone_number: Boolean (Optional) - Pass True, if you require + the user's phone number to complete the order + :param need_email: Boolean (Optional) - Pass True, if you require the user's email to complete the order + :param need_shipping_address: Boolean (Optional) - Pass True, if you require the user's + shipping address to complete the order + :param is_flexible: Boolean (Optional) - Pass True, if the final price depends on the shipping method + :param disable_notification: Boolean (Optional) - Sends the message silently. + Users will receive a notification with no sound. + :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message + :param reply_markup: types.InlineKeyboardMarkup (Optional) - A JSON-serialized object for an inline keyboard. + If empty, one 'Pay total price' button will be shown. If not empty, the first button must be a Pay button. + """ + self.chat_id = chat_id + self.title = title + self.description = description + self.payload = payload + self.provider_token = provider_token + self.start_parameter = start_parameter + self.currency = currency + self.prices = prices + self.photo_url = photo_url + self.photo_size = photo_size + self.photo_width = photo_width + self.photo_height = photo_height + self.need_name = need_name + self.need_phone_number = need_phone_number + self.need_email = need_email + self.need_shipping_address = need_shipping_address + self.is_flexible = is_flexible + self.disable_notification = disable_notification + self.reply_to_message_id = reply_to_message_id + self.reply_markup = reply_markup + + 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), + } + + +class AnswerShippingQuery(BaseResponse): + """ + + """ + __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): + """ + :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 + False if there are any problems (for example, if delivery to the specified address is not possible) + :param shipping_options: [types.ShippingOption] (Optional) - Required if ok is True. + A JSON-serialized array of available shipping options. + :param error_message: String (Optional) - Required if ok is False. + Error message in human readable form that explains why it is impossible to complete the order + (e.g. "Sorry, delivery to your desired address is unavailable'). + Telegram will display this message to the user. + """ + self.shipping_query_id = shipping_query_id + self.ok = ok + self.shipping_options = shipping_options + self.error_message = error_message + + 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 + } + + +class AnswerPreCheckoutQuery(BaseResponse): + """ + + """ + __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): + """ + :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.) + and the bot is ready to proceed with the order. Use False if there are any problems. + :param error_message: String (Optional) - Required if ok is False. + Error message in human readable form that explains the reason for failure to proceed with the checkout + (e.g. "Sorry, somebody just bought the last of our amazing black T-shirts while you were busy + filling out your payment details. Please choose a different color or garment!"). + Telegram will display this message to the user. + """ + self.pre_checkout_query_id = pre_checkout_query_id + self.ok = ok + self.error_message = error_message + + def prepare(self): + return { + 'pre_checkout_query_id': self.pre_checkout_query_id, + 'ok': self.ok, + 'error_message': self.error_message + } + + +class SendGame(BaseResponse): + """ + + """ + __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): + """ + :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. + Set up your games via Botfather. + :param disable_notification: Boolean (Optional) - Sends the message silently. + Users will receive a notification with no sound. + :param reply_to_message_id: Integer (Optional) - If the message is a reply, ID of the original message + :param reply_markup: types.InlineKeyboardMarkup (Optional) - A JSON-serialized object for an inline keyboard. + If empty, one ‘Play game_title’ button will be shown. If not empty, the first button must launch the game. + """ + self.chat_id = chat_id + self.game_short_name = game_short_name + self.disable_notification = disable_notification + self.reply_to_message_id = reply_to_message_id + self.reply_markup = reply_markup + + 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) + } From b061fa67b5b5c5612dee9483c7f18f0821e8abc6 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Wed, 9 Aug 2017 04:18:31 +0300 Subject: [PATCH 11/12] Descriptions --- aiogram/dispatcher/webhook.py | 112 ++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 38 deletions(-) diff --git a/aiogram/dispatcher/webhook.py b/aiogram/dispatcher/webhook.py index 21f32954..cda7f4e7 100644 --- a/aiogram/dispatcher/webhook.py +++ b/aiogram/dispatcher/webhook.py @@ -153,6 +153,7 @@ class SendMessage(BaseResponse): You can send message with webhook by using this instance of this object. All arguments is equal with :method:`Bot.send_message` method. """ + __slots__ = ('chat_id', 'text', 'parse_mode', 'disable_web_page_preview', 'disable_notification', 'reply_to_message_id', 'reply_markup') @@ -203,7 +204,7 @@ class SendMessage(BaseResponse): class ForwardMessage(BaseResponse): """ - Forward message from + Use that response type for forward messages of any kind on to webhook. """ __slots__ = ('chat_id', 'from_chat_id', 'message_id', 'disable_notification') @@ -238,8 +239,9 @@ class ForwardMessage(BaseResponse): class SendPhoto(BaseResponse): """ - + Use that response type for send photo on to webhook. """ + __slots__ = ('chat_id', 'photo', 'caption', 'disable_notification', 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_PHOTO @@ -286,8 +288,9 @@ class SendPhoto(BaseResponse): class SendAudio(BaseResponse): """ - + 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') @@ -347,8 +350,9 @@ class SendAudio(BaseResponse): class SendDocument(BaseResponse): """ - + Use that response type for send document on to webhook. """ + __slots__ = ('chat_id', 'document', 'caption', 'disable_notification', 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_DOCUMENT @@ -396,8 +400,9 @@ class SendDocument(BaseResponse): class SendVideo(BaseResponse): """ - + 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') @@ -458,8 +463,9 @@ class SendVideo(BaseResponse): class SendVoice(BaseResponse): """ - + Use that response type for send voice on to webhook. """ + __slots__ = ('chat_id', 'voice', 'caption', 'duration', 'disable_notification', 'reply_to_message_id', 'reply_markup') @@ -511,8 +517,9 @@ class SendVoice(BaseResponse): class SendVideoNote(BaseResponse): """ - + 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') @@ -563,8 +570,9 @@ class SendVideoNote(BaseResponse): class SendLocation(BaseResponse): """ - + Use that response type for send location on to webhook. """ + __slots__ = ('chat_id', 'latitude', 'longitude', 'disable_notification', 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_LOCATION @@ -607,8 +615,9 @@ class SendLocation(BaseResponse): class SendVenue(BaseResponse): """ - + 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') @@ -665,8 +674,9 @@ class SendVenue(BaseResponse): class SendContact(BaseResponse): """ - + 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') @@ -715,8 +725,9 @@ class SendContact(BaseResponse): class SendChatAction(BaseResponse): """ - + Use that response type for send chat action on to webhook. """ + __slots__ = ('chat_id', 'action') method = api.Methods.SEND_CHAT_ACTION @@ -742,8 +753,9 @@ class SendChatAction(BaseResponse): class KickChatMember(BaseResponse): """ - + Use that response type for kick chat member on to webhook. """ + __slots__ = ('chat_id', 'user_id', 'until_date') method = api.Methods.KICK_CHAT_MEMBER @@ -773,8 +785,9 @@ class KickChatMember(BaseResponse): class UnbanChatMember(BaseResponse): """ - + Use that response type for unban chat member on to webhook. """ + __slots__ = ('chat_id', 'user_id') method = api.Methods.UNBAN_CHAT_MEMBER @@ -795,10 +808,11 @@ class UnbanChatMember(BaseResponse): } -class Empty(BaseResponse): +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') @@ -849,8 +863,9 @@ class Empty(BaseResponse): 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') @@ -912,8 +927,9 @@ class PromoteChatMember(BaseResponse): class DeleteChatPhoto(BaseResponse): """ - + Use that response type for delete chat photo on to webhook. """ + __slots__ = ('chat_id',) method = api.Methods.DELETE_CHAT_PHOTO @@ -933,8 +949,9 @@ class DeleteChatPhoto(BaseResponse): class SetChatTitle(BaseResponse): """ - + Use that response type for set chat title on to webhook. """ + __slots__ = ('chat_id', 'title') method = api.Methods.SET_CHAT_TITLE @@ -957,8 +974,9 @@ class SetChatTitle(BaseResponse): class SetChatDescription(BaseResponse): """ - + Use that response type for set chat description on to webhook. """ + __slots__ = ('chat_id', 'description') method = api.Methods.SET_CHAT_DESCRIPTION @@ -981,8 +999,9 @@ class SetChatDescription(BaseResponse): class PinChatMessage(BaseResponse): """ - + Use that response type for pin chat message on to webhook. """ + __slots__ = ('chat_id', 'message_id', 'disable_notification') method = api.Methods.PIN_CHAT_MESSAGE @@ -1010,8 +1029,9 @@ class PinChatMessage(BaseResponse): class UnpinChatMessage(BaseResponse): """ - + Use that response type for unpin chat message on to webhook. """ + __slots__ = ('chat_id',) method = api.Methods.UNPIN_CHAT_MESSAGE @@ -1031,8 +1051,9 @@ class UnpinChatMessage(BaseResponse): class LeaveChat(BaseResponse): """ - + Use that response type for leave chat on to webhook. """ + __slots__ = ('chat_id',) method = api.Methods.LEAVE_CHAT @@ -1052,8 +1073,9 @@ class LeaveChat(BaseResponse): class AnswerCallbackQuery(BaseResponse): """ - + Use that response type for answer callback query on to webhook. """ + __slots__ = ('callback_query_id', 'text', 'show_alert', 'url', 'cache_time') method = api.Methods.ANSWER_CALLBACK_QUERY @@ -1096,8 +1118,9 @@ class AnswerCallbackQuery(BaseResponse): class EditMessageText(BaseResponse): """ - + 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') @@ -1147,8 +1170,9 @@ class EditMessageText(BaseResponse): 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') method = api.Methods.EDIT_MESSAGE_CAPTION @@ -1187,8 +1211,9 @@ class EditMessageCaption(BaseResponse): 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') method = api.Methods.EDIT_MESSAGE_REPLY_MARKUP @@ -1222,8 +1247,9 @@ class EditMessageReplyMarkup(BaseResponse): class DeleteMessage(BaseResponse): """ - + Use that response type for delete message on to webhook. """ + __slots__ = ('chat_id', 'message_id') method = api.Methods.DELETE_MESSAGE @@ -1246,8 +1272,9 @@ class DeleteMessage(BaseResponse): class SendSticker(BaseResponse): """ - + Use that response type for send sticker on to webhook. """ + __slots__ = ('chat_id', 'sticker', 'disable_notification', 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_STICKER @@ -1291,11 +1318,12 @@ class SendSticker(BaseResponse): 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') - method = api.Methods + method = api.Methods.CREATE_NEW_STICKER_SET def __init__(self, user_id: Integer, name: String, title: String, @@ -1341,8 +1369,9 @@ class CreateNewStickerSet(BaseResponse): class AddStickerToSet(BaseResponse): """ - + Use that response type for add sticker to set on to webhook. """ + __slots__ = ('user_id', 'name', 'png_sticker', 'emojis', 'mask_position') method = api.Methods.ADD_STICKER_TO_SET @@ -1381,8 +1410,9 @@ class AddStickerToSet(BaseResponse): class SetStickerPositionInSet(BaseResponse): """ - + Use that response type for set sticker position in set on to webhook. """ + __slots__ = ('sticker', 'position') method = api.Methods.SET_STICKER_POSITION_IN_SET @@ -1404,8 +1434,9 @@ class SetStickerPositionInSet(BaseResponse): class DeleteStickerFromSet(BaseResponse): """ - + Use that response type for delete sticker from set on to webhook. """ + __slots__ = ('sticker',) method = api.Methods.DELETE_STICKER_FROM_SET @@ -1424,8 +1455,9 @@ class DeleteStickerFromSet(BaseResponse): 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') @@ -1486,8 +1518,9 @@ class AnswerInlineQuery(BaseResponse): class SendInvoice(BaseResponse): """ - + 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', @@ -1594,8 +1627,9 @@ class SendInvoice(BaseResponse): class AnswerShippingQuery(BaseResponse): """ - + Use that response type for answer shipping query on to webhook. """ + __slots__ = ('shipping_query_id', 'ok', 'shipping_options', 'error_message') method = api.Methods.ANSWER_SHIPPING_QUERY @@ -1631,8 +1665,9 @@ class AnswerShippingQuery(BaseResponse): class AnswerPreCheckoutQuery(BaseResponse): """ - + Use that response type for answer pre checkout query on to webhook. """ + __slots__ = ('pre_checkout_query_id', 'ok', 'error_message') method = api.Methods.ANSWER_PRE_CHECKOUT_QUERY @@ -1664,8 +1699,9 @@ class AnswerPreCheckoutQuery(BaseResponse): class SendGame(BaseResponse): """ - + Use that response type for send game on to webhook. """ + __slots__ = ('chat_id', 'game_short_name', 'disable_notification', 'reply_to_message_id', 'reply_markup') method = api.Methods.SEND_GAME From 326f82f717f04dd5101974fb745a44425251e0f1 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Wed, 9 Aug 2017 04:44:15 +0300 Subject: [PATCH 12/12] Added webhook docs --- docs/source/dispatcher/dispatcher.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/source/dispatcher/dispatcher.rst b/docs/source/dispatcher/dispatcher.rst index 851097f9..1d2c2f92 100644 --- a/docs/source/dispatcher/dispatcher.rst +++ b/docs/source/dispatcher/dispatcher.rst @@ -32,3 +32,10 @@ Middlewares .. automodule:: aiogram.dispatcher.middlewares :members: :show-inheritance: + + +Webhook +------- +.. automodule:: aiogram.dispatcher.webhook + :members: + :show-inheritance: