Merge branch 'develop' into asyncio

# Conflicts:
#	pyrogram/client/dispatcher/dispatcher.py
#	pyrogram/client/methods/messages/get_history.py
This commit is contained in:
Dan 2019-02-04 12:59:20 +01:00
commit dd86aba9d3
19 changed files with 177 additions and 83 deletions

View File

@ -87,3 +87,4 @@ MESSAGE_POLL_CLOSED You can't interact with a closed poll
MEDIA_INVALID The media is invalid MEDIA_INVALID The media is invalid
BOT_SCORE_NOT_MODIFIED The bot score was not modified BOT_SCORE_NOT_MODIFIED The bot score was not modified
USER_BOT_REQUIRED The method can be used by bots only USER_BOT_REQUIRED The method can be used by bots only
IMAGE_PROCESS_FAILED The server failed to process your image
1 id message
87 MEDIA_INVALID The media is invalid
88 BOT_SCORE_NOT_MODIFIED The bot score was not modified
89 USER_BOT_REQUIRED The method can be used by bots only
90 IMAGE_PROCESS_FAILED The server failed to process your image

View File

@ -1,2 +1,3 @@
id message id message
FLOOD_WAIT_X A wait of {x} seconds is required 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

1 id message
2 FLOOD_WAIT_X A wait of {x} seconds is required
3 TAKEOUT_INIT_DELAY_X You have to confirm the data export request using one of your mobile devices or wait {x} seconds

View File

@ -99,6 +99,7 @@ To get started, press the Next button.
resources/ErrorHandling resources/ErrorHandling
resources/TestServers resources/TestServers
resources/AdvancedUsage resources/AdvancedUsage
resources/VoiceCalls
resources/Changelog resources/Changelog
.. toctree:: .. toctree::

View File

@ -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: 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). - 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::
Note that ``.stop_propagation()`` is just an elegant and intuitive way to raise a ``StopPropagation`` error; Internally, the propagation is stopped by handling a custom exception. ``.stop_propagation()`` is just an elegant
this means that any code coming *after* calling it won't be executed as your function just raised a custom exception and intuitive way to ``raise StopPropagation``; this also means that any code coming *after* calling the method
to signal the dispatcher not to propagate the update anymore. 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()``: Example with ``stop_propagation()``:
@ -139,10 +140,82 @@ Example with ``raise StopPropagation``:
def _(client, message): def _(client, message):
print(2) print(2)
The handler in group number 2 will never be executed because the propagation was stopped before. The output of both Each handler is registered in a different group, but the handler in group number 2 will never be executed because the
examples will be: propagation was stopped earlier. The output of both (equivalent) examples will be:
.. code-block:: text .. code-block:: text
0 0
1 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

View File

@ -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 <https://t.me/bakatrouble>`_.
.. note::
This page will be updated with more information once voice calls become eventually more usable and more integrated
in Pyrogram itself.

View File

@ -6,13 +6,15 @@ to make it only work for specific messages in a specific chat.
from pyrogram import Client, Emoji, Filters from pyrogram import Client, Emoji, Filters
MENTION = "[{}](tg://user?id={})" TARGET = "PyrogramChat" # Target chat. Can also be a list of multiple chat ids/usernames
MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!" 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 = 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): def welcome(client, message):
# Build the new members list (with mentions) by using their first_name # 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] new_members = [MENTION.format(i.first_name, i.id) for i in message.new_chat_members]

View File

