Merge branch 'new-api' into new-api-docs
# Conflicts: # compiler/api/compiler.py # docs/source/pyrogram/index.rst
This commit is contained in:
commit
5e5289596b
@ -25,7 +25,7 @@ DESTINATION = "pyrogram/api"
|
|||||||
NOTICE_PATH = "NOTICE"
|
NOTICE_PATH = "NOTICE"
|
||||||
SECTION_RE = re.compile(r"---(\w+)---")
|
SECTION_RE = re.compile(r"---(\w+)---")
|
||||||
LAYER_RE = re.compile(r"//\sLAYER\s(\d+)")
|
LAYER_RE = re.compile(r"//\sLAYER\s(\d+)")
|
||||||
COMBINATOR_RE = re.compile(r"^([\w.]+)#([0-9a-f]+)\s(?:.*)=\s([\w<>.]+);$", re.MULTILINE)
|
COMBINATOR_RE = re.compile(r"^([\w.]+)#([0-9a-f]+)\s(?:.*)=\s([\w<>.]+);(?: // Docs: (.+))?$", re.MULTILINE)
|
||||||
ARGS_RE = re.compile("[^{](\w+):([\w?!.<>]+)")
|
ARGS_RE = re.compile("[^{](\w+):([\w?!.<>]+)")
|
||||||
FLAGS_RE = re.compile(r"flags\.(\d+)\?")
|
FLAGS_RE = re.compile(r"flags\.(\d+)\?")
|
||||||
FLAGS_RE_2 = re.compile(r"flags\.(\d+)\?([\w<>.]+)")
|
FLAGS_RE_2 = re.compile(r"flags\.(\d+)\?([\w<>.]+)")
|
||||||
@ -38,7 +38,7 @@ types_to_functions = {}
|
|||||||
constructors_to_functions = {}
|
constructors_to_functions = {}
|
||||||
|
|
||||||
|
|
||||||
def get_docstring_arg_type(t: str, is_list: bool = False):
|
def get_docstring_arg_type(t: str, is_list: bool = False, is_pyrogram_type: bool = False):
|
||||||
if t in core_types:
|
if t in core_types:
|
||||||
if t == "long":
|
if t == "long":
|
||||||
return "``int`` ``64-bit``"
|
return "``int`` ``64-bit``"
|
||||||
@ -60,11 +60,17 @@ def get_docstring_arg_type(t: str, is_list: bool = False):
|
|||||||
elif t.startswith("Vector"):
|
elif t.startswith("Vector"):
|
||||||
return "List of " + get_docstring_arg_type(t.split("<")[1][:-1], is_list=True)
|
return "List of " + get_docstring_arg_type(t.split("<")[1][:-1], is_list=True)
|
||||||
else:
|
else:
|
||||||
|
if is_pyrogram_type:
|
||||||
|
t = "pyrogram." + t
|
||||||
|
|
||||||
t = types_to_constructors.get(t, [t])
|
t = types_to_constructors.get(t, [t])
|
||||||
n = len(t) - 1
|
n = len(t) - 1
|
||||||
|
|
||||||
t = (("e" if is_list else "E") + "ither " if n else "") + ", ".join(
|
t = (("e" if is_list else "E") + "ither " if n else "") + ", ".join(
|
||||||
":obj:`{0} <pyrogram.api.types.{0}>`".format(i)
|
":obj:`{1} <pyrogram.api.types.{0}{1}>`".format(
|
||||||
|
"pyrogram." if is_pyrogram_type else "",
|
||||||
|
i.lstrip("pyrogram.")
|
||||||
|
)
|
||||||
for i in t
|
for i in t
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -94,7 +100,15 @@ def get_references(t: str):
|
|||||||
|
|
||||||
|
|
||||||
class Combinator:
|
class Combinator:
|
||||||
def __init__(self, section: str, namespace: str, name: str, id: str, args: list, has_flags: bool, return_type: str):
|
def __init__(self,
|
||||||
|
section: str,
|
||||||
|
namespace: str,
|
||||||
|
name: str,
|
||||||
|
id: str,
|
||||||
|
args: list,
|
||||||
|
has_flags: bool,
|
||||||
|
return_type: str,
|
||||||
|
docs: str):
|
||||||
self.section = section
|
self.section = section
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
self.name = name
|
self.name = name
|
||||||
@ -102,6 +116,7 @@ class Combinator:
|
|||||||
self.args = args
|
self.args = args
|
||||||
self.has_flags = has_flags
|
self.has_flags = has_flags
|
||||||
self.return_type = return_type
|
self.return_type = return_type
|
||||||
|
self.docs = docs
|
||||||
|
|
||||||
|
|
||||||
def snek(s: str):
|
def snek(s: str):
|
||||||
@ -131,11 +146,15 @@ def start():
|
|||||||
|
|
||||||
with open("{}/source/auth_key.tl".format(HOME), encoding="utf-8") as auth, \
|
with open("{}/source/auth_key.tl".format(HOME), encoding="utf-8") as auth, \
|
||||||
open("{}/source/sys_msgs.tl".format(HOME), encoding="utf-8") as system, \
|
open("{}/source/sys_msgs.tl".format(HOME), encoding="utf-8") as system, \
|
||||||
open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api:
|
open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api, \
|
||||||
schema = (auth.read() + system.read() + api.read()).splitlines()
|
open("{}/source/pyrogram.tl".format(HOME), encoding="utf-8") as pyrogram:
|
||||||
|
schema = (auth.read() + system.read() + api.read() + pyrogram.read()).splitlines()
|
||||||
|
|
||||||
with open("{}/template/class.txt".format(HOME), encoding="utf-8") as f:
|
with open("{}/template/mtproto.txt".format(HOME), encoding="utf-8") as f:
|
||||||
template = f.read()
|
mtproto_template = f.read()
|
||||||
|
|
||||||
|
with open("{}/template/pyrogram.txt".format(HOME), encoding="utf-8") as f:
|
||||||
|
pyrogram_template = f.read()
|
||||||
|
|
||||||
with open(NOTICE_PATH, encoding="utf-8") as f:
|
with open(NOTICE_PATH, encoding="utf-8") as f:
|
||||||
notice = []
|
notice = []
|
||||||
@ -165,9 +184,9 @@ def start():
|
|||||||
|
|
||||||
combinator = COMBINATOR_RE.match(line)
|
combinator = COMBINATOR_RE.match(line)
|
||||||
if combinator:
|
if combinator:
|
||||||
name, id, return_type = combinator.groups()
|
name, id, return_type, docs = combinator.groups()
|
||||||
namespace, name = name.split(".") if "." in name else ("", name)
|
namespace, name = name.split(".") if "." in name else ("", name)
|
||||||
args = ARGS_RE.findall(line)
|
args = ARGS_RE.findall(line.split(" //")[0])
|
||||||
|
|
||||||
# Pingu!
|
# Pingu!
|
||||||
has_flags = not not FLAGS_RE_3.findall(line)
|
has_flags = not not FLAGS_RE_3.findall(line)
|
||||||
@ -195,7 +214,8 @@ def start():
|
|||||||
".".join(
|
".".join(
|
||||||
return_type.split(".")[:-1]
|
return_type.split(".")[:-1]
|
||||||
+ [capit(return_type.split(".")[-1])]
|
+ [capit(return_type.split(".")[-1])]
|
||||||
)
|
),
|
||||||
|
docs
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -254,6 +274,7 @@ def start():
|
|||||||
) if c.args else "pass"
|
) if c.args else "pass"
|
||||||
|
|
||||||
docstring_args = []
|
docstring_args = []
|
||||||
|
docs = c.docs.split("|")[1:] if c.docs else None
|
||||||
|
|
||||||
for i, arg in enumerate(sorted_args):
|
for i, arg in enumerate(sorted_args):
|
||||||
arg_name, arg_type = arg
|
arg_name, arg_type = arg
|
||||||
@ -261,11 +282,21 @@ def start():
|
|||||||
flag_number = is_optional.group(1) if is_optional else -1
|
flag_number = is_optional.group(1) if is_optional else -1
|
||||||
arg_type = arg_type.split("?")[-1]
|
arg_type = arg_type.split("?")[-1]
|
||||||
|
|
||||||
|
if docs:
|
||||||
docstring_args.append(
|
docstring_args.append(
|
||||||
"{}{}: {}".format(
|
"{} ({}{}):\n {}\n".format(
|
||||||
arg_name,
|
arg_name,
|
||||||
" (optional)".format(flag_number) if is_optional else "",
|
get_docstring_arg_type(arg_type, is_pyrogram_type=True),
|
||||||
get_docstring_arg_type(arg_type)
|
", optional" if "Optional" in docs[i] else "",
|
||||||
|
re.sub("Optional\. ", "", docs[i].split("§")[1].rstrip(".") + ".")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
docstring_args.append(
|
||||||
|
"{}: {}{}".format(
|
||||||
|
arg_name,
|
||||||
|
"``optional`` ".format(flag_number) if is_optional else "",
|
||||||
|
get_docstring_arg_type(arg_type, is_pyrogram_type=c.namespace == "pyrogram")
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -370,9 +401,25 @@ def start():
|
|||||||
read_types += "\n "
|
read_types += "\n "
|
||||||
read_types += "{} = Object.read(b)\n ".format(arg_name)
|
read_types += "{} = Object.read(b)\n ".format(arg_name)
|
||||||
|
|
||||||
|
if c.docs:
|
||||||
|
description = c.docs.split("|")[0].split("§")[1]
|
||||||
|
docstring_args = description + "\n\n " + docstring_args
|
||||||
|
|
||||||
with open("{}/{}.py".format(path, snek(c.name)), "w", encoding="utf-8") as f:
|
with open("{}/{}.py".format(path, snek(c.name)), "w", encoding="utf-8") as f:
|
||||||
|
if c.docs:
|
||||||
f.write(
|
f.write(
|
||||||
template.format(
|
pyrogram_template.format(
|
||||||
|
notice=notice,
|
||||||
|
class_name=capit(c.name),
|
||||||
|
docstring_args=docstring_args,
|
||||||
|
object_id=c.id,
|
||||||
|
arguments=arguments,
|
||||||
|
fields=fields
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
f.write(
|
||||||
|
mtproto_template.format(
|
||||||
notice=notice,
|
notice=notice,
|
||||||
class_name=capit(c.name),
|
class_name=capit(c.name),
|
||||||
docstring_args=docstring_args,
|
docstring_args=docstring_args,
|
||||||
|
22
compiler/api/source/pyrogram.tl
Normal file
22
compiler/api/source/pyrogram.tl
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Pyrogram
|
||||||
|
|
||||||
|
---types---
|
||||||
|
|
||||||
|
pyrogram.update#b0700000 flags:# update_id:int message:flags.0?Message edited_message:flags.1?Message channel_post:flags.2?Message edited_channel_post:flags.3?Message inline_query:flags.4?InlineQuery chosen_inline_result:flags.5?ChosenInlineResult callback_query:flags.6?CallbackQuery shipping_query:flags.7?ShippingQuery pre_checkout_query:flags.8?PreCheckoutQuery = pyrogram.Update;
|
||||||
|
pyrogram.user#b0700001 flags:# id:int is_bot:Bool first_name:string last_name:flags.0?string username:flags.1?string language_code:flags.2?string phone_number:flags.3?string = pyrogram.User;
|
||||||
|
pyrogram.chat#b0700002 flags:# id:int type:string title:flags.0?string username:flags.1?string first_name:flags.2?string last_name:flags.3?string all_members_are_administrators:flags.4?Bool photo:flags.5?ChatPhoto description:flags.6?string invite_link:flags.7?string pinned_message:flags.8?Message sticker_set_name:flags.9?string can_set_sticker_set:flags.10?Bool = pyrogram.Chat;
|
||||||
|
pyrogram.message#b0700003 flags:# message_id:int from_user:flags.0?User date:int chat:Chat forward_from:flags.1?User forward_from_chat:flags.2?Chat forward_from_message_id:flags.3?int forward_signature:flags.4?string forward_date:flags.5?int reply_to_message:flags.6?Message edit_date:flags.7?int media_group_id:flags.8?string author_signature:flags.9?string text:flags.10?string entities:flags.11?Vector<MessageEntity> caption_entities:flags.12?Vector<MessageEntity> audio:flags.13?Audio document:flags.14?Document game:flags.15?Game photo:flags.16?Vector<PhotoSize> sticker:flags.17?Sticker video:flags.18?Video voice:flags.19?Voice video_note:flags.20?VideoNote caption:flags.21?string contact:flags.22?Contact location:flags.23?Location venue:flags.24?Venue new_chat_members:flags.25?Vector<User> left_chat_member:flags.26?User new_chat_title:flags.27?string new_chat_photo:flags.28?Vector<PhotoSize> delete_chat_photo:flags.29?true group_chat_created:flags.30?true supergroup_chat_created:flags.31?true channel_chat_created:flags.32?true migrate_to_chat_id:flags.33?int migrate_from_chat_id:flags.34?int pinned_message:flags.35?Message invoice:flags.36?Invoice successful_payment:flags.37?SuccessfulPayment connected_website:flags.38?string views:flags.39?int via_bot:flags.40?User = pyrogram.Message;
|
||||||
|
pyrogram.messageEntity#b0700004 flags:# type:string offset:int length:int url:flags.0?string user:flags.1?User = pyrogram.MessageEntity;
|
||||||
|
pyrogram.photoSize#b0700005 flags:# file_id:string width:int height:int file_size:flags.0?int = pyrogram.PhotoSize;
|
||||||
|
pyrogram.audio#b0700006 flags:# file_id:string duration:int performer:flags.0?string title:flags.1?string mime_type:flags.2?string file_size:flags.3?int = pyrogram.Audio;
|
||||||
|
pyrogram.document#b0700007 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int = pyrogram.Document;
|
||||||
|
pyrogram.video#b0700008 flags:# file_id:string width:int height:int duration:int thumb:flags.0?PhotoSize mime_type:flags.1?string file_size:flags.2?int = pyrogram.Video;
|
||||||
|
pyrogram.voice#b0700009 flags:# file_id:string duration:int mime_type:flags.0?string file_size:flags.1?int = pyrogram.Voice;
|
||||||
|
pyrogram.videoNote#b0700010 flags:# file_id:string length:int duration:int thumb:flags.0?PhotoSize file_size:flags.1?int = pyrogram.VideoNote;
|
||||||
|
pyrogram.contact#b0700011 flags:# phone_number:string first_name:string last_name:flags.0?string user_id:flags.1?int = pyrogram.Contact;
|
||||||
|
pyrogram.location#b0700012 longitude:double latitude:double = pyrogram.Location;
|
||||||
|
pyrogram.venue#b0700013 flags:# location:Location title:string address:string foursquare_id:flags.0?string = pyrogram.Venue;
|
||||||
|
pyrogram.userProfilePhotos#b0700014 total_count:int photos:Vector<Vector<PhotoSize>> = pyrogram.UserProfilePhotos;
|
||||||
|
pyrogram.chatPhoto#b0700015 small_file_id:string big_file_id:string = pyrogram.ChatPhoto;
|
||||||
|
pyrogram.chatMember#b0700016 flags:# user:User status:string until_date:flags.0?int can_be_edited:flags.1?Bool can_change_info:flags.2?Bool can_post_messages:flags.3?Bool can_edit_messages:flags.4?Bool can_delete_messages:flags.5?Bool can_invite_users:flags.6?Bool can_restrict_members:flags.7?Bool can_pin_messages:flags.8?Bool can_promote_members:flags.9?Bool can_send_messages:flags.10?Bool can_send_media_messages:flags.11?Bool can_send_other_messages:flags.12?Bool can_add_web_page_previews:flags.13?Bool = pyrogram.ChatMember;
|
||||||
|
pyrogram.sticker#b0700017 flags:# file_id:string width:int height:int thumb:flags.0?PhotoSize emoji:flags.1?string set_name:flags.2?string mask_position:flags.3?MaskPosition file_size:flags.4?int = pyrogram.Sticker;
|
@ -6,8 +6,7 @@ from pyrogram.api.core import *
|
|||||||
|
|
||||||
|
|
||||||
class {class_name}(Object):
|
class {class_name}(Object):
|
||||||
"""
|
"""{docstring_args}
|
||||||
{docstring_args}
|
|
||||||
"""
|
"""
|
||||||
ID = {object_id}
|
ID = {object_id}
|
||||||
|
|
11
compiler/api/template/pyrogram.txt
Normal file
11
compiler/api/template/pyrogram.txt
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{notice}
|
||||||
|
|
||||||
|
from pyrogram.api.core import Object
|
||||||
|
|
||||||
|
class {class_name}(Object):
|
||||||
|
"""{docstring_args}
|
||||||
|
"""
|
||||||
|
ID = {object_id}
|
||||||
|
|
||||||
|
def __init__(self{arguments}):
|
||||||
|
{fields}
|
6
docs/source/pyrogram/Filters.rst
Normal file
6
docs/source/pyrogram/Filters.rst
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Filters
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.Filters
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
@ -1,6 +0,0 @@
|
|||||||
InputMedia
|
|
||||||
==========
|
|
||||||
|
|
||||||
.. autoclass:: pyrogram.InputMedia
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
6
docs/source/pyrogram/InputMediaPhoto.rst
Normal file
6
docs/source/pyrogram/InputMediaPhoto.rst
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
InputMediaPhoto
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.InputMediaPhoto
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
6
docs/source/pyrogram/InputMediaVideo.rst
Normal file
6
docs/source/pyrogram/InputMediaVideo.rst
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
InputMediaVideo
|
||||||
|
===============
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.InputMediaVideo
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
6
docs/source/pyrogram/MessageHandler.rst
Normal file
6
docs/source/pyrogram/MessageHandler.rst
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
MessageHandler
|
||||||
|
==============
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.MessageHandler
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
6
docs/source/pyrogram/RawUpdateHandler.rst
Normal file
6
docs/source/pyrogram/RawUpdateHandler.rst
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
\RawUpdateHandler
|
||||||
|
================
|
||||||
|
|
||||||
|
.. autoclass:: pyrogram.RawUpdateHandler
|
||||||
|
:members:
|
||||||
|
:undoc-members:
|
@ -9,11 +9,38 @@ the same parameters as well, thus offering a familiar look to Bot developers.
|
|||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
Client
|
Client
|
||||||
|
MessageHandler
|
||||||
|
RawUpdateHandler
|
||||||
|
Filters
|
||||||
ChatAction
|
ChatAction
|
||||||
ParseMode
|
ParseMode
|
||||||
Emoji
|
Emoji
|
||||||
InputMedia
|
|
||||||
InputPhoneContact
|
|
||||||
Error
|
Error
|
||||||
|
|
||||||
|
Types
|
||||||
|
-----
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
../types/pyrogram/User
|
||||||
|
../types/pyrogram/Chat
|
||||||
|
../types/pyrogram/Message
|
||||||
|
../types/pyrogram/MessageEntity
|
||||||
|
../types/pyrogram/PhotoSize
|
||||||
|
../types/pyrogram/Audio
|
||||||
|
../types/pyrogram/Document
|
||||||
|
../types/pyrogram/Video
|
||||||
|
../types/pyrogram/Voice
|
||||||
|
../types/pyrogram/VideoNote
|
||||||
|
../types/pyrogram/Contact
|
||||||
|
../types/pyrogram/Location
|
||||||
|
../types/pyrogram/Venue
|
||||||
|
../types/pyrogram/UserProfilePhotos
|
||||||
|
../types/pyrogram/ChatPhoto
|
||||||
|
../types/pyrogram/ChatMember
|
||||||
|
InputMediaPhoto
|
||||||
|
InputMediaVideo
|
||||||
|
InputPhoneContact
|
||||||
|
../types/pyrogram/Sticker
|
||||||
|
|
||||||
|
|
||||||
.. _Telegram Bot API: https://core.telegram.org/bots/api#available-methods
|
.. _Telegram Bot API: https://core.telegram.org/bots/api#available-methods
|
||||||
|
@ -26,9 +26,13 @@ __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
|
|||||||
__version__ = "0.6.5"
|
__version__ = "0.6.5"
|
||||||
|
|
||||||
from .api.errors import Error
|
from .api.errors import Error
|
||||||
|
from .api.types.pyrogram import *
|
||||||
from .client import ChatAction
|
from .client import ChatAction
|
||||||
from .client import Client
|
from .client import Client
|
||||||
from .client import ParseMode
|
from .client import ParseMode
|
||||||
from .client.input_media import InputMedia
|
from .client.input_media_photo import InputMediaPhoto
|
||||||
|
from .client.input_media_video import InputMediaVideo
|
||||||
from .client.input_phone_contact import InputPhoneContact
|
from .client.input_phone_contact import InputPhoneContact
|
||||||
from .client import Emoji
|
from .client import Emoji
|
||||||
|
from .client.handlers import MessageHandler, RawUpdateHandler
|
||||||
|
from .client.filters import Filters
|
||||||
|
@ -37,6 +37,9 @@ class Object:
|
|||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return dumps(self, cls=Encoder, indent=4)
|
return dumps(self, cls=Encoder, indent=4)
|
||||||
|
|
||||||
|
def __bool__(self) -> bool:
|
||||||
|
return True
|
||||||
|
|
||||||
def __eq__(self, other) -> bool:
|
def __eq__(self, other) -> bool:
|
||||||
return self.__dict__ == other.__dict__
|
return self.__dict__ == other.__dict__
|
||||||
|
|
||||||
@ -47,6 +50,15 @@ class Object:
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def remove_none(obj):
|
||||||
|
if isinstance(obj, (list, tuple, set)):
|
||||||
|
return type(obj)(remove_none(x) for x in obj if x is not None)
|
||||||
|
elif isinstance(obj, dict):
|
||||||
|
return type(obj)((remove_none(k), remove_none(v)) for k, v in obj.items() if k is not None and v is not None)
|
||||||
|
else:
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
class Encoder(JSONEncoder):
|
class Encoder(JSONEncoder):
|
||||||
def default(self, o: Object):
|
def default(self, o: Object):
|
||||||
try:
|
try:
|
||||||
@ -57,6 +69,9 @@ class Encoder(JSONEncoder):
|
|||||||
else:
|
else:
|
||||||
return repr(o)
|
return repr(o)
|
||||||
|
|
||||||
|
if "pyrogram" in objects.get(getattr(o, "ID", "")):
|
||||||
|
return remove_none(OrderedDict([i for i in content.items()]))
|
||||||
|
else:
|
||||||
return OrderedDict(
|
return OrderedDict(
|
||||||
[("_", objects.get(getattr(o, "ID", None), None))]
|
[("_", objects.get(getattr(o, "ID", None), None))]
|
||||||
+ [i for i in content.items()]
|
+ [i for i in content.items()]
|
||||||
|
@ -18,5 +18,5 @@
|
|||||||
|
|
||||||
from .chat_action import ChatAction
|
from .chat_action import ChatAction
|
||||||
from .client import Client
|
from .client import Client
|
||||||
from .parse_mode import ParseMode
|
|
||||||
from .emoji import Emoji
|
from .emoji import Emoji
|
||||||
|
from .parse_mode import ParseMode
|
||||||
|
@ -36,6 +36,7 @@ from queue import Queue
|
|||||||
from signal import signal, SIGINT, SIGTERM, SIGABRT
|
from signal import signal, SIGINT, SIGTERM, SIGABRT
|
||||||
from threading import Event, Thread
|
from threading import Event, Thread
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
from pyrogram.api import functions, types
|
from pyrogram.api import functions, types
|
||||||
from pyrogram.api.core import Object
|
from pyrogram.api.core import Object
|
||||||
from pyrogram.api.errors import (
|
from pyrogram.api.errors import (
|
||||||
@ -44,12 +45,15 @@ from pyrogram.api.errors import (
|
|||||||
PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded,
|
PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded,
|
||||||
PasswordHashInvalid, FloodWait, PeerIdInvalid, FilePartMissing,
|
PasswordHashInvalid, FloodWait, PeerIdInvalid, FilePartMissing,
|
||||||
ChatAdminRequired, FirstnameInvalid, PhoneNumberBanned,
|
ChatAdminRequired, FirstnameInvalid, PhoneNumberBanned,
|
||||||
VolumeLocNotFound, UserMigrate)
|
VolumeLocNotFound, UserMigrate, FileIdInvalid)
|
||||||
from pyrogram.crypto import AES
|
from pyrogram.crypto import AES
|
||||||
from pyrogram.session import Auth, Session
|
from pyrogram.session import Auth, Session
|
||||||
from pyrogram.session.internals import MsgId
|
from pyrogram.session.internals import MsgId
|
||||||
|
from . import message_parser
|
||||||
|
from .dispatcher import Dispatcher
|
||||||
from .input_media import InputMedia
|
from .input_media import InputMedia
|
||||||
from .style import Markdown, HTML
|
from .style import Markdown, HTML
|
||||||
|
from .utils import decode
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -130,6 +134,18 @@ class Client:
|
|||||||
UPDATES_WORKERS = 1
|
UPDATES_WORKERS = 1
|
||||||
DOWNLOAD_WORKERS = 1
|
DOWNLOAD_WORKERS = 1
|
||||||
|
|
||||||
|
MEDIA_TYPE_ID = {
|
||||||
|
0: "Thumbnail",
|
||||||
|
2: "Photo",
|
||||||
|
3: "Voice",
|
||||||
|
4: "Video",
|
||||||
|
5: "Document",
|
||||||
|
8: "Sticker",
|
||||||
|
9: "Audio",
|
||||||
|
10: "GIF",
|
||||||
|
13: "VideoNote"
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
session_name: str,
|
session_name: str,
|
||||||
api_id: int or str = None,
|
api_id: int or str = None,
|
||||||
@ -181,10 +197,62 @@ class Client:
|
|||||||
self.is_idle = None
|
self.is_idle = None
|
||||||
|
|
||||||
self.updates_queue = Queue()
|
self.updates_queue = Queue()
|
||||||
self.update_queue = Queue()
|
self.download_queue = Queue()
|
||||||
|
|
||||||
|
self.dispatcher = Dispatcher(self, workers)
|
||||||
self.update_handler = None
|
self.update_handler = None
|
||||||
|
|
||||||
self.download_queue = Queue()
|
def on_message(self, filters=None, group: int = 0):
|
||||||
|
"""Use this decorator to automatically register a function for handling
|
||||||
|
messages. This does the same thing as :meth:`add_handler` using the
|
||||||
|
MessageHandler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filters (:obj:`Filters <pyrogram.Filters>`):
|
||||||
|
Pass one or more filters to allow only a subset of messages to be passed
|
||||||
|
in your function.
|
||||||
|
|
||||||
|
group (``int``, optional):
|
||||||
|
The group identifier, defaults to 0.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func):
|
||||||
|
self.add_handler(pyrogram.MessageHandler(func, filters), group)
|
||||||
|
return func
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def on_raw_update(self, group: int = 0):
|
||||||
|
"""Use this decorator to automatically register a function for handling
|
||||||
|
raw updates. This does the same thing as :meth:`add_handler` using the
|
||||||
|
RawUpdateHandler.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
group (``int``, optional):
|
||||||
|
The group identifier, defaults to 0.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def decorator(func):
|
||||||
|
self.add_handler(pyrogram.RawUpdateHandler(func), group)
|
||||||
|
return func
|
||||||
|
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
def add_handler(self, handler, group: int = 0):
|
||||||
|
"""Use this method to register an event handler.
|
||||||
|
|
||||||
|
You can register multiple handlers, but at most one handler within a group
|
||||||
|
will be used for a single event. To handle the same event more than once, register
|
||||||
|
your handler using a different group id (lower group id == higher priority).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
handler (:obj:`Handler <pyrogram.handler.Handler>`):
|
||||||
|
The handler to be registered.
|
||||||
|
|
||||||
|
group (``int``, optional):
|
||||||
|
The group identifier, defaults to 0.
|
||||||
|
"""
|
||||||
|
self.dispatcher.add_handler(handler, group)
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
"""Use this method to start the Client after creating it.
|
"""Use this method to start the Client after creating it.
|
||||||
@ -232,12 +300,11 @@ class Client:
|
|||||||
for i in range(self.UPDATES_WORKERS):
|
for i in range(self.UPDATES_WORKERS):
|
||||||
Thread(target=self.updates_worker, name="UpdatesWorker#{}".format(i + 1)).start()
|
Thread(target=self.updates_worker, name="UpdatesWorker#{}".format(i + 1)).start()
|
||||||
|
|
||||||
for i in range(self.workers):
|
|
||||||
Thread(target=self.update_worker, name="UpdateWorker#{}".format(i + 1)).start()
|
|
||||||
|
|
||||||
for i in range(self.DOWNLOAD_WORKERS):
|
for i in range(self.DOWNLOAD_WORKERS):
|
||||||
Thread(target=self.download_worker, name="DownloadWorker#{}".format(i + 1)).start()
|
Thread(target=self.download_worker, name="DownloadWorker#{}".format(i + 1)).start()
|
||||||
|
|
||||||
|
self.dispatcher.start()
|
||||||
|
|
||||||
mimetypes.init()
|
mimetypes.init()
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
@ -253,12 +320,11 @@ class Client:
|
|||||||
for _ in range(self.UPDATES_WORKERS):
|
for _ in range(self.UPDATES_WORKERS):
|
||||||
self.updates_queue.put(None)
|
self.updates_queue.put(None)
|
||||||
|
|
||||||
for _ in range(self.workers):
|
|
||||||
self.update_queue.put(None)
|
|
||||||
|
|
||||||
for _ in range(self.DOWNLOAD_WORKERS):
|
for _ in range(self.DOWNLOAD_WORKERS):
|
||||||
self.download_queue.put(None)
|
self.download_queue.put(None)
|
||||||
|
|
||||||
|
self.dispatcher.stop()
|
||||||
|
|
||||||
def authorize_bot(self):
|
def authorize_bot(self):
|
||||||
try:
|
try:
|
||||||
r = self.send(
|
r = self.send(
|
||||||
@ -682,7 +748,7 @@ class Client:
|
|||||||
if len(self.channels_pts[channel_id]) > 50:
|
if len(self.channels_pts[channel_id]) > 50:
|
||||||
self.channels_pts[channel_id] = self.channels_pts[channel_id][25:]
|
self.channels_pts[channel_id] = self.channels_pts[channel_id][25:]
|
||||||
|
|
||||||
self.update_queue.put((update, updates.users, updates.chats))
|
self.dispatcher.updates.put((update, updates.users, updates.chats))
|
||||||
elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)):
|
elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)):
|
||||||
diff = self.send(
|
diff = self.send(
|
||||||
functions.updates.GetDifference(
|
functions.updates.GetDifference(
|
||||||
@ -692,7 +758,7 @@ class Client:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.update_queue.put((
|
self.dispatcher.updates.put((
|
||||||
types.UpdateNewMessage(
|
types.UpdateNewMessage(
|
||||||
message=diff.new_messages[0],
|
message=diff.new_messages[0],
|
||||||
pts=updates.pts,
|
pts=updates.pts,
|
||||||
@ -702,30 +768,7 @@ class Client:
|
|||||||
diff.chats
|
diff.chats
|
||||||
))
|
))
|
||||||
elif isinstance(updates, types.UpdateShort):
|
elif isinstance(updates, types.UpdateShort):
|
||||||
self.update_queue.put((updates.update, [], []))
|
self.dispatcher.updates.put((updates.update, [], []))
|
||||||
except Exception as e:
|
|
||||||
log.error(e, exc_info=True)
|
|
||||||
|
|
||||||
log.debug("{} stopped".format(name))
|
|
||||||
|
|
||||||
def update_worker(self):
|
|
||||||
name = threading.current_thread().name
|
|
||||||
log.debug("{} started".format(name))
|
|
||||||
|
|
||||||
while True:
|
|
||||||
update = self.update_queue.get()
|
|
||||||
|
|
||||||
if update is None:
|
|
||||||
break
|
|
||||||
|
|
||||||
try:
|
|
||||||
if self.update_handler:
|
|
||||||
self.update_handler(
|
|
||||||
self,
|
|
||||||
update[0],
|
|
||||||
{i.id: i for i in update[1]},
|
|
||||||
{i.id: i for i in update[2]}
|
|
||||||
)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(e, exc_info=True)
|
log.error(e, exc_info=True)
|
||||||
|
|
||||||
@ -752,47 +795,6 @@ class Client:
|
|||||||
while self.is_idle:
|
while self.is_idle:
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
def set_update_handler(self, callback: callable):
|
|
||||||
"""Use this method to set the update handler.
|
|
||||||
|
|
||||||
You must call this method *before* you *start()* the Client.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
callback (``callable``):
|
|
||||||
A function that will be called when a new update is received from the server. It takes
|
|
||||||
*(client, update, users, chats)* as positional arguments (Look at the section below for
|
|
||||||
a detailed description).
|
|
||||||
|
|
||||||
Other Parameters:
|
|
||||||
client (:class:`Client <pyrogram.Client>`):
|
|
||||||
The Client itself, useful when you want to call other API methods inside the update handler.
|
|
||||||
|
|
||||||
update (``Update``):
|
|
||||||
The received update, which can be one of the many single Updates listed in the *updates*
|
|
||||||
field you see in the :obj:`Update <pyrogram.api.types.Update>` type.
|
|
||||||
|
|
||||||
users (``dict``):
|
|
||||||
Dictionary of all :obj:`User <pyrogram.api.types.User>` mentioned in the update.
|
|
||||||
You can access extra info about the user (such as *first_name*, *last_name*, etc...) by using
|
|
||||||
the IDs you find in the *update* argument (e.g.: *users[1768841572]*).
|
|
||||||
|
|
||||||
chats (``dict``):
|
|
||||||
Dictionary of all :obj:`Chat <pyrogram.api.types.Chat>` and
|
|
||||||
:obj:`Channel <pyrogram.api.types.Channel>` mentioned in the update.
|
|
||||||
You can access extra info about the chat (such as *title*, *participants_count*, etc...)
|
|
||||||
by using the IDs you find in the *update* argument (e.g.: *chats[1701277281]*).
|
|
||||||
|
|
||||||
Note:
|
|
||||||
The following Empty or Forbidden types may exist inside the *users* and *chats* dictionaries.
|
|
||||||
They mean you have been blocked by the user or banned from the group/channel.
|
|
||||||
|
|
||||||
- :obj:`UserEmpty <pyrogram.api.types.UserEmpty>`
|
|
||||||
- :obj:`ChatEmpty <pyrogram.api.types.ChatEmpty>`
|
|
||||||
- :obj:`ChatForbidden <pyrogram.api.types.ChatForbidden>`
|
|
||||||
- :obj:`ChannelForbidden <pyrogram.api.types.ChannelForbidden>`
|
|
||||||
"""
|
|
||||||
self.update_handler = callback
|
|
||||||
|
|
||||||
def send(self, data: Object):
|
def send(self, data: Object):
|
||||||
"""Use this method to send Raw Function queries.
|
"""Use this method to send Raw Function queries.
|
||||||
|
|
||||||
@ -1122,7 +1124,9 @@ class Client:
|
|||||||
|
|
||||||
photo (``str``):
|
photo (``str``):
|
||||||
Photo to send.
|
Photo to send.
|
||||||
Pass a file path as string to send a photo that exists on your local machine.
|
Pass a file_id as string to send a photo that exists on the Telegram servers,
|
||||||
|
pass an HTTP URL as a string for Telegram to get a photo from the Internet, or
|
||||||
|
pass a file path as string to upload a new photo that exists on your local machine.
|
||||||
|
|
||||||
caption (``bool``, optional):
|
caption (``bool``, optional):
|
||||||
Photo caption, 0-200 characters.
|
Photo caption, 0-200 characters.
|
||||||
@ -1156,23 +1160,55 @@ class Client:
|
|||||||
The size of the file.
|
The size of the file.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
On success, the sent Message is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>`
|
||||||
"""
|
"""
|
||||||
|
file = None
|
||||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||||
|
|
||||||
|
if os.path.exists(photo):
|
||||||
file = self.save_file(photo, progress=progress)
|
file = self.save_file(photo, progress=progress)
|
||||||
|
media = types.InputMediaUploadedPhoto(
|
||||||
|
file=file,
|
||||||
|
ttl_seconds=ttl_seconds
|
||||||
|
)
|
||||||
|
elif photo.startswith("http"):
|
||||||
|
media = types.InputMediaPhotoExternal(
|
||||||
|
url=photo,
|
||||||
|
ttl_seconds=ttl_seconds
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
decoded = decode(photo)
|
||||||
|
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
||||||
|
unpacked = struct.unpack(fmt, decoded)
|
||||||
|
except (AssertionError, binascii.Error, struct.error):
|
||||||
|
raise FileIdInvalid from None
|
||||||
|
else:
|
||||||
|
if unpacked[0] != 2:
|
||||||
|
media_type = Client.MEDIA_TYPE_ID.get(unpacked[0], None)
|
||||||
|
|
||||||
|
if media_type:
|
||||||
|
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
||||||
|
else:
|
||||||
|
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
||||||
|
|
||||||
|
media = types.InputMediaPhoto(
|
||||||
|
id=types.InputPhoto(
|
||||||
|
id=unpacked[2],
|
||||||
|
access_hash=unpacked[3]
|
||||||
|
),
|
||||||
|
ttl_seconds=ttl_seconds
|
||||||
|
)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
r = self.send(
|
r = self.send(
|
||||||
functions.messages.SendMedia(
|
functions.messages.SendMedia(
|
||||||
peer=self.resolve_peer(chat_id),
|
peer=self.resolve_peer(chat_id),
|
||||||
media=types.InputMediaUploadedPhoto(
|
media=media,
|
||||||
file=file,
|
|
||||||
ttl_seconds=ttl_seconds
|
|
||||||
),
|
|
||||||
silent=disable_notification or None,
|
silent=disable_notification or None,
|
||||||
reply_to_msg_id=reply_to_message_id,
|
reply_to_msg_id=reply_to_message_id,
|
||||||
random_id=self.rnd_id(),
|
random_id=self.rnd_id(),
|
||||||
@ -1182,7 +1218,12 @@ class Client:
|
|||||||
except FilePartMissing as e:
|
except FilePartMissing as e:
|
||||||
self.save_file(photo, file_id=file.id, file_part=e.x)
|
self.save_file(photo, file_id=file.id, file_part=e.x)
|
||||||
else:
|
else:
|
||||||
return r
|
for i in r.updates:
|
||||||
|
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
||||||
|
users = {i.id: i for i in r.users}
|
||||||
|
chats = {i.id: i for i in r.chats}
|
||||||
|
|
||||||
|
return message_parser.parse_message(self, i.message, users, chats)
|
||||||
|
|
||||||
def send_audio(self,
|
def send_audio(self,
|
||||||
chat_id: int or str,
|
chat_id: int or str,
|
||||||
@ -1208,7 +1249,9 @@ class Client:
|
|||||||
|
|
||||||
audio (``str``):
|
audio (``str``):
|
||||||
Audio file to send.
|
Audio file to send.
|
||||||
Pass a file path as string to send an audio file that exists on your local machine.
|
Pass a file_id as string to send an audio file that exists on the Telegram servers,
|
||||||
|
pass an HTTP URL as a string for Telegram to get an audio file from the Internet, or
|
||||||
|
pass a file path as string to upload a new audio file that exists on your local machine.
|
||||||
|
|
||||||
caption (``str``, optional):
|
caption (``str``, optional):
|
||||||
Audio caption, 0-200 characters.
|
Audio caption, 0-200 characters.
|
||||||
@ -1246,20 +1289,17 @@ class Client:
|
|||||||
The size of the file.
|
The size of the file.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
On success, the sent Message is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>`
|
||||||
"""
|
"""
|
||||||
|
file = None
|
||||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||||
file = self.save_file(audio, progress=progress)
|
|
||||||
|
|
||||||
while True:
|
if os.path.exists(audio):
|
||||||
try:
|
file = self.save_file(audio, progress=progress)
|
||||||
r = self.send(
|
media = types.InputMediaUploadedDocument(
|
||||||
functions.messages.SendMedia(
|
|
||||||
peer=self.resolve_peer(chat_id),
|
|
||||||
media=types.InputMediaUploadedDocument(
|
|
||||||
mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"),
|
mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"),
|
||||||
file=file,
|
file=file,
|
||||||
attributes=[
|
attributes=[
|
||||||
@ -1270,7 +1310,40 @@ class Client:
|
|||||||
),
|
),
|
||||||
types.DocumentAttributeFilename(os.path.basename(audio))
|
types.DocumentAttributeFilename(os.path.basename(audio))
|
||||||
]
|
]
|
||||||
),
|
)
|
||||||
|
elif audio.startswith("http"):
|
||||||
|
media = types.InputMediaDocumentExternal(
|
||||||
|
url=audio
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
decoded = decode(audio)
|
||||||
|
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
||||||
|
unpacked = struct.unpack(fmt, decoded)
|
||||||
|
except (AssertionError, binascii.Error, struct.error):
|
||||||
|
raise FileIdInvalid from None
|
||||||
|
else:
|
||||||
|
if unpacked[0] != 9:
|
||||||
|
media_type = Client.MEDIA_TYPE_ID.get(unpacked[0], None)
|
||||||
|
|
||||||
|
if media_type:
|
||||||
|
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
||||||
|
else:
|
||||||
|
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
||||||
|
|
||||||
|
media = types.InputMediaDocument(
|
||||||
|
id=types.InputDocument(
|
||||||
|
id=unpacked[2],
|
||||||
|
access_hash=unpacked[3]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
r = self.send(
|
||||||
|
functions.messages.SendMedia(
|
||||||
|
peer=self.resolve_peer(chat_id),
|
||||||
|
media=media,
|
||||||
silent=disable_notification or None,
|
silent=disable_notification or None,
|
||||||
reply_to_msg_id=reply_to_message_id,
|
reply_to_msg_id=reply_to_message_id,
|
||||||
random_id=self.rnd_id(),
|
random_id=self.rnd_id(),
|
||||||
@ -1280,7 +1353,12 @@ class Client:
|
|||||||
except FilePartMissing as e:
|
except FilePartMissing as e:
|
||||||
self.save_file(audio, file_id=file.id, file_part=e.x)
|
self.save_file(audio, file_id=file.id, file_part=e.x)
|
||||||
else:
|
else:
|
||||||
return r
|
for i in r.updates:
|
||||||
|
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
||||||
|
users = {i.id: i for i in r.users}
|
||||||
|
chats = {i.id: i for i in r.chats}
|
||||||
|
|
||||||
|
return message_parser.parse_message(self, i.message, users, chats)
|
||||||
|
|
||||||
def send_document(self,
|
def send_document(self,
|
||||||
chat_id: int or str,
|
chat_id: int or str,
|
||||||
|
19
pyrogram/client/dispatcher/__init__.py
Normal file
19
pyrogram/client/dispatcher/__init__.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from .dispatcher import Dispatcher
|
148
pyrogram/client/dispatcher/dispatcher.py
Normal file
148
pyrogram/client/dispatcher/dispatcher.py
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import threading
|
||||||
|
from collections import OrderedDict
|
||||||
|
from queue import Queue
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
from pyrogram.api import types
|
||||||
|
from .. import message_parser
|
||||||
|
from ..handlers import RawUpdateHandler
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Dispatcher:
|
||||||
|
MESSAGE_UPDATES = (
|
||||||
|
types.UpdateNewMessage,
|
||||||
|
types.UpdateNewChannelMessage
|
||||||
|
)
|
||||||
|
|
||||||
|
EDIT_UPDATES = (
|
||||||
|
types.UpdateEditMessage,
|
||||||
|
types.UpdateEditChannelMessage
|
||||||
|
)
|
||||||
|
|
||||||
|
ALLOWED_UPDATES = MESSAGE_UPDATES + EDIT_UPDATES
|
||||||
|
|
||||||
|
def __init__(self, client, workers):
|
||||||
|
self.client = client
|
||||||
|
self.workers = workers
|
||||||
|
self.updates = Queue()
|
||||||
|
self.groups = OrderedDict()
|
||||||
|
|
||||||
|
def start(self):
|
||||||
|
for i in range(self.workers):
|
||||||
|
Thread(
|
||||||
|
target=self.update_worker,
|
||||||
|
name="UpdateWorker#{}".format(i + 1)
|
||||||
|
).start()
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
for _ in range(self.workers):
|
||||||
|
self.updates.put(None)
|
||||||
|
|
||||||
|
def add_handler(self, handler, group: int):
|
||||||
|
if group not in self.groups:
|
||||||
|
self.groups[group] = []
|
||||||
|
self.groups = OrderedDict(sorted(self.groups.items()))
|
||||||
|
|
||||||
|
self.groups[group].append(handler)
|
||||||
|
|
||||||
|
def dispatch(self, update, users: dict = None, chats: dict = None, is_raw: bool = False):
|
||||||
|
for group in self.groups.values():
|
||||||
|
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)
|
||||||
|
|
||||||
|
if not handler.check(message):
|
||||||
|
continue
|
||||||
|
|
||||||
|
args = (self.client, message)
|
||||||
|
|
||||||
|
handler.callback(*args)
|
||||||
|
break
|
||||||
|
|
||||||
|
def update_worker(self):
|
||||||
|
name = threading.current_thread().name
|
||||||
|
log.debug("{} started".format(name))
|
||||||
|
|
||||||
|
while True:
|
||||||
|
update = self.updates.get()
|
||||||
|
|
||||||
|
if update is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
try:
|
||||||
|
users = {i.id: i for i in update[1]}
|
||||||
|
chats = {i.id: i for i in update[2]}
|
||||||
|
update = update[0]
|
||||||
|
|
||||||
|
self.dispatch(update, users=users, chats=chats, is_raw=True)
|
||||||
|
|
||||||
|
if isinstance(update, Dispatcher.ALLOWED_UPDATES):
|
||||||
|
if isinstance(update.message, types.Message):
|
||||||
|
parser = message_parser.parse_message
|
||||||
|
elif isinstance(update.message, types.MessageService):
|
||||||
|
parser = message_parser.parse_message_service
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
message = parser(
|
||||||
|
self.client,
|
||||||
|
update.message,
|
||||||
|
users,
|
||||||
|
chats
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
is_edited_message = isinstance(update, Dispatcher.EDIT_UPDATES)
|
||||||
|
|
||||||
|
self.dispatch(
|
||||||
|
pyrogram.Update(
|
||||||
|
update_id=0,
|
||||||
|
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)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
log.error(e, exc_info=True)
|
||||||
|
|
||||||
|
log.debug("{} stopped".format(name))
|
19
pyrogram/client/filters/__init__.py
Normal file
19
pyrogram/client/filters/__init__.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from .filters import Filters
|
57
pyrogram/client/filters/filter.py
Normal file
57
pyrogram/client/filters/filter.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
class Filter:
|
||||||
|
def __call__(self, message):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def __invert__(self):
|
||||||
|
return InvertFilter(self)
|
||||||
|
|
||||||
|
def __and__(self, other):
|
||||||
|
return AndFilter(self, other)
|
||||||
|
|
||||||
|
def __or__(self, other):
|
||||||
|
return OrFilter(self, other)
|
||||||
|
|
||||||
|
|
||||||
|
class InvertFilter(Filter):
|
||||||
|
def __init__(self, base):
|
||||||
|
self.base = base
|
||||||
|
|
||||||
|
def __call__(self, message):
|
||||||
|
return not self.base(message)
|
||||||
|
|
||||||
|
|
||||||
|
class AndFilter(Filter):
|
||||||
|
def __init__(self, base, other):
|
||||||
|
self.base = base
|
||||||
|
self.other = other
|
||||||
|
|
||||||
|
def __call__(self, message):
|
||||||
|
return self.base(message) and self.other(message)
|
||||||
|
|
||||||
|
|
||||||
|
class OrFilter(Filter):
|
||||||
|
def __init__(self, base, other):
|
||||||
|
self.base = base
|
||||||
|
self.other = other
|
||||||
|
|
||||||
|
def __call__(self, message):
|
||||||
|
return self.base(message) or self.other(message)
|
218
pyrogram/client/filters/filters.py
Normal file
218
pyrogram/client/filters/filters.py
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .filter import Filter
|
||||||
|
|
||||||
|
|
||||||
|
def build(name: str, func: callable, **kwargs) -> type:
|
||||||
|
d = {"__call__": func}
|
||||||
|
d.update(kwargs)
|
||||||
|
|
||||||
|
return type(name, (Filter,), d)()
|
||||||
|
|
||||||
|
|
||||||
|
class Filters:
|
||||||
|
"""This class provides access to all the Filters available in Pyrogram.
|
||||||
|
It is intended to be used when adding an handler."""
|
||||||
|
|
||||||
|
text = build("Text", lambda _, m: bool(m.text and not m.text.startswith("/")))
|
||||||
|
"""Filter text messages."""
|
||||||
|
|
||||||
|
reply = build("Reply", lambda _, m: bool(m.reply_to_message))
|
||||||
|
"""Filter messages that are replies to other messages."""
|
||||||
|
|
||||||
|
forwarded = build("Forwarded", lambda _, m: bool(m.forward_date))
|
||||||
|
"""Filter messages that are forwarded."""
|
||||||
|
|
||||||
|
caption = build("Caption", lambda _, m: bool(m.caption))
|
||||||
|
"""Filter media messages that contain captions."""
|
||||||
|
|
||||||
|
edited = build("Edited", lambda _, m: bool(m.edit_date))
|
||||||
|
"""Filter edited messages."""
|
||||||
|
|
||||||
|
audio = build("Audio", lambda _, m: bool(m.audio))
|
||||||
|
"""Filter messages that contain an :obj:`Audio <pyrogram.Audio>`."""
|
||||||
|
|
||||||
|
document = build("Document", lambda _, m: bool(m.document))
|
||||||
|
"""Filter messages that contain a :obj:`Document <pyrogram.Document>`."""
|
||||||
|
|
||||||
|
photo = build("Photo", lambda _, m: bool(m.photo))
|
||||||
|
"""Filter messages that contain a :obj:`Photo <pyrogram.Photo>`."""
|
||||||
|
|
||||||
|
sticker = build("Sticker", lambda _, m: bool(m.sticker))
|
||||||
|
"""Filter messages that contain a :obj:`Sticker <pyrogram.Sticker>`."""
|
||||||
|
|
||||||
|
video = build("Video", lambda _, m: bool(m.video))
|
||||||
|
"""Filter messages that contain a :obj:`Video <pyrogram.Video>`."""
|
||||||
|
|
||||||
|
voice = build("Voice", lambda _, m: bool(m.voice))
|
||||||
|
"""Filter messages that contain a :obj:`Voice <pyrogram.Voice>` note."""
|
||||||
|
|
||||||
|
video_note = build("Voice", lambda _, m: bool(m.video_note))
|
||||||
|
"""Filter messages that contain a :obj:`VideoNote <pyrogram.VideoNote>`."""
|
||||||
|
|
||||||
|
contact = build("Contact", lambda _, m: bool(m.contact))
|
||||||
|
"""Filter messages that contain a :obj:`Contact <pyrogram.Contact>`."""
|
||||||
|
|
||||||
|
location = build("Location", lambda _, m: bool(m.location))
|
||||||
|
"""Filter messages that contain a :obj:`Location <pyrogram.Location>`."""
|
||||||
|
|
||||||
|
venue = build("Venue", lambda _, m: bool(m.venue))
|
||||||
|
"""Filter messages that contain a :obj:`Venue <pyrogram.Venue>`."""
|
||||||
|
|
||||||
|
private = build("Private", lambda _, m: bool(m.chat.type == "private"))
|
||||||
|
"""Filter messages sent in private chats."""
|
||||||
|
|
||||||
|
group = build("Group", lambda _, m: bool(m.chat.type in {"group", "supergroup"}))
|
||||||
|
"""Filter messages sent in group or supergroup chats."""
|
||||||
|
|
||||||
|
channel = build("Channel", lambda _, m: bool(m.chat.type == "channel"))
|
||||||
|
"""Filter messages sent in channels."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def command(command: str or list):
|
||||||
|
"""Filter commands, i.e.: text messages starting with '/'.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command (``str`` | ``list``):
|
||||||
|
The command or list of commands as strings the filter should look for.
|
||||||
|
"""
|
||||||
|
return build(
|
||||||
|
"Command",
|
||||||
|
lambda _, m: bool(
|
||||||
|
m.text
|
||||||
|
and m.text.startswith("/")
|
||||||
|
and (m.text[1:].split()[0] in _.c)
|
||||||
|
),
|
||||||
|
c=(
|
||||||
|
{command}
|
||||||
|
if not isinstance(command, list)
|
||||||
|
else {c for c in command}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def regex(pattern, flags: int = 0):
|
||||||
|
"""Filter messages that match a given RegEx pattern.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
pattern (``str``):
|
||||||
|
The RegEx pattern.
|
||||||
|
|
||||||
|
flags (``int``, optional):
|
||||||
|
RegEx flags.
|
||||||
|
"""
|
||||||
|
return build(
|
||||||
|
"Regex", lambda _, m: bool(_.p.search(m.text or "")),
|
||||||
|
p=re.compile(pattern, flags)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def user(user: int or str or list):
|
||||||
|
"""Filter messages coming from specific users.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
user (``int`` | ``str`` | ``list``):
|
||||||
|
The user or list of user IDs (int) or usernames (str) the filter should look for.
|
||||||
|
"""
|
||||||
|
return build(
|
||||||
|
"User",
|
||||||
|
lambda _, m: bool(m.from_user
|
||||||
|
and (m.from_user.id in _.u
|
||||||
|
or (m.from_user.username
|
||||||
|
and m.from_user.username.lower() in _.u))),
|
||||||
|
u=(
|
||||||
|
{user.lower().strip("@") if type(user) is str else user}
|
||||||
|
if not isinstance(user, list)
|
||||||
|
else {i.lower().strip("@") if type(i) is str else i for i in user}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def chat(chat: int or str or list):
|
||||||
|
"""Filter messages coming from specific chats.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat (``int`` | ``str`` | ``list``):
|
||||||
|
The chat or list of chat IDs (int) or usernames (str) the filter should look for.
|
||||||
|
"""
|
||||||
|
return build(
|
||||||
|
"Chat",
|
||||||
|
lambda _, m: bool(m.chat
|
||||||
|
and (m.chat.id in _.c
|
||||||
|
or (m.chat.username
|
||||||
|
and m.chat.username.lower() in _.c))),
|
||||||
|
c=(
|
||||||
|
{chat.lower().strip("@") if type(chat) is str else chat}
|
||||||
|
if not isinstance(chat, list)
|
||||||
|
else {i.lower().strip("@") if type(i) is str else i for i in chat}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
new_chat_members = build("NewChatMembers", lambda _, m: bool(m.new_chat_members))
|
||||||
|
"""Filter service messages for new chat members."""
|
||||||
|
|
||||||
|
left_chat_member = build("LeftChatMember", lambda _, m: bool(m.left_chat_member))
|
||||||
|
"""Filter service messages for members that left the chat."""
|
||||||
|
|
||||||
|
new_chat_title = build("NewChatTitle", lambda _, m: bool(m.new_chat_title))
|
||||||
|
"""Filter service messages for new chat titles."""
|
||||||
|
|
||||||
|
new_chat_photo = build("NewChatPhoto", lambda _, m: bool(m.new_chat_photo))
|
||||||
|
"""Filter service messages for new chat photos."""
|
||||||
|
|
||||||
|
delete_chat_photo = build("DeleteChatPhoto", lambda _, m: bool(m.delete_chat_photo))
|
||||||
|
"""Filter service messages for deleted photos."""
|
||||||
|
|
||||||
|
group_chat_created = build("GroupChatCreated", lambda _, m: bool(m.group_chat_created))
|
||||||
|
"""Filter service messages for group chat creations."""
|
||||||
|
|
||||||
|
supergroup_chat_created = build("SupergroupChatCreated", lambda _, m: bool(m.supergroup_chat_created))
|
||||||
|
"""Filter service messages for supergroup chat creations."""
|
||||||
|
|
||||||
|
channel_chat_created = build("ChannelChatCreated", lambda _, m: bool(m.channel_chat_created))
|
||||||
|
"""Filter service messages for channel chat creations."""
|
||||||
|
|
||||||
|
migrate_to_chat_id = build("MigrateToChatId", lambda _, m: bool(m.migrate_to_chat_id))
|
||||||
|
"""Filter service messages that contain migrate_to_chat_id."""
|
||||||
|
|
||||||
|
migrate_from_chat_id = build("MigrateFromChatId", lambda _, m: bool(m.migrate_from_chat_id))
|
||||||
|
"""Filter service messages that contain migrate_from_chat_id."""
|
||||||
|
|
||||||
|
pinned_message = build("PinnedMessage", lambda _, m: bool(m.pinned_message))
|
||||||
|
"""Filter service messages for pinned messages."""
|
||||||
|
|
||||||
|
service = build(
|
||||||
|
"Service",
|
||||||
|
lambda _, m: bool(
|
||||||
|
_.new_chat_members(m)
|
||||||
|
or _.left_chat_member(m)
|
||||||
|
or _.new_chat_title(m)
|
||||||
|
or _.new_chat_photo(m)
|
||||||
|
or _.delete_chat_photo(m)
|
||||||
|
or _.group_chat_created(m)
|
||||||
|
or _.supergroup_chat_created(m)
|
||||||
|
or _.channel_chat_created(m)
|
||||||
|
or _.migrate_to_chat_id(m)
|
||||||
|
or _.migrate_from_chat_id(m)
|
||||||
|
or _.pinned_m(m)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
"""Filter all service messages"""
|
19
pyrogram/client/handlers/__init__.py
Normal file
19
pyrogram/client/handlers/__init__.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from .handlers import MessageHandler, RawUpdateHandler
|
23
pyrogram/client/handlers/handler.py
Normal file
23
pyrogram/client/handlers/handler.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
class Handler:
|
||||||
|
def __init__(self, callback: callable, filters=None):
|
||||||
|
self.callback = callback
|
||||||
|
self.filters = filters
|
93
pyrogram/client/handlers/handlers.py
Normal file
93
pyrogram/client/handlers/handlers.py
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from .handler import Handler
|
||||||
|
|
||||||
|
|
||||||
|
class MessageHandler(Handler):
|
||||||
|
"""The Message handler class. It is used to handle text, media and service messages coming from
|
||||||
|
any chat (private, group, channel).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
callback (``callable``):
|
||||||
|
Pass a function that will be called when a new Message arrives. It takes *(client, message)*
|
||||||
|
as positional arguments (look at the section below for a detailed description).
|
||||||
|
|
||||||
|
filters (:obj:`Filters <pyrogram.Filters>`):
|
||||||
|
Pass one or more filters to allow only a subset of messages to be passed
|
||||||
|
in your callback function.
|
||||||
|
|
||||||
|
Other parameters:
|
||||||
|
client (:obj:`Client <pyrogram.Client>`):
|
||||||
|
The Client itself, useful when you want to call other API methods inside the message handler.
|
||||||
|
|
||||||
|
message (:obj:`Message <pyrogram.Message>`):
|
||||||
|
The received message.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, callback: callable, filters=None):
|
||||||
|
super().__init__(callback, filters)
|
||||||
|
|
||||||
|
def check(self, message):
|
||||||
|
return (
|
||||||
|
self.filters(message)
|
||||||
|
if self.filters
|
||||||
|
else True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class RawUpdateHandler(Handler):
|
||||||
|
"""The Raw Update handler class. It is used to handle raw updates.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
callback (``callable``):
|
||||||
|
A function that will be called when a new update is received from the server. It takes
|
||||||
|
*(client, update, users, chats)* as positional arguments (look at the section below for
|
||||||
|
a detailed description).
|
||||||
|
|
||||||
|
Other Parameters:
|
||||||
|
client (:class:`Client <pyrogram.Client>`):
|
||||||
|
The Client itself, useful when you want to call other API methods inside the update handler.
|
||||||
|
|
||||||
|
update (``Update``):
|
||||||
|
The received update, which can be one of the many single Updates listed in the *updates*
|
||||||
|
field you see in the :obj:`Update <pyrogram.api.types.Update>` type.
|
||||||
|
|
||||||
|
users (``dict``):
|
||||||
|
Dictionary of all :obj:`User <pyrogram.api.types.User>` mentioned in the update.
|
||||||
|
You can access extra info about the user (such as *first_name*, *last_name*, etc...) by using
|
||||||
|
the IDs you find in the *update* argument (e.g.: *users[1768841572]*).
|
||||||
|
|
||||||
|
chats (``dict``):
|
||||||
|
Dictionary of all :obj:`Chat <pyrogram.api.types.Chat>` and
|
||||||
|
:obj:`Channel <pyrogram.api.types.Channel>` mentioned in the update.
|
||||||
|
You can access extra info about the chat (such as *title*, *participants_count*, etc...)
|
||||||
|
by using the IDs you find in the *update* argument (e.g.: *chats[1701277281]*).
|
||||||
|
|
||||||
|
Note:
|
||||||
|
The following Empty or Forbidden types may exist inside the *users* and *chats* dictionaries.
|
||||||
|
They mean you have been blocked by the user or banned from the group/channel.
|
||||||
|
|
||||||
|
- :obj:`UserEmpty <pyrogram.api.types.UserEmpty>`
|
||||||
|
- :obj:`ChatEmpty <pyrogram.api.types.ChatEmpty>`
|
||||||
|
- :obj:`ChatForbidden <pyrogram.api.types.ChatForbidden>`
|
||||||
|
- :obj:`ChannelForbidden <pyrogram.api.types.ChannelForbidden>`
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, callback: callable):
|
||||||
|
super().__init__(callback)
|
44
pyrogram/client/input_media_photo.py
Normal file
44
pyrogram/client/input_media_photo.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
class InputMediaPhoto:
|
||||||
|
"""This object represents a photo to be sent inside an album.
|
||||||
|
It is intended to be used with :obj:`send_media_group <pyrogram.Client.send_media_group>`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
media (:obj:`str`):
|
||||||
|
Photo file to send.
|
||||||
|
Pass a file path as string to send a photo that exists on your local machine.
|
||||||
|
|
||||||
|
caption (:obj:`str`):
|
||||||
|
Caption of the photo to be sent, 0-200 characters
|
||||||
|
|
||||||
|
parse_mode (:obj:`str`):
|
||||||
|
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 caption.
|
||||||
|
Defaults to Markdown.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
media: str,
|
||||||
|
caption: str = "",
|
||||||
|
parse_mode: str = ""):
|
||||||
|
self.media = media
|
||||||
|
self.caption = caption
|
||||||
|
self.parse_mode = parse_mode
|
64
pyrogram/client/input_media_video.py
Normal file
64
pyrogram/client/input_media_video.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
class InputMediaVideo:
|
||||||
|
"""This object represents a video to be sent inside an album.
|
||||||
|
It is intended to be used with :obj:`send_media_group <pyrogram.Client.send_media_group>`.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
media (:obj:`str`):
|
||||||
|
Video file to send.
|
||||||
|
Pass a file path as string to send a video that exists on your local machine.
|
||||||
|
|
||||||
|
caption (:obj:`str`, optional):
|
||||||
|
Caption of the video to be sent, 0-200 characters
|
||||||
|
|
||||||
|
parse_mode (:obj:`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 caption.
|
||||||
|
Defaults to Markdown.
|
||||||
|
|
||||||
|
width (:obj:`int`, optional):
|
||||||
|
Video width.
|
||||||
|
|
||||||
|
height (:obj:`int`, optional):
|
||||||
|
Video height.
|
||||||
|
|
||||||
|
duration (:obj:`int`, optional):
|
||||||
|
Video duration.
|
||||||
|
|
||||||
|
supports_streaming (:obj:`bool`, optional):
|
||||||
|
Pass True, if the uploaded video is suitable for streaming.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
media: str,
|
||||||
|
caption: str = "",
|
||||||
|
parse_mode: str = "",
|
||||||
|
width: int = 0,
|
||||||
|
height: int = 0,
|
||||||
|
duration: int = 0,
|
||||||
|
supports_streaming: bool = True):
|
||||||
|
self.media = media
|
||||||
|
self.caption = caption
|
||||||
|
self.parse_mode = parse_mode
|
||||||
|
self.width = width
|
||||||
|
self.height = height
|
||||||
|
self.duration = duration
|
||||||
|
self.supports_streaming = supports_streaming
|
539
pyrogram/client/message_parser.py
Normal file
539
pyrogram/client/message_parser.py
Normal file
@ -0,0 +1,539 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from struct import pack
|
||||||
|
|
||||||
|
import pyrogram
|
||||||
|
from pyrogram.api import types
|
||||||
|
from .utils import encode
|
||||||
|
|
||||||
|
# TODO: Organize the code better?
|
||||||
|
|
||||||
|
ENTITIES = {
|
||||||
|
types.MessageEntityMention.ID: "mention",
|
||||||
|
types.MessageEntityHashtag.ID: "hashtag",
|
||||||
|
types.MessageEntityBotCommand.ID: "bot_command",
|
||||||
|
types.MessageEntityUrl.ID: "url",
|
||||||
|
types.MessageEntityEmail.ID: "email",
|
||||||
|
types.MessageEntityBold.ID: "bold",
|
||||||
|
types.MessageEntityItalic.ID: "italic",
|
||||||
|
types.MessageEntityCode.ID: "code",
|
||||||
|
types.MessageEntityPre.ID: "pre",
|
||||||
|
types.MessageEntityTextUrl.ID: "text_link",
|
||||||
|
types.MessageEntityMentionName.ID: "text_mention"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_entities(entities: list, users: dict) -> list:
|
||||||
|
output_entities = []
|
||||||
|
|
||||||
|
for entity in entities:
|
||||||
|
entity_type = ENTITIES.get(entity.ID, None)
|
||||||
|
|
||||||
|
if entity_type:
|
||||||
|
output_entities.append(pyrogram.MessageEntity(
|
||||||
|
type=entity_type,
|
||||||
|
offset=entity.offset,
|
||||||
|
length=entity.length,
|
||||||
|
url=getattr(entity, "url", None),
|
||||||
|
user=parse_user(
|
||||||
|
users.get(
|
||||||
|
getattr(entity, "user_id", None),
|
||||||
|
None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
))
|
||||||
|
|
||||||
|
return output_entities
|
||||||
|
|
||||||
|
|
||||||
|
def parse_user(user: types.User) -> pyrogram.User or None:
|
||||||
|
return pyrogram.User(
|
||||||
|
id=user.id,
|
||||||
|
is_bot=user.bot,
|
||||||
|
first_name=user.first_name,
|
||||||
|
last_name=user.last_name,
|
||||||
|
username=user.username,
|
||||||
|
language_code=user.lang_code,
|
||||||
|
phone_number=user.phone
|
||||||
|
) if user else None
|
||||||
|
|
||||||
|
|
||||||
|
def parse_chat(message: types.Message, users: dict, chats: dict) -> pyrogram.Chat:
|
||||||
|
if isinstance(message.to_id, types.PeerUser):
|
||||||
|
return parse_user_chat(users[message.to_id.user_id if message.out else message.from_id])
|
||||||
|
elif isinstance(message.to_id, types.PeerChat):
|
||||||
|
return parse_chat_chat(chats[message.to_id.chat_id])
|
||||||
|
else:
|
||||||
|
return parse_channel_chat(chats[message.to_id.channel_id])
|
||||||
|
|
||||||
|
|
||||||
|
def parse_user_chat(user: types.User) -> pyrogram.Chat:
|
||||||
|
return pyrogram.Chat(
|
||||||
|
id=user.id,
|
||||||
|
type="private",
|
||||||
|
username=user.username,
|
||||||
|
first_name=user.first_name,
|
||||||
|
last_name=user.last_name
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_chat_chat(chat: types.Chat) -> pyrogram.Chat:
|
||||||
|
return pyrogram.Chat(
|
||||||
|
id=-chat.id,
|
||||||
|
type="group",
|
||||||
|
title=chat.title,
|
||||||
|
all_members_are_administrators=not chat.admins_enabled
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_channel_chat(channel: types.Channel) -> pyrogram.Chat:
|
||||||
|
return pyrogram.Chat(
|
||||||
|
id=int("-100" + str(channel.id)),
|
||||||
|
type="supergroup" if channel.megagroup else "channel",
|
||||||
|
title=channel.title,
|
||||||
|
username=channel.username
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_thumb(thumb: types.PhotoSize or types.PhotoCachedSize) -> pyrogram.PhotoSize or None:
|
||||||
|
if isinstance(thumb, (types.PhotoSize, types.PhotoCachedSize)):
|
||||||
|
loc = thumb.location
|
||||||
|
|
||||||
|
if isinstance(thumb, types.PhotoSize):
|
||||||
|
file_size = thumb.size
|
||||||
|
else:
|
||||||
|
file_size = len(thumb.bytes)
|
||||||
|
|
||||||
|
if isinstance(loc, types.FileLocation):
|
||||||
|
return pyrogram.PhotoSize(
|
||||||
|
file_id=encode(
|
||||||
|
pack(
|
||||||
|
"<iiqqqqi",
|
||||||
|
0,
|
||||||
|
loc.dc_id,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
loc.volume_id,
|
||||||
|
loc.secret,
|
||||||
|
loc.local_id
|
||||||
|
)
|
||||||
|
),
|
||||||
|
width=thumb.w,
|
||||||
|
height=thumb.h,
|
||||||
|
file_size=file_size
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Reorganize code, maybe split parts as well
|
||||||
|
def parse_message(
|
||||||
|
client,
|
||||||
|
message: types.Message,
|
||||||
|
users: dict,
|
||||||
|
chats: dict,
|
||||||
|
replies: int = 1
|
||||||
|
) -> pyrogram.Message:
|
||||||
|
entities = parse_entities(message.entities, users)
|
||||||
|
|
||||||
|
forward_from = None
|
||||||
|
forward_from_chat = None
|
||||||
|
forward_from_message_id = None
|
||||||
|
forward_signature = None
|
||||||
|
forward_date = None
|
||||||
|
|
||||||
|
forward_header = message.fwd_from # type: types.MessageFwdHeader
|
||||||
|
|
||||||
|
if forward_header:
|
||||||
|
forward_date = forward_header.date
|
||||||
|
|
||||||
|
if forward_header.from_id:
|
||||||
|
forward_from = parse_user(users[forward_header.from_id])
|
||||||
|
else:
|
||||||
|
forward_from_chat = parse_channel_chat(chats[forward_header.channel_id])
|
||||||
|
forward_from_message_id = forward_header.channel_post
|
||||||
|
forward_signature = forward_header.post_author
|
||||||
|
|
||||||
|
photo = None
|
||||||
|
location = None
|
||||||
|
contact = None
|
||||||
|
venue = None
|
||||||
|
audio = None
|
||||||
|
voice = None
|
||||||
|
video = None
|
||||||
|
video_note = None
|
||||||
|
sticker = None
|
||||||
|
document = None
|
||||||
|
|
||||||
|
media = message.media
|
||||||
|
|
||||||
|
if media:
|
||||||
|
if isinstance(media, types.MessageMediaPhoto):
|
||||||
|
photo = media.photo
|
||||||
|
|
||||||
|
if isinstance(photo, types.Photo):
|
||||||
|
sizes = photo.sizes
|
||||||
|
photo_sizes = []
|
||||||
|
|
||||||
|
for size in sizes:
|
||||||
|
if isinstance(size, (types.PhotoSize, types.PhotoCachedSize)):
|
||||||
|
loc = size.location
|
||||||
|
|
||||||
|
if isinstance(size, types.PhotoSize):
|
||||||
|
file_size = size.size
|
||||||
|
else:
|
||||||
|
file_size = len(size.bytes)
|
||||||
|
|
||||||
|
if isinstance(loc, types.FileLocation):
|
||||||
|
photo_size = pyrogram.PhotoSize(
|
||||||
|
file_id=encode(
|
||||||
|
pack(
|
||||||
|
"<iiqqqqi",
|
||||||
|
2,
|
||||||
|
loc.dc_id,
|
||||||
|
photo.id,
|
||||||
|
photo.access_hash,
|
||||||
|
loc.volume_id,
|
||||||
|
loc.secret,
|
||||||
|
loc.local_id
|
||||||
|
)
|
||||||
|
),
|
||||||
|
width=size.w,
|
||||||
|
height=size.h,
|
||||||
|
file_size=file_size
|
||||||
|
)
|
||||||
|
|
||||||
|
photo_sizes.append(photo_size)
|
||||||
|
|
||||||
|
photo = photo_sizes
|
||||||
|
elif isinstance(media, types.MessageMediaGeo):
|
||||||
|
geo_point = media.geo
|
||||||
|
|
||||||
|
if isinstance(geo_point, types.GeoPoint):
|
||||||
|
location = pyrogram.Location(
|
||||||
|
longitude=geo_point.long,
|
||||||
|
latitude=geo_point.lat
|
||||||
|
)
|
||||||
|
elif isinstance(media, types.MessageMediaContact):
|
||||||
|
contact = pyrogram.Contact(
|
||||||
|
phone_number=media.phone_number,
|
||||||
|
first_name=media.first_name,
|
||||||
|
last_name=media.last_name,
|
||||||
|
user_id=media.user_id
|
||||||
|
)
|
||||||
|
elif isinstance(media, types.MessageMediaVenue):
|
||||||
|
venue = pyrogram.Venue(
|
||||||
|
location=pyrogram.Location(
|
||||||
|
longitude=media.geo.long,
|
||||||
|
latitude=media.geo.lat
|
||||||
|
),
|
||||||
|
title=media.title,
|
||||||
|
address=media.address,
|
||||||
|
foursquare_id=media.venue_id
|
||||||
|
)
|
||||||
|
elif isinstance(media, types.MessageMediaDocument):
|
||||||
|
doc = media.document
|
||||||
|
|
||||||
|
if isinstance(doc, types.Document):
|
||||||
|
attributes = {type(i): i for i in doc.attributes}
|
||||||
|
|
||||||
|
if types.DocumentAttributeAudio in attributes:
|
||||||
|
audio_attributes = attributes[types.DocumentAttributeAudio]
|
||||||
|
|
||||||
|
if audio_attributes.voice:
|
||||||
|
voice = pyrogram.Voice(
|
||||||
|
file_id=encode(
|
||||||
|
pack(
|
||||||
|
"<iiqq",
|
||||||
|
3,
|
||||||
|
doc.dc_id,
|
||||||
|
doc.id,
|
||||||
|
doc.access_hash
|
||||||
|
)
|
||||||
|
),
|
||||||
|
duration=audio_attributes.duration,
|
||||||
|
mime_type=doc.mime_type,
|
||||||
|
file_size=doc.size
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
audio = pyrogram.Audio(
|
||||||
|
file_id=encode(
|
||||||
|
pack(
|
||||||
|
"<iiqq",
|
||||||
|
9,
|
||||||
|
doc.dc_id,
|
||||||
|
doc.id,
|
||||||
|
doc.access_hash
|
||||||
|
)
|
||||||
|
),
|
||||||
|
duration=audio_attributes.duration,
|
||||||
|
performer=audio_attributes.performer,
|
||||||
|
title=audio_attributes.title,
|
||||||
|
mime_type=doc.mime_type,
|
||||||
|
file_size=doc.size
|
||||||
|
)
|
||||||
|
elif types.DocumentAttributeAnimated in attributes:
|
||||||
|
document = pyrogram.Document(
|
||||||
|
file_id=encode(
|
||||||
|
pack(
|
||||||
|
"<iiqq",
|
||||||
|
10,
|
||||||
|
doc.dc_id,
|
||||||
|
doc.id,
|
||||||
|
doc.access_hash
|
||||||
|
)
|
||||||
|
),
|
||||||
|
thumb=parse_thumb(doc.thumb),
|
||||||
|
file_name=getattr(
|
||||||
|
attributes.get(
|
||||||
|
types.DocumentAttributeFilename, None
|
||||||
|
), "file_name", None
|
||||||
|
),
|
||||||
|
mime_type=doc.mime_type,
|
||||||
|
file_size=doc.size
|
||||||
|
)
|
||||||
|
elif types.DocumentAttributeVideo in attributes:
|
||||||
|
video_attributes = attributes[types.DocumentAttributeVideo]
|
||||||
|
|
||||||
|
if video_attributes.round_message:
|
||||||
|
video_note = pyrogram.VideoNote(
|
||||||
|
file_id=encode(
|
||||||
|
pack(
|
||||||
|
"<iiqq",
|
||||||
|
13,
|
||||||
|
doc.dc_id,
|
||||||
|
doc.id,
|
||||||
|
doc.access_hash
|
||||||
|
)
|
||||||
|
),
|
||||||
|
length=video_attributes.w,
|
||||||
|
duration=video_attributes.duration,
|
||||||
|
thumb=parse_thumb(doc.thumb),
|
||||||
|
file_size=doc.size
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
video = pyrogram.Video(
|
||||||
|
file_id=encode(
|
||||||
|
pack(
|
||||||
|
"<iiqq",
|
||||||
|
4,
|
||||||
|
doc.dc_id,
|
||||||
|
doc.id,
|
||||||
|
doc.access_hash
|
||||||
|
)
|
||||||
|
),
|
||||||
|
width=video_attributes.w,
|
||||||
|
height=video_attributes.h,
|
||||||
|
duration=video_attributes.duration,
|
||||||
|
thumb=parse_thumb(doc.thumb),
|
||||||
|
mime_type=doc.mime_type,
|
||||||
|
file_size=doc.size
|
||||||
|
)
|
||||||
|
elif types.DocumentAttributeSticker in attributes:
|
||||||
|
image_size_attributes = attributes[types.DocumentAttributeImageSize]
|
||||||
|
|
||||||
|
sticker = pyrogram.Sticker(
|
||||||
|
file_id=encode(
|
||||||
|
pack(
|
||||||
|
"<iiqq",
|
||||||
|
8,
|
||||||
|
doc.dc_id,
|
||||||
|
doc.id,
|
||||||
|
doc.access_hash
|
||||||
|
)
|
||||||
|
),
|
||||||
|
width=image_size_attributes.w,
|
||||||
|
height=image_size_attributes.h,
|
||||||
|
thumb=parse_thumb(doc.thumb),
|
||||||
|
# TODO: Emoji, set_name and mask_position
|
||||||
|
file_size=doc.size,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
document = pyrogram.Document(
|
||||||
|
file_id=encode(
|
||||||
|
pack(
|
||||||
|
"<iiqq",
|
||||||
|
5,
|
||||||
|
doc.dc_id,
|
||||||
|
doc.id,
|
||||||
|
doc.access_hash
|
||||||
|
)
|
||||||
|
),
|
||||||
|
thumb=parse_thumb(doc.thumb),
|
||||||
|
file_name=getattr(
|
||||||
|
attributes.get(
|
||||||
|
types.DocumentAttributeFilename, None
|
||||||
|
), "file_name", None
|
||||||
|
),
|
||||||
|
mime_type=doc.mime_type,
|
||||||
|
file_size=doc.size
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
media = None
|
||||||
|
|
||||||
|
m = pyrogram.Message(
|
||||||
|
message_id=message.id,
|
||||||
|
date=message.date,
|
||||||
|
chat=parse_chat(message, users, chats),
|
||||||
|
from_user=parse_user(users.get(message.from_id, None)),
|
||||||
|
text=message.message or None if media is None else None,
|
||||||
|
caption=message.message or None if media is not None else None,
|
||||||
|
entities=entities or None if media is None else None,
|
||||||
|
caption_entities=entities or None if media is not None else None,
|
||||||
|
author_signature=message.post_author,
|
||||||
|
forward_from=forward_from,
|
||||||
|
forward_from_chat=forward_from_chat,
|
||||||
|
forward_from_message_id=forward_from_message_id,
|
||||||
|
forward_signature=forward_signature,
|
||||||
|
forward_date=forward_date,
|
||||||
|
edit_date=message.edit_date,
|
||||||
|
media_group_id=message.grouped_id,
|
||||||
|
photo=photo,
|
||||||
|
location=location,
|
||||||
|
contact=contact,
|
||||||
|
venue=venue,
|
||||||
|
audio=audio,
|
||||||
|
voice=voice,
|
||||||
|
video=video,
|
||||||
|
video_note=video_note,
|
||||||
|
sticker=sticker,
|
||||||
|
document=document,
|
||||||
|
views=message.views,
|
||||||
|
via_bot=parse_user(users.get(message.via_bot_id, None))
|
||||||
|
)
|
||||||
|
|
||||||
|
if message.reply_to_msg_id and replies:
|
||||||
|
reply_to_message = client.get_messages(m.chat.id, [message.reply_to_msg_id])
|
||||||
|
|
||||||
|
message = reply_to_message.messages[0]
|
||||||
|
users = {i.id: i for i in reply_to_message.users}
|
||||||
|
chats = {i.id: i for i in reply_to_message.chats}
|
||||||
|
|
||||||
|
if isinstance(message, types.Message):
|
||||||
|
m.reply_to_message = parse_message(client, message, users, chats, replies - 1)
|
||||||
|
elif isinstance(message, types.MessageService):
|
||||||
|
m.reply_to_message = parse_message_service(client, message, users, chats)
|
||||||
|
|
||||||
|
return m
|
||||||
|
|
||||||
|
|
||||||
|
def parse_message_service(
|
||||||
|
client,
|
||||||
|
message: types.MessageService,
|
||||||
|
users: dict,
|
||||||
|
chats: dict
|
||||||
|
) -> pyrogram.Message:
|
||||||
|
action = message.action
|
||||||
|
|
||||||
|
new_chat_members = None
|
||||||
|
left_chat_member = None
|
||||||
|
new_chat_title = None
|
||||||
|
delete_chat_photo = None
|
||||||
|
migrate_to_chat_id = None
|
||||||
|
migrate_from_chat_id = None
|
||||||
|
group_chat_created = None
|
||||||
|
channel_chat_created = None
|
||||||
|
new_chat_photo = None
|
||||||
|
|
||||||
|
if isinstance(action, types.MessageActionChatAddUser):
|
||||||
|
new_chat_members = [parse_user(users[i]) for i in action.users]
|
||||||
|
elif isinstance(action, types.MessageActionChatJoinedByLink):
|
||||||
|
new_chat_members = [parse_user(users[action.inviter_id])]
|
||||||
|
elif isinstance(action, types.MessageActionChatDeleteUser):
|
||||||
|
left_chat_member = parse_user(users[action.user_id])
|
||||||
|
elif isinstance(action, types.MessageActionChatEditTitle):
|
||||||
|
new_chat_title = action.title
|
||||||
|
elif isinstance(action, types.MessageActionChatDeletePhoto):
|
||||||
|
delete_chat_photo = True
|
||||||
|
elif isinstance(action, types.MessageActionChatMigrateTo):
|
||||||
|
migrate_to_chat_id = action.channel_id
|
||||||
|
elif isinstance(action, types.MessageActionChannelMigrateFrom):
|
||||||
|
migrate_from_chat_id = action.chat_id
|
||||||
|
elif isinstance(action, types.MessageActionChatCreate):
|
||||||
|
group_chat_created = True
|
||||||
|
elif isinstance(action, types.MessageActionChannelCreate):
|
||||||
|
channel_chat_created = True
|
||||||
|
elif isinstance(action, types.MessageActionChatEditPhoto):
|
||||||
|
photo = action.photo
|
||||||
|
|
||||||
|
if isinstance(photo, types.Photo):
|
||||||
|
sizes = photo.sizes
|
||||||
|
photo_sizes = []
|
||||||
|
|
||||||
|
for size in sizes:
|
||||||
|
if isinstance(size, (types.PhotoSize, types.PhotoCachedSize)):
|
||||||
|
loc = size.location
|
||||||
|
|
||||||
|
if isinstance(size, types.PhotoSize):
|
||||||
|
file_size = size.size
|
||||||
|
else:
|
||||||
|
file_size = len(size.bytes)
|
||||||
|
|
||||||
|
if isinstance(loc, types.FileLocation):
|
||||||
|
photo_size = pyrogram.PhotoSize(
|
||||||
|
file_id=encode(
|
||||||
|
pack(
|
||||||
|
"<iiqqqqi",
|
||||||
|
2,
|
||||||
|
loc.dc_id,
|
||||||
|
photo.id,
|
||||||
|
photo.access_hash,
|
||||||
|
loc.volume_id,
|
||||||
|
loc.secret,
|
||||||
|
loc.local_id
|
||||||
|
)
|
||||||
|
),
|
||||||
|
width=size.w,
|
||||||
|
height=size.h,
|
||||||
|
file_size=file_size
|
||||||
|
)
|
||||||
|
|
||||||
|
photo_sizes.append(photo_size)
|
||||||
|
|
||||||
|
new_chat_photo = photo_sizes
|
||||||
|
|
||||||
|
m = pyrogram.Message(
|
||||||
|
message_id=message.id,
|
||||||
|
date=message.date,
|
||||||
|
chat=parse_chat(message, users, chats),
|
||||||
|
from_user=parse_user(users.get(message.from_id, None)),
|
||||||
|
new_chat_members=new_chat_members,
|
||||||
|
left_chat_member=left_chat_member,
|
||||||
|
new_chat_title=new_chat_title,
|
||||||
|
new_chat_photo=new_chat_photo,
|
||||||
|
delete_chat_photo=delete_chat_photo,
|
||||||
|
migrate_to_chat_id=int("-100" + str(migrate_to_chat_id)) if migrate_to_chat_id else None,
|
||||||
|
migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None,
|
||||||
|
group_chat_created=group_chat_created,
|
||||||
|
channel_chat_created=channel_chat_created
|
||||||
|
# TODO: supergroup_chat_created
|
||||||
|
)
|
||||||
|
|
||||||
|
if isinstance(action, types.MessageActionPinMessage):
|
||||||
|
pin_message = client.get_messages(m.chat.id, [message.reply_to_msg_id])
|
||||||
|
|
||||||
|
message = pin_message.messages[0]
|
||||||
|
users = {i.id: i for i in pin_message.users}
|
||||||
|
chats = {i.id: i for i in pin_message.chats}
|
||||||
|
|
||||||
|
if isinstance(message, types.Message):
|
||||||
|
m.pinned_message = parse_message(client, message, users, chats)
|
||||||
|
elif isinstance(message, types.MessageService):
|
||||||
|
# TODO: We can't pin a service message, can we?
|
||||||
|
m.pinned_message = parse_message_service(client, message, users, chats)
|
||||||
|
|
||||||
|
return m
|
55
pyrogram/client/utils.py
Normal file
55
pyrogram/client/utils.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||||
|
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
from base64 import b64decode, b64encode
|
||||||
|
|
||||||
|
|
||||||
|
def decode(s: str) -> bytes:
|
||||||
|
s = b64decode(s + "=" * (-len(s) % 4), "-_")
|
||||||
|
r = b""
|
||||||
|
|
||||||
|
assert s[-1] == 2
|
||||||
|
|
||||||
|
i = 0
|
||||||
|
while i < len(s) - 1:
|
||||||
|
if s[i] != 0:
|
||||||
|
r += bytes([s[i]])
|
||||||
|
else:
|
||||||
|
r += b"\x00" * s[i + 1]
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def encode(s: bytes) -> str:
|
||||||
|
r = b""
|
||||||
|
n = 0
|
||||||
|
|
||||||
|
for i in s + bytes([2]):
|
||||||
|
if i == 0:
|
||||||
|
n += 1
|
||||||
|
else:
|
||||||
|
if n:
|
||||||
|
r += b"\x00" + bytes([n])
|
||||||
|
n = 0
|
||||||
|
|
||||||
|
r += bytes([i])
|
||||||
|
|
||||||
|
return b64encode(r, b"-_").decode().rstrip("=")
|
Loading…
Reference in New Issue
Block a user