2
0
mirror of https://github.com/pyrogram/pyrogram synced 2025-08-29 13:27:47 +00:00

Merge branch 'develop'

This commit is contained in:
Dan 2018-11-13 13:17:33 +01:00
commit c3d8fbc441
13 changed files with 252 additions and 218 deletions

View File

@ -1,4 +1,5 @@
id message
CHAT_WRITE_FORBIDDEN You don't have rights to send messages in this chat
RIGHT_FORBIDDEN One or more admin rights can't be applied to this kind of chat (channel/supergroup)
CHAT_ADMIN_INVITE_REQUIRED You don't have rights to invite other users
CHAT_ADMIN_INVITE_REQUIRED You don't have rights to invite other users
MESSAGE_DELETE_FORBIDDEN You don't have rights to delete messages in this chat
1 id message
2 CHAT_WRITE_FORBIDDEN You don't have rights to send messages in this chat
3 RIGHT_FORBIDDEN One or more admin rights can't be applied to this kind of chat (channel/supergroup)
4 CHAT_ADMIN_INVITE_REQUIRED You don't have rights to invite other users
5 MESSAGE_DELETE_FORBIDDEN You don't have rights to delete messages in this chat

View File

@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance
"e" if sys.getfilesystemencoding() != "utf-8" else "\xe8"
)
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
__version__ = "0.9.1"
__version__ = "0.9.2"
from .api.errors import Error
from .client.types import (

View File

@ -22,10 +22,9 @@ from collections import OrderedDict
from queue import Queue
from threading import Thread
import pyrogram
from pyrogram.api import types
from ..ext import utils
from ..handlers import RawUpdateHandler, CallbackQueryHandler, MessageHandler, DeletedMessagesHandler, UserStatusHandler
from ..handlers import CallbackQueryHandler, MessageHandler, DeletedMessagesHandler, UserStatusHandler, RawUpdateHandler
log = logging.getLogger(__name__)
@ -46,15 +45,39 @@ class Dispatcher:
types.UpdateDeleteChannelMessages
)
CALLBACK_QUERY_UPDATES = (
types.UpdateBotCallbackQuery,
types.UpdateInlineBotCallbackQuery
)
MESSAGE_UPDATES = NEW_MESSAGE_UPDATES + EDIT_MESSAGE_UPDATES
def __init__(self, client, workers):
UPDATES = None
def __init__(self, client, workers: int):
self.client = client
self.workers = workers
self.workers_list = []
self.updates = Queue()
self.groups = OrderedDict()
Dispatcher.UPDATES = {
Dispatcher.MESSAGE_UPDATES:
lambda upd, usr, cht: (utils.parse_messages(self.client, upd.message, usr, cht), MessageHandler),
Dispatcher.DELETE_MESSAGE_UPDATES:
lambda upd, usr, cht: (utils.parse_deleted_messages(upd), DeletedMessagesHandler),
Dispatcher.CALLBACK_QUERY_UPDATES:
lambda upd, usr, cht: (utils.parse_callback_query(self.client, upd, usr), CallbackQueryHandler),
(types.UpdateUserStatus,):
lambda upd, usr, cht: (utils.parse_user_status(upd.status, upd.user_id), UserStatusHandler)
}
Dispatcher.UPDATES = {key: value for key_tuple, value in Dispatcher.UPDATES.items() for key in key_tuple}
def start(self):
for i in range(self.workers):
self.workers_list.append(
@ -70,8 +93,8 @@ class Dispatcher:
for _ in range(self.workers):
self.updates.put(None)
for i in self.workers_list:
i.join()
for worker in self.workers_list:
worker.join()
self.workers_list.clear()
@ -84,60 +107,10 @@ class Dispatcher:
def remove_handler(self, handler, group: int):
if group not in self.groups:
raise ValueError("Group {} does not exist. "
"Handler was not removed.".format(group))
raise ValueError("Group {} does not exist. Handler was not removed.".format(group))
self.groups[group].remove(handler)
def dispatch(self, update, users: dict = None, chats: dict = None, is_raw: bool = False):
for group in self.groups.values():
try:
for handler in group:
if is_raw:
if not isinstance(handler, RawUpdateHandler):
continue
args = (self.client, update, users, chats)
else:
message = (update.message
or update.channel_post
or update.edited_message
or update.edited_channel_post)
deleted_messages = (update.deleted_channel_posts
or update.deleted_messages)
callback_query = update.callback_query
user_status = update.user_status
if message and isinstance(handler, MessageHandler):
if not handler.check(message):
continue
args = (self.client, message)
elif deleted_messages and isinstance(handler, DeletedMessagesHandler):
if not handler.check(deleted_messages):
continue
args = (self.client, deleted_messages)
elif callback_query and isinstance(handler, CallbackQueryHandler):
if not handler.check(callback_query):
continue
args = (self.client, callback_query)
elif user_status and isinstance(handler, UserStatusHandler):
if not handler.check(user_status):
continue
args = (self.client, user_status)
else:
continue
handler.callback(*args)
break
except Exception as e:
log.error(e, exc_info=True)
def update_worker(self):
name = threading.current_thread().name
log.debug("{} started".format(name))
@ -153,79 +126,32 @@ class Dispatcher:
chats = {i.id: i for i in update[2]}
update = update[0]
self.dispatch(update, users=users, chats=chats, is_raw=True)
parser = Dispatcher.UPDATES.get(type(update), None)
if isinstance(update, Dispatcher.MESSAGE_UPDATES):
if isinstance(update.message, types.MessageEmpty):
continue
message = utils.parse_messages(
self.client,
update.message,
users,
chats
)
is_edited_message = isinstance(update, Dispatcher.EDIT_MESSAGE_UPDATES)
self.dispatch(
pyrogram.Update(
message=((message if message.chat.type != "channel"
else None) if not is_edited_message
else None),
edited_message=((message if message.chat.type != "channel"
else None) if is_edited_message
else None),
channel_post=((message if message.chat.type == "channel"
else None) if not is_edited_message
else None),
edited_channel_post=((message if message.chat.type == "channel"
else None) if is_edited_message
else None)
)
)
elif isinstance(update, Dispatcher.DELETE_MESSAGE_UPDATES):
is_channel = hasattr(update, 'channel_id')
messages = utils.parse_deleted_messages(
update.messages,
(update.channel_id if is_channel else None)
)
self.dispatch(
pyrogram.Update(
deleted_messages=(messages if not is_channel else None),
deleted_channel_posts=(messages if is_channel else None)
)
)
elif isinstance(update, types.UpdateBotCallbackQuery):
self.dispatch(
pyrogram.Update(
callback_query=utils.parse_callback_query(
self.client, update, users
)
)
)
elif isinstance(update, types.UpdateInlineBotCallbackQuery):
self.dispatch(
pyrogram.Update(
callback_query=utils.parse_inline_callback_query(
self.client, update, users
)
)
)
elif isinstance(update, types.UpdateUserStatus):
self.dispatch(
pyrogram.Update(
user_status=utils.parse_user_status(
update.status, update.user_id
)
)
)
else:
if parser is None:
continue
update, handler_type = parser(update, users, chats)
for group in self.groups.values():
for handler in group:
args = None
if isinstance(handler, RawUpdateHandler):
args = (update, users, chats)
elif isinstance(handler, handler_type):
if handler.check(update):
args = (update,)
if args is None:
continue
try:
handler.callback(self.client, *args)
except Exception as e:
log.error(e, exc_info=True)
finally:
break
except Exception as e:
log.error(e, exc_info=True)

View File

@ -606,6 +606,8 @@ def parse_messages(
forward_from_message_id=forward_from_message_id,
forward_signature=forward_signature,
forward_date=forward_date,
mentioned=message.mentioned,
media=bool(media) or None,
edit_date=message.edit_date,
media_group_id=message.grouped_id,
photo=photo,
@ -640,7 +642,7 @@ def parse_messages(
replies=replies - 1
)
except MessageIdsEmpty:
m.reply_to_message = None
pass
elif isinstance(message, types.MessageService):
action = message.action
@ -727,6 +729,7 @@ def parse_messages(
date=message.date,
chat=parse_chat(message, users, chats),
from_user=parse_user(users.get(message.from_id, None)),
service=True,
new_chat_members=new_chat_members,
left_chat_member=left_chat_member,
new_chat_title=new_chat_title,
@ -741,23 +744,26 @@ def parse_messages(
)
if isinstance(action, types.MessageActionPinMessage):
m.pinned_message = client.get_messages(
m.chat.id,
reply_to_message_ids=message.id,
replies=0
)
try:
m.pinned_message = client.get_messages(
m.chat.id,
reply_to_message_ids=message.id,
replies=0
)
except MessageIdsEmpty:
pass
else:
m = pyrogram_types.Message(message_id=message.id, client=proxy(client))
m = pyrogram_types.Message(message_id=message.id, client=proxy(client), empty=True)
parsed_messages.append(m)
return parsed_messages if is_list else parsed_messages[0]
def parse_deleted_messages(
messages: list,
channel_id: int
) -> pyrogram_types.Messages:
def parse_deleted_messages(update) -> pyrogram_types.Messages:
messages = update.messages
channel_id = getattr(update, "channel_id", None)
parsed_messages = []
for message in messages:
@ -864,42 +870,40 @@ def parse_profile_photos(photos):
)
def parse_callback_query(client, callback_query, users):
peer = callback_query.peer
def parse_callback_query(client, update, users):
message = None
inline_message_id = None
if isinstance(peer, types.PeerUser):
peer_id = peer.user_id
elif isinstance(peer, types.PeerChat):
peer_id = -peer.chat_id
else:
peer_id = int("-100" + str(peer.channel_id))
if isinstance(update, types.UpdateBotCallbackQuery):
peer = update.peer
return pyrogram_types.CallbackQuery(
id=str(callback_query.query_id),
from_user=parse_user(users[callback_query.user_id]),
message=client.get_messages(peer_id, callback_query.msg_id),
chat_instance=str(callback_query.chat_instance),
data=callback_query.data.decode(),
game_short_name=callback_query.game_short_name,
client=client
)
if isinstance(peer, types.PeerUser):
peer_id = peer.user_id
elif isinstance(peer, types.PeerChat):
peer_id = -peer.chat_id
else:
peer_id = int("-100" + str(peer.channel_id))
def parse_inline_callback_query(client, callback_query, users):
return pyrogram_types.CallbackQuery(
id=str(callback_query.query_id),
from_user=parse_user(users[callback_query.user_id]),
chat_instance=str(callback_query.chat_instance),
inline_message_id=b64encode(
message = client.get_messages(peer_id, update.msg_id)
elif isinstance(update, types.UpdateInlineBotCallbackQuery):
inline_message_id = b64encode(
pack(
"<iqq",
callback_query.msg_id.dc_id,
callback_query.msg_id.id,
callback_query.msg_id.access_hash
update.msg_id.dc_id,
update.msg_id.id,
update.msg_id.access_hash
),
b"-_"
).decode().rstrip("="),
game_short_name=callback_query.game_short_name,
).decode().rstrip("=")
return pyrogram_types.CallbackQuery(
id=str(update.query_id),
from_user=parse_user(users[update.user_id]),
message=message,
inline_message_id=inline_message_id,
chat_instance=str(update.chat_instance),
data=update.data.decode(),
game_short_name=update.game_short_name,
client=client
)

View File

@ -166,7 +166,37 @@ class Filters:
inline_keyboard = create("InlineKeyboard", lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup))
"""Filter messages containing inline keyboard markups"""
dan = create("Dan", lambda _, m: bool(m.from_user and m.from_user.id == 23122162))
mentioned = create("Mentioned", lambda _, m: bool(m.mentioned))
"""Filter messages containing mentions"""
service = create("Service", lambda _, m: bool(m.service))
"""Filter service messages. A service message contains any of the following fields set
- left_chat_member
- new_chat_title
- new_chat_photo
- delete_chat_photo
- group_chat_created
- supergroup_chat_created
- channel_chat_created
- migrate_to_chat_id
- migrate_from_chat_id
- pinned_message"""
media = create("Media", lambda _, m: bool(m.media))
"""Filter media messages. A media message contains any of the following fields set
- audio
- document
- photo
- sticker
- video
- animation
- voice
- video_note
- contact
- location
- venue"""
@staticmethod
def command(command: str or list,
@ -252,15 +282,16 @@ class Filters:
Args:
users (``int`` | ``str`` | ``list``):
Pass one or more user ids/usernames to filter users.
For you yourself, "me" or "self" can be used as well.
Defaults to None (no users).
"""
def __init__(self, users: int or str or list = None):
users = [] if users is None else users if type(users) is list else [users]
super().__init__(
{i.lower().strip("@") if type(i) is str else i for i in users}
{"me" if i in ["me", "self"] else i.lower().strip("@") if type(i) is str else i for i in users}
if type(users) is list else
{users.lower().strip("@") if type(users) is str else users}
{"me" if users in ["me", "self"] else users.lower().strip("@") if type(users) is str else users}
)
def __call__(self, message):
@ -268,7 +299,9 @@ class Filters:
message.from_user
and (message.from_user.id in self
or (message.from_user.username
and message.from_user.username.lower() in self))
and message.from_user.username.lower() in self)
or ("me" in self
and message.from_user.is_self))
)
# noinspection PyPep8Naming
@ -281,15 +314,16 @@ class Filters:
Args:
chats (``int`` | ``str`` | ``list``):
Pass one or more chat ids/usernames to filter chats.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
Defaults to None (no chats).
"""
def __init__(self, chats: int or str or list = None):
chats = [] if chats is None else chats if type(chats) is list else [chats]
super().__init__(
{i.lower().strip("@") if type(i) is str else i for i in chats}
{"me" if i in ["me", "self"] else i.lower().strip("@") if type(i) is str else i for i in chats}
if type(chats) is list else
{chats.lower().strip("@") if type(chats) is str else chats}
{"me" if chats in ["me", "self"] else chats.lower().strip("@") if type(chats) is str else chats}
)
def __call__(self, message):
@ -297,41 +331,10 @@ class Filters:
message.chat
and (message.chat.id in self
or (message.chat.username
and message.chat.username.lower() in self))
and message.chat.username.lower() in self)
or ("me" in self and message.from_user
and message.from_user.is_self
and not message.outgoing))
)
service = create(
"Service",
lambda _, m: bool(
Filters.new_chat_members(m)
or Filters.left_chat_member(m)
or Filters.new_chat_title(m)
or Filters.new_chat_photo(m)
or Filters.delete_chat_photo(m)
or Filters.group_chat_created(m)
or Filters.supergroup_chat_created(m)
or Filters.channel_chat_created(m)
or Filters.migrate_to_chat_id(m)
or Filters.migrate_from_chat_id(m)
or Filters.pinned_message(m)
)
)
"""Filter all service messages."""
media = create(
"Media",
lambda _, m: bool(
Filters.audio(m)
or Filters.document(m)
or Filters.photo(m)
or Filters.sticker(m)
or Filters.video(m)
or Filters.animation(m)
or Filters.voice(m)
or Filters.video_note(m)
or Filters.contact(m)
or Filters.location(m)
or Filters.venue(m)
)
)
"""Filter all media messages."""
dan = create("Dan", lambda _, m: bool(m.from_user and m.from_user.id == 23122162))

