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
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
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
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/TestServers
resources/AdvancedUsage
resources/VoiceCalls
resources/Changelog
.. 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:
- 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

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
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]

View File

@ -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,

View File

@ -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.

View File

@ -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)

View File

@ -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
)

View File

@ -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])

View File

@ -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
)

View File

@ -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
)

View File

@ -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
)

View File

@ -16,12 +16,17 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
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 <pyrogram.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()

View File

@ -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

View File

@ -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 <pyrogram.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 <pyrogram.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

View File

@ -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

View File

@ -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()