diff --git a/.gitignore b/.gitignore index bfc2fb83..0b1a0699 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ config.ini # Pyrogram generated code -pyrogram/api/errors/exceptions/ +pyrogram/errors/exceptions/ pyrogram/api/functions/ pyrogram/api/types/ pyrogram/api/all.py diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index dcf8bb79..5adf4956 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -66,10 +66,11 @@ Messages delete_messages get_messages get_history + get_history_count iter_history send_poll vote_poll - close_poll + stop_poll retract_vote download_media @@ -100,6 +101,7 @@ Chats iter_chat_members get_dialogs iter_dialogs + get_dialogs_count restrict_chat update_chat_username @@ -112,6 +114,7 @@ Users get_me get_users get_user_profile_photos + get_user_profile_photos_count set_user_profile_photo delete_user_profile_photos update_username @@ -124,6 +127,7 @@ Contacts add_contacts get_contacts + get_contacts_count delete_contacts Password diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index ce07e73f..3c2311fe 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -49,7 +49,7 @@ class BaseClient: DIALOGS_AT_ONCE = 100 UPDATES_WORKERS = 1 DOWNLOAD_WORKERS = 4 - OFFLINE_SLEEP = 300 + OFFLINE_SLEEP = 900 WORKERS = 4 WORKDIR = "." CONFIG_FILE = "./config.ini" diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index f1358b86..0ad52d0a 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -24,7 +24,7 @@ import pyrogram from pyrogram.api import types from ..handlers import ( CallbackQueryHandler, MessageHandler, DeletedMessagesHandler, - UserStatusHandler, RawUpdateHandler, InlineQueryHandler + UserStatusHandler, RawUpdateHandler, InlineQueryHandler, PollHandler ) log = logging.getLogger(__name__) @@ -76,12 +76,16 @@ class Dispatcher: async def inline_query_parser(update, users, chats): return pyrogram.InlineQuery._parse(self.client, update, users), InlineQueryHandler + async def poll_parser(update, users, chats): + return pyrogram.Poll._parse_update(self.client, update), PollHandler + self.update_parsers = { Dispatcher.MESSAGE_UPDATES: message_parser, Dispatcher.DELETE_MESSAGES_UPDATES: deleted_messages_parser, Dispatcher.CALLBACK_QUERY_UPDATES: callback_query_parser, (types.UpdateUserStatus,): user_status_parser, - (types.UpdateBotInlineQuery,): inline_query_parser + (types.UpdateBotInlineQuery,): inline_query_parser, + (types.UpdateMessagePoll,): poll_parser } self.update_parsers = {key: value for key_tuple, value in self.update_parsers.items() for key in key_tuple} diff --git a/pyrogram/client/handlers/__init__.py b/pyrogram/client/handlers/__init__.py index 5e392949..c88c12fe 100644 --- a/pyrogram/client/handlers/__init__.py +++ b/pyrogram/client/handlers/__init__.py @@ -21,10 +21,11 @@ from .deleted_messages_handler import DeletedMessagesHandler from .disconnect_handler import DisconnectHandler from .inline_query_handler import InlineQueryHandler from .message_handler import MessageHandler +from .poll_handler import PollHandler from .raw_update_handler import RawUpdateHandler from .user_status_handler import UserStatusHandler __all__ = [ "MessageHandler", "DeletedMessagesHandler", "CallbackQueryHandler", "RawUpdateHandler", "DisconnectHandler", - "UserStatusHandler", "InlineQueryHandler" + "UserStatusHandler", "InlineQueryHandler", "PollHandler" ] diff --git a/pyrogram/client/handlers/poll_handler.py b/pyrogram/client/handlers/poll_handler.py new file mode 100644 index 00000000..567fcec0 --- /dev/null +++ b/pyrogram/client/handlers/poll_handler.py @@ -0,0 +1,48 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from .handler import Handler + + +class PollHandler(Handler): + """The Poll handler class. Used to handle polls updates. + + It is intended to be used with :meth:`add_handler() ` + + For a nicer way to register this handler, have a look at the + :meth:`on_poll() ` decorator. + + Args: + callback (``callable``): + Pass a function that will be called when a new poll update arrives. It takes *(client, poll)* + as positional arguments (look at the section below for a detailed description). + + filters (:obj:`Filters `): + Pass one or more filters to allow only a subset of polls to be passed + in your callback function. + + Other parameters: + client (:obj:`Client `): + The Client itself, useful when you want to call other API methods inside the poll handler. + + poll (:obj:`Poll `): + The received poll. + """ + + def __init__(self, callback: callable, filters=None): + super().__init__(callback, filters) diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index c708453f..8db44abe 100644 --- a/pyrogram/client/methods/chats/__init__.py +++ b/pyrogram/client/methods/chats/__init__.py @@ -39,6 +39,7 @@ from .set_chat_title import SetChatTitle from .unban_chat_member import UnbanChatMember from .unpin_chat_message import UnpinChatMessage from .update_chat_username import UpdateChatUsername +from .get_dialogs_count import GetDialogsCount class Chats( @@ -64,6 +65,7 @@ class Chats( IterDialogs, IterChatMembers, UpdateChatUsername, - RestrictChat + RestrictChat, + GetDialogsCount ): pass diff --git a/pyrogram/client/methods/chats/get_dialogs_count.py b/pyrogram/client/methods/chats/get_dialogs_count.py new file mode 100644 index 00000000..49e6eff3 --- /dev/null +++ b/pyrogram/client/methods/chats/get_dialogs_count.py @@ -0,0 +1,54 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class GetDialogsCount(BaseClient): + async def get_dialogs_count(self, pinned_only: bool = False) -> int: + """Use this method to get the total count of your dialogs. + + pinned_only (``bool``, *optional*): + Pass True if you want to count only pinned dialogs. + Defaults to False. + + Returns: + On success, an integer is returned. + + Raises: + :class:`RPCError ` in case of a Telegram RPC error. + """ + + if pinned_only: + return len((await self.send(functions.messages.GetPinnedDialogs())).dialogs) + else: + r = await self.send( + functions.messages.GetDialogs( + offset_date=0, + offset_id=0, + offset_peer=types.InputPeerEmpty(), + limit=1, + hash=0 + ) + ) + + if isinstance(r, types.messages.Dialogs): + return len(r.dialogs) + else: + return r.count diff --git a/pyrogram/client/methods/contacts/__init__.py b/pyrogram/client/methods/contacts/__init__.py index ab9ae6ef..a966d10a 100644 --- a/pyrogram/client/methods/contacts/__init__.py +++ b/pyrogram/client/methods/contacts/__init__.py @@ -19,11 +19,13 @@ from .add_contacts import AddContacts from .delete_contacts import DeleteContacts from .get_contacts import GetContacts +from .get_contacts_count import GetContactsCount class Contacts( GetContacts, DeleteContacts, - AddContacts + AddContacts, + GetContactsCount ): pass diff --git a/pyrogram/client/methods/contacts/get_contacts_count.py b/pyrogram/client/methods/contacts/get_contacts_count.py new file mode 100644 index 00000000..f2b6fda0 --- /dev/null +++ b/pyrogram/client/methods/contacts/get_contacts_count.py @@ -0,0 +1,34 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from pyrogram.api import functions +from ...ext import BaseClient + + +class GetContactsCount(BaseClient): + async def get_contacts_count(self) -> int: + """Use this method to get the total count of contacts from your Telegram address book. + + Returns: + On success, an integer is returned. + + Raises: + :class:`RPCError ` in case of a Telegram RPC error. + """ + + return len((await self.send(functions.contacts.GetContacts(hash=0))).contacts) diff --git a/pyrogram/client/methods/decorators/__init__.py b/pyrogram/client/methods/decorators/__init__.py index 33f55a3d..2a2861ae 100644 --- a/pyrogram/client/methods/decorators/__init__.py +++ b/pyrogram/client/methods/decorators/__init__.py @@ -21,6 +21,7 @@ from .on_deleted_messages import OnDeletedMessages from .on_disconnect import OnDisconnect from .on_inline_query import OnInlineQuery from .on_message import OnMessage +from .on_poll import OnPoll from .on_raw_update import OnRawUpdate from .on_user_status import OnUserStatus @@ -32,6 +33,7 @@ class Decorators( OnRawUpdate, OnDisconnect, OnUserStatus, - OnInlineQuery + OnInlineQuery, + OnPoll ): pass diff --git a/pyrogram/client/methods/decorators/on_poll.py b/pyrogram/client/methods/decorators/on_poll.py new file mode 100644 index 00000000..56dcd757 --- /dev/null +++ b/pyrogram/client/methods/decorators/on_poll.py @@ -0,0 +1,59 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Tuple + +import pyrogram +from pyrogram.client.filters.filter import Filter +from pyrogram.client.handlers.handler import Handler +from ...ext import BaseClient + + +class OnPoll(BaseClient): + def on_poll( + self=None, + filters=None, + group: int = 0 + ) -> callable: + """Use this decorator to automatically register a function for handling poll updates. + This does the same thing as :meth:`add_handler` using the :class:`PollHandler`. + + Args: + filters (:obj:`Filters `): + Pass one or more filters to allow only a subset of polls to be passed + in your function. + + group (``int``, *optional*): + The group identifier, defaults to 0. + """ + + def decorator(func: callable) -> Tuple[Handler, int]: + if isinstance(func, tuple): + func = func[0].callback + + handler = pyrogram.PollHandler(func, filters) + + if isinstance(self, Filter): + return pyrogram.PollHandler(func, self), group if filters is None else filters + + if self is not None: + self.add_handler(handler, group) + + return handler, group + + return decorator diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index dde50b7b..e843aa7c 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -16,7 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .close_poll import ClosePoll from .delete_messages import DeleteMessages from .download_media import DownloadMedia from .edit_message_caption import EditMessageCaption @@ -25,6 +24,7 @@ from .edit_message_reply_markup import EditMessageReplyMarkup from .edit_message_text import EditMessageText from .forward_messages import ForwardMessages from .get_history import GetHistory +from .get_history_count import GetHistoryCount from .get_messages import GetMessages from .iter_history import IterHistory from .retract_vote import RetractVote @@ -44,6 +44,7 @@ from .send_venue import SendVenue from .send_video import SendVideo from .send_video_note import SendVideoNote from .send_voice import SendVoice +from .stop_poll import StopPoll from .vote_poll import VotePoll @@ -72,10 +73,11 @@ class Messages( SendVoice, SendPoll, VotePoll, - ClosePoll, + StopPoll, RetractVote, DownloadMedia, IterHistory, - SendCachedMedia + SendCachedMedia, + GetHistoryCount ): pass diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index d1e58b98..3392e7fd 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -37,7 +37,7 @@ class GetHistory(BaseClient): offset_id: int = 0, offset_date: int = 0, reverse: bool = False - ): + ) -> "pyrogram.Messages": """Use this method to retrieve a chunk of the history of a chat. You can get up to 100 messages at once. diff --git a/pyrogram/client/methods/messages/get_history_count.py b/pyrogram/client/methods/messages/get_history_count.py new file mode 100644 index 00000000..8883c051 --- /dev/null +++ b/pyrogram/client/methods/messages/get_history_count.py @@ -0,0 +1,84 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import logging +import time +from typing import Union + +from pyrogram.api import types, functions +from pyrogram.client.ext import BaseClient +from pyrogram.errors import FloodWait + +log = logging.getLogger(__name__) + + +class GetHistoryCount(BaseClient): + async def get_history_count( + self, + chat_id: Union[int, str] + ) -> int: + """Use this method to get the total count of messages in a chat. + + .. note:: + + Due to Telegram latest internal changes, the server can't reliably find anymore the total count of messages + a **private** or a **basic group** chat has with a single method call. To overcome this limitation, Pyrogram + has to iterate over all the messages. Channels and supergroups are not affected by this limitation. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + Returns: + On success, an integer is returned. + + Raises: + :class:`RPCError ` in case of a Telegram RPC error. + """ + + peer = await self.resolve_peer(chat_id) + + if not isinstance(peer, types.InputPeerChannel): + offset = 0 + limit = 100 + + while True: + try: + r = await self.send( + functions.messages.GetHistory( + peer=peer, + offset_id=1, + offset_date=0, + add_offset=-offset - limit, + limit=limit, + max_id=0, + min_id=0, + hash=0 + ) + ) + except FloodWait as e: + log.warning("Sleeping for {}s".format(e.x)) + time.sleep(e.x) + continue + + if not r.messages: + return offset + + offset += len(r.messages) + + return (await self.get_history(chat_id=chat_id, limit=1)).total_count diff --git a/pyrogram/client/methods/messages/retract_vote.py b/pyrogram/client/methods/messages/retract_vote.py index 8fa8996c..7f48c8ea 100644 --- a/pyrogram/client/methods/messages/retract_vote.py +++ b/pyrogram/client/methods/messages/retract_vote.py @@ -18,16 +18,17 @@ from typing import Union +import pyrogram from pyrogram.api import functions from pyrogram.client.ext import BaseClient class RetractVote(BaseClient): - def retract_vote( + async def retract_vote( self, chat_id: Union[int, str], - message_id: id - ) -> bool: + message_id: int + ) -> "pyrogram.Poll": """Use this method to retract your vote in a poll. Args: @@ -37,20 +38,20 @@ class RetractVote(BaseClient): For a contact that exists in your Telegram address book you can use his phone number (str). message_id (``int``): - Unique poll message identifier inside this chat. + Identifier of the original message with the poll. Returns: - On success, True is returned. + On success, the :obj:`Poll ` with the retracted vote is returned. Raises: :class:`RPCError ` in case of a Telegram RPC error. """ - self.send( + r = await self.send( functions.messages.SendVote( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), msg_id=message_id, options=[] ) ) - return True + return pyrogram.Poll._parse(self, r.updates[0]) diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index e394c127..7dfd0eb9 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -83,7 +83,7 @@ class SendAnimation(BaseClient): thumb (``str``, *optional*): Thumbnail of the animation file sent. The thumbnail should be in JPEG format and less than 200 KB in size. - A thumbnail's width and height should not exceed 90 pixels. + A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. disable_notification (``bool``, *optional*): diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index d69b2a7f..81ceb3f3 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -84,7 +84,7 @@ class SendAudio(BaseClient): thumb (``str``, *optional*): Thumbnail of the music file album cover. The thumbnail should be in JPEG format and less than 200 KB in size. - A thumbnail's width and height should not exceed 90 pixels. + A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. disable_notification (``bool``, *optional*): diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index df28d5c9..663cc911 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -62,7 +62,7 @@ class SendDocument(BaseClient): thumb (``str``, *optional*): Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 KB in size. - A thumbnail's width and height should not exceed 90 pixels. + A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py index 13e55b08..6b4e227a 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/client/methods/messages/send_poll.py @@ -47,10 +47,10 @@ class SendPoll(BaseClient): For a contact that exists in your Telegram address book you can use his phone number (str). question (``str``): - The poll question, as string. + Poll question, 1-255 characters. options (List of ``str``): - The poll options, as list of strings (2 to 10 options are allowed). + List of answer options, 2-10 strings 1-100 characters each. disable_notification (``bool``, *optional*): Sends the message silently. diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 3a70843e..8164a2ee 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -84,7 +84,7 @@ class SendVideo(BaseClient): thumb (``str``, *optional*): Thumbnail of the video sent. The thumbnail should be in JPEG format and less than 200 KB in size. - A thumbnail's width and height should not exceed 90 pixels. + A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. supports_streaming (``bool``, *optional*): diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index 156433ba..71b0a110 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -69,7 +69,7 @@ class SendVideoNote(BaseClient): thumb (``str``, *optional*): Thumbnail of the video sent. The thumbnail should be in JPEG format and less than 200 KB in size. - A thumbnail's width and height should not exceed 90 pixels. + A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. disable_notification (``bool``, *optional*): diff --git a/pyrogram/client/methods/messages/close_poll.py b/pyrogram/client/methods/messages/stop_poll.py similarity index 66% rename from pyrogram/client/methods/messages/close_poll.py rename to pyrogram/client/methods/messages/stop_poll.py index 1b1164c2..383b1dcb 100644 --- a/pyrogram/client/methods/messages/close_poll.py +++ b/pyrogram/client/methods/messages/stop_poll.py @@ -18,19 +18,21 @@ from typing import Union +import pyrogram from pyrogram.api import functions, types from pyrogram.client.ext import BaseClient -class ClosePoll(BaseClient): - def close_poll( +class StopPoll(BaseClient): + async def stop_poll( self, chat_id: Union[int, str], - message_id: id - ) -> bool: - """Use this method to close (stop) a poll. + message_id: int, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> "pyrogram.Poll": + """Use this method to stop a poll which was sent by you. - Closed polls can't be reopened and nobody will be able to vote in it anymore. + Stopped polls can't be reopened and nobody will be able to vote in it anymore. Args: chat_id (``int`` | ``str``): @@ -39,19 +41,22 @@ class ClosePoll(BaseClient): For a contact that exists in your Telegram address book you can use his phone number (str). message_id (``int``): - Unique poll message identifier inside this chat. + Identifier of the original message with the poll. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. Returns: - On success, True is returned. + On success, the stopped :obj:`Poll ` with the final results is returned. Raises: :class:`RPCError ` in case of a Telegram RPC error. """ - poll = self.get_messages(chat_id, message_id).poll + poll = (await self.get_messages(chat_id, message_id)).poll - self.send( + r = await self.send( functions.messages.EditMessage( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), id=message_id, media=types.InputMediaPoll( poll=types.Poll( @@ -60,8 +65,9 @@ class ClosePoll(BaseClient): question="", answers=[] ) - ) + ), + reply_markup=reply_markup.write() if reply_markup else None ) ) - return True + return pyrogram.Poll._parse(self, r.updates[0]) diff --git a/pyrogram/client/methods/messages/vote_poll.py b/pyrogram/client/methods/messages/vote_poll.py index 2a9de874..b0eeb925 100644 --- a/pyrogram/client/methods/messages/vote_poll.py +++ b/pyrogram/client/methods/messages/vote_poll.py @@ -18,17 +18,18 @@ from typing import Union +import pyrogram from pyrogram.api import functions from pyrogram.client.ext import BaseClient class VotePoll(BaseClient): - def vote_poll( + async def vote_poll( self, chat_id: Union[int, str], message_id: id, option: int - ) -> bool: + ) -> "pyrogram.Poll": """Use this method to vote a poll. Args: @@ -38,25 +39,26 @@ class VotePoll(BaseClient): For a contact that exists in your Telegram address book you can use his phone number (str). message_id (``int``): - Unique poll message identifier inside this chat. + Identifier of the original message with the poll. option (``int``): Index of the poll option you want to vote for (0 to 9). Returns: - On success, True is returned. + On success, the :obj:`Poll ` with the chosen option is returned. Raises: :class:`RPCError ` in case of a Telegram RPC error. """ - poll = self.get_messages(chat_id, message_id).poll - self.send( + poll = (await self.get_messages(chat_id, message_id)).poll + + r = await self.send( functions.messages.SendVote( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), msg_id=message_id, - options=[poll.options[option].data] + options=[poll.options[option]._data] ) ) - return True + return pyrogram.Poll._parse(self, r.updates[0]) diff --git a/pyrogram/client/methods/users/__init__.py b/pyrogram/client/methods/users/__init__.py index f8c39650..d67a18bd 100644 --- a/pyrogram/client/methods/users/__init__.py +++ b/pyrogram/client/methods/users/__init__.py @@ -19,6 +19,7 @@ from .delete_user_profile_photos import DeleteUserProfilePhotos from .get_me import GetMe from .get_user_profile_photos import GetUserProfilePhotos +from .get_user_profile_photos_count import GetUserProfilePhotosCount from .get_users import GetUsers from .set_user_profile_photo import SetUserProfilePhoto from .update_username import UpdateUsername @@ -30,6 +31,7 @@ class Users( DeleteUserProfilePhotos, GetUsers, GetMe, - UpdateUsername + UpdateUsername, + GetUserProfilePhotosCount ): pass diff --git a/pyrogram/client/methods/users/get_user_profile_photos_count.py b/pyrogram/client/methods/users/get_user_profile_photos_count.py new file mode 100644 index 00000000..688ad7b5 --- /dev/null +++ b/pyrogram/client/methods/users/get_user_profile_photos_count.py @@ -0,0 +1,54 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class GetUserProfilePhotosCount(BaseClient): + async def get_user_profile_photos_count(self, user_id: Union[int, str]) -> int: + """Use this method to get the total count of profile pictures for a user. + + Args: + user_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + Returns: + On success, an integer is returned. + + Raises: + :class:`RPCError ` in case of a Telegram RPC error. + """ + + r = await self.send( + functions.photos.GetUserPhotos( + user_id=await self.resolve_peer(user_id), + offset=0, + max_id=0, + limit=1 + ) + ) + + if isinstance(r, types.photos.Photos): + return len(r.photos) + else: + return r.count diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/client/types/input_media/input_media_animation.py index e77499b5..6c06df7b 100644 --- a/pyrogram/client/types/input_media/input_media_animation.py +++ b/pyrogram/client/types/input_media/input_media_animation.py @@ -31,7 +31,7 @@ class InputMediaAnimation(InputMedia): thumb (``str``, *optional*): Thumbnail of the animation file sent. The thumbnail should be in JPEG format and less than 200 KB in size. - A thumbnail's width and height should not exceed 90 pixels. + A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/client/types/input_media/input_media_audio.py index e8f1c257..6b7659fe 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/client/types/input_media/input_media_audio.py @@ -32,7 +32,7 @@ class InputMediaAudio(InputMedia): thumb (``str``, *optional*): Thumbnail of the music file album cover. The thumbnail should be in JPEG format and less than 200 KB in size. - A thumbnail's width and height should not exceed 90 pixels. + A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): diff --git a/pyrogram/client/types/input_media/input_media_document.py b/pyrogram/client/types/input_media/input_media_document.py index 9391e7d8..a5d36261 100644 --- a/pyrogram/client/types/input_media/input_media_document.py +++ b/pyrogram/client/types/input_media/input_media_document.py @@ -31,7 +31,7 @@ class InputMediaDocument(InputMedia): thumb (``str``): Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 KB in size. - A thumbnail's width and height should not exceed 90 pixels. + A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): diff --git a/pyrogram/client/types/input_media/input_media_video.py b/pyrogram/client/types/input_media/input_media_video.py index 5c918f13..27d166bd 100644 --- a/pyrogram/client/types/input_media/input_media_video.py +++ b/pyrogram/client/types/input_media/input_media_video.py @@ -33,7 +33,7 @@ class InputMediaVideo(InputMedia): thumb (``str``): Thumbnail of the video sent. The thumbnail should be in JPEG format and less than 200 KB in size. - A thumbnail's width and height should not exceed 90 pixels. + A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 693d2e92..c5678e3f 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -79,7 +79,7 @@ class Message(PyrogramType, Update): forward_from (:obj:`User `, *optional*): For forwarded messages, sender of the original message. - forward_from_name (``str``, *optional*): + forward_sender_name (``str``, *optional*): For messages forwarded from users who have hidden their accounts, name of the user. forward_from_chat (:obj:`Chat `, *optional*): @@ -186,6 +186,9 @@ class Message(PyrogramType, Update): web page preview. In future versions this property could turn into a full web page object that contains more details. + poll (:obj:`Poll `, *optional*): + Message is a native poll, information about the poll. + new_chat_members (List of :obj:`User `, *optional*): New members that were added to the group or supergroup and information about them (the bot itself may be one of these members). @@ -267,7 +270,7 @@ class Message(PyrogramType, Update): # TODO: Add game missing field. Also invoice, successful_payment, connected_website __slots__ = [ - "message_id", "date", "chat", "from_user", "forward_from", "forward_from_name", "forward_from_chat", + "message_id", "date", "chat", "from_user", "forward_from", "forward_sender_name", "forward_from_chat", "forward_from_message_id", "forward_signature", "forward_date", "reply_to_message", "mentioned", "empty", "service", "media", "edit_date", "media_group_id", "author_signature", "text", "entities", "caption_entities", "audio", "document", "photo", "sticker", "animation", "game", "video", "voice", "video_note", "caption", @@ -286,7 +289,7 @@ class Message(PyrogramType, Update): chat: Chat = None, from_user: User = None, forward_from: User = None, - forward_from_name: str = None, + forward_sender_name: str = None, forward_from_chat: Chat = None, forward_from_message_id: int = None, forward_signature: str = None, @@ -348,7 +351,7 @@ class Message(PyrogramType, Update): self.chat = chat self.from_user = from_user self.forward_from = forward_from - self.forward_from_name = forward_from_name + self.forward_sender_name = forward_sender_name self.forward_from_chat = forward_from_chat self.forward_from_message_id = forward_from_message_id self.forward_signature = forward_signature @@ -487,7 +490,7 @@ class Message(PyrogramType, Update): entities = list(filter(lambda x: x is not None, entities)) forward_from = None - forward_from_name = None + forward_sender_name = None forward_from_chat = None forward_from_message_id = None forward_signature = None @@ -501,7 +504,7 @@ class Message(PyrogramType, Update): if forward_header.from_id: forward_from = User._parse(client, users[forward_header.from_id]) elif forward_header.from_name: - forward_from_name = forward_header.from_name + forward_sender_name = forward_header.from_name else: forward_from_chat = Chat._parse_channel_chat(client, chats[forward_header.channel_id]) forward_from_message_id = forward_header.channel_post @@ -607,7 +610,7 @@ class Message(PyrogramType, Update): caption_entities=entities or None if media is not None else None, author_signature=message.post_author, forward_from=forward_from, - forward_from_name=forward_from_name, + forward_sender_name=forward_sender_name, forward_from_chat=forward_from_chat, forward_from_message_id=forward_from_message_id, forward_signature=forward_signature, @@ -795,7 +798,7 @@ class Message(PyrogramType, Update): thumb (``str``, *optional*): Thumbnail of the animation file sent. The thumbnail should be in JPEG format and less than 200 KB in size. - A thumbnail's width and height should not exceed 90 pixels. + A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. disable_notification (``bool``, *optional*): @@ -930,7 +933,7 @@ class Message(PyrogramType, Update): thumb (``str``, *optional*): Thumbnail of the music file album cover. The thumbnail should be in JPEG format and less than 200 KB in size. - A thumbnail's width and height should not exceed 90 pixels. + A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. disable_notification (``bool``, *optional*): @@ -1257,7 +1260,7 @@ class Message(PyrogramType, Update): thumb (``str``, *optional*): Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 KB in size. - A thumbnail's width and height should not exceed 90 pixels. + A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): @@ -2064,7 +2067,7 @@ class Message(PyrogramType, Update): thumb (``str``, *optional*): Thumbnail of the video sent. The thumbnail should be in JPEG format and less than 200 KB in size. - A thumbnail's width and height should not exceed 90 pixels. + A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. supports_streaming (``bool``, *optional*): @@ -2189,7 +2192,7 @@ class Message(PyrogramType, Update): thumb (``str``, *optional*): Thumbnail of the video sent. The thumbnail should be in JPEG format and less than 200 KB in size. - A thumbnail's width and height should not exceed 90 pixels. + A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. disable_notification (``bool``, *optional*): diff --git a/pyrogram/client/types/messages_and_media/poll.py b/pyrogram/client/types/messages_and_media/poll.py index 68667334..fa68f669 100644 --- a/pyrogram/client/types/messages_and_media/poll.py +++ b/pyrogram/client/types/messages_and_media/poll.py @@ -16,91 +16,125 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import List +from typing import List, Union import pyrogram from pyrogram.api import types from .poll_option import PollOption from ..pyrogram_type import PyrogramType +from ..update import Update -class Poll(PyrogramType): +class Poll(PyrogramType, Update): """This object represents a Poll. Args: - id (``int``): - The poll id in this chat. - - closed (``bool``): - Whether the poll is closed or not. + id (``str``): + Unique poll identifier. question (``str``): - Poll question. + Poll question, 1-255 characters. options (List of :obj:`PollOption`): - The available poll options. + List of poll options. + + is_closed (``bool``): + True, if the poll is closed. total_voters (``int``): - Total amount of voters for this poll. + Total count of voters for this poll. - option_chosen (``int``, *optional*): - The index of your chosen option (in case you voted already), None otherwise. + chosen_option (``int``, *optional*): + Index of your chosen option (0-9), None in case you haven't voted yet. """ - __slots__ = ["id", "closed", "question", "options", "total_voters", "option_chosen"] + __slots__ = ["id", "question", "options", "is_closed", "total_voters", "chosen_option"] def __init__( self, *, client: "pyrogram.client.ext.BaseClient", - id: int, - closed: bool, + id: str, question: str, options: List[PollOption], + is_closed: bool, total_voters: int, - option_chosen: int = None + chosen_option: int = None ): super().__init__(client) self.id = id - self.closed = closed self.question = question self.options = options + self.is_closed = is_closed self.total_voters = total_voters - self.option_chosen = option_chosen + self.chosen_option = chosen_option @staticmethod - def _parse(client, media_poll: types.MessageMediaPoll) -> "Poll": + def _parse(client, media_poll: Union[types.MessageMediaPoll, types.UpdateMessagePoll]) -> "Poll": poll = media_poll.poll results = media_poll.results.results total_voters = media_poll.results.total_voters - option_chosen = None - + chosen_option = None options = [] for i, answer in enumerate(poll.answers): - voters = 0 + voter_count = 0 if results: result = results[i] - voters = result.voters + voter_count = result.voters if result.chosen: - option_chosen = i + chosen_option = i - options.append(PollOption( - text=answer.text, - voters=voters, - data=answer.option, - client=client - )) + options.append( + PollOption( + text=answer.text, + voter_count=voter_count, + data=answer.option, + client=client + ) + ) return Poll( - id=poll.id, - closed=poll.closed, + id=str(poll.id), question=poll.question, options=options, + is_closed=poll.closed, total_voters=total_voters, - option_chosen=option_chosen, + chosen_option=chosen_option, + client=client + ) + + @staticmethod + def _parse_update(client, update: types.UpdateMessagePoll): + if update.poll is not None: + return Poll._parse(client, update) + + results = update.results.results + chosen_option = None + options = [] + + for i, result in enumerate(results): + if result.chosen: + chosen_option = i + + options.append( + PollOption( + text="", + voter_count=result.voters, + data=result.option, + client=client + ) + ) + + return Poll( + id=str(update.poll_id), + question="", + options=options, + is_closed=False, + total_voters=update.results.total_voters, + chosen_option=chosen_option, client=client ) diff --git a/pyrogram/client/types/messages_and_media/poll_option.py b/pyrogram/client/types/messages_and_media/poll_option.py index c45c1db2..4b32623a 100644 --- a/pyrogram/client/types/messages_and_media/poll_option.py +++ b/pyrogram/client/types/messages_and_media/poll_option.py @@ -21,32 +21,29 @@ from ..pyrogram_type import PyrogramType class PollOption(PyrogramType): - """This object represents a Poll Option. + """This object contains information about one answer option in a poll. Args: text (``str``): - Text of the poll option. + Option text, 1-100 characters. - voters (``int``): - The number of users who voted this option. - It will be 0 until you vote for the poll. - - data (``bytes``): - Unique data that identifies this option among all the other options in a poll. + voter_count (``int``): + Number of users that voted for this option. + Equals to 0 until you vote. """ - __slots__ = ["text", "voters", "data"] + __slots__ = ["text", "voter_count", "_data"] def __init__( self, *, client: "pyrogram.client.ext.BaseClient", text: str, - voters: int, + voter_count: int, data: bytes ): super().__init__(client) self.text = text - self.voters = voters - self.data = data + self.voter_count = voter_count + self._data = data # Hidden diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index caf34ce9..a45a8f8d 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -50,6 +50,9 @@ class Video(PyrogramType): mime_type (``str``, *optional*): Mime type of a file as defined by sender. + supports_streaming (``bool``, *optional*): + True, if the video was uploaded with streaming support. + file_size (``int``, *optional*): File size. @@ -57,7 +60,10 @@ class Video(PyrogramType): Date the video was sent in Unix time. """ - __slots__ = ["file_id", "thumb", "file_name", "mime_type", "file_size", "date", "width", "height", "duration"] + __slots__ = [ + "file_id", "width", "height", "duration", "thumb", "file_name", "mime_type", "supports_streaming", "file_size", + "date" + ] def __init__( self, @@ -70,20 +76,22 @@ class Video(PyrogramType): thumb: PhotoSize = None, file_name: str = None, mime_type: str = None, + supports_streaming: bool = None, file_size: int = None, date: int = None ): super().__init__(client) self.file_id = file_id - self.thumb = thumb - self.file_name = file_name - self.mime_type = mime_type - self.file_size = file_size - self.date = date self.width = width self.height = height self.duration = duration + self.thumb = thumb + self.file_name = file_name + self.mime_type = mime_type + self.supports_streaming = supports_streaming + self.file_size = file_size + self.date = date @staticmethod def _parse(client, video: types.Document, video_attributes: types.DocumentAttributeVideo, @@ -102,9 +110,10 @@ class Video(PyrogramType): height=video_attributes.h, duration=video_attributes.duration, thumb=PhotoSize._parse(client, video.thumbs), - mime_type=video.mime_type, - file_size=video.size, file_name=file_name, + mime_type=video.mime_type, + supports_streaming=video_attributes.supports_streaming, + file_size=video.size, date=video.date, client=client ) diff --git a/pyrogram/client/types/pyrogram_type.py b/pyrogram/client/types/pyrogram_type.py index af828926..5f757d43 100644 --- a/pyrogram/client/types/pyrogram_type.py +++ b/pyrogram/client/types/pyrogram_type.py @@ -51,7 +51,7 @@ def default(o: PyrogramType): return remove_none( OrderedDict( [("_", "pyrogram." + o.__class__.__name__)] - + [i for i in content.items()] + + [i for i in content.items() if not i[0].startswith("_")] ) ) except AttributeError: diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 4124e497..6b4b9f38 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -185,6 +185,12 @@ class Chat(PyrogramType): if isinstance(chat_full, types.UserFull): parsed_chat = Chat._parse_user_chat(client, chat_full.user) parsed_chat.description = chat_full.about + + if chat_full.pinned_msg_id: + parsed_chat.pinned_message = await client.get_messages( + parsed_chat.id, + message_ids=chat_full.pinned_msg_id + ) else: full_chat = chat_full.full_chat chat = None diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 35911210..046d2ebc 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -36,6 +36,9 @@ class ChatMember(PyrogramType): date (``int``, *optional*): Date when the user joined, unix time. Not available for creator. + is_member (``bool``, *optional*): + Restricted only. True, if the user is a member of the chat at the moment of the request. + invited_by (:obj:`User `, *optional*): Administrators and self member only. Information about the user who invited this member. In case the user joined by himself this will be the same as "user". @@ -51,7 +54,7 @@ class ChatMember(PyrogramType): Information about the member permissions. """ - __slots__ = ["user", "status", "date", "invited_by", "promoted_by", "restricted_by", "permissions"] + __slots__ = ["user", "status", "date", "is_member", "invited_by", "promoted_by", "restricted_by", "permissions"] def __init__( self, @@ -60,6 +63,7 @@ class ChatMember(PyrogramType): user: "pyrogram.User", status: str, date: int = None, + is_member: bool = None, invited_by: "pyrogram.User" = None, promoted_by: "pyrogram.User" = None, restricted_by: "pyrogram.User" = None, @@ -70,6 +74,7 @@ class ChatMember(PyrogramType): self.user = user self.status = status self.date = date + self.is_member = is_member self.invited_by = invited_by self.promoted_by = promoted_by self.restricted_by = restricted_by @@ -123,12 +128,9 @@ class ChatMember(PyrogramType): if isinstance(member, types.ChannelParticipantBanned): return ChatMember( user=user, - status=( - "kicked" if member.banned_rights.view_messages - else "left" if member.left - else "restricted" - ), + status="kicked" if member.banned_rights.view_messages else "restricted", date=member.date, + is_member=not member.left, restricted_by=pyrogram.User._parse(client, users[member.kicked_by]), permissions=pyrogram.ChatPermissions._parse(member), client=client diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index 1bbd3b4b..d406d783 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -34,7 +34,7 @@ class Dialog(PyrogramType): The last message sent in the dialog at this time. unread_messages_count (``int``): - Amount of unread messages in this dialogs. + Amount of unread messages in this dialog. unread_mentions_count (``int``): Amount of unread messages containing a mention in this dialog.