diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 82474096..9250c566 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -87,3 +87,4 @@ MESSAGE_POLL_CLOSED You can't interact with a closed poll MEDIA_INVALID The media is invalid BOT_SCORE_NOT_MODIFIED The bot score was not modified USER_BOT_REQUIRED The method can be used by bots only +IMAGE_PROCESS_FAILED The server failed to process your image \ No newline at end of file diff --git a/compiler/error/source/420_FLOOD.tsv b/compiler/error/source/420_FLOOD.tsv index bf404156..3d5ceabd 100644 --- a/compiler/error/source/420_FLOOD.tsv +++ b/compiler/error/source/420_FLOOD.tsv @@ -1,2 +1,3 @@ id message FLOOD_WAIT_X A wait of {x} seconds is required +TAKEOUT_INIT_DELAY_X You have to confirm the data export request using one of your mobile devices or wait {x} seconds diff --git a/docs/source/index.rst b/docs/source/index.rst index 067e6fbf..1d77c02f 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -99,6 +99,7 @@ To get started, press the Next button. resources/ErrorHandling resources/TestServers resources/AdvancedUsage + resources/VoiceCalls resources/Changelog .. toctree:: diff --git a/docs/source/resources/MoreOnUpdates.rst b/docs/source/resources/MoreOnUpdates.rst index 44295f35..9712a5d2 100644 --- a/docs/source/resources/MoreOnUpdates.rst +++ b/docs/source/resources/MoreOnUpdates.rst @@ -91,13 +91,14 @@ Stop Propagation In order to prevent further propagation of an update in the dispatching phase, you can do *one* of the following: - Call the update's bound-method ``.stop_propagation()`` (preferred way). -- Manually ``raise StopPropagation`` error (more suitable for raw updates only). +- Manually ``raise StopPropagation`` exception (more suitable for raw updates only). .. note:: - Note that ``.stop_propagation()`` is just an elegant and intuitive way to raise a ``StopPropagation`` error; - this means that any code coming *after* calling it won't be executed as your function just raised a custom exception - to signal the dispatcher not to propagate the update anymore. + Internally, the propagation is stopped by handling a custom exception. ``.stop_propagation()`` is just an elegant + and intuitive way to ``raise StopPropagation``; this also means that any code coming *after* calling the method + won't be executed as your function just raised an exception to signal the dispatcher not to propagate the + update anymore. Example with ``stop_propagation()``: @@ -139,10 +140,82 @@ Example with ``raise StopPropagation``: def _(client, message): print(2) -The handler in group number 2 will never be executed because the propagation was stopped before. The output of both -examples will be: +Each handler is registered in a different group, but the handler in group number 2 will never be executed because the +propagation was stopped earlier. The output of both (equivalent) examples will be: .. code-block:: text 0 1 + +Continue Propagation +^^^^^^^^^^^^^^^^^^^^ + +As opposed to `stopping the update propagation <#stop-propagation>`_ and also as an alternative to the +`handler groups <#handler-groups>`_, you can signal the internal dispatcher to continue the update propagation within +the group regardless of the next handler's filters. This allows you to register multiple handlers with overlapping +filters in the same group; to let the dispatcher process the next handler you can do *one* of the following in each +handler you want to grant permission to continue: + +- Call the update's bound-method ``.continue_propagation()`` (preferred way). +- Manually ``raise ContinuePropagation`` exception (more suitable for raw updates only). + +.. note:: + + Internally, the propagation is continued by handling a custom exception. ``.continue_propagation()`` is just an + elegant and intuitive way to ``raise ContinuePropagation``; this also means that any code coming *after* calling the + method won't be executed as your function just raised an exception to signal the dispatcher to continue with the + next available handler. + + +Example with ``continue_propagation()``: + +.. code-block:: python + + @app.on_message(Filters.private) + def _(client, message): + print(0) + message.continue_propagation() + + + @app.on_message(Filters.private) + def _(client, message): + print(1) + message.continue_propagation() + + + @app.on_message(Filters.private) + def _(client, message): + print(2) + +Example with ``raise ContinuePropagation``: + +.. code-block:: python + + from pyrogram import ContinuePropagation + + @app.on_message(Filters.private) + def _(client, message): + print(0) + raise ContinuePropagation + + + @app.on_message(Filters.private) + def _(client, message): + print(1) + raise ContinuePropagation + + + @app.on_message(Filters.private) + def _(client, message): + print(2) + +Three handlers are registered in the same group, and all of them will be executed because the propagation was continued +in each handler (except in the last one, where is useless to do so since there is no more handlers after). +The output of both (equivalent) examples will be: + +.. code-block:: text + + 0 + 1 + 2 \ No newline at end of file diff --git a/docs/source/resources/VoiceCalls.rst b/docs/source/resources/VoiceCalls.rst new file mode 100644 index 00000000..c1a8cc53 --- /dev/null +++ b/docs/source/resources/VoiceCalls.rst @@ -0,0 +1,10 @@ +Voice Calls +=========== + +A working proof-of-concept of Telegram voice calls using Pyrogram can be found here: +https://github.com/bakatrouble/pylibtgvoip. Thanks to `@bakatrouble `_. + +.. note:: + + This page will be updated with more information once voice calls become eventually more usable and more integrated + in Pyrogram itself. diff --git a/examples/welcome.py b/examples/welcome.py index 06a38cb7..ab252672 100644 --- a/examples/welcome.py +++ b/examples/welcome.py @@ -6,13 +6,15 @@ to make it only work for specific messages in a specific chat. from pyrogram import Client, Emoji, Filters -MENTION = "[{}](tg://user?id={})" -MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!" +TARGET = "PyrogramChat" # Target chat. Can also be a list of multiple chat ids/usernames +MENTION = "[{}](tg://user?id={})" # User mention markup +MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!" # Welcome message app = Client("my_account") -@app.on_message(Filters.chat("PyrogramChat") & Filters.new_chat_members) +# Filter in only new_chat_members updates generated in TARGET chat +@app.on_message(Filters.chat(TARGET) & Filters.new_chat_members) def welcome(client, message): # Build the new members list (with mentions) by using their first_name new_members = [MENTION.format(i.first_name, i.id) for i in message.new_chat_members] diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 17ee71f6..2028dfbc 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -40,7 +40,8 @@ from .client.types import ( Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, - Poll, PollOption, ChatPreview, StopPropagation, Game, CallbackGame, GameHighScore, GameHighScores + Poll, PollOption, ChatPreview, StopPropagation, ContinuePropagation, Game, CallbackGame, GameHighScore, + GameHighScores ) from .client import ( Client, ChatAction, ParseMode, Emoji, diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 7af15224..863856f3 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -141,8 +141,9 @@ class Client(Methods, BaseClient): Only applicable for new sessions. first_name (``str``, *optional*): - Pass a First Name to avoid entering it manually. It will be used to automatically - create a new Telegram account in case the phone number you passed is not registered yet. + Pass a First Name as string to avoid entering it manually. Or pass a callback function which accepts no + arguments and must return the correct name as string (e.g., "Dan"). It will be used to automatically create + a new Telegram account in case the phone number you passed is not registered yet. Only applicable for new sessions. last_name (``str``, *optional*): @@ -160,7 +161,8 @@ class Client(Methods, BaseClient): Path of the configuration file. Defaults to ./config.ini plugins (``dict``, *optional*): - TODO: doctrings + Your Smart Plugins settings as dict, e.g.: *dict(root="plugins")*. + This is an alternative way to setup plugins if you don't want to use the *config.ini* file. no_updates (``bool``, *optional*): Pass True to completely disable incoming updates for the current session. diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py index 0413f369..2c9e31d2 100644 --- a/pyrogram/client/dispatcher/dispatcher.py +++ b/pyrogram/client/dispatcher/dispatcher.py @@ -125,34 +125,36 @@ class Dispatcher: parser = self.update_parsers.get(type(update), None) - if parser is None: - continue - - parsed_update, handler_type = await parser(update, users, chats) + parsed_update, handler_type = ( + await parser(update, users, chats) + if parser is not None + else (None, type(None)) + ) for group in self.groups.values(): - try: - for handler in group: - args = None + for handler in group: + args = None - if isinstance(handler, RawUpdateHandler): - args = (update, users, chats) - elif isinstance(handler, handler_type): - if handler.check(parsed_update): - args = (parsed_update,) + if isinstance(handler, handler_type): + if handler.check(parsed_update): + args = (parsed_update,) + elif isinstance(handler, RawUpdateHandler): + args = (update, users, chats) - if args is None: - continue + if args is None: + continue - try: - await handler.callback(self.client, *args) - except StopIteration: - raise - except Exception as e: - log.error(e, exc_info=True) + try: + await handler.callback(self.client, *args) + except pyrogram.StopPropagation: + raise + except pyrogram.ContinuePropagation: + continue + except Exception as e: + log.error(e, exc_info=True) - break - except StopIteration: break + except pyrogram.StopPropagation: + pass except Exception as e: log.error(e, exc_info=True) diff --git a/pyrogram/client/handlers/callback_query_handler.py b/pyrogram/client/handlers/callback_query_handler.py index e991c019..88ddd5a0 100644 --- a/pyrogram/client/handlers/callback_query_handler.py +++ b/pyrogram/client/handlers/callback_query_handler.py @@ -45,10 +45,3 @@ class CallbackQueryHandler(Handler): def __init__(self, callback: callable, filters=None): super().__init__(callback, filters) - - def check(self, callback_query): - return ( - self.filters(callback_query) - if callable(self.filters) - else True - ) diff --git a/pyrogram/client/handlers/deleted_messages_handler.py b/pyrogram/client/handlers/deleted_messages_handler.py index c084f353..52177dcc 100644 --- a/pyrogram/client/handlers/deleted_messages_handler.py +++ b/pyrogram/client/handlers/deleted_messages_handler.py @@ -48,8 +48,4 @@ class DeletedMessagesHandler(Handler): super().__init__(callback, filters) def check(self, messages): - return ( - self.filters(messages.messages[0]) - if callable(self.filters) - else True - ) + return super().check(messages.messages[0]) diff --git a/pyrogram/client/handlers/handler.py b/pyrogram/client/handlers/handler.py index 9fd0e206..36963280 100644 --- a/pyrogram/client/handlers/handler.py +++ b/pyrogram/client/handlers/handler.py @@ -21,3 +21,10 @@ class Handler: def __init__(self, callback: callable, filters=None): self.callback = callback self.filters = filters + + def check(self, update): + return ( + self.filters(update) + if callable(self.filters) + else True + ) diff --git a/pyrogram/client/handlers/message_handler.py b/pyrogram/client/handlers/message_handler.py index 8a52a0b5..67b4587e 100644 --- a/pyrogram/client/handlers/message_handler.py +++ b/pyrogram/client/handlers/message_handler.py @@ -46,10 +46,3 @@ class MessageHandler(Handler): def __init__(self, callback: callable, filters=None): super().__init__(callback, filters) - - def check(self, message): - return ( - self.filters(message) - if callable(self.filters) - else True - ) diff --git a/pyrogram/client/handlers/user_status_handler.py b/pyrogram/client/handlers/user_status_handler.py index 643a064d..856ef81d 100644 --- a/pyrogram/client/handlers/user_status_handler.py +++ b/pyrogram/client/handlers/user_status_handler.py @@ -45,10 +45,3 @@ class UserStatusHandler(Handler): def __init__(self, callback: callable, filters=None): super().__init__(callback, filters) - - def check(self, user_status): - return ( - self.filters(user_status) - if callable(self.filters) - else True - ) diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index 80dea33c..ae9925eb 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -16,12 +16,17 @@ # 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 import pyrogram from pyrogram.api import functions +from pyrogram.api.errors import FloodWait from ...ext import BaseClient +log = logging.getLogger(__name__) + class GetHistory(BaseClient): async def get_history(self, @@ -66,21 +71,28 @@ class GetHistory(BaseClient): :class:`Error ` in case of a Telegram RPC error. """ - messages = await pyrogram.Messages._parse( - self, - await self.send( - functions.messages.GetHistory( - peer=await self.resolve_peer(chat_id), - offset_id=offset_id, - offset_date=offset_date, - add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0), - limit=limit, - max_id=0, - min_id=0, - hash=0 + while True: + try: + messages = await pyrogram.Messages._parse( + self, + await self.send( + functions.messages.GetHistory( + peer=await self.resolve_peer(chat_id), + offset_id=offset_id, + offset_date=offset_date, + add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0), + limit=limit, + max_id=0, + min_id=0, + hash=0 + ) + ) ) - ) - ) + except FloodWait as e: + log.warning("Sleeping for {}s".format(e.x)) + await asyncio.sleep(e.x) + else: + break if reverse: messages.messages.reverse() diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index ca332a22..a29b0816 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -30,7 +30,7 @@ from .messages_and_media import ( Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos, Message, Messages, MessageEntity, Poll, PollOption, Game ) -from .update import StopPropagation +from .update import StopPropagation, ContinuePropagation from .user_and_chats import ( Chat, ChatMember, ChatMembers, ChatPhoto, Dialog, Dialogs, User, UserStatus, ChatPreview diff --git a/pyrogram/client/types/bots/callback_query.py b/pyrogram/client/types/bots/callback_query.py index b1ff95e1..8eda4c33 100644 --- a/pyrogram/client/types/bots/callback_query.py +++ b/pyrogram/client/types/bots/callback_query.py @@ -40,15 +40,15 @@ class CallbackQuery(PyrogramType, Update): Sender. chat_instance (``str``, *optional*): + Global identifier, uniquely corresponding to the chat to which the message with the callback button was + sent. Useful for high scores in games. + + message (:obj:`Message `, *optional*): Message with the callback button that originated the query. Note that message content and message date will not be available if the message is too old. - message (:obj:`Message `, *optional*): - Identifier of the message sent via the bot in inline mode, that originated the query. - inline_message_id (``str``): - Global identifier, uniquely corresponding to the chat to which the message with the callback button was - sent. Useful for high scores in games. + Identifier of the message sent via the bot in inline mode, that originated the query. data (``bytes``, *optional*): Data associated with the callback button. Be aware that a bad client can send arbitrary data in this field. @@ -72,9 +72,9 @@ class CallbackQuery(PyrogramType, Update): self.id = id self.from_user = from_user + self.chat_instance = chat_instance self.message = message self.inline_message_id = inline_message_id - self.chat_instance = chat_instance self.data = data self.game_short_name = game_short_name diff --git a/pyrogram/client/types/update.py b/pyrogram/client/types/update.py index 80c233c0..2ec22f5a 100644 --- a/pyrogram/client/types/update.py +++ b/pyrogram/client/types/update.py @@ -21,6 +21,13 @@ class StopPropagation(StopIteration): pass +class ContinuePropagation(StopIteration): + pass + + class Update: def stop_propagation(self): raise StopPropagation + + def continue_propagation(self): + raise ContinuePropagation diff --git a/setup.py b/setup.py index cc2a3880..6062c987 100644 --- a/setup.py +++ b/setup.py @@ -127,7 +127,7 @@ class Generate(Command): docs_compiler.start() -if len(argv) > 1 and argv[1] in ["bdist_wheel", "install"]: +if len(argv) > 1 and argv[1] in ["bdist_wheel", "install", "develop"]: error_compiler.start() api_compiler.start() docs_compiler.start()