@ -40,7 +40,8 @@ from .client.types import (
Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus, Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus,
UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply,
InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove, 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 ( from .client import (
Client, ChatAction, ParseMode, Emoji, Client, ChatAction, ParseMode, Emoji,

View File

@ -141,8 +141,9 @@ class Client(Methods, BaseClient):
Only applicable for new sessions. Only applicable for new sessions.
first_name (``str``, *optional*): first_name (``str``, *optional*):
Pass a First Name to avoid entering it manually. It will be used to automatically Pass a First Name as string to avoid entering it manually. Or pass a callback function which accepts no
create a new Telegram account in case the phone number you passed is not registered yet. 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. Only applicable for new sessions.
last_name (``str``, *optional*): last_name (``str``, *optional*):
@ -160,7 +161,8 @@ class Client(Methods, BaseClient):
Path of the configuration file. Defaults to ./config.ini Path of the configuration file. Defaults to ./config.ini
plugins (``dict``, *optional*): 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*): no_updates (``bool``, *optional*):
Pass True to completely disable incoming updates for the current session. Pass True to completely disable incoming updates for the current session.

View File

@ -125,34 +125,36 @@ class Dispatcher:
parser = self.update_parsers.get(type(update), None) parser = self.update_parsers.get(type(update), None)
if parser is None: parsed_update, handler_type = (
continue await parser(update, users, chats)
if parser is not None
parsed_update, handler_type = await parser(update, users, chats) else (None, type(None))
)
for group in self.groups.values(): for group in self.groups.values():
try: for handler in group:
for handler in group: args = None
args = None
if isinstance(handler, RawUpdateHandler): if isinstance(handler, handler_type):
args = (update, users, chats) if handler.check(parsed_update):
elif isinstance(handler, handler_type): args = (parsed_update,)
if handler.check(parsed_update): elif isinstance(handler, RawUpdateHandler):
args = (parsed_update,) args = (update, users, chats)
if args is None: if args is None:
continue continue
try: try:
await handler.callback(self.client, *args) await handler.callback(self.client, *args)
except StopIteration: except pyrogram.StopPropagation:
raise raise
except Exception as e: except pyrogram.ContinuePropagation:
log.error(e, exc_info=True) continue
except Exception as e:
log.error(e, exc_info=True)
break
except StopIteration:
break break
except pyrogram.StopPropagation:
pass
except Exception as e: except Exception as e:
log.error(e, exc_info=True) log.error(e, exc_info=True)

View File

@ -45,10 +45,3 @@ class CallbackQueryHandler(Handler):
def __init__(self, callback: callable, filters=None): def __init__(self, callback: callable, filters=None):
super().__init__(callback, filters) super().__init__(callback, filters)
def check(self, callback_query):
return (
self.filters(callback_query)
if callable(self.filters)
else True
)

View File

@ -48,8 +48,4 @@ class DeletedMessagesHandler(Handler):
super().__init__(callback, filters) super().__init__(callback, filters)
def check(self, messages): def check(self, messages):
return ( return super().check(messages.messages[0])
self.filters(messages.messages[0])
if callable(self.filters)
else True
)

View File

@ -21,3 +21,10 @@ class Handler:
def __init__(self, callback: callable, filters=None): def __init__(self, callback: callable, filters=None):
self.callback = callback self.callback = callback
self.filters = filters self.filters = filters
def check(self, update):
return (
self.filters(update)
if callable(self.filters)
else True
)

View File

@ -46,10 +46,3 @@ class MessageHandler(Handler):
def __init__(self, callback: callable, filters=None): def __init__(self, callback: callable, filters=None):
super().__init__(callback, filters) super().__init__(callback, filters)
def check(self, message):
return (
self.filters(message)
if callable(self.filters)
else True
)

View File

@ -45,10 +45,3 @@ class UserStatusHandler(Handler):
def __init__(self, callback: callable, filters=None): def __init__(self, callback: callable, filters=None):
super().__init__(callback, filters) super().__init__(callback, filters)
def check(self, user_status):
return (
self.filters(user_status)
if callable(self.filters)
else True
)

View File

@ -16,12 +16,17 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import logging
import time
from typing import Union from typing import Union
import pyrogram import pyrogram
from pyrogram.api import functions from pyrogram.api import functions
from pyrogram.api.errors import FloodWait
from ...ext import BaseClient from ...ext import BaseClient
log = logging.getLogger(__name__)
class GetHistory(BaseClient): class GetHistory(BaseClient):
async def get_history(self, async def get_history(self,
@ -66,21 +71,28 @@ class GetHistory(BaseClient):
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error. :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
messages = await pyrogram.Messages._parse( while True:
self, try:
await self.send( messages = await pyrogram.Messages._parse(
functions.messages.GetHistory( self,
peer=await self.resolve_peer(chat_id), await self.send(
offset_id=offset_id, functions.messages.GetHistory(
offset_date=offset_date, peer=await self.resolve_peer(chat_id),
add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0), offset_id=offset_id,
limit=limit, offset_date=offset_date,
max_id=0, add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0),
min_id=0, limit=limit,
hash=0 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: if reverse:
messages.messages.reverse() messages.messages.reverse()

View File

@ -30,7 +30,7 @@ from .messages_and_media import (
Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos, Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos,
Message, Messages, MessageEntity, Poll, PollOption, Game Message, Messages, MessageEntity, Poll, PollOption, Game
) )
from .update import StopPropagation from .update import StopPropagation, ContinuePropagation
from .user_and_chats import ( from .user_and_chats import (
Chat, ChatMember, ChatMembers, ChatPhoto, Chat, ChatMember, ChatMembers, ChatPhoto,
Dialog, Dialogs, User, UserStatus, ChatPreview Dialog, Dialogs, User, UserStatus, ChatPreview

View File

@ -40,15 +40,15 @@ class CallbackQuery(PyrogramType, Update):
Sender. Sender.
chat_instance (``str``, *optional*): 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 <pyrogram.Message>`, *optional*):
Message with the callback button that originated the query. Note that message content and message date will 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. not be available if the message is too old.
message (:obj:`Message <pyrogram.Message>`, *optional*):
Identifier of the message sent via the bot in inline mode, that originated the query.
inline_message_id (``str``): inline_message_id (``str``):
Global identifier, uniquely corresponding to the chat to which the message with the callback button was Identifier of the message sent via the bot in inline mode, that originated the query.
sent. Useful for high scores in games.
data (``bytes``, *optional*): data (``bytes``, *optional*):
Data associated with the callback button. Be aware that a bad client can send arbitrary data in this field. 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.id = id
self.from_user = from_user self.from_user = from_user
self.chat_instance = chat_instance
self.message = message self.message = message
self.inline_message_id = inline_message_id self.inline_message_id = inline_message_id
self.chat_instance = chat_instance
self.data = data self.data = data
self.game_short_name = game_short_name self.game_short_name = game_short_name

View File

@ -21,6 +21,13 @@ class StopPropagation(StopIteration):
pass pass
class ContinuePropagation(StopIteration):
pass
class Update: class Update:
def stop_propagation(self): def stop_propagation(self):
raise StopPropagation raise StopPropagation
def continue_propagation(self):
raise ContinuePropagation

View File

@ -127,7 +127,7 @@ class Generate(Command):
docs_compiler.start() 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() error_compiler.start()
api_compiler.start() api_compiler.start()
docs_compiler.start() docs_compiler.start()