View File

@ -22,7 +22,7 @@ from ...ext import BaseClient
class OnCallbackQuery(BaseClient):
def on_callback_query(self, filters=None, group: int = 0):
def on_callback_query(self=None, filters=None, group: int = 0):
"""Use this decorator to automatically register a function for handling
callback queries. This does the same thing as :meth:`add_handler` using the
:class:`CallbackQueryHandler`.
@ -37,6 +37,9 @@ class OnCallbackQuery(BaseClient):
"""
def decorator(func):
if isinstance(func, tuple):
func = func[0].callback
handler = pyrogram.CallbackQueryHandler(func, filters)
if isinstance(self, Filter):

View File

@ -22,7 +22,7 @@ from ...ext import BaseClient
class OnDeletedMessages(BaseClient):
def on_deleted_messages(self, filters=None, group: int = 0):
def on_deleted_messages(self=None, filters=None, group: int = 0):
"""Use this decorator to automatically register a function for handling
deleted messages. This does the same thing as :meth:`add_handler` using the
:class:`DeletedMessagesHandler`.
@ -37,6 +37,9 @@ class OnDeletedMessages(BaseClient):
"""
def decorator(func):
if isinstance(func, tuple):
func = func[0].callback
handler = pyrogram.DeletedMessagesHandler(func, filters)
if isinstance(self, Filter):

