From 4e093680c47184f7e616c26ed2f3eaaac95bbf0a Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Tue, 11 Jul 2017 23:08:20 +0300 Subject: [PATCH 01/11] Update to Bot API 3.1 --- aiogram/bot/api.py | 9 +++ aiogram/bot/base.py | 66 ++++++++++++++++- aiogram/bot/bot.py | 134 ++++++++++++++++++++++++++++++++++- aiogram/types/__init__.py | 2 + aiogram/types/chat.py | 13 +++- aiogram/types/chat_member.py | 49 ++++++++++++- aiogram/types/chat_photo.py | 22 ++++++ 7 files changed, 285 insertions(+), 10 deletions(-) create mode 100644 aiogram/types/chat_photo.py diff --git a/aiogram/bot/api.py b/aiogram/bot/api.py index 29edff20..879415f9 100644 --- a/aiogram/bot/api.py +++ b/aiogram/bot/api.py @@ -116,6 +116,15 @@ class Methods: GET_CHAT_ADMINISTRATORS = 'getChatAdministrators' GET_CHAT_MEMBERS_COUNT = 'getChatMembersCount' GET_CHAT_MEMBER = 'getChatMember' + RESTRICT_CHAT_MEMBER = 'restrictChatMember' + PROMOTE_CHAT_MEMBER = 'promoteChatMember' + EXPORT_CHAT_INVITE_LINK = 'exportChatInviteLink' + SET_CHAT_PHOTO = 'setChatPhoto' + DELETE_CHAT_PHOTO = 'deleteChatPhoto' + SET_CHAT_TITLE = 'setChatTitle' + SET_CHAT_DESCRIPTION = 'setChatDescription' + PIN_CHAT_MESSAGE = 'pinChatMessage' + UNPIN_CHAT_MESSAGE = 'unpinChatMessage' ANSWER_CALLBACK_QUERY = 'answerCallbackQuery' ANSWER_INLINE_QUERY = 'answerInlineQuery' EDIT_MESSAGE_TEXT = 'editMessageText' diff --git a/aiogram/bot/base.py b/aiogram/bot/base.py index c957bf44..599e77a8 100644 --- a/aiogram/bot/base.py +++ b/aiogram/bot/base.py @@ -119,7 +119,7 @@ class BaseBot: 'sticker': api.Methods.SEND_STICKER, 'video': api.Methods.SEND_VIDEO, 'voice': api.Methods.SEND_VOICE, - 'video_note': api.Methods.SEND_VIDEO_NOTE + 'video_note': api.Methods.SEND_VIDEO_NOTE, } method = methods[file_type] @@ -274,10 +274,70 @@ class BaseBot: payload = generate_payload(**locals()) return await self.request(api.Methods.GET_FILE, payload) - async def kick_chat_user(self, chat_id, user_id) -> bool: + async def kick_chat_member(self, chat_id, user_id) -> bool: payload = generate_payload(**locals()) return await self.request(api.Methods.KICK_CHAT_MEMBER, payload) + async def promote_chat_member(self, chat_id: int, user_id: int, can_change_info: bool, can_post_messages: bool, + can_edit_messages: bool, can_delete_messages: bool, can_invite_users: bool, + can_restrict_members: bool, can_pin_messages: bool, + can_promote_members: bool) -> bool: + payload = generate_payload(**locals()) + + return await self.request(api.Methods.PROMOTE_CHAT_MEMBER, payload) + + async def restrict_chat_member(self, chat_id: int, user_id: int, until_date: int, can_send_messages: bool, + can_send_media_messages: bool, can_send_other_messages: bool, + can_add_web_page_previews: bool) -> bool: + payload = generate_payload(**locals()) + + return await self.request(api.Methods.RESTRICT_CHAT_MEMBER, payload) + + async def export_chat_invite_link(self, chat_id: int) -> str: + payload = generate_payload(**locals()) + + return await self.request(api.Methods.EXPORT_CHAT_INVITE_LINK, payload) + + async def set_chat_photo(self, chat_id: int, photo) -> bool: + payload = generate_payload(**locals(), exclude=['photo']) + + if isinstance(photo, str): + payload['photo'] = photo + req = self.request(api.Methods.SET_CHAT_PHOTO, payload) + elif isinstance(photo, io.IOBase): + data = {'photo': photo.read()} + req = self.request(api.Methods.SET_CHAT_PHOTO, payload, data) + else: + data = {'photo': photo} + req = self.request(api.Methods.SET_CHAT_PHOTO, payload, data) + + return await req + + async def delete_chat_photo(self, chat_id: int) -> bool: + payload = generate_payload(**locals()) + + return await self.request(api.Methods.DELETE_CHAT_PHOTO, payload) + + async def set_chat_title(self, chat_id: int, title: str) -> bool: + payload = generate_payload(**locals()) + + return await self.request(api.Methods.SET_CHAT_TITLE, payload) + + async def set_chat_description(self, chat_id: int, description: str) -> bool: + payload = generate_payload(**locals()) + + return await self.request(api.Methods.SET_CHAT_DESCRIPTION, payload) + + async def pin_chat_message(self, chat_id: int, message_id: int, disable_notification: bool) -> bool: + payload = generate_payload(**locals()) + + return await self.request(api.Methods.PIN_CHAT_MESSAGE, payload) + + async def unpin_chat_message(self, chat_id: int) -> bool: + payload = generate_payload(**locals()) + + return await self.request(api.Methods.UNPIN_CHAT_MESSAGE, payload) + async def unban_chat_member(self, chat_id, user_id) -> bool: payload = generate_payload(**locals()) return await self.request(api.Methods.UNBAN_CHAT_MEMBER, payload) @@ -407,4 +467,4 @@ class BaseBot: inline_message_id: str = None) -> dict: payload = generate_payload(**locals()) - return await self.request(api.Methods.GET_GAME_HIGH_SCORES, payload) \ No newline at end of file + return await self.request(api.Methods.GET_GAME_HIGH_SCORES, payload) diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py index fe4b84ae..ffec10f4 100644 --- a/aiogram/bot/bot.py +++ b/aiogram/bot/bot.py @@ -1,7 +1,9 @@ +import datetime import json +import time -from .. import types from .base import BaseBot +from .. import types class Bot(BaseBot): @@ -431,7 +433,7 @@ class Bot(BaseBot): file = await super(Bot, self).get_file(file_id) return self.prepare_object(types.File.de_json(file)) - async def kick_chat_user(self, chat_id, user_id) -> bool: + async def kick_chat_member(self, chat_id, user_id) -> bool: """ Use this method to kick a user from a group or a supergroup. In the case of supergroups, the user will not be able to return to the group on their own using invite links, etc., @@ -441,7 +443,133 @@ class Bot(BaseBot): :param user_id: int :return: bool """ - return await super(Bot, self).kick_chat_user(chat_id, user_id) + return await super(Bot, self).kick_chat_member(chat_id, user_id) + + async def promote_chat_member(self, chat_id: int, user_id: int, can_change_info: bool, can_post_messages: bool, + can_edit_messages: bool, can_delete_messages: bool, can_invite_users: bool, + can_restrict_members: bool, can_pin_messages: bool, + can_promote_members: bool) -> bool: + """ + Use this method to promote or demote a user in a supergroup or a channel. + The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. + Pass False for all boolean parameters to demote a user. + + :param chat_id: int + :param user_id: int + :param can_change_info: bool + :param can_post_messages: bool + :param can_edit_messages: bool + :param can_delete_messages: bool + :param can_invite_users: bool + :param can_restrict_members: bool + :param can_pin_messages: bool + :param can_promote_members: bool + :return: bool + """ + return await super(Bot, self).promote_chat_member(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) + + async def restrict_chat_member(self, chat_id: int, user_id: int, until_date: int, can_send_messages: bool, + can_send_media_messages: bool, can_send_other_messages: bool, + can_add_web_page_previews: bool) -> bool: + """ + Use this method to restrict a user in a supergroup. + The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights. + Pass True for all boolean parameters to lift restrictions from a user. + + :param chat_id: int + :param user_id: int + :param until_date: int + :param can_send_messages: bool + :param can_send_media_messages: bool + :param can_send_other_messages: bool + :param can_add_web_page_previews: bool + :return: bool + """ + if isinstance(until_date, datetime.datetime): + until_date = int(time.mktime(until_date.timetuple())) + return await super(Bot, self).restrict_chat_member(chat_id, user_id, until_date, can_send_messages, + can_send_media_messages, can_send_other_messages, + can_add_web_page_previews) + + async def export_chat_invite_link(self, chat_id: int) -> str: + """ + Use this method to export an invite link to a supergroup or a channel. + The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. + + :param chat_id: int + :return: + """ + return await super(Bot, self).export_chat_invite_link(chat_id) + + async def set_chat_photo(self, chat_id: int, photo) -> bool: + """ + Use this method to set a new profile photo for the chat. + Photos can't be changed for private chats. + The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. + + :param chat_id: int + :param photo: file or str + :return: bool + """ + return await super(Bot, self).set_chat_photo(chat_id, photo) + + async def delete_chat_photo(self, chat_id: int) -> bool: + """ + Use this method to delete a chat photo. + Photos can't be changed for private chats. + The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. + + :param chat_id: int + :return: bool + """ + return await super(Bot, self).delete_chat_photo(chat_id) + + async def set_chat_title(self, chat_id: int, title: str) -> bool: + """ + Use this method to change the title of a chat. + Titles can't be changed for private chats. + The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. + + :param chat_id: int + :param title: str + :return: bool + """ + return await super(Bot, self).set_chat_title(chat_id, title) + + async def set_chat_description(self, chat_id: int, description: str) -> bool: + """ + Use this method to change the description of a supergroup or a channel. + The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. + + :param chat_id: int + :param description: str + :return: bool + """ + return await super(Bot, self).set_chat_description(chat_id, description) + + async def pin_chat_message(self, chat_id: int, message_id: int, disable_notification: bool) -> bool: + """ + Use this method to pin a message in a supergroup. + The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. + + :param chat_id: int + :param message_id: int + :param disable_notification: bool + :return: bool + """ + return await super(Bot, self).pin_chat_message(chat_id, message_id, disable_notification) + + async def unpin_chat_message(self, chat_id: int) -> bool: + """ + Use this method to unpin a message in a supergroup chat. + The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. + + :param chat_id: int + :return: bool + """ + return await super(Bot, self).unpin_chat_message(chat_id) async def unban_chat_member(self, chat_id, user_id) -> bool: """ diff --git a/aiogram/types/__init__.py b/aiogram/types/__init__.py index 52ad44fe..67af8891 100644 --- a/aiogram/types/__init__.py +++ b/aiogram/types/__init__.py @@ -3,6 +3,7 @@ from .audio import Audio from .callback_query import CallbackQuery from .chat import Chat, ChatType, ChatActions from .chat_member import ChatMember, ChatMemberStatus +from .chat_photo import ChatPhoto from .chosen_inline_result import ChosenInlineResult from .contact import Contact from .document import Document @@ -52,6 +53,7 @@ __all__ = [ 'ChatActions', 'ChatMember', 'ChatMemberStatus', + 'ChatPhoto', 'ChatType', 'ChosenInlineResult', 'Contact', diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py index 52040cc1..55632167 100644 --- a/aiogram/types/chat.py +++ b/aiogram/types/chat.py @@ -1,3 +1,4 @@ +from . import ChatPhoto from .base import Deserializable @@ -8,7 +9,8 @@ class Chat(Deserializable): https://core.telegram.org/bots/api#chat """ - def __init__(self, id, type, title, username, first_name, last_name, all_members_are_administrators): + def __init__(self, id, type, title, username, first_name, last_name, all_members_are_administrators, photo, + description, invite_link): self.id: int = id self.type: str = type self.title: str = title @@ -16,6 +18,9 @@ class Chat(Deserializable): self.first_name: str = first_name self.last_name: str = last_name self.all_members_are_administrators: bool = all_members_are_administrators + self.photo: ChatPhoto = photo + self.description: str = description + self.invite_link: str = invite_link @classmethod def de_json(cls, raw_data) -> 'Chat': @@ -28,8 +33,12 @@ class Chat(Deserializable): first_name: str = raw_data.get('first_name') last_name: str = raw_data.get('last_name') all_members_are_administrators: bool = raw_data.get('all_members_are_administrators', False) + photo = raw_data.get('photo') + description = raw_data.get('description') + invite_link = raw_data.get('invite_link') - return Chat(id, type, title, username, first_name, last_name, all_members_are_administrators) + return Chat(id, type, title, username, first_name, last_name, all_members_are_administrators, photo, + description, invite_link) @property def full_name(self): diff --git a/aiogram/types/chat_member.py b/aiogram/types/chat_member.py index c61dd1fb..189aaa8b 100644 --- a/aiogram/types/chat_member.py +++ b/aiogram/types/chat_member.py @@ -1,3 +1,5 @@ +import datetime + from .base import Deserializable from .user import User @@ -8,10 +10,34 @@ class ChatMember(Deserializable): https://core.telegram.org/bots/api#chatmember """ - def __init__(self, user, status): + + def __init__(self, user, status, until_date, can_be_edited, can_change_info, can_post_messages, + can_edit_messages, can_delete_messages, can_invite_users, can_restrict_members, + can_pin_messages, can_promote_members, can_send_messages, can_send_media_messages, + can_send_other_messages, can_add_web_page_previews + ): self.user: User = user self.status: str = status + self.until_date: datetime.datetime = until_date + self.can_be_edited: bool = can_be_edited + self.can_change_info: bool = can_change_info + self.can_post_messages: bool = can_post_messages + self.can_edit_messages: bool = can_edit_messages + self.can_delete_messages: bool = can_delete_messages + self.can_invite_users: bool = can_invite_users + self.can_restrict_members: bool = can_restrict_members + self.can_pin_messages: bool = can_pin_messages + self.can_promote_members: bool = can_promote_members + self.can_send_messages: bool = can_send_messages + self.can_send_media_messages: bool = can_send_media_messages + 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): + return datetime.datetime.fromtimestamp(unix_time) + @classmethod def de_json(cls, raw_data): raw_data = cls.check_json(raw_data) @@ -19,7 +45,26 @@ class ChatMember(Deserializable): user = User.deserialize(raw_data.get('user')) status = raw_data.get('status') - return ChatMember(user, status) + until_date = cls._parse_date(raw_data.get('until_date')) + can_be_edited = raw_data.get('can_be_edited') + can_change_info = raw_data.get('can_change_info') + can_post_messages = raw_data.get('can_post_messages') + can_edit_messages = raw_data.get('can_edit_messages') + can_delete_messages = raw_data.get('can_delete_messages') + can_invite_users = raw_data.get('can_invite_users') + can_restrict_members = raw_data.get('can_restrict_members') + can_pin_messages = raw_data.get('can_pin_messages') + can_promote_members = raw_data.get('can_promote_members') + can_send_messages = raw_data.get('can_send_messages') + can_send_media_messages = raw_data.get('can_send_media_messages') + can_send_other_messages = raw_data.get('can_send_other_messages') + can_add_web_page_previews = raw_data.get('can_add_web_page_previews') + + return ChatMember(user, status, until_date, can_be_edited, can_change_info, can_post_messages, + can_edit_messages, can_delete_messages, can_invite_users, can_restrict_members, + can_pin_messages, can_promote_members, can_send_messages, can_send_media_messages, + can_send_other_messages, can_add_web_page_previews + ) class ChatMemberStatus: diff --git a/aiogram/types/chat_photo.py b/aiogram/types/chat_photo.py new file mode 100644 index 00000000..4ea30a15 --- /dev/null +++ b/aiogram/types/chat_photo.py @@ -0,0 +1,22 @@ +from .base import Deserializable + + +class ChatPhoto(Deserializable): + """ + This object represents a chat photo. + + https://core.telegram.org/bots/api#chatphoto + """ + + def __init__(self, small_file_id, big_file_id): + self.small_file_id: str = small_file_id + self.big_file_id: str = big_file_id + + @classmethod + def de_json(cls, raw_data): + raw_data = cls.check_json(raw_data) + + small_file_id = raw_data.get('small_file_id') + big_file_id = raw_data.get('big_file_id') + + return ChatPhoto(small_file_id, big_file_id) From 14527ef2bf67c56850c201fbf1dce7243f32a474 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Tue, 11 Jul 2017 23:10:44 +0300 Subject: [PATCH 02/11] Fix send file --- aiogram/bot/base.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/aiogram/bot/base.py b/aiogram/bot/base.py index 599e77a8..f5eaecf1 100644 --- a/aiogram/bot/base.py +++ b/aiogram/bot/base.py @@ -128,13 +128,10 @@ class BaseBot: req = self.request(method, payload) elif isinstance(file, io.IOBase): data = {file_type: file.read()} - req = self.request(api.Methods.SEND_PHOTO, payload, data) - elif isinstance(file, bytes): - data = {file_type: file} - req = self.request(api.Methods.SEND_PHOTO, payload, data) + req = self.request(method, payload, data) else: data = {file_type: file} - req = self.request(api.Methods.SEND_PHOTO, payload, data) + req = self.request(method, payload, data) return await req From 019bf06f71d2b540e6adaa96b04099b837d90948 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Tue, 11 Jul 2017 23:16:08 +0300 Subject: [PATCH 03/11] Change API Url getting method. --- aiogram/bot/api.py | 10 +++++++++- aiogram/bot/base.py | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/aiogram/bot/api.py b/aiogram/bot/api.py index 879415f9..12f45622 100644 --- a/aiogram/bot/api.py +++ b/aiogram/bot/api.py @@ -83,7 +83,7 @@ def _compose_data(params, files=None): async def request(session, token, method, data=None, files=None): log.debug(f"Make request: '{method}' with data: {data or {}} and files {files or {}}") data = _compose_data(data, files) - url = API_URL.format(token=token, method=method) + url = Methods.api_url(token=token, method=method) async with session.post(url, data=data) as response: return await _check_result(method, response) @@ -137,3 +137,11 @@ class Methods: SEND_GAME = 'sendGame' SET_GAME_SCORE = 'setGameScore' GET_GAME_HIGH_SCORES = 'getGameHighScores' + + @staticmethod + def api_url(token, method): + return API_URL.format(token=token, method=method) + + @staticmethod + def file_url(token, path): + return FILE_URL.format(token=token, path=path) diff --git a/aiogram/bot/base.py b/aiogram/bot/base.py index f5eaecf1..7b806ec1 100644 --- a/aiogram/bot/base.py +++ b/aiogram/bot/base.py @@ -94,7 +94,7 @@ class BaseBot: destination = io.BytesIO() session = self.create_temp_session() - url = api.FILE_URL.format(token=self.__token, path=file_path) + url = api.Methods.file_url(token=self.__token, path=file_path) dest = destination if isinstance(destination, io.IOBase) else open(destination, 'wb') try: From d2c99fccf405359c6724ae5b04eccb175f0f5d2d Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Tue, 11 Jul 2017 23:23:35 +0300 Subject: [PATCH 04/11] Oops. Found types in Base Bot object --- aiogram/bot/base.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/aiogram/bot/base.py b/aiogram/bot/base.py index 7b806ec1..e77b9dcb 100644 --- a/aiogram/bot/base.py +++ b/aiogram/bot/base.py @@ -5,7 +5,6 @@ import json import aiohttp from . import api -from .. import types from ..utils.payload import generate_payload @@ -135,7 +134,7 @@ class BaseBot: return await req - async def get_me(self) -> types.User: + async def get_me(self) -> dict: return await self.request(api.Methods.GET_ME) async def get_updates(self, offset=None, limit=None, timeout=None, allowed_updates=None) -> [dict, ...]: @@ -156,7 +155,7 @@ class BaseBot: payload = {} return await self.request(api.Methods.DELETE_WEBHOOK, payload) - async def get_webhook_info(self) -> types.WebhookInfo: + async def get_webhook_info(self) -> dict: payload = {} return await self.request(api.Methods.GET_WEBHOOK_INFO, payload) @@ -252,7 +251,7 @@ class BaseBot: return await self.request(api.Methods.SEND_VENUE, payload) async def send_contact(self, chat_id, phone_number, first_name, last_name=None, disable_notification=None, - reply_to_message_id=None, reply_markup=None) -> types.Message: + reply_to_message_id=None, reply_markup=None) -> dict: if reply_markup and isinstance(reply_markup, dict): reply_markup = json.dumps(reply_markup) @@ -343,11 +342,11 @@ class BaseBot: payload = generate_payload(**locals()) return await self.request(api.Methods.LEAVE_CHAT, payload) - async def get_chat(self, chat_id) -> types.Chat: + async def get_chat(self, chat_id) -> dict: payload = generate_payload(**locals()) return await self.request(api.Methods.GET_CHAT, payload) - async def get_chat_administrators(self, chat_id) -> [types.ChatMember]: + async def get_chat_administrators(self, chat_id) -> [dict]: payload = generate_payload(**locals()) return await self.request(api.Methods.GET_CHAT_ADMINISTRATORS, payload) @@ -355,7 +354,7 @@ class BaseBot: payload = generate_payload(**locals()) return await self.request(api.Methods.GET_CHAT_MEMBERS_COUNT, payload) - async def get_chat_member(self, chat_id, user_id) -> types.ChatMember: + async def get_chat_member(self, chat_id, user_id) -> dict: payload = generate_payload(**locals()) return await self.request(api.Methods.GET_CHAT_MEMBER, payload) @@ -415,7 +414,7 @@ class BaseBot: need_name: bool = None, need_phone_number: bool = None, need_email: bool = None, need_shipping_address: bool = None, is_flexible: bool = None, disable_notification: bool = None, reply_to_message_id: int = None, - reply_markup: types.InlineKeyboardMarkup = None) -> dict: + reply_markup: dict or st = None) -> dict: if reply_markup and isinstance(reply_markup, dict): reply_markup = json.dumps(reply_markup) @@ -427,7 +426,7 @@ class BaseBot: return await self.request(api.Methods.SEND_INVOICE, payload_) async def answer_shipping_query(self, shipping_query_id: str, ok: bool, - shipping_options: [types.ShippingOption] = None, error_message: str = None) -> bool: + shipping_options = None, error_message: str = None) -> bool: if shipping_options and isinstance(shipping_options, list): shipping_options = json.dumps(shipping_options) @@ -442,7 +441,7 @@ class BaseBot: async def send_game(self, chat_id: int, game_short_name: str, disable_notification: bool = None, reply_to_message_id: int = None, - reply_markup: types.InlineKeyboardMarkup = None) -> dict: + reply_markup: dict or str = None) -> dict: if reply_markup and isinstance(reply_markup, dict): reply_markup = json.dumps(reply_markup) From 0ae4f6d9484695f36e53266b079e280eedce9f7f Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Tue, 11 Jul 2017 23:41:40 +0300 Subject: [PATCH 05/11] Small fix --- aiogram/bot/api.py | 3 ++- aiogram/bot/base.py | 2 +- aiogram/bot/bot.py | 2 +- aiogram/dispatcher/__init__.py | 2 +- aiogram/types/chat.py | 2 +- aiogram/utils/json.py | 12 ++++++++++++ requirements.txt | 1 + 7 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 aiogram/utils/json.py diff --git a/aiogram/bot/api.py b/aiogram/bot/api.py index 12f45622..0709074d 100644 --- a/aiogram/bot/api.py +++ b/aiogram/bot/api.py @@ -4,6 +4,7 @@ import os import aiohttp from ..exceptions import ValidationError, TelegramAPIError +from ..utils import json log = logging.getLogger('aiogram') @@ -40,7 +41,7 @@ async def _check_result(method_name, response): raise TelegramAPIError(f"The server returned HTTP {response.status}. Response body:\n[{body}]", method_name, response.status, body) - result_json = await response.json() + result_json = await response.json(loads=json.loads) if not result_json.get('ok'): body = await response.text() diff --git a/aiogram/bot/base.py b/aiogram/bot/base.py index e77b9dcb..b3b6ce7d 100644 --- a/aiogram/bot/base.py +++ b/aiogram/bot/base.py @@ -1,10 +1,10 @@ import asyncio import io -import json import aiohttp from . import api +from ..utils import json from ..utils.payload import generate_payload diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py index ffec10f4..47ebf95b 100644 --- a/aiogram/bot/bot.py +++ b/aiogram/bot/bot.py @@ -1,5 +1,5 @@ import datetime -import json +from ..utils import json import time from .base import BaseBot diff --git a/aiogram/dispatcher/__init__.py b/aiogram/dispatcher/__init__.py index 6156c790..5254fb87 100644 --- a/aiogram/dispatcher/__init__.py +++ b/aiogram/dispatcher/__init__.py @@ -12,7 +12,7 @@ log = logging.getLogger(__name__) class Dispatcher: def __init__(self, bot, loop=None): - self.bot: Bot = bot + self.bot: 'Bot' = bot if loop is None: loop = self.bot.loop diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py index 55632167..8c65bdfc 100644 --- a/aiogram/types/chat.py +++ b/aiogram/types/chat.py @@ -1,4 +1,4 @@ -from . import ChatPhoto +from .chat_photo import ChatPhoto from .base import Deserializable diff --git a/aiogram/utils/json.py b/aiogram/utils/json.py new file mode 100644 index 00000000..bc127402 --- /dev/null +++ b/aiogram/utils/json.py @@ -0,0 +1,12 @@ +try: + import ujson as json +except ImportError: + import json + + +def dumps(data): + return json.dumps(data) + + +def loads(data): + return json.loads(data) diff --git a/requirements.txt b/requirements.txt index 66620d60..ba3d4c48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +ujson aiohttp>=2.1.0 appdirs>=1.4.3 async-timeout>=1.2.1 From 49961e5b2b05eac67e0c701a9a3fe7638a11a964 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Tue, 11 Jul 2017 23:49:36 +0300 Subject: [PATCH 06/11] Change version --- aiogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiogram/__init__.py b/aiogram/__init__.py index 03743dd0..40faa6fa 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -2,7 +2,7 @@ from .bot import Bot major_version = 0 -minor_version = 2 +minor_version = 3 build_type = 'b' build_number = 1 From 5b87708049034064285396e4a626da062a8c9e1e Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Wed, 12 Jul 2017 00:16:29 +0300 Subject: [PATCH 07/11] Provide some actions from data types. --- aiogram/bot/bot.py | 2 +- aiogram/types/base.py | 2 +- aiogram/types/chat.py | 35 ++++++++++++++++++++++++++++++++++- aiogram/types/file.py | 3 +++ aiogram/types/message.py | 3 +++ aiogram/types/user.py | 3 +++ 6 files changed, 45 insertions(+), 3 deletions(-) diff --git a/aiogram/bot/bot.py b/aiogram/bot/bot.py index 47ebf95b..b64d74ad 100644 --- a/aiogram/bot/bot.py +++ b/aiogram/bot/bot.py @@ -549,7 +549,7 @@ class Bot(BaseBot): """ return await super(Bot, self).set_chat_description(chat_id, description) - async def pin_chat_message(self, chat_id: int, message_id: int, disable_notification: bool) -> bool: + async def pin_chat_message(self, chat_id: int, message_id: int, disable_notification: bool = False) -> bool: """ Use this method to pin a message in a supergroup. The bot must be an administrator in the chat for this to work and must have the appropriate admin rights. diff --git a/aiogram/types/base.py b/aiogram/types/base.py index a0e0ca69..829004b3 100644 --- a/aiogram/types/base.py +++ b/aiogram/types/base.py @@ -61,7 +61,7 @@ class Deserializable: return result @property - def bot(self): + def bot(self) -> 'Bot': """ Bot instance """ diff --git a/aiogram/types/chat.py b/aiogram/types/chat.py index 8c65bdfc..d2816f29 100644 --- a/aiogram/types/chat.py +++ b/aiogram/types/chat.py @@ -1,5 +1,5 @@ -from .chat_photo import ChatPhoto from .base import Deserializable +from .chat_photo import ChatPhoto class Chat(Deserializable): @@ -60,6 +60,39 @@ class Chat(Deserializable): return self.full_name return None + async def set_photo(self, photo): + return await self.bot.set_chat_photo(self.id, photo) + + async def delete_photo(self): + return await self.bot.delete_chat_photo(self.id) + + async def set_title(self, title): + return await self.bot.set_chat_title(self.id, title) + + async def set_description(self, description): + return await self.bot.delete_chat_description(self.id, description) + + async def pin_message(self, message_id: int, disable_notification: bool = False): + return await self.bot.pin_chat_message(self.id, message_id, disable_notification) + + async def unpin_message(self): + return await self.bot.unpin_chat_message(self.id) + + async def leave(self): + return await self.bot.leave_chat(self.id) + + async def get_administrators(self): + return await self.bot.get_chat_administrators(self.id) + + async def get_members_count(self): + return await self.bot.get_chat_members_count(self.id) + + async def get_member(self, user_id): + return await self.bot.get_chat_member(self.id, user_id) + + async def do(self, action): + return await self.bot.send_chat_action(self.id, action) + class ChatType: """ diff --git a/aiogram/types/file.py b/aiogram/types/file.py index da10af51..0f07f7ad 100644 --- a/aiogram/types/file.py +++ b/aiogram/types/file.py @@ -26,3 +26,6 @@ class File(Deserializable): file_path = raw_data.get('file_path') return File(file_id, file_size, file_path) + + async def download(self, destination=None, timeout=30, chunk_size=65536, seek=True): + return await self.bot.download_file(self.file_path, destination, timeout, chunk_size, seek) diff --git a/aiogram/types/message.py b/aiogram/types/message.py index 3cd85eee..dd8828c4 100644 --- a/aiogram/types/message.py +++ b/aiogram/types/message.py @@ -216,6 +216,9 @@ class Message(Deserializable): return False return True + async def pin(self, disable_notification: bool = False): + return await self.chat.pin_message(self.message_id, disable_notification) + class ContentType: """ diff --git a/aiogram/types/user.py b/aiogram/types/user.py index 247bae09..ed42fa8d 100644 --- a/aiogram/types/user.py +++ b/aiogram/types/user.py @@ -69,3 +69,6 @@ class User(Deserializable): if not hasattr(self, '_locale'): setattr(self, '_locale', babel.core.Locale.parse(self.language_code, sep='-')) return getattr(self, '_locale') + + async def get_user_profile_photos(self, offset=None, limit=None): + return await self.bot.get_user_profile_photos(self.id, offset, limit) From 667a7421c838f6464992df63ead1b4d860500dd7 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Wed, 12 Jul 2017 20:51:46 +0300 Subject: [PATCH 08/11] Nothing. --- aiogram/bot/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aiogram/bot/base.py b/aiogram/bot/base.py index b3b6ce7d..1f84a72d 100644 --- a/aiogram/bot/base.py +++ b/aiogram/bot/base.py @@ -414,7 +414,7 @@ class BaseBot: need_name: bool = None, need_phone_number: bool = None, need_email: bool = None, need_shipping_address: bool = None, is_flexible: bool = None, disable_notification: bool = None, reply_to_message_id: int = None, - reply_markup: dict or st = None) -> dict: + reply_markup: dict or str = None) -> dict: if reply_markup and isinstance(reply_markup, dict): reply_markup = json.dumps(reply_markup) @@ -426,7 +426,7 @@ class BaseBot: return await self.request(api.Methods.SEND_INVOICE, payload_) async def answer_shipping_query(self, shipping_query_id: str, ok: bool, - shipping_options = None, error_message: str = None) -> bool: + shipping_options=None, error_message: str = None) -> bool: if shipping_options and isinstance(shipping_options, list): shipping_options = json.dumps(shipping_options) From ce6b1ab3de84d6ff1452440a38acfd53f0bd9b24 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Wed, 19 Jul 2017 02:32:24 +0300 Subject: [PATCH 09/11] Fix inline query. --- aiogram/types/inline_query_result.py | 82 ++++++++++++++-------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/aiogram/types/inline_query_result.py b/aiogram/types/inline_query_result.py index 5f5139df..efb14748 100644 --- a/aiogram/types/inline_query_result.py +++ b/aiogram/types/inline_query_result.py @@ -15,7 +15,7 @@ class InputMessageContent(Serializable): """ def to_json(self): return {k: v.to_json() if hasattr(v, 'to_json') else v for k, v in self.__dict__.items() if - not k.startswith('_')} + v is not None and not k.startswith('_')} class InlineQueryResult(InputMessageContent): @@ -74,10 +74,10 @@ class InlineQueryResultArticle(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultarticle """ - def __init__(self, type: str, id: str, title: str, input_message_content: InputMessageContent, + def __init__(self, id: str, title: str, input_message_content: InputMessageContent, reply_markup: InlineKeyboardMarkup = None, url: str = None, hide_url: bool = None, description: str = None, thumb_url: str = None, thumb_width: int = None, thumb_height: int = None): - self.type: str = type + self.type = 'article' self.id: str = id self.title: str = title self.input_message_content: InputMessageContent = input_message_content @@ -99,10 +99,10 @@ class InlineQueryResultPhoto(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultphoto """ - def __init__(self, type: str, id: str, photo_url: str, thumb_url: str, photo_width: int = None, + def __init__(self, id: str, photo_url: str, thumb_url: str, photo_width: int = None, photo_height: int = None, title: str = None, description: str = None, caption: str = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None): - self.type: str = type + self.type = 'photo' self.id: str = id self.photo_url: str = photo_url self.thumb_url: str = thumb_url @@ -126,10 +126,10 @@ class InlineQueryResultGif(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultgif """ - def __init__(self, type: str, id: str, gif_url: str, thumb_url: str, gif_width: int = None, gif_height: int = None, + def __init__(self, id: str, gif_url: str, thumb_url: str, gif_width: int = None, gif_height: int = None, gif_duration: int = None, title: str = None, caption: str = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None): - self.type: str = type + self.type = 'gif' self.id: str = id self.gif_url: str = gif_url self.gif_width: int = gif_width @@ -153,10 +153,10 @@ class InlineQueryResultMpeg4Gif(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultmpeg4gif """ - def __init__(self, type: str, id: str, mpeg4_url: str, thumb_url: str, mpeg4_width: int = None, + def __init__(self, id: str, mpeg4_url: str, thumb_url: str, mpeg4_width: int = None, mpeg4_height: int = None, mpeg4_duration: int = None, title: str = None, caption: str = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None): - self.type: str = type + self.type = 'mpeg4_gif' self.id: str = id self.mpeg4_url: str = mpeg4_url self.mpeg4_width: int = mpeg4_width @@ -178,11 +178,11 @@ class InlineQueryResultVideo(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultvideo """ - def __init__(self, type: str, id: str, video_url: str, mime_type: str, thumb_url: str, title: str, + def __init__(self, id: str, video_url: str, mime_type: str, thumb_url: str, title: str, caption: str = None, video_width: int = None, video_height: int = None, video_duration: int = None, description: str = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None): - self.type: str = type + self.type = 'video' self.id: str = id self.video_url: str = video_url self.mime_type: str = mime_type @@ -204,10 +204,10 @@ class InlineQueryResultAudio(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultaudio """ - def __init__(self, type: str, id: str, audio_url: str, title: str, caption: str = None, performer: str = None, + def __init__(self, id: str, audio_url: str, title: str, caption: str = None, performer: str = None, audio_duration: int = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None): - self.type: str = type + self.type = 'audio' self.id: str = id self.audio_url: str = audio_url self.title: str = title @@ -229,9 +229,9 @@ class InlineQueryResultVoice(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultvoice """ - def __init__(self, type: str, id: str, voice_url: str, title: str, caption: str = None, voice_duration: int = None, + def __init__(self, id: str, voice_url: str, title: str, caption: str = None, voice_duration: int = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None): - self.type: str = type + self.type = 'voice' self.id: str = id self.voice_url: str = voice_url self.title: str = title @@ -250,11 +250,11 @@ class InlineQueryResultDocument(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultdocument """ - def __init__(self, type: str, id: str, title: str, document_url: str, mime_type: str, caption: str = None, + def __init__(self, id: str, title: str, document_url: str, mime_type: str, caption: str = None, description: str = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None, thumb_url: str = None, thumb_width: int = None, thumb_height: int = None): - self.type: str = type + self.type = 'document' self.id: str = id self.title: str = title self.caption: str = caption @@ -276,10 +276,10 @@ class InlineQueryResultLocation(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultlocation """ - def __init__(self, type: str, id: str, latitude: float, longitude: float, title: str, + def __init__(self, id: str, latitude: float, longitude: float, title: str, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None, thumb_url: str = None, thumb_width: int = None, thumb_height: int = None): - self.type: str = type + self.type = 'location' self.id: str = id self.latitude: float = latitude self.longitude: float = longitude @@ -299,11 +299,11 @@ class InlineQueryResultVenue(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultvenue """ - def __init__(self, type: str, id: str, latitude: float, longitude: float, title: str, address: str, + def __init__(self, id: str, latitude: float, longitude: float, title: str, address: str, foursquare_id: str = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None, thumb_url: str = None, thumb_width: int = None, thumb_height: int = None): - self.type: str = type + self.type = 'venue' self.id: str = id self.latitude: float = latitude self.longitude: float = longitude @@ -328,10 +328,10 @@ class InlineQueryResultContact(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultcontact """ - def __init__(self, type: str, id: str, phone_number: str, first_name: str, last_name: str = None, + def __init__(self, id: str, phone_number: str, first_name: str, last_name: str = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None, thumb_url: str = None, thumb_width: int = None, thumb_height: int = None): - self.type: str = type + self.type = 'contact' self.id: str = id self.phone_number: str = phone_number self.first_name: str = first_name @@ -349,8 +349,8 @@ class InlineQueryResultGame(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultgame """ - def __init__(self, type: str, id: str, game_short_name: str, reply_markup: InlineKeyboardMarkup = None): - self.type: str = type + def __init__(self, id: str, game_short_name: str, reply_markup: InlineKeyboardMarkup = None): + self.type = 'game' self.id: str = id self.game_short_name: str = game_short_name self.reply_markup: InlineKeyboardMarkup = reply_markup @@ -367,10 +367,10 @@ class InlineQueryResultCachedPhoto(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultcachedphoto """ - def __init__(self, type: str, id: str, photo_file_id: str, title: str = None, description: str = None, + def __init__(self, id: str, photo_file_id: str, title: str = None, description: str = None, caption: str = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None): - self.type: str = type + self.type = 'photo' self.id: str = id self.photo_file_id: str = photo_file_id self.title: str = title @@ -391,9 +391,9 @@ class InlineQueryResultCachedGif(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultcachedgif """ - def __init__(self, type: str, id: str, gif_file_id: str, title: str = None, caption: str = None, + def __init__(self, id: str, gif_file_id: str, title: str = None, caption: str = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None): - self.type: str = type + self.type = 'gif' self.id: str = id self.gif_file_id: str = gif_file_id self.title: str = title @@ -413,9 +413,9 @@ class InlineQueryResultCachedMpeg4Gif(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultcachedmpeg4gif """ - def __init__(self, type: str, id: str, mpeg4_file_id: str, title: str = None, caption: str = None, + def __init__(self, id: str, mpeg4_file_id: str, title: str = None, caption: str = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None): - self.type: str = type + self.type = 'mpeg4_gif' self.id: str = id self.mpeg4_file_id: str = mpeg4_file_id self.title: str = title @@ -435,9 +435,9 @@ class InlineQueryResultCachedSticker(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultcachedsticker """ - def __init__(self, type: str, id: str, sticker_file_id: str, reply_markup: InlineKeyboardMarkup = None, + def __init__(self, id: str, sticker_file_id: str, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None): - self.type: str = type + self.type = 'sticker' self.id: str = id self.sticker_file_id: str = sticker_file_id self.reply_markup: InlineKeyboardMarkup = reply_markup @@ -455,10 +455,10 @@ class InlineQueryResultCachedDocument(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultcacheddocument """ - def __init__(self, type: str, id: str, title: str, document_file_id: str, description: str = None, + def __init__(self, id: str, title: str, document_file_id: str, description: str = None, caption: str = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None): - self.type: str = type + self.type = 'document' self.id: str = id self.title: str = title self.document_file_id: str = document_file_id @@ -477,9 +477,9 @@ class InlineQueryResultCachedVideo(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultcachedvideo """ - def __init__(self, type: str, id: str, video_file_id: str, title: str, description: str = None, caption: str = None, + def __init__(self, id: str, video_file_id: str, title: str, description: str = None, caption: str = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None): - self.type: str = type + self.type = 'video' self.id: str = id self.video_file_id: str = video_file_id self.title: str = title @@ -500,9 +500,9 @@ class InlineQueryResultCachedVoice(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultcachedvoice """ - def __init__(self, type: str, id: str, voice_file_id: str, title: str, caption: str = None, + def __init__(self, id: str, voice_file_id: str, title: str, caption: str = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None): - self.type: str = type + self.type = 'voice' self.id: str = id self.voice_file_id: str = voice_file_id self.title: str = title @@ -521,9 +521,9 @@ class InlineQueryResultCachedAudio(InlineQueryResult): https://core.telegram.org/bots/api#inlinequeryresultcachedaudio """ - def __init__(self, type: str, id: str, audio_file_id: str, caption: str = None, + def __init__(self, id: str, audio_file_id: str, caption: str = None, reply_markup: InlineKeyboardMarkup = None, input_message_content: InputMessageContent = None): - self.type: str = type + self.type = 'audio' self.id: str = id self.audio_file_id: str = audio_file_id self.caption: str = caption From 1e6aa381fd16ab8fcc83ecfbce76db957ba58962 Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Wed, 19 Jul 2017 02:56:28 +0300 Subject: [PATCH 10/11] Add inline example. --- examples/inline_bot.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 examples/inline_bot.py diff --git a/examples/inline_bot.py b/examples/inline_bot.py new file mode 100644 index 00000000..e4cd7125 --- /dev/null +++ b/examples/inline_bot.py @@ -0,0 +1,26 @@ +import asyncio +import logging + +from aiogram import Bot, types +from aiogram.dispatcher import Dispatcher + +API_TOKEN = 'BOT TOKEN HERE' + +logging.basicConfig(level=logging.DEBUG) + +loop = asyncio.get_event_loop() +bot = Bot(token=API_TOKEN, loop=loop) +dp = Dispatcher(bot) + + +@dp.inline_handler() +async def inline_echo(inline_query: types.InlineQuery): + item = types.InlineQueryResultArticle('1', 'echo', types.InputTextMessageContent(inline_query.query)) + await bot.answer_inline_query(inline_query.id, results=[item], cache_time=1) + + +if __name__ == '__main__': + try: + loop.run_until_complete(dp.start_pooling()) + except KeyboardInterrupt: + loop.stop() From e4c89d0413c1e9bd61e946554a346b8eb922df8a Mon Sep 17 00:00:00 2001 From: Alex Root Junior Date: Wed, 19 Jul 2017 02:58:17 +0300 Subject: [PATCH 11/11] Change version number --- aiogram/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aiogram/__init__.py b/aiogram/__init__.py index 40faa6fa..5f32025b 100644 --- a/aiogram/__init__.py +++ b/aiogram/__init__.py @@ -3,8 +3,8 @@ from .bot import Bot major_version = 0 minor_version = 3 -build_type = 'b' -build_number = 1 +build_type = '' +build_number = '' __version__ = f"{major_version}.{minor_version}{build_type}{build_number}"