View File

@ -21,7 +21,7 @@ from ...ext import BaseClient
class OnDisconnect(BaseClient):
def on_disconnect(self):
def on_disconnect(self=None):
"""Use this decorator to automatically register a function for handling
disconnections. This does the same thing as :meth:`add_handler` using the
:class:`DisconnectHandler`.

View File

@ -37,6 +37,9 @@ class OnMessage(BaseClient):
"""
def decorator(func):
if isinstance(func, tuple):
func = func[0].callback
handler = pyrogram.MessageHandler(func, filters)
if isinstance(self, Filter):

View File

@ -32,6 +32,9 @@ class OnRawUpdate(BaseClient):
"""
def decorator(func):
if isinstance(func, tuple):
func = func[0].callback
handler = pyrogram.RawUpdateHandler(func)
if isinstance(self, int):

View File

@ -36,6 +36,9 @@ class OnUserStatus(BaseClient):
"""
def decorator(func):
if isinstance(func, tuple):
func = func[0].callback
handler = pyrogram.UserStatusHandler(func, filters)
if isinstance(self, Filter):

View File

@ -55,6 +55,24 @@ class Message(Object):
For replies, the original message. Note that the Message object in this field will not contain
further reply_to_message fields even if it itself is a reply.
mentioned (``bool``, *optional*):
The message contains a mention.
empty (``bool``, *optional*):
The message is empty.
A message can be empty in case it was deleted or you tried to retrieve a message that doesn't exist yet.
service (``bool``, *optional*):
The message is a service message.
A service message has one and only one of these fields set: left_chat_member, new_chat_title,
new_chat_photo, delete_chat_photo, group_chat_created, supergroup_chat_created, channel_chat_created,
migrate_to_chat_id, migrate_from_chat_id, pinned_message.
media (``bool``` *optional*):
The message is a media message.
A media message has one and only one of these fields set: audio, document, photo, sticker, video, animation,
voice, video_note, contact, location, venue.
edit_date (``int``, *optional*):
Date the message was last edited in Unix time.
@ -206,6 +224,10 @@ class Message(Object):
forward_signature: str = None,
forward_date: int = None,
reply_to_message=None,
mentioned=None,
empty=None,
service=None,
media=None,
edit_date: int = None,
media_group_id: str = None,
author_signature: str = None,
@ -253,6 +275,10 @@ class Message(Object):
self.forward_signature = forward_signature # flags.4?string
self.forward_date = forward_date # flags.5?int
self.reply_to_message = reply_to_message # flags.6?Message
self.mentioned = mentioned
self.empty = empty
self.service = service
self.media = media
self.edit_date = edit_date # flags.7?int
self.media_group_id = media_group_id # flags.8?string
self.author_signature = author_signature # flags.9?string
@ -364,6 +390,54 @@ class Message(Object):
reply_markup=reply_markup
)
def edit(self, text: str, parse_mode: str = "", disable_web_page_preview: bool = None, reply_markup=None):
"""Bound method *edit* of :obj:`Message <pyrogram.Message>
Use as a shortcut for:
.. code-block:: python
client.edit_message_text(
chat_id=message.chat.id,
message_id=message.message_id,
text="hello",
)
Example:
.. code-block:: python
message.edit("hello")
Args:
text (``str``):
New text of the message.
parse_mode (``str``, *optional*):
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your message.
Defaults to Markdown.
disable_web_page_preview (``bool``, *optional*):
Disables link previews for links in this message.
reply_markup (:obj:`InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object.
Returns:
On success, the edited :obj:`Message <pyrogram.Message>` is returned.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
return self._client.edit_message_text(
chat_id=self.chat.id,
message_id=self.message_id,
text=text,
parse_mode=parse_mode,
disable_web_page_preview=disable_web_page_preview,
reply_markup=reply_markup
)
def forward(self,
chat_id: int or str,
disable_notification: bool = None):
@ -560,7 +634,7 @@ class Message(Object):
else:
raise ValueError("The message doesn't contain any keyboard")
def download(self, file_name: str = "", block: bool = True):
def download(self, file_name: str = "", block: bool = True, progress: callable = None, progress_args: tuple = None):
"""Bound method *download* of :obj:`Message <pyrogram.Message>`.
Use as a shortcut for:
@ -585,6 +659,15 @@ class Message(Object):
Blocks the code execution until the file has been downloaded.
Defaults to True.
progress (``callable``):
Pass a callback function to view the download progress.
The function must take *(client, current, total, \*args)* as positional arguments (look at the section
below for a detailed description).
progress_args (``tuple``):
Extra custom arguments for the progress callback function. Useful, for example, if you want to pass
a chat_id and a message_id in order to edit a message with the updated progress.
Returns:
On success, the absolute path of the downloaded file as string is returned, None otherwise.
@ -595,5 +678,7 @@ class Message(Object):
return self._client.download_media(
message=self,
file_name=file_name,
block=block
block=block,
progress=progress,
progress_args=progress_args,
)

View File

@ -37,7 +37,7 @@ class Connection:
4: TCPIntermediateO
}
def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, mode: int = 1):
def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, mode: int = 3):
self.dc_id = dc_id
self.ipv6 = ipv6
self.proxy = proxy