diff --git a/README.rst b/README.rst
index ae6f084f..67dbd3a5 100644
--- a/README.rst
+++ b/README.rst
@@ -1,158 +1,79 @@
|header|
-Table of Contents
-=================
+Pyrogram |twitter|
+==================
-- `About`_
+.. code-block:: python
- - `Features`_
+ from pyrogram import Client, Filters
- - `Requirements`_
-
-- `Getting Started`_
-
- - `Installation`_
-
- - `Configuration`_
-
- - `Usage`_
-
-- `Documentation`_
-
-- `Contribution`_
-
-- `Feedback`_
-
-- `License`_
+ app = Client("my_account")
-About
-=====
+ @app.on_message(Filters.private)
+ def hello(client, message):
+ client.send_message(
+ message.chat.id, "Hello {}".format(message.from_user.first_name))
+
+
+ app.start()
+ app.idle()
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for building
-custom Telegram applications in Python that interact with the MTProto API as both User and Bot.
+custom Telegram applications that interact with the MTProto API as both User and Bot.
Features
--------
-- **Easy to setup**: Pyrogram can be easily installed using pip and requires very few lines of code to get started with.
-
-- **Easy to use**: Pyrogram provides idiomatic, clean and readable Python code making the Telegram API simple to use.
-
-- **High-level**: Pyrogram automatically handles all the low-level details of communication with Telegram servers.
-
-- **Updated**: Pyrogram makes use of the latest Telegram MTProto API version, currently Layer 76.
-
-- **Fast**: Pyrogram critical parts are boosted up by `TgCrypto`_, a high-performance Crypto Library written in pure C.
-
-- **Documented**: Pyrogram API methods are documented and resemble the well established Telegram Bot API,
- thus offering a familiar look to Bot developers.
-
-- **Full API support**: Beside the simple Bot API-like methods, Pyrogram also provides an easy access to every single
- Telegram MTProto API method allowing you to programmatically execute any action an official client is able to do, and more.
-
+- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
+- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
+- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
+- **Updated** to the latest Telegram API version, currently Layer 76 running on MTProto 2.0.
+- **Documented**: Pyrogram API methods are documented and resemble the Telegram Bot API.
+- **Full API**, allowing to execute any advanced action an official client is able to do, and more.
Requirements
------------
- Python 3.4 or higher.
+- A `Telegram API key`_.
-- A Telegram API key.
-
+Installing
+----------
+
+.. code:: shell
+
+ pip3 install pyrogram
Getting Started
-===============
+---------------
-Installation
+- The Docs contain lots of resources to help you getting started with Pyrogram: https://docs.pyrogram.ml.
+- Reading Examples_ in this repository is also a good way for learning how things work.
+- Seeking extra help? Don't be shy, come join and ask our Community_!
+- For other requests you can send an Email_ or a Message_.
+
+Contributing
------------
-- You can install and upgrade Pyrogram using pip:
-
- .. code:: shell
-
- $ pip3 install --upgrade pyrogram
-
-Configuration
--------------
-
-- Create a new ``config.ini`` file at the root of your working directory, copy-paste
- the following and replace the **api_id** and **api_hash** values with `your own`_:
-
- .. code:: ini
-
- [pyrogram]
- api_id = 12345
- api_hash = 0123456789abcdef0123456789abcdef
-
-Usage
------
-
-- And here is how Pyrogram looks like:
-
- .. code:: python
-
- from pyrogram import Client
-
- client = Client("example")
- client.start()
-
- client.send_message("me", "Hi there! I'm using Pyrogram")
-
- client.stop()
-
-That's all you need for getting started with Pyrogram. For more detailed information,
-please refer to the Documentation_ and the Examples_ folder.
-
-
-Documentation
-=============
-
-- The entire Pyrogram documentation resides at https://docs.pyrogram.ml.
-
-
-Contribution
-============
-
Pyrogram is brand new! **You are welcome to try it and help make it better** by either submitting pull
requests or reporting issues/bugs as well as suggesting best practices, ideas, enhancements on both code
and documentation. Any help is appreciated!
-
-Feedback
-========
-
-Means for getting in touch:
-
-- `Community`_
-- `GitHub`_
-- `Email`_
-
-
-License
-=======
+Copyright & License
+-------------------
- Copyright (C) 2017-2018 Dan Tès
-
-- Licensed under the terms of the
- `GNU Lesser General Public License v3 or later (LGPLv3+)`_
-
+- Licensed under the terms of the `GNU Lesser General Public License v3 or later (LGPLv3+)`_
.. _`Telegram`: https://telegram.org/
-
-.. _`your own`: https://docs.pyrogram.ml/start/ProjectSetup#api-keys
-
-.. _`Examples`: https://github.com/pyrogram/pyrogram/blob/master/examples/README.md
-
+.. _`Telegram API key`: https://docs.pyrogram.ml/start/ProjectSetup#api-keys
.. _`Community`: https://t.me/PyrogramChat
-
-.. _`bot-like`: https://core.telegram.org/bots/api#available-methods
-
+.. _`Examples`: https://github.com/pyrogram/pyrogram/tree/master/examples
.. _`GitHub`: https://github.com/pyrogram/pyrogram/issues
-
.. _`Email`: admin@pyrogram.ml
-
+.. _`Message`: https://t.me/haskell
.. _TgCrypto: https://github.com/pyrogram/tgcrypto
-
.. _`GNU Lesser General Public License v3 or later (LGPLv3+)`: COPYING.lesser
.. |header| raw:: html
@@ -190,6 +111,9 @@ License
+.. |twitter| image:: https://media.pyrogram.ml/images/twitter.svg
+ :target: https://twitter.com/intent/tweet?text=Build%20custom%20Telegram%20applications%20with%20Pyrogram&url=https://github.com/pyrogram/pyrogram&hashtags=Telegram,MTProto,Python
+
.. |logo| image:: https://pyrogram.ml/images/logo.png
:target: https://pyrogram.ml
:alt: Pyrogram
diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py
index e1e95328..15b1ec76 100644
--- a/compiler/api/compiler.py
+++ b/compiler/api/compiler.py
@@ -22,20 +22,93 @@ import shutil
HOME = "compiler/api"
DESTINATION = "pyrogram/api"
-notice_path = "NOTICE"
+NOTICE_PATH = "NOTICE"
SECTION_RE = re.compile(r"---(\w+)---")
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?!.<>]+)")
FLAGS_RE = re.compile(r"flags\.(\d+)\?")
FLAGS_RE_2 = re.compile(r"flags\.(\d+)\?([\w<>.]+)")
FLAGS_RE_3 = re.compile(r"flags:#")
+INT_RE = re.compile(r"int(\d+)")
core_types = ["int", "long", "int128", "int256", "double", "bytes", "string", "Bool"]
+types_to_constructors = {}
+types_to_functions = {}
+constructors_to_functions = {}
+
+
+def get_docstring_arg_type(t: str, is_list: bool = False, is_pyrogram_type: bool = False):
+ if t in core_types:
+ if t == "long":
+ return "``int`` ``64-bit``"
+ elif "int" in t:
+ size = INT_RE.match(t)
+ return "``int`` ``{}-bit``".format(size.group(1)) if size else "``int`` ``32-bit``"
+ elif t == "double":
+ return "``float`` ``64-bit``"
+ elif t == "string":
+ return "``str``"
+ else:
+ return "``{}``".format(t.lower())
+ elif t == "true":
+ return "``bool``"
+ elif t == "Object" or t == "X":
+ return "Any object from :obj:`pyrogram.api.types`"
+ elif t == "!X":
+ return "Any method from :obj:`pyrogram.api.functions`"
+ elif t.startswith("Vector"):
+ return "List of " + get_docstring_arg_type(t.split("<", 1)[1][:-1], True, is_pyrogram_type)
+ else:
+ if is_pyrogram_type:
+ t = "pyrogram." + t
+
+ t = types_to_constructors.get(t, [t])
+ n = len(t) - 1
+
+ t = (("e" if is_list else "E") + "ither " if n else "") + ", ".join(
+ ":obj:`{1} `".format(
+ "pyrogram." if is_pyrogram_type else "",
+ i.lstrip("pyrogram.")
+ )
+ for i in t
+ )
+
+ if n:
+ t = t.split(", ")
+ t = ", ".join(t[:-1]) + " or " + t[-1]
+
+ return t
+
+
+def get_references(t: str):
+ t = constructors_to_functions.get(t)
+
+ if t:
+ n = len(t) - 1
+
+ t = ", ".join(
+ ":obj:`{0} `".format(i)
+ for i in t
+ )
+
+ if n:
+ t = t.split(", ")
+ t = ", ".join(t[:-1]) + " and " + t[-1]
+
+ return t
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.namespace = namespace
self.name = name
@@ -43,6 +116,7 @@ class Combinator:
self.args = args
self.has_flags = has_flags
self.return_type = return_type
+ self.docs = docs
def snek(s: str):
@@ -72,13 +146,17 @@ def start():
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/main_api.tl".format(HOME), encoding="utf-8") as api:
- schema = (auth.read() + system.read() + api.read()).splitlines()
+ open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api, \
+ 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:
- template = f.read()
+ with open("{}/template/mtproto.txt".format(HOME), encoding="utf-8") as f:
+ mtproto_template = f.read()
- with open(notice_path, encoding="utf-8") as f:
+ 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:
notice = []
for line in f.readlines():
@@ -106,9 +184,9 @@ def start():
combinator = COMBINATOR_RE.match(line)
if combinator:
- name, id, return_type = combinator.groups()
+ name, id, return_type, docs = combinator.groups()
namespace, name = name.split(".") if "." in name else ("", name)
- args = ARGS_RE.findall(line)
+ args = ARGS_RE.findall(line.split(" //")[0])
# Pingu!
has_flags = not not FLAGS_RE_3.findall(line)
@@ -129,23 +207,37 @@ def start():
Combinator(
section,
namespace,
- name,
+ capit(name),
"0x{}".format(id.zfill(8)),
args,
has_flags,
- return_type
+ ".".join(
+ return_type.split(".")[:-1]
+ + [capit(return_type.split(".")[-1])]
+ ),
+ docs
)
)
- by_types = {}
for c in combinators:
- return_type = capit(c.return_type)
+ return_type = c.return_type
- if c.section == "types":
- if return_type not in by_types:
- by_types[return_type] = []
+ if return_type.startswith("Vector"):
+ return_type = return_type.split("<")[1][:-1]
- by_types[return_type].append(".".join(filter(None, [c.namespace, capit(c.name)])))
+ d = types_to_constructors if c.section == "types" else types_to_functions
+
+ if return_type not in d:
+ d[return_type] = []
+
+ d[return_type].append(".".join(filter(None, [c.namespace, c.name])))
+
+ for k, v in types_to_constructors.items():
+ for i in v:
+ try:
+ constructors_to_functions[i] = types_to_functions[k]
+ except KeyError:
+ pass
total = len(combinators)
current = 0
@@ -182,52 +274,30 @@ def start():
) if c.args else "pass"
docstring_args = []
+ # docs = c.docs.split("|")[1:] if c.docs else None
for i, arg in enumerate(sorted_args):
arg_name, arg_type = arg
- is_optional = arg_type.startswith("flags.")
+ is_optional = FLAGS_RE.match(arg_type)
+ flag_number = is_optional.group(1) if is_optional else -1
arg_type = arg_type.split("?")[-1]
- if arg_type in core_types:
- if "int" in arg_type or arg_type == "long":
- arg_type = ":obj:`int`"
- elif arg_type == "double":
- arg_type = ":obj:`float`"
- else:
- arg_type = ":obj:`{}`".format(arg_type.lower())
- elif arg_type == "true":
- arg_type = ":obj:`bool`"
- else:
- if arg_type.startswith("Vector"):
- sub_type = arg_type.split("<")[1][:-1]
-
- if sub_type in core_types:
- if "int" in sub_type or sub_type == "long":
- arg_type = "List of :obj:`int`"
- elif sub_type == "double":
- arg_type = "List of :obj:`float`"
- else:
- arg_type = "List of :obj:`{}`".format(sub_type.lower())
- else:
- arg_type = "List of :class:`pyrogram.api.types.{}`".format(
- ".".join(
- sub_type.split(".")[:-1]
- + [capit(sub_type.split(".")[-1])]
- )
- )
- else:
- arg_type = ":class:`pyrogram.api.types.{}`".format(
- ".".join(
- arg_type.split(".")[:-1]
- + [capit(arg_type.split(".")[-1])]
- )
- )
+ # if c.namespace == "pyrogram":
+ # docstring_args.append(
+ # "{} ({}{}):\n {}\n".format(
+ # arg_name,
+ # get_docstring_arg_type(arg_type, is_pyrogram_type=True),
+ # ", optional" if "Optional" in docs[i] else "",
+ # re.sub("Optional\. ", "", docs[i].split("§")[1].rstrip(".") + ".")
+ # )
+ # )
+ # else:
docstring_args.append(
- "{}: {}{}".format(
+ "{}{}: {}".format(
arg_name,
- arg_type,
- " (optional)" if is_optional else ""
+ " (optional)".format(flag_number) if is_optional else "",
+ get_docstring_arg_type(arg_type, is_pyrogram_type=c.namespace == "pyrogram")
)
)
@@ -236,81 +306,16 @@ def start():
else:
docstring_args = "No parameters required."
- docstring_args = "Attributes:\n ID (:obj:`int`): ``{}``\n\n ".format(c.id) + docstring_args
+ docstring_args = "Attributes:\n ID: ``{}``\n\n ".format(c.id) + docstring_args
if c.section == "functions":
- docstring_args += "\n\n Returns:\n "
- if c.return_type in core_types:
- if "int" in c.return_type or c.return_type == "long":
- return_type = ":obj:`int`"
- elif c.return_type == "double":
- return_type = ":obj:`float`"
- else:
- return_type = ":obj:`{}`".format(c.return_type.lower())
- else:
- if c.return_type.startswith("Vector"):
- sub_type = c.return_type.split("<")[1][:-1]
+ docstring_args += "\n\n Raises:\n :obj:`Error `"
+ docstring_args += "\n\n Returns:\n " + get_docstring_arg_type(c.return_type)
+ else:
+ references = get_references(".".join(filter(None, [c.namespace, c.name])))
- if sub_type in core_types:
- if "int" in sub_type or sub_type == "long":
- return_type = "List of :obj:`int`"
- elif sub_type == "double":
- return_type = "List of :obj:`float`"
- else:
- return_type = "List of :obj:`{}`".format(c.return_type.lower())
- else:
- if c.section == "functions":
- try:
- constructors = by_types[capit(sub_type)]
- except KeyError:
- return_type = "List of :class:`pyrogram.api.types.{}`".format(
- ".".join(
- sub_type.split(".")[:-1]
- + [capit(sub_type.split(".")[-1])]
- )
- )
- else:
- constructors = ["List of :class:`pyrogram.api.types.{}`".format(
- ".".join(
- i.split(".")[:-1]
- + [capit(i.split(".")[-1])]
- )
- ) for i in constructors]
-
- return_type = " | ".join(constructors)
- else:
- return_type = "List of :class:`pyrogram.api.types.{}`".format(
- ".".join(
- sub_type.split(".")[:-1]
- + [capit(sub_type.split(".")[-1])]
- )
- )
- else:
- if c.section == "functions":
- try:
- constructors = by_types[capit(c.return_type)]
- except KeyError:
- return_type = ":class:`pyrogram.api.types.{}`".format(
- ".".join(filter(None, [c.namespace, capit(c.name)]))
- )
- else:
- constructors = [":class:`pyrogram.api.types.{}`".format(
- ".".join(
- i.split(".")[:-1]
- + [capit(i.split(".")[-1])]
- )
- ) for i in constructors]
-
- return_type = " | ".join(constructors)
- else:
- return_type = ":class:`pyrogram.api.types.{}`".format(
- ".".join(filter(None, [c.namespace, capit(c.name)]))
- )
-
- docstring_args += return_type
-
- if c.section == "functions":
- docstring_args += "\n\n Raises:\n :class:`pyrogram.Error`"
+ if references:
+ docstring_args += "\n\n See Also:\n This object can be returned by " + references + "."
if c.has_flags:
write_flags = []
@@ -397,22 +402,38 @@ def start():
read_types += "\n "
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:
- f.write(
- template.format(
- notice=notice,
- class_name=capit(c.name),
- docstring_args=docstring_args,
- object_id=c.id,
- arguments=arguments,
- fields=fields,
- read_flags=read_flags,
- read_types=read_types,
- write_flags=write_flags,
- write_types=write_types,
- return_arguments=", ".join([i[0] for i in sorted_args])
+ if c.docs:
+ f.write(
+ 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,
+ class_name=capit(c.name),
+ docstring_args=docstring_args,
+ object_id=c.id,
+ arguments=arguments,
+ fields=fields,
+ read_flags=read_flags,
+ read_types=read_types,
+ write_flags=write_flags,
+ write_types=write_types,
+ return_arguments=", ".join([i[0] for i in sorted_args])
+ )
)
- )
with open("{}/all.py".format(DESTINATION), "w", encoding="utf-8") as f:
f.write(notice + "\n\n")
@@ -443,5 +464,5 @@ def start():
if "__main__" == __name__:
HOME = "."
DESTINATION = "../../pyrogram/api"
- notice_path = "../../NOTICE"
+ NOTICE_PATH = "../../NOTICE"
start()
diff --git a/compiler/api/source/pyrogram.tl b/compiler/api/source/pyrogram.tl
new file mode 100644
index 00000000..4a6fb182
--- /dev/null
+++ b/compiler/api/source/pyrogram.tl
@@ -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 photo:flags.4?ChatPhoto = 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 caption_entities:flags.12?Vector audio:flags.13?Audio document:flags.14?Document game:flags.15?Game photo:flags.16?Vector 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 left_chat_member:flags.26?User new_chat_title:flags.27?string new_chat_photo:flags.28?Vector 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 file_size:flags.0?int date:flags.1?int width:int height:int = pyrogram.PhotoSize;
+pyrogram.audio#b0700006 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int duration:int performer:flags.5?string title:flags.6?string = 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 date:flags.4?int = pyrogram.Document;
+pyrogram.video#b0700008 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int width:int height:int duration:int = pyrogram.Video;
+pyrogram.voice#b0700009 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int duration:int = pyrogram.Voice;
+pyrogram.videoNote#b0700010 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int length:int duration: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> = 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 thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int width:int height:int emoji:flags.5?string set_name:flags.6?string mask_position:flags.7?MaskPosition = pyrogram.Sticker;
diff --git a/compiler/api/template/class.txt b/compiler/api/template/mtproto.txt
similarity index 94%
rename from compiler/api/template/class.txt
rename to compiler/api/template/mtproto.txt
index d29caf05..81c99062 100644
--- a/compiler/api/template/class.txt
+++ b/compiler/api/template/mtproto.txt
@@ -6,8 +6,7 @@ from pyrogram.api.core import *
class {class_name}(Object):
- """
- {docstring_args}
+ """{docstring_args}
"""
ID = {object_id}
diff --git a/compiler/api/template/pyrogram.txt b/compiler/api/template/pyrogram.txt
new file mode 100644
index 00000000..adbe4151
--- /dev/null
+++ b/compiler/api/template/pyrogram.txt
@@ -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}
diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index 6a6345d1..494697de 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -20,17 +20,17 @@ import ast
import os
import shutil
-home = "compiler/docs"
-destination = "docs/source"
+HOME = "compiler/docs"
+DESTINATION = "docs/source"
-functions_path = "pyrogram/api/functions"
-types_path = "pyrogram/api/types"
+FUNCTIONS_PATH = "pyrogram/api/functions"
+TYPES_PATH = "pyrogram/api/types"
-functions_base = "functions"
-types_base = "types"
+FUNCTIONS_BASE = "functions"
+TYPES_BASE = "types"
-shutil.rmtree(types_base, ignore_errors=True)
-shutil.rmtree(functions_base, ignore_errors=True)
+shutil.rmtree(TYPES_BASE, ignore_errors=True)
+shutil.rmtree(FUNCTIONS_BASE, ignore_errors=True)
def generate(source_path, base):
@@ -57,9 +57,9 @@ def generate(source_path, base):
if level:
full_path = base + "/" + full_path
- os.makedirs(os.path.dirname(destination + "/" + full_path), exist_ok=True)
+ os.makedirs(os.path.dirname(DESTINATION + "/" + full_path), exist_ok=True)
- with open(destination + "/" + full_path, "w", encoding="utf-8") as f:
+ with open(DESTINATION + "/" + full_path, "w", encoding="utf-8") as f:
f.write(
page_template.format(
title=name,
@@ -94,7 +94,10 @@ def generate(source_path, base):
inner_path = base + "/index" + ".rst"
module = "pyrogram.api.{}".format(base)
- with open(destination + "/" + inner_path, "w", encoding="utf-8") as f:
+ with open(DESTINATION + "/" + inner_path, "w", encoding="utf-8") as f:
+ if k == base:
+ f.write(":tocdepth: 1\n\n")
+
f.write(
toctree.format(
title=k.title(),
@@ -111,20 +114,20 @@ def start():
global page_template
global toctree
- with open(home + "/template/page.txt", encoding="utf-8") as f:
+ with open(HOME + "/template/page.txt", encoding="utf-8") as f:
page_template = f.read()
- with open(home + "/template/toctree.txt", encoding="utf-8") as f:
+ with open(HOME + "/template/toctree.txt", encoding="utf-8") as f:
toctree = f.read()
- generate(types_path, types_base)
- generate(functions_path, functions_base)
+ generate(TYPES_PATH, TYPES_BASE)
+ generate(FUNCTIONS_PATH, FUNCTIONS_BASE)
if "__main__" == __name__:
- functions_path = "../../pyrogram/api/functions"
- types_path = "../../pyrogram/api/types"
- home = "."
- destination = "../../docs/source"
+ FUNCTIONS_PATH = "../../pyrogram/api/functions"
+ TYPES_PATH = "../../pyrogram/api/types"
+ HOME = "."
+ DESTINATION = "../../docs/source"
start()
diff --git a/compiler/error/compiler.py b/compiler/error/compiler.py
index fee3be4d..aaefde9f 100644
--- a/compiler/error/compiler.py
+++ b/compiler/error/compiler.py
@@ -21,9 +21,9 @@ import os
import re
import shutil
-home = "compiler/error"
-dest = "pyrogram/api/errors/exceptions"
-notice_path = "NOTICE"
+HOME = "compiler/error"
+DEST = "pyrogram/api/errors/exceptions"
+NOTICE_PATH = "NOTICE"
def snek(s):
@@ -38,12 +38,12 @@ def caml(s):
def start():
- shutil.rmtree(dest, ignore_errors=True)
- os.makedirs(dest)
+ shutil.rmtree(DEST, ignore_errors=True)
+ os.makedirs(DEST)
- files = [i for i in os.listdir("{}/source".format(home))]
+ files = [i for i in os.listdir("{}/source".format(HOME))]
- with open(notice_path, encoding="utf-8") as f:
+ with open(NOTICE_PATH, encoding="utf-8") as f:
notice = []
for line in f.readlines():
@@ -51,7 +51,7 @@ def start():
notice = "\n".join(notice)
- with open("{}/all.py".format(dest), "w", encoding="utf-8") as f_all:
+ with open("{}/all.py".format(DEST), "w", encoding="utf-8") as f_all:
f_all.write(notice + "\n\n")
f_all.write("count = {count}\n\n")
f_all.write("exceptions = {\n")
@@ -63,7 +63,7 @@ def start():
f_all.write(" {}: {{\n".format(code))
- init = "{}/__init__.py".format(dest)
+ init = "{}/__init__.py".format(DEST)
if not os.path.exists(init):
with open(init, "w", encoding="utf-8") as f_init:
@@ -72,8 +72,8 @@ def start():
with open(init, "a", encoding="utf-8") as f_init:
f_init.write("from .{}_{} import *\n".format(name.lower(), code))
- with open("{}/source/{}".format(home, i), encoding="utf-8") as f_csv, \
- open("{}/{}_{}.py".format(dest, name.lower(), code), "w", encoding="utf-8") as f_class:
+ with open("{}/source/{}".format(HOME, i), encoding="utf-8") as f_csv, \
+ open("{}/{}_{}.py".format(DEST, name.lower(), code), "w", encoding="utf-8") as f_class:
reader = csv.reader(f_csv, delimiter="\t")
super_class = caml(name)
@@ -98,10 +98,10 @@ def start():
sub_classes.append((sub_class, id, message))
- with open("{}/template/class.txt".format(home), "r", encoding="utf-8") as f_class_template:
+ with open("{}/template/class.txt".format(HOME), "r", encoding="utf-8") as f_class_template:
class_template = f_class_template.read()
- with open("{}/template/sub_class.txt".format(home), "r", encoding="utf-8") as f_sub_class_template:
+ with open("{}/template/sub_class.txt".format(HOME), "r", encoding="utf-8") as f_sub_class_template:
sub_class_template = f_sub_class_template.read()
class_template = class_template.format(
@@ -123,18 +123,18 @@ def start():
f_all.write("}\n")
- with open("{}/all.py".format(dest), encoding="utf-8") as f:
+ with open("{}/all.py".format(DEST), encoding="utf-8") as f:
content = f.read()
- with open("{}/all.py".format(dest), "w", encoding="utf-8") as f:
+ with open("{}/all.py".format(DEST), "w", encoding="utf-8") as f:
f.write(re.sub("{count}", str(count), content))
print("Compiling Errors: [100%]")
if "__main__" == __name__:
- home = "."
- dest = "../../pyrogram/api/errors/exceptions"
- notice_path = "../../NOTICE"
+ HOME = "."
+ DEST = "../../pyrogram/api/errors/exceptions"
+ NOTICE_PATH = "../../NOTICE"
start()
diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv
index ffba8987..2ec8b7fd 100644
--- a/compiler/error/source/400_BAD_REQUEST.tsv
+++ b/compiler/error/source/400_BAD_REQUEST.tsv
@@ -48,4 +48,15 @@ ABOUT_TOO_LONG The about text is too long
MULTI_MEDIA_TOO_LONG The album contains more than 10 items
USERNAME_OCCUPIED The username is already in use
BOT_INLINE_DISABLED The inline feature of the bot is disabled
-INLINE_RESULT_EXPIRED The inline bot query expired
\ No newline at end of file
+INLINE_RESULT_EXPIRED The inline bot query expired
+INVITE_HASH_INVALID The invite link hash is invalid
+USER_ALREADY_PARTICIPANT The user is already a participant of this chat
+TTL_MEDIA_INVALID This kind of media does not support self-destruction
+MAX_ID_INVALID The max_id parameter is invalid
+CHANNEL_INVALID The channel parameter is invalid
+DC_ID_INVALID The dc_id parameter is invalid
+LIMIT_INVALID The limit parameter is invalid
+OFFSET_INVALID The offset parameter is invalid
+EMAIL_INVALID The email provided is invalid
+USER_IS_BOT A bot cannot send messages to other bots or to itself
+WEBPAGE_CURL_FAILED Telegram could not fetch the provided URL
\ No newline at end of file
diff --git a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv
index 4ee4f042..dcdc1ee0 100644
--- a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv
+++ b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv
@@ -1,2 +1,4 @@
id message
AUTH_RESTART User authorization has restarted
+RPC_CALL_FAIL Telegram is having internal problems. Please try again later
+RPC_MCGET_FAIL Telegram is having internal problems. Please try again later
\ No newline at end of file
diff --git a/compiler/error/template/class.txt b/compiler/error/template/class.txt
index fd240f4b..e520d80c 100644
--- a/compiler/error/template/class.txt
+++ b/compiler/error/template/class.txt
@@ -6,7 +6,7 @@ from ..error import Error
class {super_class}(Error):
{docstring}
CODE = {code}
- """:obj:`int`: Error Code"""
+ """``int``: Error Code"""
NAME = __doc__
diff --git a/compiler/error/template/sub_class.txt b/compiler/error/template/sub_class.txt
index a33503ec..e13e4cf2 100644
--- a/compiler/error/template/sub_class.txt
+++ b/compiler/error/template/sub_class.txt
@@ -1,7 +1,7 @@
class {sub_class}({super_class}):
{docstring}
ID = {id}
- """:obj:`str`: Error ID"""
+ """``str``: Error ID"""
MESSAGE = __doc__
diff --git a/docs/source/_static/pyrogram.ico b/docs/source/_images/favicon.ico
similarity index 100%
rename from docs/source/_static/pyrogram.ico
rename to docs/source/_images/favicon.ico
diff --git a/docs/source/_static/pyrogram.png b/docs/source/_images/logo.png
similarity index 100%
rename from docs/source/_static/pyrogram.png
rename to docs/source/_images/logo.png
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 439c0a1a..cecf047f 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -36,11 +36,12 @@ from pyrogram import __version__
# ones.
extensions = [
'sphinx.ext.autodoc',
- 'sphinx.ext.napoleon'
+ 'sphinx.ext.napoleon',
+ 'sphinx.ext.autosummary'
]
# Don't show source files on docs
-html_show_sourcelink = False
+html_show_sourcelink = True
# Order by source, not alphabetically
autodoc_member_order = 'bysource'
@@ -84,41 +85,48 @@ language = None
exclude_patterns = []
# The name of the Pygments (syntax highlighting) style to use.
-pygments_style = 'sphinx'
+pygments_style = 'tango'
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
+html_title = "Pyrogram Documentation"
+
+# Overridden by template
+html_show_copyright = False
+
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
-html_theme_options = {
- 'collapse_navigation': False
-}
-
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
-# html_theme_options = {}
+html_theme_options = {
+ 'canonical_url': "https://docs.pyrogram.ml/",
+ 'collapse_navigation': False,
+ 'sticky_navigation': False,
+ 'logo_only': True,
+ 'display_version': True
+}
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
-html_logo = '_static/pyrogram.png'
+html_logo = '_images/logo.png'
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
-html_favicon = '_static/pyrogram.ico'
+html_favicon = '_images/favicon.ico'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
-html_static_path = ['_static']
+# html_static_path = ['_static']
# Custom sidebar templates, must be a dictionary that maps document names
# to template names.
diff --git a/docs/source/errors/UnknownError.rst b/docs/source/errors/UnknownError.rst
index 18b4a4e8..030f3e02 100644
--- a/docs/source/errors/UnknownError.rst
+++ b/docs/source/errors/UnknownError.rst
@@ -3,6 +3,6 @@ Unknown Error
.. module:: pyrogram.api.errors.UnknownError
-.. autoclass:: pyrogram.api.errors.error.UnknownError
+.. autoexception:: pyrogram.api.errors.error.UnknownError
:members:
:show-inheritance:
diff --git a/docs/source/getting_started/BasicUsage.rst b/docs/source/getting_started/BasicUsage.rst
deleted file mode 100644
index e9411c60..00000000
--- a/docs/source/getting_started/BasicUsage.rst
+++ /dev/null
@@ -1,73 +0,0 @@
-Basic Usage
-===========
-
-.. note::
-
- All the snippets below assume you have successfully created and started a :obj:`pyrogram.Client` instance.
- You also must be authorized, that is, a valid *.session file does exist in your working directory.
-
-Simple API Access
------------------
-
-The easiest way to interact with the API is via the :obj:`pyrogram.Client` class which exposes bot-like_ methods.
-The purpose of this Client class is to make it even simpler to work with Telegram's API by abstracting the
-raw functions listed in the API scheme.
-
-The result is a much cleaner interface that allows you to:
-
-- Get information about the authorized user:
-
- .. code-block:: python
-
- print(client.get_me())
-
-- Send a message to yourself (Saved Messages):
-
- .. code-block:: python
-
- client.send_message(
- chat_id="me",
- text="Hi there! I'm using Pyrogram"
- )
-
-.. seealso:: For a complete list of the available methods have a look at the :obj:`pyrogram.Client` class.
-
-.. _using-raw-functions:
-
-Using Raw Functions
--------------------
-
-If you want **complete**, low-level access to the Telegram API you have to use the raw
-:obj:`functions ` and :obj:`types ` exposed by the ``pyrogram.api``
-package and call any Telegram API method you wish using the :obj:`send ` method provided by
-the Client class.
-
-Here some examples:
-
-- Update first name, last name and bio:
-
- .. code-block:: python
-
- from pyrogram.api import functions
-
- client.send(
- functions.account.UpdateProfile(
- first_name="Dan", last_name="Tès",
- about="Bio written from Pyrogram"
- )
- )
-
-- Share your Last Seen time only with your contacts:
-
- .. code-block:: python
-
- from pyrogram.api import functions, types
-
- client.send(
- functions.account.SetPrivacy(
- key=types.InputPrivacyKeyStatusTimestamp(),
- rules=[types.InputPrivacyValueAllowContacts()]
- )
- )
-
-.. _bot-like: https://core.telegram.org/bots/api#available-methods
\ No newline at end of file
diff --git a/docs/source/getting_started/ProjectSetup.rst b/docs/source/getting_started/ProjectSetup.rst
deleted file mode 100644
index bc2b6b74..00000000
--- a/docs/source/getting_started/ProjectSetup.rst
+++ /dev/null
@@ -1,66 +0,0 @@
-Project Setup
-=============
-
-This section provides all the information you need to setup your project with Pyrogram.
-There are a few steps you have to follow before you can actually use the library to make API calls.
-
-API Keys
---------
-
-The very first step requires you to obtain a valid Telegram API key.
-If you already have one you can skip this, otherwise:
-
-#. Visit https://my.telegram.org/apps and log in with your Telegram Account.
-#. Fill out the form to register a new Telegram application.
-#. Done. The Telegram API key consists of two parts: the **App api_id** and the **App api_hash**
-
-.. important:: This key should be kept secret.
-
-Configuration
--------------
-
-Create a new ``config.ini`` file at the root of your working directory,
-copy-paste the following and replace the **api_id** and **api_hash** values with `your own <#api-keys>`_:
-
-.. code-block:: ini
-
- [pyrogram]
- api_id = 12345
- api_hash = 0123456789abcdef0123456789abcdef
-
-Authorization
--------------
-
-Telegram requires that users be authorized in order to use the API.
-Pyrogram automatically manages this access, all you need to do is create an instance of
-the :class:`pyrogram.Client` class by passing to it a ```` of your choice
-and call the :obj:`start ` method:
-
-.. code-block:: python
-
- from pyrogram import Client
-
- client = Client(session_name="example")
- client.start()
-
-This starts an interactive shell asking you to input your **phone number** (including your `Country Code`_)
-and the **phone code** you will receive:
-
-.. code::
-
- Enter phone number: +39**********
- Is "+39**********" correct? (y/n): y
- Enter phone code: 32768
-
-After successfully authorizing yourself, a new file called ``example.session`` will be created allowing
-Pyrogram executing API calls with your identity.
-
-.. important:: Your *.session file(s) must be kept secret.
-
-.. note::
-
- The authorization process is executed only once.
- However, the code above is always required; as long as a valid session file exists,
- Pyrogram will use that and won't ask you to enter your phone number again when you restart your script.
-
-.. _`Country Code`: https://en.wikipedia.org/wiki/List_of_country_calling_codes
\ No newline at end of file
diff --git a/docs/source/getting_started/QuickInstallation.rst b/docs/source/getting_started/QuickInstallation.rst
deleted file mode 100644
index 4d2912b8..00000000
--- a/docs/source/getting_started/QuickInstallation.rst
+++ /dev/null
@@ -1,37 +0,0 @@
-Quick Installation
-==================
-
-The most straightforward and recommended way to install or upgrade Pyrogram is by using **pip**:
-
-.. code-block:: bash
-
- $ pip install --upgrade pyrogram
-
-Bleeding Edge
--------------
-
-If you want the latest development version of the library, you can either install it automatically with:
-
-.. code-block:: bash
-
- $ pip install git+https://github.com/pyrogram/pyrogram.git
-
-or manually, using:
-
-.. code-block:: bash
-
- $ git clone https://github.com/pyrogram/pyrogram.git
- $ cd pyrogram
- $ python setup.py install
-
-Verifying
----------
-
-To verify that Pyrogram is correctly installed, open a Python shell and try to import it.
-If no errors show up you are good to go.
-
-.. code-block:: bash
-
- >>> import pyrogram
- >>> pyrogram.__version__
- '0.3.2'
diff --git a/docs/source/index.rst b/docs/source/index.rst
index dfa6d67e..fced42bb 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -3,16 +3,10 @@ Welcome to Pyrogram
.. raw:: html
-
- Watch
- Star
- Fork
-
-
@@ -32,83 +26,72 @@ Welcome to Pyrogram
-
-
-
+
+
+.. code-block:: python
+
+ from pyrogram import Client, Filters
+
+ app = Client("my_account")
+
+
+ @app.on_message(Filters.private)
+ def hello(client, message):
+ client.send_message(
+ message.chat.id, "Hello {}".format(message.from_user.first_name))
+
+
+ app.start()
+ app.idle()
+
+Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the library.
+Contents are organized by topic and can be accessed from the sidebar, or by following them one by one using the Next
+button at the end of each page. But first, here's a brief overview of what is this all about.
+
About
-----
-Pyrogram is a fully functional Telegram Client Library written from the ground up in Python.
-It offers **simple** and **complete** access to the Telegram Messenger API and is designed for Python developers
-keen on building custom Telegram applications.
+Pyrogram is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for building
+custom Telegram applications that interact with the MTProto API as both User and Bot.
Features
--------
-- **Easy to setup**: Pyrogram can be easily installed and upgraded using **pip**, requires
- a minimal set of dependencies (which are also automatically managed) and very few lines
- of code to get started with.
+- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
+- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
+- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
+- **Updated** to the latest Telegram API version, currently Layer 76 running on MTProto 2.0.
+- **Documented**: Pyrogram API methods are documented and resemble the Telegram Bot API.
+- **Full API**, allowing to execute any advanced action an official client is able to do, and more.
-- **Easy to use**: Pyrogram provides idiomatic, developer-friendly, clean and readable
- Python code (either generated or hand-written) making the Telegram API simple to use.
-
-- **High level**: Pyrogram automatically handles all the low-level details of
- communication with the Telegram servers by implementing the
- `MTProto Mobile Protocol v2.0`_ and the mechanisms needed for establishing
- a reliable connection.
-
-- **Fast**: Pyrogram's speed is boosted up by `TgCrypto`_, a high-performance, easy-to-install
- crypto library written in C.
-
-- **Updated**: Pyrogram makes use of the latest Telegram API version, currently `Layer 75`_.
-
-- **Documented**: Pyrogram API public methods are documented and resemble the well
- established Telegram Bot API, thus offering a familiar look to Bot developers.
-
-- **Full API support**: Beside the simple, bot-like methods offered by the Pyrogram API,
- the library also provides a complete, low-level access to every single Telegram API method.
-
-Preview
--------
-
-.. code-block:: python
-
- from pyrogram import Client
-
- client = Client("example")
- client.start()
-
- client.send_message("me", "Hi there! I'm using Pyrogram")
- client.send_photo("me", "/home/dan/pic.jpg", "Nice photo!")
-
- client.stop()
-
-To get started, press Next.
+To get started, press the Next button.
.. toctree::
:hidden:
:caption: Getting Started
- getting_started/QuickInstallation
- getting_started/ProjectSetup
- getting_started/BasicUsage
+ start/QuickInstallation
+ start/ProjectSetup
+ start/BasicUsage
.. toctree::
:hidden:
:caption: Resources
- resources/TextFormatting
resources/UpdateHandling
- resources/ErrorHandling
- resources/ProxyServer
+ resources/SOCKS5Proxy
+ resources/TgCrypto
resources/AutoAuthorization
- resources/FastCrypto
+ resources/TextFormatting
+ resources/BotsInteraction
+ resources/ErrorHandling
.. toctree::
:hidden:
@@ -123,8 +106,6 @@ To get started, press Next.
functions/index
types/index
-.. _`MTProto Mobile Protocol v2.0`: https://core.telegram.org/mtproto
+.. _`Telegram`: https://telegram.org/
-.. _TgCrypto: https://docs.pyrogram.ml/resources/FastCrypto/
-
-.. _`Layer 75`: https://github.com/pyrogram/pyrogram/blob/master/compiler/api/source/main_api.tl
\ No newline at end of file
+.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto/
diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst
index c0c4e667..806950a8 100644
--- a/docs/source/pyrogram/Client.rst
+++ b/docs/source/pyrogram/Client.rst
@@ -1,5 +1,52 @@
Client
======
+.. currentmodule:::: pyrogram.Client
+
.. autoclass:: pyrogram.Client
:members:
+
+ **Available methods**
+
+ .. autosummary::
+ :nosignatures:
+
+ start
+ stop
+ idle
+ on_message
+ on_raw_update
+ add_handler
+ send
+ resolve_peer
+ get_me
+ send_message
+ forward_messages
+ send_photo
+ send_audio
+ send_document
+ send_video
+ send_voice
+ send_video_note
+ send_media_group
+ send_location
+ send_venue
+ send_contact
+ send_chat_action
+ send_sticker
+ download_media
+ get_user_profile_photos
+ edit_message_text
+ edit_message_caption
+ delete_messages
+ join_chat
+ leave_chat
+ export_chat_invite_link
+ enable_cloud_password
+ change_cloud_password
+ remove_cloud_password
+ add_contacts
+ delete_contacts
+ get_inline_bot_results
+ send_inline_bot_result
+ get_messages
\ No newline at end of file
diff --git a/docs/source/pyrogram/Emoji.rst b/docs/source/pyrogram/Emoji.rst
new file mode 100644
index 00000000..6f0d7fda
--- /dev/null
+++ b/docs/source/pyrogram/Emoji.rst
@@ -0,0 +1,6 @@
+Emoji
+======
+
+.. autoclass:: pyrogram.Emoji
+ :members:
+ :undoc-members:
diff --git a/docs/source/pyrogram/Error.rst b/docs/source/pyrogram/Error.rst
index b5474e73..96a140fa 100644
--- a/docs/source/pyrogram/Error.rst
+++ b/docs/source/pyrogram/Error.rst
@@ -1,3 +1,5 @@
+:tocdepth: 1
+
Error
=====
diff --git a/docs/source/pyrogram/Filters.rst b/docs/source/pyrogram/Filters.rst
new file mode 100644
index 00000000..083bd64a
--- /dev/null
+++ b/docs/source/pyrogram/Filters.rst
@@ -0,0 +1,6 @@
+Filters
+=======
+
+.. autoclass:: pyrogram.Filters
+ :members:
+ :undoc-members:
diff --git a/docs/source/pyrogram/InputMedia.rst b/docs/source/pyrogram/InputMedia.rst
deleted file mode 100644
index c637bdc0..00000000
--- a/docs/source/pyrogram/InputMedia.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-InputMedia
-==========
-
-.. autoclass:: pyrogram.InputMedia
- :members:
- :undoc-members:
diff --git a/docs/source/pyrogram/InputMediaPhoto.rst b/docs/source/pyrogram/InputMediaPhoto.rst
new file mode 100644
index 00000000..abc3f456
--- /dev/null
+++ b/docs/source/pyrogram/InputMediaPhoto.rst
@@ -0,0 +1,6 @@
+InputMediaPhoto
+===============
+
+.. autoclass:: pyrogram.InputMediaPhoto
+ :members:
+ :undoc-members:
diff --git a/docs/source/pyrogram/InputMediaVideo.rst b/docs/source/pyrogram/InputMediaVideo.rst
new file mode 100644
index 00000000..de9c480b
--- /dev/null
+++ b/docs/source/pyrogram/InputMediaVideo.rst
@@ -0,0 +1,6 @@
+InputMediaVideo
+===============
+
+.. autoclass:: pyrogram.InputMediaVideo
+ :members:
+ :undoc-members:
diff --git a/docs/source/pyrogram/InputPhoneContact.rst b/docs/source/pyrogram/InputPhoneContact.rst
new file mode 100644
index 00000000..e5c4a20d
--- /dev/null
+++ b/docs/source/pyrogram/InputPhoneContact.rst
@@ -0,0 +1,6 @@
+InputPhoneContact
+=================
+
+.. autoclass:: pyrogram.InputPhoneContact
+ :members:
+ :undoc-members:
diff --git a/docs/source/pyrogram/MessageHandler.rst b/docs/source/pyrogram/MessageHandler.rst
new file mode 100644
index 00000000..de908bd3
--- /dev/null
+++ b/docs/source/pyrogram/MessageHandler.rst
@@ -0,0 +1,6 @@
+MessageHandler
+==============
+
+.. autoclass:: pyrogram.MessageHandler
+ :members:
+ :undoc-members:
diff --git a/docs/source/pyrogram/RawUpdateHandler.rst b/docs/source/pyrogram/RawUpdateHandler.rst
new file mode 100644
index 00000000..a6d21ef3
--- /dev/null
+++ b/docs/source/pyrogram/RawUpdateHandler.rst
@@ -0,0 +1,6 @@
+RawUpdateHandler
+================
+
+.. autoclass:: pyrogram.RawUpdateHandler
+ :members:
+ :undoc-members:
diff --git a/docs/source/pyrogram/index.rst b/docs/source/pyrogram/index.rst
index 160766eb..9a86c11d 100644
--- a/docs/source/pyrogram/index.rst
+++ b/docs/source/pyrogram/index.rst
@@ -1,17 +1,45 @@
Pyrogram
========
-In this section you can find a detailed description of the Pyrogram API.
+In this section you can find a detailed description of the Pyrogram package and its high-level API.
-:obj:`pyrogram.Client` is the main class you have to deal with.
-You will notice that methods are named after the well established `Telegram Bot API`_ and that most of them accept
-the same parameters as well, thus offering a familiar look to Bot developers.
+:class:`Client ` is the main class. It exposes easy-to-use methods that are named
+after the well established `Telegram Bot API`_ methods, thus offering a familiar look to Bot developers.
.. toctree::
Client
+ MessageHandler
+ RawUpdateHandler
+ Filters
ChatAction
ParseMode
- InputMedia
+ Emoji
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
diff --git a/docs/source/resources/AutoAuthorization.rst b/docs/source/resources/AutoAuthorization.rst
new file mode 100644
index 00000000..46f0809d
--- /dev/null
+++ b/docs/source/resources/AutoAuthorization.rst
@@ -0,0 +1,64 @@
+Auto Authorization
+==================
+
+Manually writing phone number, phone code and password on the terminal every time you want to login can be tedious.
+Pyrogram is able to automate both **Log In** and **Sign Up** processes, all you need to do is pass the relevant
+parameters when creating a new :class:`Client `.
+
+.. note:: If you omit any of the optional parameter required for the authorization, Pyrogram will ask you to
+ manually write it. For instance, if you don't want to set a ``last_name`` when creating a new account you
+ have to explicitly pass an empty string ""; the default value (None) will trigger the input() call.
+
+Log In
+-------
+
+To automate the **Log In** process, pass your ``phone_number`` and ``password`` (if you have one) in the Client parameters.
+If you want to retrieve the phone code programmatically, pass a callback function in the ``phone_code`` field — this
+function must return the correct phone code as string (e.g., "12345") — otherwise, ignore this parameter, Pyrogram will
+ask you to input the phone code manually.
+
+.. code-block:: python
+
+ from pyrogram import Client
+
+ def phone_code_callback():
+ code = ... # Get your code programmatically
+ return code # Must be string, e.g., "12345"
+
+
+ app = Client(
+ session_name="example",
+ phone_number="39**********",
+ phone_code=phone_code_callback,
+ password="password" # (if you have one)
+ )
+
+ app.start()
+ print(app.get_me())
+
+Sign Up
+-------
+
+To automate the **Sign Up** process (i.e., automatically create a new Telegram account), simply fill **both**
+``first_name`` and ``last_name`` fields alongside the other parameters; they will be used to automatically create a new
+Telegram account in case the phone number you passed is not registered yet.
+
+.. code-block:: python
+
+ from pyrogram import Client
+
+ def phone_code_callback():
+ code = ... # Get your code programmatically
+ return code # Must be string, e.g., "12345"
+
+
+ app = Client(
+ session_name="example",
+ phone_number="39**********",
+ phone_code=phone_code_callback,
+ first_name="Pyrogram",
+ last_name="" # Can be an empty string
+ )
+
+ app.start()
+ print(app.get_me())
\ No newline at end of file
diff --git a/docs/source/resources/BotsInteraction.rst b/docs/source/resources/BotsInteraction.rst
new file mode 100644
index 00000000..cbbe23c1
--- /dev/null
+++ b/docs/source/resources/BotsInteraction.rst
@@ -0,0 +1,40 @@
+Bots Interaction
+================
+
+Users can interact with other bots via plain text messages as well as inline queries.
+
+Inline Bots
+-----------
+
+- If a bot accepts inline queries, you can call it by using
+ :meth:`get_inline_bot_results() ` to get the list of its inline results
+ for a query:
+
+ .. code-block:: python
+
+ # Get bot results for "Fuzz Universe" from the inline bot @vid
+ bot_results = app.get_inline_bot_results("vid", "Fuzz Universe")
+
+ .. figure:: https://i.imgur.com/IAqLs54.png
+ :width: 90%
+ :align: center
+ :figwidth: 60%
+
+ ``get_inline_bot_results()`` is the equivalent action of writing ``@vid Fuzz Universe`` and getting the
+ results list.
+
+- After you retrieved the bot results, you can use
+ :meth:`send_inline_bot_result() ` to send a chosen result to any chat:
+
+ .. code-block:: python
+
+ # Send the first result (bot_results.results[0]) to your own chat (Saved Messages)
+ app.send_inline_bot_result("me", bot_results.query_id, bot_results.results[0].id)
+
+ .. figure:: https://i.imgur.com/wwxr7B7.png
+ :width: 90%
+ :align: center
+ :figwidth: 60%
+
+ ``send_inline_bot_result()`` is the equivalent action of choosing a result from the list and sending it
+ to a chat.
diff --git a/docs/source/resources/ErrorHandling.rst b/docs/source/resources/ErrorHandling.rst
index 74a9091a..0d5cf6f9 100644
--- a/docs/source/resources/ErrorHandling.rst
+++ b/docs/source/resources/ErrorHandling.rst
@@ -29,8 +29,7 @@ Examples
)
try:
- # Something
- pass
+ ...
except BadRequest:
pass
except Flood:
@@ -44,18 +43,18 @@ Examples
except UnknownError:
pass
-Exceptions may also contain some informative values which can be useful.
-e.g. :obj:`FloodWait ` holds the amount of seconds you have to wait before you
-can try again. The value is always stored in the ``x`` field of the returned exception object:
+Exception objects may also contain some informative values.
+E.g.: :obj:`FloodWait ` holds the amount of seconds you have to wait
+before you can try again. The value is always stored in the ``x`` field of the returned exception object:
.. code-block:: python
+ import time
from pyrogram.api.errors import FloodWait
try:
- # something
- pass
+ ...
except FloodWait as e:
- print(e.x)
+ time.sleep(e.x)
**TODO: Better explanation on how to deal with exceptions**
\ No newline at end of file
diff --git a/docs/source/resources/FastCrypto.rst b/docs/source/resources/FastCrypto.rst
deleted file mode 100644
index 0cefd146..00000000
--- a/docs/source/resources/FastCrypto.rst
+++ /dev/null
@@ -1,37 +0,0 @@
-Fast Crypto
-===========
-
-Pyrogram's speed can be *dramatically* boosted up by installing TgCrypto_, a high-performance, easy-to-install crypto
-library specifically written in C for Pyrogram [#f1]_. TgCrypto is a replacement for the painfully slow PyAES and
-implements the crypto algorithms MTProto requires, namely AES-IGE and AES-CTR 256 bit.
-
-Installation
-------------
-
-.. code-block:: bash
-
- $ pip install --upgrade tgcrypto
-
-
-.. note:: Being a C extension for Python, TgCrypto is an optional but *highly recommended* dependency; when TgCrypto
- is not detected on your system, Pyrogram will automatically fall back to PyAES and will show you a warning.
-
-The reason about being an optional package is that TgCrypto requires some extra system tools in order to be compiled.
-Usually the errors you receive when trying to install TgCrypto are enough to understand what you should do next.
-
-- **Windows**: Install `Visual C++ 2015 Build Tools `_.
-
-- **macOS**: A pop-up will automatically ask you to install the command line developer tools as soon as you issue the
- installation command.
-
-- **Linux**: Depending on your distro, install a proper C compiler (``gcc``, ``clang``) and the Python header files
- (``python3-dev``).
-
-- **Termux (Android)**: Install ``clang`` and ``python-dev`` packages.
-
-More help on the `Pyrogram group chat `_.
-
-.. _TgCrypto: https://github.com/pyrogram/tgcrypto
-
-.. [#f1] Although TgCrypto is intended for Pyrogram, it is shipped as a standalone package and can thus be used for
- other projects too.
\ No newline at end of file
diff --git a/docs/source/resources/SOCKS5Proxy.rst b/docs/source/resources/SOCKS5Proxy.rst
new file mode 100644
index 00000000..761899e6
--- /dev/null
+++ b/docs/source/resources/SOCKS5Proxy.rst
@@ -0,0 +1,50 @@
+SOCKS5 Proxy
+============
+
+Pyrogram supports proxies with and without authentication. This feature allows Pyrogram to exchange data with Telegram
+through an intermediate SOCKS5 proxy server.
+
+Usage
+-----
+
+- To use Pyrogram with a proxy, simply append the following to your ``config.ini`` file and replace the values
+ with your own settings:
+
+ .. code-block:: ini
+
+ [proxy]
+ enabled = True
+ hostname = 11.22.33.44
+ port = 1080
+ username =
+ password =
+
+ To enable or disable the proxy without deleting your settings from the config file,
+ change the ``enabled`` value as follows:
+
+ - ``1``, ``yes``, ``True`` or ``on``: Enables the proxy
+ - ``0``, ``no``, ``False`` or ``off``: Disables the proxy
+
+- Alternatively, you can setup your proxy without the need of the ``config.ini`` file by using the *proxy* parameter
+ in the Client class:
+
+ .. code-block:: python
+
+ from pyrogram import Client
+
+ app = Client(
+ session_name="example",
+ proxy=dict(
+ hostname="11.22.33.44",
+ port=1080,
+ username="",
+ password=""
+ )
+ )
+
+ app.start()
+
+ ...
+
+.. note:: If your proxy doesn't require authorization you can omit ``username`` and ``password`` by either leaving the
+ values blank/empty or completely delete the lines.
\ No newline at end of file
diff --git a/docs/source/resources/TextFormatting.rst b/docs/source/resources/TextFormatting.rst
index d17ab8e8..124d02da 100644
--- a/docs/source/resources/TextFormatting.rst
+++ b/docs/source/resources/TextFormatting.rst
@@ -8,8 +8,8 @@ Beside bold, italic, and pre-formatted code, **Pyrogram does also support inline
Markdown Style
--------------
-To use this mode, pass :obj:`pyrogram.ParseMode.MARKDOWN` or "markdown" in the *parse_mode* field when using
-:obj:`send_message `. Use the following syntax in your message:
+To use this mode, pass :obj:`MARKDOWN ` or "markdown" in the *parse_mode* field when using
+:obj:`send_message() `. Use the following syntax in your message:
.. code::
@@ -30,8 +30,8 @@ To use this mode, pass :obj:`pyrogram.ParseMode.MARKDOWN` or "markdown" in the *
HTML Style
----------
-To use this mode, pass :obj:`pyrogram.ParseMode.HTML` or "html" in the *parse_mode* field when using
-:obj:`send_message `. The following tags are currently supported:
+To use this mode, pass :obj:`HTML ` or "html" in the *parse_mode* field when using
+:obj:`send_message() `. The following tags are currently supported:
.. code::
@@ -56,14 +56,14 @@ Examples
.. code-block:: python
- client.send_message(
+ app.send_message(
chat_id="me",
text=(
- "**bold**\n"
- "__italic__\n"
- "[mention](tg://user?id=23122162)\n"
- "[url](https://pyrogram.ml)\n"
- "`code`\n"
+ "**bold**, "
+ "__italic__, "
+ "[mention](tg://user?id=23122162), "
+ "[url](https://pyrogram.ml), "
+ "`code`"
)
)
@@ -71,7 +71,7 @@ Examples
.. code-block:: python
- client.send_message(
+ app.send_message(
chat_id="me",
text=(
# Code block language is optional
@@ -88,15 +88,15 @@ Examples
from pyrogram import ParseMode
- client.send_message(
+ app.send_message(
chat_id="me",
text=(
- "bold, bold\n"
- "italic, italic\n"
- "inline URL\n"
- "inline mention of a user\n"
- "inline fixed-width code
\n"
- "pre-formatted fixed-width code block
\n"
+ "bold, bold, "
+ "italic, italic, "
+ "inline URL, "
+ "inline mention of a user, "
+ "inline fixed-width code
, "
+ "pre-formatted fixed-width code block
"
),
parse_mode=ParseMode.HTML
)
diff --git a/docs/source/resources/TgCrypto.rst b/docs/source/resources/TgCrypto.rst
new file mode 100644
index 00000000..734c48e4
--- /dev/null
+++ b/docs/source/resources/TgCrypto.rst
@@ -0,0 +1,32 @@
+TgCrypto
+========
+
+Pyrogram's speed can be *dramatically* boosted up by TgCrypto_, a high-performance, easy-to-install Telegram Crypto
+Library specifically written in C for Pyrogram [#f1]_ as a Python extension.
+
+TgCrypto is a replacement for the much slower PyAES and implements the crypto algorithms Telegram requires, namely
+**AES-IGE 256 bit** (used in MTProto v2.0) and **AES-CTR 256 bit** (used for CDN encrypted files).
+
+Installation
+------------
+
+.. code-block:: bash
+
+ $ pip3 install --upgrade tgcrypto
+
+.. note:: Being a C extension for Python, TgCrypto is an optional but *highly recommended* dependency; when TgCrypto is
+ not detected in your system, Pyrogram will automatically fall back to PyAES and will show you a warning.
+
+The reason about being an optional package is that TgCrypto requires some extra system tools in order to be compiled.
+The errors you receive when trying to install TgCrypto are system dependent, but also descriptive enough to understand
+what you should do next:
+
+- **Windows**: Install `Visual C++ 2015 Build Tools `_.
+- **macOS**: A pop-up will automatically ask you to install the command line developer tools.
+- **Linux**: Install a proper C compiler (``gcc``, ``clang``) and the Python header files (``python3-dev``).
+- **Termux (Android)**: Install ``clang`` and ``python-dev`` packages.
+
+.. _TgCrypto: https://github.com/pyrogram/tgcrypto
+
+.. [#f1] Although TgCrypto is intended for Pyrogram, it is shipped as a standalone package and can thus be used for
+ other Python projects too.
diff --git a/docs/source/resources/UpdateHandling.rst b/docs/source/resources/UpdateHandling.rst
index 8fc12543..2b2c05d9 100644
--- a/docs/source/resources/UpdateHandling.rst
+++ b/docs/source/resources/UpdateHandling.rst
@@ -1,46 +1,188 @@
Update Handling
===============
-Updates are events that happen in your Telegram account (incoming messages, new channel posts, user name changes, ...)
-and can be handled by using a callback function, that is, a function called every time an ``Update`` is received from
-Telegram.
+Updates are events that happen in your Telegram account (incoming messages, new channel posts, new members join, ...)
+and are handled by registering one or more callback functions with an Handler. There are multiple Handlers to choose
+from, one for each kind of update:
-To set an update handler simply call :obj:`set_update_handler `
-by passing the name of your defined callback function as argument *before* you start the Client.
+- `MessageHandler <../pyrogram/MessageHandler.html>`_
+- `RawUpdateHandler <../pyrogram/RawUpdateHandler.html>`_
-Here's a complete example on how to set it up:
+Registering an Handler
+----------------------
-.. code-block:: python
+We shall examine the :obj:`MessageHandler `, which will be in charge for handling
+:obj:`Message ` objects.
- from pyrogram import Client
-
-
- def callback(update):
- print(update)
-
-
- client = Client(session_name="example")
- client.set_update_handler(callback)
-
- client.start()
- client.idle()
-
-The last line, :obj:`client.idle() ` is not strictly necessary but highly recommended;
-it will block your script execution until you press :obj:`CTRL`:obj:`C` and automatically call the
-:obj:`stop ` method which stops the Client and gently close the underlying connection.
-
-Examples
---------
-
-- Echo:
+- The easiest and nicest way to register a MessageHandler is by decorating your function with the
+ :meth:`on_message() ` decorator. Here's a full example that prints out the content
+ of a message as soon as it arrives.
.. code-block:: python
- from pyrogram.api import types
+ from pyrogram import Client
- def callback(update):
- if isinstance(update, types.UpdateShortMessage) and not update.out:
- client.send_message(update.user_id, update.message)
+ app = Client("my_account")
- This checks if the update type is :obj:`UpdateShortMessage ` and that the
- update is not generated by yourself (i.e., the message is not outgoing), then sends back the same message.
\ No newline at end of file
+
+ @app.on_message()
+ def my_handler(client, message):
+ print(message)
+
+
+ app.start()
+ app.idle()
+
+- If you prefer not to use decorators, there is an alternative way for registering Handlers.
+ This is useful, for example, when you want to keep your callback functions in separate files.
+
+ .. code-block:: python
+
+ from pyrogram import Client, MessageHandler
+
+
+ def my_handler(client, message):
+ print(message)
+
+
+ app = Client("my_account")
+
+ app.add_handler(MessageHandler(my_handler))
+
+ app.start()
+ app.idle()
+
+Using Filters
+-------------
+
+For a finer grained control over what kind of messages will be allowed or not in your callback functions, you can use
+:class:`Filters `.
+
+- This example will show you how to **only** handle messages containing an
+ :obj:`Audio ` object and filter out any other message:
+
+ .. code-block:: python
+
+ from pyrogram import Filters
+
+
+ @app.on_message(Filters.audio)
+ def my_handler(client, message):
+ print(message)
+
+- or, without decorators:
+
+ .. code-block:: python
+
+ from pyrogram import Filters, MessageHandler
+
+
+ def my_handler(client, message):
+ print(message)
+
+
+ app.add_handler(MessageHandler(my_handler, Filters.audio))
+
+Combining Filters
+-----------------
+
+Filters can also be used in a more advanced way by combining more filters together using bitwise operators:
+
+- Use ``~`` to invert a filter (behaves like the ``not`` operator).
+- Use ``&`` and ``|`` to merge two filters (behave like ``and``, ``or`` operators respectively).
+
+Here are some examples:
+
+- Message is a **text** message **and** is **not edited**.
+
+ .. code-block:: python
+
+ @app.on_message(Filters.text & ~Filters.edited)
+ def my_handler(client, message):
+ print(message)
+
+- Message is a **sticker** **and** is coming from a **channel or** a **private** chat.
+
+ .. code-block:: python
+
+ @app.on_message(Filters.sticker & (Filters.channel | Filters.private))
+ def my_handler(client, message):
+ print(message)
+
+Advanced Filters
+----------------
+
+Some filters, like :obj:`command() ` or :obj:`regex() `
+can also accept arguments:
+
+- Message is either a */start* or */help* **command**.
+
+ .. code-block:: python
+
+ @app.on_message(Filters.command(["start", "help"]))
+ def my_handler(client, message):
+ print(message)
+
+- Message is a **text** message matching the given **regex** pattern.
+
+ .. code-block:: python
+
+ @app.on_message(Filters.regex("pyrogram"))
+ def my_handler(client, message):
+ print(message)
+
+More handlers using different filters can also live together.
+
+.. code-block:: python
+
+ @app.on_message(Filters.command("start"))
+ def start_command(client, message):
+ print("This is the /start command")
+
+
+ @app.on_message(Filters.command("help"))
+ def help_command(client, message):
+ print("This is the /help command")
+
+
+ @app.on_message(Filters.chat("PyrogramChat"))
+ def from_pyrogramchat(client, message):
+ print("New message in @PyrogramChat")
+
+Handler Groups
+--------------
+
+If you register handlers with overlapping filters, only the first one is executed and any other handler will be ignored.
+
+In order to process the same message more than once, you can register your handler in a different group.
+Groups are identified by a number (number 0 being the default) and are sorted. This means that a lower group number has
+a higher priority.
+
+For example, in:
+
+.. code-block:: python
+
+ @app.on_message(Filters.text | Filters.sticker)
+ def text_or_sticker(client, message):
+ print("Text or Sticker")
+
+
+ @app.on_message(Filters.text)
+ def just_text(client, message):
+ print("Just Text")
+
+``just_text`` is never executed. To enable it, simply register the function using a different group:
+
+.. code-block:: python
+
+ @app.on_message(Filters.text, group=1)
+ def just_text(client, message):
+ print("Just Text")
+
+or, if you want ``just_text`` to be fired *before* ``text_or_sticker``:
+
+.. code-block:: python
+
+ @app.on_message(Filters.text, group=-1)
+ def just_text(client, message):
+ print("Just Text")
\ No newline at end of file
diff --git a/docs/source/sitemap.py b/docs/source/sitemap.py
new file mode 100644
index 00000000..539bac0d
--- /dev/null
+++ b/docs/source/sitemap.py
@@ -0,0 +1,68 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2018 Dan Tès
+#
+# 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 .
+
+import datetime
+import os
+import re
+
+canonical = "https://docs.pyrogram.ml"
+
+dirs = {
+ "start": ("weekly", 0.9),
+ "resources": ("weekly", 0.8),
+ "pyrogram": ("weekly", 0.8),
+ "functions": ("monthly", 0.7),
+ "types": ("monthly", 0.7),
+ "errors": ("weekly", 0.6)
+}
+
+
+def now():
+ return datetime.datetime.today().strftime("%Y-%m-%d")
+
+
+with open("sitemap.xml", "w") as f:
+ f.write("\n")
+ f.write("\n")
+
+ urls = [(canonical, now(), "weekly", 1.0)]
+
+
+ def search(path):
+ try:
+ for j in os.listdir(path):
+ search("{}/{}".format(path, j))
+ except NotADirectoryError:
+ d = path.split("/")[0]
+ path = "{}/{}".format(canonical, path.split(".")[0])
+ path = re.sub("^(.+)/index$", "\g<1>", path)
+ urls.append((path, now(), dirs[d][0], dirs[d][1]))
+
+
+ for i in dirs.keys():
+ search(i)
+
+ for i in urls:
+ f.write(" \n")
+ f.write(" {}\n".format(i[0]))
+ f.write(" {}\n".format(i[1]))
+ f.write(" {}\n".format(i[2]))
+ f.write(" {}\n".format(i[3]))
+ f.write(" \n\n")
+
+ f.write("")
diff --git a/docs/source/start/BasicUsage.rst b/docs/source/start/BasicUsage.rst
new file mode 100644
index 00000000..94dfd00c
--- /dev/null
+++ b/docs/source/start/BasicUsage.rst
@@ -0,0 +1,97 @@
+Basic Usage
+===========
+
+.. note::
+
+ All the snippets below assume you have successfully created and started a :class:`Client `
+ instance. You also must be authorized, that is, a valid *.session file does exist in your working directory.
+
+Simple API Access
+-----------------
+
+The easiest way to interact with the Telegram API is via the :class:`Client ` class, which
+exposes `Bot API-like`_ methods:
+
+- Get information about the authorized user:
+
+ .. code-block:: python
+
+ print(app.get_me())
+
+- Send a message to yourself (Saved Messages):
+
+ .. code-block:: python
+
+ app.send_message("me", "Hi there! I'm using Pyrogram")
+
+- Upload a new photo (with caption):
+
+ .. code-block:: python
+
+ app.send_photo("me", "/home/dan/perla.jpg", "Cute!")
+
+.. seealso:: For a complete list of the available methods and an exhaustive description for each of them, have a look
+ at the :class:`Client ` class.
+
+.. _using-raw-functions:
+
+Using Raw Functions
+-------------------
+
+If you want **complete**, low-level access to the Telegram API you have to use the raw
+:mod:`functions ` and :mod:`types ` exposed by the ``pyrogram.api``
+package and call any Telegram API method you wish using the :meth:`send() ` method provided by
+the Client class.
+
+Here some examples:
+
+- Update first name, last name and bio:
+
+ .. code-block:: python
+
+ from pyrogram.api import functions
+
+ ...
+
+ app.send(
+ functions.account.UpdateProfile(
+ first_name="Dan", last_name="Tès",
+ about="Bio written from Pyrogram"
+ )
+ )
+
+- Share your Last Seen time only with your contacts:
+
+ .. code-block:: python
+
+ from pyrogram.api import functions, types
+
+ ...
+
+ app.send(
+ functions.account.SetPrivacy(
+ key=types.InputPrivacyKeyStatusTimestamp(),
+ rules=[types.InputPrivacyValueAllowContacts()]
+ )
+ )
+
+- Invite users to your channel/supergroup:
+
+ .. code-block:: python
+
+ from pyrogram.api import functions, types
+
+ ...
+
+ app.send(
+ functions.channels.InviteToChannel(
+ channel=app.resolve_peer(123456789), # ID or Username
+ users=[ # The users you want to invite
+ app.resolve_peer(23456789), # By ID
+ app.resolve_peer("username"), # By username
+ app.resolve_peer("393281234567"), # By phone number
+ ]
+ )
+ )
+
+.. _`Bot API-like`: https://core.telegram.org/bots/api#available-methods
\ No newline at end of file
diff --git a/docs/source/start/ProjectSetup.rst b/docs/source/start/ProjectSetup.rst
new file mode 100644
index 00000000..4e397413
--- /dev/null
+++ b/docs/source/start/ProjectSetup.rst
@@ -0,0 +1,100 @@
+Project Setup
+=============
+
+This section provides all the information you need to setup your project with Pyrogram.
+There are a few steps you have to follow before you can actually use the library to make API calls.
+
+API Keys
+--------
+
+The very first step requires you to obtain a valid Telegram API key.
+If you already have one you can skip this step, otherwise:
+
+#. Visit https://my.telegram.org/apps and log in with your Telegram Account.
+#. Fill out the form to register a new Telegram application.
+#. Done. The Telegram API key consists of two parts: the **App api_id** and the **App api_hash**.
+
+.. important:: This key should be kept secret.
+
+Configuration
+-------------
+
+There are two ways to configure a Pyrogram application project, and you can choose the one that fits better for you:
+
+- Create a new ``config.ini`` file at the root of your working directory, copy-paste the following and replace the
+ **api_id** and **api_hash** values with `your own <#api-keys>`_. This is the preferred method because allows you
+ to keep your credentials out of your code without having to deal with how to load them:
+
+ .. code-block:: ini
+
+ [pyrogram]
+ api_id = 12345
+ api_hash = 0123456789abcdef0123456789abcdef
+
+- Alternatively, you can pass your API key to Pyrogram by simply using the *api_id* and *api_hash*
+ parameters of the Client class. This way you can have full control on how to store and load your credentials:
+
+ .. code-block:: python
+
+ from pyrogram import Client
+
+ app = Client(
+ session_name="my_account",
+ api_id=12345
+ api_hash="0123456789abcdef0123456789abcdef"
+ )
+
+.. note:: The examples below assume you have created a ``config.ini`` file, thus they won't show the *api_id*
+ and *api_hash* parameters usage.
+
+User Authorization
+------------------
+
+In order to use the API, Telegram requires that Users be authorized via their phone numbers.
+Pyrogram automatically manages this access, all you need to do is create an instance of
+the :class:`Client ` class by passing to it a ``session_name`` of your choice
+(e.g.: "my_account") and call the :meth:`start() ` method:
+
+.. code-block:: python
+
+ from pyrogram import Client
+
+ app = Client("my_account")
+ app.start()
+
+This starts an interactive shell asking you to input your **phone number** (including your `Country Code`_)
+and the **phone code** you will receive:
+
+.. code::
+
+ Enter phone number: +39**********
+ Is "+39**********" correct? (y/n): y
+ Enter phone code: 32768
+
+After successfully authorizing yourself, a new file called ``my_account.session`` will be created allowing
+Pyrogram executing API calls with your identity. This file will be loaded again when you restart your app,
+and as long as you keep the session alive, Pyrogram won't ask you again to enter your phone number.
+
+.. important:: Your *.session file(s) must be kept secret.
+
+Bot Authorization
+-----------------
+
+Being written entirely from the ground up, Pyrogram is also able to authorize Bots.
+Bots are a special kind of users which also make use of MTProto. This means that you can use Pyrogram to
+execute API calls with a Bot identity.
+
+Instead of phone numbers, Bots are authorized via their tokens which are created by BotFather_:
+
+.. code-block:: python
+
+ from pyrogram import Client
+
+ app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
+ app.start()
+
+That's all, no further action is needed. The session file will be named after the Bot user_id, which is
+``123456.session`` for the example above.
+
+.. _`Country Code`: https://en.wikipedia.org/wiki/List_of_country_calling_codes
+.. _BotFather: https://t.me/botfather
\ No newline at end of file
diff --git a/docs/source/start/QuickInstallation.rst b/docs/source/start/QuickInstallation.rst
new file mode 100644
index 00000000..7044bfee
--- /dev/null
+++ b/docs/source/start/QuickInstallation.rst
@@ -0,0 +1,37 @@
+Quick Installation
+==================
+
+- The easiest way to install and upgrade Pyrogram is by using **pip**:
+
+ .. code-block:: bash
+
+ $ pip3 install --upgrade pyrogram
+
+- or, with TgCrypto_ (recommended):
+
+ .. code-block:: bash
+
+ $ pip3 install --upgrade pyrogram[tgcrypto]
+
+Bleeding Edge
+-------------
+
+If you want the latest development version of the library, you can install it with:
+
+.. code-block:: bash
+
+ $ pip3 install --upgrade git+https://github.com/pyrogram/pyrogram.git
+
+Verifying
+---------
+
+To verify that Pyrogram is correctly installed, open a Python shell and import it.
+If no error shows up you are good to go.
+
+.. code-block:: bash
+
+ >>> import pyrogram
+ >>> pyrogram.__version__
+ '0.7.0'
+
+.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto
\ No newline at end of file
diff --git a/examples/README.md b/examples/README.md
index 66ca9405..4d709e99 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -5,16 +5,13 @@ You can start with [hello_world.py](https://github.com/pyrogram/pyrogram/blob/ma
with the more advanced examples.
Every script is working right away (provided you correctly set up your credentials), meaning
-you can simply copy-paste and run, the only things you have to change are the target chats (username, id) and file paths for
-sending media (photo, video, ...).
+you can simply copy-paste and run, the only things you have to change are your session names and the target chats
-- [**hello_world.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/hello_world.py)
+- [**echo_bot.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/echo_bot.py)
- [**get_history.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/get_history.py)
- [**get_participants.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/get_participants.py)
- [**get_participants2.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/get_participants2.py)
+- [**hello_world.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/hello_world.py)
- [**inline_bots.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/inline_bots.py)
-- [**updates.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/updates.py)
-- [**simple_echo.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/simple_echo.py)
-- [**advanced_echo.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/advanced_echo.py)
-- [**advanced_echo2.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/advanced_echo2.py)
+- [**raw_update_handler.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/raw_update_handler.py)
- [**welcome_bot.py**](https://github.com/pyrogram/pyrogram/blob/master/examples/welcome_bot.py)
diff --git a/examples/advanced_echo.py b/examples/advanced_echo.py
deleted file mode 100644
index 9cc2fb6e..00000000
--- a/examples/advanced_echo.py
+++ /dev/null
@@ -1,64 +0,0 @@
-from pyrogram import Client
-from pyrogram.api import types
-
-"""This is a more advanced example bot that will reply to all private and basic groups text messages
-by also mentioning the Users.
-
-Beware! This script will make you reply to ALL new messages in private chats and in every basic group you are in.
-Make sure you add an extra check to filter them:
-
-# Filter Groups by ID
-if message.to_id.chat_id == MY_GROUP_ID:
- ...
-"""
-
-
-def update_handler(client, update, users, chats):
- if isinstance(update, types.UpdateNewMessage): # Filter by UpdateNewMessage (PM and Chats)
- message = update.message
-
- if isinstance(message, types.Message): # Filter by Message to exclude MessageService and MessageEmpty
- if isinstance(message.to_id, types.PeerUser): # Private Messages
- text = '[{}](tg://user?id={}) said "{}" to me ([{}](tg://user?id={}))'.format(
- users[message.from_id].first_name,
- users[message.from_id].id,
- message.message,
- users[message.to_id.user_id].first_name,
- users[message.to_id.user_id].id
- )
-
- client.send_message(
- message.from_id, # Send the message to the private chat (from_id)
- text,
- reply_to_message_id=message.id
- )
- else: # Group chats
- text = '[{}](tg://user?id={}) said "{}" in **{}** group'.format(
- users[message.from_id].first_name,
- users[message.from_id].id,
- message.message,
- chats[message.to_id.chat_id].title
- )
-
- client.send_message(
- message.to_id, # Send the message to the group chat (to_id)
- text,
- reply_to_message_id=message.id
- )
-
-
-def main():
- # Pyrogram setup
- client = Client("example")
-
- # Set the update_handler callback function
- client.set_update_handler(update_handler)
- client.start()
-
- # Blocks the program execution until you press CTRL+C then
- # automatically stops the Client by closing the underlying connection
- client.idle()
-
-
-if __name__ == "__main__":
- main()
diff --git a/examples/advanced_echo2.py b/examples/advanced_echo2.py
deleted file mode 100644
index 460c4cf8..00000000
--- a/examples/advanced_echo2.py
+++ /dev/null
@@ -1,55 +0,0 @@
-from pyrogram import Client
-from pyrogram.api import types
-
-"""This example is similar to advanced_echo.py, except for the fact that it will reply to Supergroup text messages only.
-
-Beware! This script will make you reply to ALL new messages in every single supergroup you are in.
-Make sure you add an extra check to filter them:
-
-# Filter Supergroups by ID
-if message.to_id.channel_id == MY_SUPERGROUP_ID:
- ...
-
-# Filter Supergroups by Username
-if chats[message.to_id.channel_id].username == MY_SUPERGROUP_USERNAME:
- ...
-"""
-
-
-def update_handler(client, update, users, chats):
- # Channels and Supergroups share the same type (Channel). The .megagroup field is used to tell them apart, and is
- # True for Supegroups, False for Channels.
- if isinstance(update, types.UpdateNewChannelMessage): # Filter by UpdateNewChannelMessage (Channels/Supergroups)
- message = update.message
-
- if isinstance(message, types.Message): # Filter by Message to exclude MessageService and MessageEmpty
- if chats[message.to_id.channel_id].megagroup: # Only handle messages from Supergroups not Channels
- text = '[{}](tg://user?id={}) said "{}" in **{}** supergroup'.format(
- users[message.from_id].first_name,
- users[message.from_id].id,
- message.message,
- chats[message.to_id.channel_id].title
- )
-
- client.send_message(
- message.to_id,
- text,
- reply_to_message_id=message.id
- )
-
-
-def main():
- # Pyrogram setup
- client = Client("example")
-
- # Set the update_handler callback function
- client.set_update_handler(update_handler)
- client.start()
-
- # Blocks the program execution until you press CTRL+C then
- # automatically stops the Client by closing the underlying connection
- client.idle()
-
-
-if __name__ == "__main__":
- main()
diff --git a/examples/data/pyrogram.png b/examples/data/pyrogram.png
deleted file mode 100644
index 57bfefe8..00000000
Binary files a/examples/data/pyrogram.png and /dev/null differ
diff --git a/examples/echo_bot.py b/examples/echo_bot.py
new file mode 100644
index 00000000..c33449f6
--- /dev/null
+++ b/examples/echo_bot.py
@@ -0,0 +1,17 @@
+from pyrogram import Client, Filters
+
+"""This simple echo bot replies to every private text message"""
+
+app = Client("my_account")
+
+
+@app.on_message(Filters.text & Filters.private)
+def echo(client, message):
+ client.send_message(
+ message.chat.id, message.text,
+ reply_to_message_id=message.message_id
+ )
+
+
+app.start()
+app.idle()
diff --git a/examples/get_history.py b/examples/get_history.py
index 34e6a34c..f7d28818 100644
--- a/examples/get_history.py
+++ b/examples/get_history.py
@@ -4,8 +4,8 @@ from pyrogram import Client
from pyrogram.api import functions
from pyrogram.api.errors import FloodWait
-client = Client("example")
-client.start()
+app = Client("my_account")
+app.start()
target = "me" # "me" refers to your own chat (Saved Messages)
history = [] # List that will contain all the messages of the target chat
@@ -14,9 +14,9 @@ offset = 0 # Offset starts at 0
while True:
try:
- messages = client.send(
+ messages = app.send(
functions.messages.GetHistory(
- client.resolve_peer(target),
+ app.resolve_peer(target),
0, 0, offset, limit, 0, 0, 0
)
)
@@ -31,7 +31,7 @@ while True:
history.extend(messages.messages)
offset += limit
-client.stop()
+app.stop()
# Now the "history" list contains all the messages sorted by date in
# descending order (from the most recent to the oldest one)
diff --git a/examples/get_participants.py b/examples/get_participants.py
index 89b01f60..9f63424b 100644
--- a/examples/get_participants.py
+++ b/examples/get_participants.py
@@ -4,8 +4,8 @@ from pyrogram import Client
from pyrogram.api import functions, types
from pyrogram.api.errors import FloodWait
-client = Client("example")
-client.start()
+app = Client("my_account")
+app.start()
target = "username" # Target channel/supergroup
users = [] # List that will contain all the users of the target chat
@@ -14,9 +14,9 @@ offset = 0 # Offset starts at 0
while True:
try:
- participants = client.send(
+ participants = app.send(
functions.channels.GetParticipants(
- channel=client.resolve_peer(target),
+ channel=app.resolve_peer(target),
filter=types.ChannelParticipantsSearch(""), # Filter by empty string (search for all)
offset=offset,
limit=limit,
@@ -35,6 +35,6 @@ while True:
users.extend(participants.users)
offset += limit
-client.stop()
+app.stop()
# Now the "users" list contains all the members of the target chat
diff --git a/examples/get_participants2.py b/examples/get_participants2.py
index 23ed328f..799fddcc 100644
--- a/examples/get_participants2.py
+++ b/examples/get_participants2.py
@@ -15,8 +15,8 @@ This can be further improved by also searching for non-ascii characters (e.g.: J
as some user names may not contain ascii letters at all.
"""
-client = Client("example")
-client.start()
+app = Client("my_account")
+app.start()
target = "username" # Target channel/supergroup username or id
users = {} # To ensure uniqueness, users will be stored in a dictionary with user_id as key
@@ -31,9 +31,9 @@ for q in queries:
while True:
try:
- participants = client.send(
+ participants = app.send(
functions.channels.GetParticipants(
- channel=client.resolve_peer(target),
+ channel=app.resolve_peer(target),
filter=types.ChannelParticipantsSearch(q),
offset=offset,
limit=limit,
@@ -60,4 +60,4 @@ for q in queries:
print("Total users: {}".format(len(users)))
-client.stop()
+app.stop()
diff --git a/examples/hello_world.py b/examples/hello_world.py
index 5c3f0304..cd338ed5 100644
--- a/examples/hello_world.py
+++ b/examples/hello_world.py
@@ -1,19 +1,18 @@
from pyrogram import Client
+"""This example demonstrates a simple API methods usage"""
+
# Create a new Client
-client = Client("example")
+app = Client("my_account")
# Start the Client
-client.start()
+app.start()
# Send a message to yourself, Markdown is enabled by default
-client.send_message("me", "Hi there! I'm using **Pyrogram**")
-
-# Send a photo with a formatted caption to yourself
-client.send_photo("me", "data/pyrogram.png", "__This is a formatted__ **caption**")
+app.send_message("me", "Hi there! I'm using **Pyrogram**")
# Send a location to yourself
-client.send_location("me", 51.500729, -0.124583)
+app.send_location("me", 51.500729, -0.124583)
# Stop the client
-client.stop()
+app.stop()
diff --git a/examples/inline_bots.py b/examples/inline_bots.py
index d5bd43fb..a0796281 100644
--- a/examples/inline_bots.py
+++ b/examples/inline_bots.py
@@ -1,15 +1,15 @@
from pyrogram import Client
# Create a new Client
-client = Client("example")
+app = Client("my_account")
# Start the Client
-client.start()
+app.start()
# Get bot results for "Fuzz Universe" from the inline bot @vid
-bot_results = client.get_inline_bot_results("vid", "Fuzz Universe")
+bot_results = app.get_inline_bot_results("vid", "Fuzz Universe")
# Send the first result (bot_results.results[0]) to your own chat (Saved Messages)
-client.send_inline_bot_result("me", bot_results.query_id, bot_results.results[0].id)
+app.send_inline_bot_result("me", bot_results.query_id, bot_results.results[0].id)
# Stop the client
-client.stop()
+app.stop()
diff --git a/examples/raw_update_handler.py b/examples/raw_update_handler.py
new file mode 100644
index 00000000..2590c64b
--- /dev/null
+++ b/examples/raw_update_handler.py
@@ -0,0 +1,14 @@
+from pyrogram import Client
+
+"""This example shows how to handle raw updates"""
+
+app = Client("my_account")
+
+
+@app.on_raw_update()
+def raw(client, update, users, chats):
+ print(update)
+
+
+app.start()
+app.idle()
diff --git a/examples/simple_echo.py b/examples/simple_echo.py
deleted file mode 100644
index 14abce2e..00000000
--- a/examples/simple_echo.py
+++ /dev/null
@@ -1,34 +0,0 @@
-from pyrogram import Client
-from pyrogram.api import types
-
-"""This simple example bot will reply to all private text messages"""
-
-
-def update_handler(client, update, users, chats):
- if isinstance(update, types.UpdateNewMessage): # Filter by UpdateNewMessage (Private Messages)
- message = update.message # type: types.Message
-
- if isinstance(message, types.Message): # Filter by Message to exclude MessageService and MessageEmpty
- if isinstance(message.to_id, types.PeerUser): # Private Messages (Message from user)
- client.send_message(
- chat_id=message.from_id,
- text=message.message,
- reply_to_message_id=message.id
- )
-
-
-def main():
- # Pyrogram setup
- client = Client("example")
-
- # Set the update_handler callback function
- client.set_update_handler(update_handler)
- client.start()
-
- # Blocks the program execution until you press CTRL+C then
- # automatically stops the Client by closing the underlying connection
- client.idle()
-
-
-if __name__ == "__main__":
- main()
diff --git a/examples/updates.py b/examples/updates.py
deleted file mode 100644
index db28eeb6..00000000
--- a/examples/updates.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from pyrogram import Client
-
-
-# This function will be called every time a new Update is received from Telegram
-def update_handler(client, update, users, chats):
- # Send EVERY update that arrives to your own chat (Saved Messages)
- # Use triple backticks to make the text look nicer.
- client.send_message("me", "```\n" + str(update) + "```")
-
-
-def main():
- # Pyrogram setup
- client = Client("example")
-
- # Set the update_handler callback function
- client.set_update_handler(update_handler)
- client.start()
-
- # Blocks the program execution until you press CTRL+C then
- # automatically stops the Client by closing the underlying connection
- client.idle()
-
-
-if __name__ == "__main__":
- main()
diff --git a/examples/welcome_bot.py b/examples/welcome_bot.py
index d2e00a88..ac85d582 100644
--- a/examples/welcome_bot.py
+++ b/examples/welcome_bot.py
@@ -1,52 +1,32 @@
-from pyrogram import Client, Emoji
-from pyrogram.api import types
+from pyrogram import Client, Emoji, Filters
"""
This is the Welcome Bot in @PyrogramChat
-The code is commented to help you understand each part
-
-It also uses the Emoji module to easily add emojis in your text messages
+It uses the Emoji module to easily add emojis in your text messages and Filters
+to make it only work for specific messages in a specific chat
"""
-# Your Supergroup ID
-SUPERGROUP_ID = 1387666944
+app = Client("my_account")
-def update_handler(client, update, users, chats):
- # Supergroup messages are contained in the "UpdateNewChannelMessage" update type
- if isinstance(update, types.UpdateNewChannelMessage):
- message = update.message
- # When a user joins, a "MessageService" is received
- if isinstance(message, types.MessageService):
- # Check if the message is sent to your SUPERGROUP_ID
- if message.to_id.channel_id == SUPERGROUP_ID:
- # A "MessageService" contains the "action" field.
- # The action for user joins is "MessageActionChatAddUser" if the user
- # joined using the username, otherwise is "MessageActionChatJoinedByLink" if
- # the user joined a private group by link
- if isinstance(message.action, (types.MessageActionChatAddUser, types.MessageActionChatJoinedByLink)):
- # Now send the welcome message. Extra info about a user (such as the first_name, username, ...)
- # are contained in the users dictionary and can be accessed by the user ID
- client.send_message(
- SUPERGROUP_ID,
- "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s "
- "group chat, [{}](tg://user?id={})!".format(
- Emoji.SPARKLES, # Add an emoji
- users[message.from_id].first_name,
- users[message.from_id].id
- ),
- reply_to_message_id=message.id,
- disable_web_page_preview=True
- )
+@app.on_message(Filters.chat("PyrogramChat") & Filters.new_chat_members)
+def welcome(client, message):
+ new_members = ", ".join([
+ "[{}](tg://user?id={})".format(i.first_name, i.id)
+ for i in message.new_chat_members]
+ )
+
+ text = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!".format(
+ Emoji.SPARKLES,
+ new_members
+ )
+
+ client.send_message(
+ message.chat.id, text,
+ reply_to_message_id=message.message_id,
+ disable_web_page_preview=True
+ )
-def main():
- client = Client("example")
- client.set_update_handler(update_handler)
-
- client.start()
- client.idle()
-
-
-if __name__ == "__main__":
- main()
+app.start()
+app.idle()
diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 96ce0e30..4dd090be 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -23,12 +23,16 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès str:
return dumps(self, cls=Encoder, indent=4)
+ def __bool__(self) -> bool:
+ return True
+
def __eq__(self, other) -> bool:
return self.__dict__ == other.__dict__
@@ -50,6 +53,15 @@ class Object:
return getattr(self, item)
+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):
def default(self, o: Object):
try:
@@ -60,7 +72,10 @@ class Encoder(JSONEncoder):
else:
return repr(o)
- return OrderedDict(
- [("_", objects.get(getattr(o, "ID", None), None))]
- + [i for i in content.items()]
- )
+ if "pyrogram" in objects.get(getattr(o, "ID", "")):
+ return remove_none(OrderedDict([i for i in content.items()]))
+ else:
+ return OrderedDict(
+ [("_", objects.get(getattr(o, "ID", None), None))]
+ + [i for i in content.items()]
+ )
diff --git a/pyrogram/client/__init__.py b/pyrogram/client/__init__.py
index abda4464..b2935dad 100644
--- a/pyrogram/client/__init__.py
+++ b/pyrogram/client/__init__.py
@@ -18,5 +18,5 @@
from .chat_action import ChatAction
from .client import Client
-from .parse_mode import ParseMode
from .emoji import Emoji
+from .parse_mode import ParseMode
diff --git a/pyrogram/client/chat_action.py b/pyrogram/client/chat_action.py
index d2a8c35a..95bffb8e 100644
--- a/pyrogram/client/chat_action.py
+++ b/pyrogram/client/chat_action.py
@@ -21,7 +21,7 @@ from pyrogram.api import types
class ChatAction:
"""This class provides a convenient access to all Chat Actions available.
- It is intended to be used with :obj:`pyrogram.Client.send_chat_action`.
+ Chat Actions are intended to be used with :meth:`send_chat_action() `.
"""
CANCEL = types.SendMessageCancelAction
diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py
index 96257ff3..a1033629 100644
--- a/pyrogram/client/client.py
+++ b/pyrogram/client/client.py
@@ -36,6 +36,7 @@ from queue import Queue
from signal import signal, SIGINT, SIGTERM, SIGABRT
from threading import Event, Thread
+import pyrogram
from pyrogram.api import functions, types
from pyrogram.api.core import Object
from pyrogram.api.errors import (
@@ -44,12 +45,15 @@ from pyrogram.api.errors import (
PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded,
PasswordHashInvalid, FloodWait, PeerIdInvalid, FilePartMissing,
ChatAdminRequired, FirstnameInvalid, PhoneNumberBanned,
- VolumeLocNotFound, UserMigrate)
+ VolumeLocNotFound, UserMigrate, FileIdInvalid)
from pyrogram.crypto import AES
from pyrogram.session import Auth, Session
from pyrogram.session.internals import MsgId
+from . import message_parser
from . import utils
-from .input_media import InputMedia
+from .dispatcher import Dispatcher
+from .input_media_photo import InputMediaPhoto
+from .input_media_video import InputMediaVideo
from .style import Markdown, HTML
from .syncer import Syncer
@@ -124,6 +128,18 @@ class Client:
DOWNLOAD_WORKERS = 1
OFFLINE_SLEEP = 300
+ MEDIA_TYPE_ID = {
+ 0: "thumbnail",
+ 2: "photo",
+ 3: "voice",
+ 4: "video",
+ 5: "document",
+ 8: "sticker",
+ 9: "audio",
+ 10: "gif",
+ 13: "video_note"
+ }
+
def __init__(self,
session_name: str,
api_id: int or str = None,
@@ -176,10 +192,64 @@ class Client:
self.is_idle = None
self.updates_queue = Queue()
- self.update_queue = Queue()
+ self.updates_workers_list = []
+ self.download_queue = Queue()
+ self.download_workers_list = []
+
+ self.dispatcher = Dispatcher(self, workers)
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 `):
+ 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 (``Handler``):
+ The handler to be registered.
+
+ group (``int``, optional):
+ The group identifier, defaults to 0.
+ """
+ self.dispatcher.add_handler(handler, group)
def start(self):
"""Use this method to start the Client after creating it.
@@ -234,13 +304,26 @@ class Client:
self.send(functions.updates.GetState())
for i in range(self.UPDATES_WORKERS):
- Thread(target=self.updates_worker, name="UpdatesWorker#{}".format(i + 1)).start()
+ self.updates_workers_list.append(
+ Thread(
+ target=self.updates_worker,
+ name="UpdatesWorker#{}".format(i + 1)
+ )
+ )
- for i in range(self.workers):
- Thread(target=self.update_worker, name="UpdateWorker#{}".format(i + 1)).start()
+ self.updates_workers_list[-1].start()
for i in range(self.DOWNLOAD_WORKERS):
- Thread(target=self.download_worker, name="DownloadWorker#{}".format(i + 1)).start()
+ self.download_workers_list.append(
+ Thread(
+ target=self.download_worker,
+ name="DownloadWorker#{}".format(i + 1)
+ )
+ )
+
+ self.download_workers_list[-1].start()
+
+ self.dispatcher.start()
mimetypes.init()
Syncer.add(self)
@@ -252,18 +335,23 @@ class Client:
if not self.is_started:
raise ConnectionError("Client is already stopped")
- self.is_started = False
- self.session.stop()
-
for _ in range(self.UPDATES_WORKERS):
self.updates_queue.put(None)
- for _ in range(self.workers):
- self.update_queue.put(None)
+ for i in self.updates_workers_list:
+ i.join()
for _ in range(self.DOWNLOAD_WORKERS):
self.download_queue.put(None)
+ for i in self.download_workers_list:
+ i.join()
+
+ self.dispatcher.stop()
+
+ self.is_started = False
+ self.session.stop()
+
Syncer.remove(self)
def authorize_bot(self):
@@ -530,73 +618,86 @@ class Client:
while True:
media = self.download_queue.get()
- temp_file_path = ""
- final_file_path = ""
if media is None:
break
+ temp_file_path = ""
+ final_file_path = ""
+
try:
media, file_name, done, progress, path = media
+ file_id = media.file_id
+ size = media.file_size
+
directory, file_name = os.path.split(file_name)
directory = directory or "downloads"
- if isinstance(media, types.MessageMediaDocument):
- document = media.document
+ try:
+ decoded = utils.decode(file_id)
+ fmt = " 24 else " 24:
+ volume_id = unpacked[4]
+ secret = unpacked[5]
+ local_id = unpacked[6]
- for i in document.attributes:
- if isinstance(i, types.DocumentAttributeFilename):
- file_name = i.file_name
- break
- elif isinstance(i, types.DocumentAttributeSticker):
- file_name = file_name.replace("doc", "sticker")
- elif isinstance(i, types.DocumentAttributeAudio):
- file_name = file_name.replace("doc", "audio")
- elif isinstance(i, types.DocumentAttributeVideo):
- file_name = file_name.replace("doc", "video")
- elif isinstance(i, types.DocumentAttributeAnimated):
- file_name = file_name.replace("doc", "gif")
+ media_type_str = Client.MEDIA_TYPE_ID.get(media_type, None)
- temp_file_path = self.get_file(
- dc_id=document.dc_id,
- id=document.id,
- access_hash=document.access_hash,
- version=document.version,
- size=document.size,
- progress=progress
- )
- elif isinstance(media, (types.MessageMediaPhoto, types.Photo)):
- if isinstance(media, types.MessageMediaPhoto):
- photo = media.photo
+ if media_type_str:
+ log.info("The file_id belongs to a {}".format(media_type_str))
else:
- photo = media
+ raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
- if isinstance(photo, types.Photo):
- if not file_name:
- file_name = "photo_{}_{}.jpg".format(
- datetime.fromtimestamp(photo.date).strftime("%Y-%m-%d_%H-%M-%S"),
- self.rnd_id()
- )
+ file_name = file_name or getattr(media, "file_name", None)
- photo_loc = photo.sizes[-1].location
+ if not file_name:
+ if media_type == 3:
+ extension = ".ogg"
+ elif media_type in (4, 10, 13):
+ extension = mimetypes.guess_extension(media.mime_type) or ".mp4"
+ elif media_type == 5:
+ extension = mimetypes.guess_extension(media.mime_type) or ".unknown"
+ elif media_type == 8:
+ extension = ".webp"
+ elif media_type == 9:
+ extension = mimetypes.guess_extension(media.mime_type) or ".mp3"
+ elif media_type == 0:
+ extension = ".jpg"
+ elif media_type == 2:
+ extension = ".jpg"
+ else:
+ continue
- temp_file_path = self.get_file(
- dc_id=photo_loc.dc_id,
- volume_id=photo_loc.volume_id,
- local_id=photo_loc.local_id,
- secret=photo_loc.secret,
- size=photo.sizes[-1].size,
- progress=progress
- )
+ file_name = "{}_{}_{}{}".format(
+ media_type_str,
+ datetime.fromtimestamp(media.date or time.time()).strftime("%Y-%m-%d_%H-%M-%S"),
+ self.rnd_id(),
+ extension
+ )
+
+ temp_file_path = self.get_file(
+ dc_id=dc_id,
+ id=id,
+ access_hash=access_hash,
+ volume_id=volume_id,
+ local_id=local_id,
+ secret=secret,
+ size=size,
+ progress=progress
+ )
if temp_file_path:
final_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name)))
@@ -680,7 +781,7 @@ class Client:
if len(self.channels_pts[channel_id]) > 50:
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)):
diff = self.send(
functions.updates.GetDifference(
@@ -690,7 +791,7 @@ class Client:
)
)
- self.update_queue.put((
+ self.dispatcher.updates.put((
types.UpdateNewMessage(
message=diff.new_messages[0],
pts=updates.pts,
@@ -700,30 +801,7 @@ class Client:
diff.chats
))
elif isinstance(updates, types.UpdateShort):
- self.update_queue.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]}
- )
+ self.dispatcher.updates.put((updates.update, [], []))
except Exception as e:
log.error(e, exc_info=True)
@@ -750,47 +828,6 @@ class Client:
while self.is_idle:
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 `):
- 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 ` type.
-
- users (``dict``):
- Dictionary of all :obj:`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 ` and
- :obj:`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 `
- - :obj:`ChatEmpty `
- - :obj:`ChatForbidden `
- - :obj:`ChannelForbidden `
- """
- self.update_handler = callback
-
def send(self, data: Object):
"""Use this method to send Raw Function queries.
@@ -826,7 +863,10 @@ class Client:
self.api_id = parser.getint("pyrogram", "api_id")
self.api_hash = parser.get("pyrogram", "api_hash")
else:
- raise AttributeError("No API Key found")
+ raise AttributeError(
+ "No API Key found. "
+ "More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration"
+ )
if self.proxy:
pass
@@ -1113,7 +1153,9 @@ class Client:
photo (``str``):
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):
Photo caption, 0-200 characters.
@@ -1147,23 +1189,55 @@ class Client:
The size of the file.
Returns:
- On success, the sent Message is returned.
+ On success, the sent :obj:`Message ` is returned.
Raises:
:class:`Error `
"""
+ file = None
style = self.html if parse_mode.lower() == "html" else self.markdown
- file = self.save_file(photo, progress=progress)
+
+ if os.path.exists(photo):
+ 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 = utils.decode(photo)
+ fmt = " 24 else "` is returned.
Raises:
:class:`Error `
"""
+ file = None
style = self.html if parse_mode.lower() == "html" else self.markdown
- file = self.save_file(audio, progress=progress)
+
+ if os.path.exists(audio):
+ file = self.save_file(audio, progress=progress)
+ media = types.InputMediaUploadedDocument(
+ mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"),
+ file=file,
+ attributes=[
+ types.DocumentAttributeAudio(
+ duration=duration,
+ performer=performer,
+ title=title
+ ),
+ types.DocumentAttributeFilename(os.path.basename(audio))
+ ]
+ )
+ elif audio.startswith("http"):
+ media = types.InputMediaDocumentExternal(
+ url=audio
+ )
+ else:
+ try:
+ decoded = utils.decode(audio)
+ fmt = " 24 else "` is returned.
Raises:
:class:`Error `
"""
+ file = None
style = self.html if parse_mode.lower() == "html" else self.markdown
- file = self.save_file(document, progress=progress)
+
+ if os.path.exists(document):
+ file = self.save_file(document, progress=progress)
+ media = types.InputMediaUploadedDocument(
+ mime_type=mimetypes.types_map.get("." + document.split(".")[-1], "text/plain"),
+ file=file,
+ attributes=[
+ types.DocumentAttributeFilename(os.path.basename(document))
+ ]
+ )
+ elif document.startswith("http"):
+ media = types.InputMediaDocumentExternal(
+ url=document
+ )
+ else:
+ try:
+ decoded = utils.decode(document)
+ fmt = " 24 else "` is returned.
Raises:
:class:`Error `
"""
- file = self.save_file(sticker, progress=progress)
+ file = None
+
+ if os.path.exists(sticker):
+ file = self.save_file(sticker, progress=progress)
+ media = types.InputMediaUploadedDocument(
+ mime_type="image/webp",
+ file=file,
+ attributes=[
+ types.DocumentAttributeFilename(os.path.basename(sticker))
+ ]
+ )
+ elif sticker.startswith("http"):
+ media = types.InputMediaDocumentExternal(
+ url=sticker
+ )
+ else:
+ try:
+ decoded = utils.decode(sticker)
+ fmt = " 24 else "` is returned.
Raises:
:class:`Error `
"""
+ file = None
style = self.html if parse_mode.lower() == "html" else self.markdown
- file = self.save_file(video, progress=progress)
- file_thumb = None if thumb is None else self.save_file(thumb)
+
+ if os.path.exists(video):
+ thumb = None if thumb is None else self.save_file(thumb)
+ file = self.save_file(video, progress=progress)
+ media = types.InputMediaUploadedDocument(
+ mime_type=mimetypes.types_map[".mp4"],
+ file=file,
+ thumb=thumb,
+ attributes=[
+ types.DocumentAttributeVideo(
+ supports_streaming=supports_streaming or None,
+ duration=duration,
+ w=width,
+ h=height
+ ),
+ types.DocumentAttributeFilename(os.path.basename(video))
+ ]
+ )
+ elif video.startswith("http"):
+ media = types.InputMediaDocumentExternal(
+ url=video
+ )
+ else:
+ try:
+ decoded = utils.decode(video)
+ fmt = " 24 else "` is returned.
Raises:
:class:`Error `
"""
+ file = None
style = self.html if parse_mode.lower() == "html" else self.markdown
- file = self.save_file(voice, progress=progress)
+
+ if os.path.exists(voice):
+ file = self.save_file(voice, progress=progress)
+ media = types.InputMediaUploadedDocument(
+ mime_type=mimetypes.types_map.get("." + voice.split(".")[-1], "audio/mpeg"),
+ file=file,
+ attributes=[
+ types.DocumentAttributeAudio(
+ voice=True,
+ duration=duration
+ )
+ ]
+ )
+ elif voice.startswith("http"):
+ media = types.InputMediaDocumentExternal(
+ url=voice
+ )
+ else:
+ try:
+ decoded = utils.decode(voice)
+ fmt = " 24 else "` is returned.
Raises:
:class:`Error `
"""
- file = self.save_file(video_note, progress=progress)
+ file = None
+
+ if os.path.exists(video_note):
+ file = self.save_file(video_note, progress=progress)
+ media = types.InputMediaUploadedDocument(
+ mime_type=mimetypes.types_map[".mp4"],
+ file=file,
+ attributes=[
+ types.DocumentAttributeVideo(
+ round_message=True,
+ duration=duration,
+ w=length,
+ h=length
+ )
+ ]
+ )
+ else:
+ try:
+ decoded = utils.decode(video_note)
+ fmt = " 24 else "` or
+ :obj:`InputMediaVideo ` objects
describing photos and videos to be sent, must include 2–10 items.
disable_notification (``bool``, optional):
@@ -1726,66 +2026,104 @@ class Client:
multi_media = []
for i in media:
- if isinstance(i, InputMedia.Photo):
- style = self.html if i.parse_mode.lower() == "html" else self.markdown
- media = self.save_file(i.media)
+ style = self.html if i.parse_mode.lower() == "html" else self.markdown
- media = self.send(
- functions.messages.UploadMedia(
- peer=self.resolve_peer(chat_id),
- media=types.InputMediaUploadedPhoto(
- file=media
+ if isinstance(i, InputMediaPhoto):
+ if os.path.exists(i.media):
+ media = self.send(
+ functions.messages.UploadMedia(
+ peer=self.resolve_peer(chat_id),
+ media=types.InputMediaUploadedPhoto(
+ file=self.save_file(i.media)
+ )
)
)
- )
- single_media = types.InputSingleMedia(
- media=types.InputMediaPhoto(
+ media = types.InputMediaPhoto(
id=types.InputPhoto(
id=media.photo.id,
access_hash=media.photo.access_hash
)
- ),
- random_id=self.rnd_id(),
- **style.parse(i.caption)
- )
+ )
+ else:
+ try:
+ decoded = utils.decode(i.media)
+ fmt = " 24 else " 24 else "` is returned.
Raises:
:class:`Error `
"""
- return self.send(
+ r = self.send(
functions.messages.SendMedia(
peer=self.resolve_peer(chat_id),
media=types.InputMediaGeoPoint(
@@ -1846,6 +2184,13 @@ class Client:
)
)
+ 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_venue(self,
chat_id: int or str,
latitude: float,
@@ -1887,12 +2232,12 @@ class Client:
If the message is a reply, ID of the original message
Returns:
- On success, the sent Message is returned.
+ On success, the sent :obj:`Message ` is returned.
Raises:
:class:`Error `
"""
- return self.send(
+ r = self.send(
functions.messages.SendMedia(
peer=self.resolve_peer(chat_id),
media=types.InputMediaVenue(
@@ -1913,11 +2258,18 @@ class Client:
)
)
+ 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_contact(self,
chat_id: int or str,
phone_number: str,
first_name: str,
- last_name: str,
+ last_name: str = "",
disable_notification: bool = None,
reply_to_message_id: int = None):
"""Use this method to send phone contacts.
@@ -1946,12 +2298,12 @@ class Client:
If the message is a reply, ID of the original message.
Returns:
- On success, the sent Message is returned.
+ On success, the sent :obj:`Message ` is returned.
Raises:
:class:`Error `
"""
- return self.send(
+ r = self.send(
functions.messages.SendMedia(
peer=self.resolve_peer(chat_id),
media=types.InputMediaContact(
@@ -1966,6 +2318,13 @@ class Client:
)
)
+ 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_chat_action(self,
chat_id: int or str,
action: callable,
@@ -1987,6 +2346,9 @@ class Client:
progress (``int``, optional):
Progress of the upload process.
+ Returns:
+ On success, True is returned.
+
Raises:
:class:`Error `
"""
@@ -2002,6 +2364,7 @@ class Client:
)
)
+ # TODO: Improvements for the new API
def get_user_profile_photos(self,
user_id: int or str,
offset: int = 0,
@@ -2061,12 +2424,15 @@ class Client:
disable_web_page_preview (``bool``, optional):
Disables link previews for links in this message.
+ Returns:
+ On success, the edited :obj:`Message ` is returned.
+
Raises:
:class:`Error `
"""
style = self.html if parse_mode.lower() == "html" else self.markdown
- return self.send(
+ r = self.send(
functions.messages.EditMessage(
peer=self.resolve_peer(chat_id),
id=message_id,
@@ -2075,6 +2441,13 @@ class Client:
)
)
+ for i in r.updates:
+ if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)):
+ 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 edit_message_caption(self,
chat_id: int or str,
message_id: int,
@@ -2100,12 +2473,15 @@ class Client:
if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption.
Defaults to Markdown.
+ Returns:
+ On success, the edited :obj:`Message ` is returned.
+
Raises:
:class:`Error `
"""
style = self.html if parse_mode.lower() == "html" else self.markdown
- return self.send(
+ r = self.send(
functions.messages.EditMessage(
peer=self.resolve_peer(chat_id),
id=message_id,
@@ -2113,6 +2489,13 @@ class Client:
)
)
+ for i in r.updates:
+ if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)):
+ 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 delete_messages(self,
chat_id: int or str,
message_ids: list,
@@ -2163,7 +2546,7 @@ class Client:
)
)
- # TODO: Remove redundant code
+ # TODO: Improvements for the new API
def save_file(self,
path: str,
file_id: int = None,
@@ -2238,6 +2621,7 @@ class Client:
finally:
session.stop()
+ # TODO: Improvements for the new API
def get_file(self,
dc_id: int,
id: int = None,
@@ -2676,15 +3060,16 @@ class Client:
return False
def download_media(self,
- message: types.Message,
+ message: pyrogram.Message,
file_name: str = "",
block: bool = True,
progress: callable = None):
"""Use this method to download the media from a Message.
Args:
- message (:obj:`Message `):
- The Message containing the media.
+ message (:obj:`Message ` | ``str``):
+ Pass a Message containing the media, the media itself (message.audio, message.video, ...) or
+ the file id as string.
file_name (``str``, optional):
A custom *file_name* to be used instead of the one provided by Telegram.
@@ -2699,6 +3084,7 @@ class Client:
progress (``callable``):
Pass a callback function to view the download progress.
The function must accept two arguments (current, total).
+ Note that this will not work in case you are downloading a media using a *file_id*.
Other Parameters:
current (``int``):
@@ -2713,67 +3099,51 @@ class Client:
Raises:
:class:`Error `
"""
- if isinstance(message, (types.Message, types.Photo)):
- done = Event()
- path = [None]
-
- if isinstance(message, types.Message):
- media = message.media
- else:
- media = message
-
- if media is not None:
- self.download_queue.put((media, file_name, done, progress, path))
+ if isinstance(message, pyrogram.Message):
+ if message.photo:
+ media = message.photo[-1]
+ elif message.audio:
+ media = message.audio
+ elif message.document:
+ media = message.document
+ elif message.video:
+ media = message.video
+ elif message.voice:
+ media = message.voice
+ elif message.video_note:
+ media = message.video_note
+ elif message.sticker:
+ media = message.sticker
else:
return
-
- if block:
- done.wait()
-
- return path[0]
-
- def download_photo(self,
- photo: types.Photo or types.UserProfilePhoto or types.ChatPhoto,
- file_name: str = "",
- block: bool = True):
- """Use this method to download a photo not contained inside a Message.
- For example, a photo of a User or a Chat/Channel.
-
- Args:
- photo (:obj:`Photo ` | :obj:`UserProfilePhoto ` | :obj:`ChatPhoto `):
- The photo object.
-
- file_name (``str``, optional):
- A custom *file_name* to be used instead of the one provided by Telegram.
- By default, all photos are downloaded in the *downloads* folder in your working directory.
- You can also specify a path for downloading photos in a custom location: paths that end with "/"
- are considered directories. All non-existent folders will be created automatically.
-
- block (``bool``, optional):
- Blocks the code execution until the photo has been downloaded.
- Defaults to True.
-
- Returns:
- On success, the absolute path of the downloaded photo as string is returned, None otherwise.
-
- Raises:
- :class:`Error `
- """
- if isinstance(photo, (types.UserProfilePhoto, types.ChatPhoto)):
- photo = types.Photo(
- id=0,
- access_hash=0,
- date=int(time.time()),
- sizes=[types.PhotoSize(
- type="",
- location=photo.photo_big,
- w=0,
- h=0,
- size=0
- )]
+ elif isinstance(message, (
+ pyrogram.PhotoSize,
+ pyrogram.Audio,
+ pyrogram.Document,
+ pyrogram.Video,
+ pyrogram.Voice,
+ pyrogram.VideoNote,
+ pyrogram.Sticker
+ )):
+ media = message
+ elif isinstance(message, str):
+ media = pyrogram.Document(
+ file_id=message,
+ file_size=0,
+ mime_type=""
)
+ else:
+ return
- return self.download_media(photo, file_name, block)
+ done = Event()
+ path = [None]
+
+ self.download_queue.put((media, file_name, done, progress, path))
+
+ if block:
+ done.wait()
+
+ return path[0]
def add_contacts(self, contacts: list):
"""Use this method to add contacts to your Telegram address book.
@@ -2946,7 +3316,7 @@ class Client:
A list of Message identifiers in the chat specified in *chat_id*.
Returns:
- List of the requested messages
+ On success, a list of the requested :obj:`Messages ` is returned.
Raises:
:class:`Error `
@@ -2964,4 +3334,21 @@ class Client:
id=message_ids
)
- return self.send(rpc)
+ r = self.send(rpc)
+
+ users = {i.id: i for i in r.users}
+ chats = {i.id: i for i in r.chats}
+
+ messages = []
+
+ for i in r.messages:
+ if isinstance(i, types.Message):
+ parser = message_parser.parse_message
+ elif isinstance(i, types.MessageService):
+ parser = message_parser.parse_message_service
+ else:
+ continue
+
+ messages.append(parser(self, i, users, chats))
+
+ return messages
diff --git a/pyrogram/client/dispatcher/__init__.py b/pyrogram/client/dispatcher/__init__.py
new file mode 100644
index 00000000..c0cb368a
--- /dev/null
+++ b/pyrogram/client/dispatcher/__init__.py
@@ -0,0 +1,19 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2018 Dan Tès
+#
+# 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 .
+
+from .dispatcher import Dispatcher
diff --git a/pyrogram/client/dispatcher/dispatcher.py b/pyrogram/client/dispatcher/dispatcher.py
new file mode 100644
index 00000000..72bcbfc9
--- /dev/null
+++ b/pyrogram/client/dispatcher/dispatcher.py
@@ -0,0 +1,159 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2018 Dan Tès
+#
+# 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 .
+
+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, MessageHandler
+
+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.workers_list = []
+ self.updates = Queue()
+ self.groups = OrderedDict()
+
+ def start(self):
+ for i in range(self.workers):
+ self.workers_list.append(
+ Thread(
+ target=self.update_worker,
+ name="UpdateWorker#{}".format(i + 1)
+ )
+ )
+
+ self.workers_list[-1].start()
+
+ def stop(self):
+ for _ in range(self.workers):
+ self.updates.put(None)
+
+ for i in self.workers_list:
+ i.join()
+
+ 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:
+ if not isinstance(handler, MessageHandler):
+ continue
+
+ 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))
diff --git a/pyrogram/client/filters/__init__.py b/pyrogram/client/filters/__init__.py
new file mode 100644
index 00000000..88ae14e3
--- /dev/null
+++ b/pyrogram/client/filters/__init__.py
@@ -0,0 +1,19 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2018 Dan Tès
+#
+# 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 .
+
+from .filters import Filters
diff --git a/pyrogram/client/filters/filter.py b/pyrogram/client/filters/filter.py
new file mode 100644
index 00000000..feec51df
--- /dev/null
+++ b/pyrogram/client/filters/filter.py
@@ -0,0 +1,57 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2018 Dan Tès
+#
+# 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 .
+
+
+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)
diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py
new file mode 100644
index 00000000..3386c352
--- /dev/null
+++ b/pyrogram/client/filters/filters.py
@@ -0,0 +1,235 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2018 Dan Tès
+#
+# 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 .
+
+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 Filters available in Pyrogram.
+ Filters are intended to be used with the :obj:`MessageHandler `."""
+
+ 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 :obj:`Audio ` objects."""
+
+ document = build("Document", lambda _, m: bool(m.document))
+ """Filter messages that contain :obj:`Document ` objects."""
+
+ photo = build("Photo", lambda _, m: bool(m.photo))
+ """Filter messages that contain :obj:`Photo ` objects."""
+
+ sticker = build("Sticker", lambda _, m: bool(m.sticker))
+ """Filter messages that contain :obj:`Sticker ` objects."""
+
+ video = build("Video", lambda _, m: bool(m.video))
+ """Filter messages that contain :obj:`Video ` objects."""
+
+ voice = build("Voice", lambda _, m: bool(m.voice))
+ """Filter messages that contain :obj:`Voice ` note objects."""
+
+ video_note = build("Voice", lambda _, m: bool(m.video_note))
+ """Filter messages that contain :obj:`VideoNote ` objects."""
+
+ contact = build("Contact", lambda _, m: bool(m.contact))
+ """Filter messages that contain :obj:`Contact ` objects."""
+
+ location = build("Location", lambda _, m: bool(m.location))
+ """Filter messages that contain :obj:`Location ` objects."""
+
+ venue = build("Venue", lambda _, m: bool(m.venue))
+ """Filter messages that contain :obj:`Venue ` objects."""
+
+ 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."""
+
+ 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."""
+
+ @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}
+ )
+ )
+
+ service = build(
+ "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 = build(
+ "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.voice(m)
+ or Filters.video_note(m)
+ or Filters.contact(m)
+ or Filters.location(m)
+ or Filters.venue(m)
+ )
+ )
+ """Filter all media messages."""
diff --git a/pyrogram/client/handlers/__init__.py b/pyrogram/client/handlers/__init__.py
new file mode 100644
index 00000000..d9c48359
--- /dev/null
+++ b/pyrogram/client/handlers/__init__.py
@@ -0,0 +1,19 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2018 Dan Tès
+#
+# 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 .
+
+from .handlers import MessageHandler, RawUpdateHandler
diff --git a/pyrogram/client/handlers/handler.py b/pyrogram/client/handlers/handler.py
new file mode 100644
index 00000000..0e46a205
--- /dev/null
+++ b/pyrogram/client/handlers/handler.py
@@ -0,0 +1,23 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2018 Dan Tès
+#
+# 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 .
+
+
+class Handler:
+ def __init__(self, callback: callable, filters=None):
+ self.callback = callback
+ self.filters = filters
diff --git a/pyrogram/client/handlers/handlers.py b/pyrogram/client/handlers/handlers.py
new file mode 100644
index 00000000..e909e218
--- /dev/null
+++ b/pyrogram/client/handlers/handlers.py
@@ -0,0 +1,95 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2018 Dan Tès
+#
+# 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 .
+
+from .handler import Handler
+
+
+class MessageHandler(Handler):
+ """The Message handler class. Used to handle text, media and service messages coming from
+ any chat (private, group, channel). It is intended to be used with
+ :meth:`add_handler() `
+
+ 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 `):
+ Pass one or more filters to allow only a subset of messages to be passed
+ in your callback function.
+
+ Other parameters:
+ client (:obj:`Client `):
+ The Client itself, useful when you want to call other API methods inside the message handler.
+
+ message (:obj:`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. Used to handle raw updates. It is intended to be used with
+ :meth:`add_handler() `
+
+ 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 `):
+ 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 ` type.
+
+ users (``dict``):
+ Dictionary of all :obj:`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 ` and
+ :obj:`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 `
+ - :obj:`ChatEmpty `
+ - :obj:`ChatForbidden `
+ - :obj:`ChannelForbidden `
+ """
+
+ def __init__(self, callback: callable):
+ super().__init__(callback)
diff --git a/pyrogram/client/input_media_photo.py b/pyrogram/client/input_media_photo.py
new file mode 100644
index 00000000..3f0e4488
--- /dev/null
+++ b/pyrogram/client/input_media_photo.py
@@ -0,0 +1,46 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2018 Dan Tès
+#
+# 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 .
+
+
+class InputMediaPhoto:
+ """This object represents a photo to be sent inside an album.
+ It is intended to be used with :obj:`send_media_group() `.
+
+ Args:
+ media (:obj:`str`):
+ Photo to send.
+ Pass a file_id as string to send a photo that exists on the Telegram servers or
+ pass a file path as string to upload a new photo that exists on your local machine.
+ Sending photo by a URL is currently unsupported.
+
+ caption (:obj:`str`, optional):
+ Caption of the photo to be sent, 0-200 characters
+
+ parse_mode (:obj:`str`, optional):
+ Use :obj:`MARKDOWN ` or :obj:`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
diff --git a/pyrogram/client/input_media_video.py b/pyrogram/client/input_media_video.py
new file mode 100644
index 00000000..7ad5ce50
--- /dev/null
+++ b/pyrogram/client/input_media_video.py
@@ -0,0 +1,66 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2018 Dan Tès
+#
+# 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 .
+
+
+class InputMediaVideo:
+ """This object represents a video to be sent inside an album.
+ It is intended to be used with :obj:`send_media_group() `.
+
+ Args:
+ media (:obj:`str`):
+ Video to send.
+ Pass a file_id as string to send a video that exists on the Telegram servers or
+ pass a file path as string to upload a new video that exists on your local machine.
+ Sending video by a URL is currently unsupported.
+
+ caption (:obj:`str`, optional):
+ Caption of the video to be sent, 0-200 characters
+
+ parse_mode (:obj:`str`, optional):
+ Use :obj:`MARKDOWN ` or :obj:`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
diff --git a/pyrogram/client/input_phone_contact.py b/pyrogram/client/input_phone_contact.py
index 9268ca0a..1ca8bf4c 100644
--- a/pyrogram/client/input_phone_contact.py
+++ b/pyrogram/client/input_phone_contact.py
@@ -22,19 +22,22 @@ from pyrogram.session.internals import MsgId
class InputPhoneContact:
"""This object represents a Phone Contact to be added in your Telegram address book.
- It is intended to be used with :obj:`pyrogram.Client.add_contacts`
+ It is intended to be used with :meth:`add_contacts() `
Args:
- phone (:obj:`str`):
+ phone (``str``):
Contact's phone number
- first_name (:obj:`str`):
+ first_name (``str``):
Contact's first name
- last_name (:obj:`str`, optional):
+ last_name (``str``, optional):
Contact's last name
"""
+ def __init__(self, phone: str, first_name: str, last_name: str = ""):
+ pass
+
def __new__(cls, phone: str, first_name: str, last_name: str = ""):
return RawInputPhoneContact(
client_id=MsgId(),
diff --git a/pyrogram/client/message_parser.py b/pyrogram/client/message_parser.py
new file mode 100644
index 00000000..51eba3ae
--- /dev/null
+++ b/pyrogram/client/message_parser.py
@@ -0,0 +1,581 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2018 Dan Tès
+#
+# 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 .
+
+from struct import pack
+
+import pyrogram
+from pyrogram.api import types, functions
+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_chat_photo(photo):
+ if not isinstance(photo, (types.UserProfilePhoto, types.ChatPhoto)):
+ return None
+
+ if not isinstance(photo.photo_small, types.FileLocation):
+ return None
+
+ if not isinstance(photo.photo_big, types.FileLocation):
+ return None
+
+ loc_small = photo.photo_small
+ loc_big = photo.photo_big
+
+ return pyrogram.ChatPhoto(
+ small_file_id=encode(
+ pack(
+ " 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,
+ photo=parse_chat_photo(user.photo)
+ ) 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,
+ photo=parse_chat_photo(user.photo)
+ )
+
+
+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,
+ photo=parse_chat_photo(chat.photo)
+ )
+
+
+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,
+ photo=parse_chat_photo(channel.photo)
+ )
+
+
+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(
+ " 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(
+ " 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[message.from_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(
+ "
+#
+# 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 .
+
import base64
import json
import logging
diff --git a/pyrogram/client/utils.py b/pyrogram/client/utils.py
index b95159cc..d4b1c38e 100644
--- a/pyrogram/client/utils.py
+++ b/pyrogram/client/utils.py
@@ -1,3 +1,23 @@
+# Pyrogram - Telegram MTProto API Client Library for Python
+# Copyright (C) 2017-2018 Dan Tès
+#
+# 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 .
+
+from base64 import b64decode, b64encode
+
from pyrogram.api import types
@@ -26,3 +46,39 @@ def get_offset_date(dialogs):
return m.date
else:
return 0
+
+
+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("=")
diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py
index 74d45845..449524b3 100644
--- a/pyrogram/session/auth.py
+++ b/pyrogram/session/auth.py
@@ -262,7 +262,6 @@ class Auth:
else:
raise e
- log.warning("Auth key creation failed. Let's try again: {}".format(repr(e)))
time.sleep(1)
continue
else:
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 5be2eaec..8ae70ccc 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -32,7 +32,7 @@ from pyrogram import __copyright__, __license__, __version__
from pyrogram.api import functions, types, core
from pyrogram.api.all import layer
from pyrogram.api.core import Message, Object, MsgContainer, Long, FutureSalt, Int
-from pyrogram.api.errors import Error
+from pyrogram.api.errors import Error, InternalServerError
from pyrogram.connection import Connection
from pyrogram.crypto import AES, KDF
from .internals import MsgId, MsgFactory, DataCenter
@@ -399,17 +399,19 @@ class Session:
else:
return result
- def send(self, data: Object):
- for i in range(self.MAX_RETRIES):
- self.is_connected.wait(self.WAIT_TIMEOUT)
+ def send(self, data: Object, retries: int = MAX_RETRIES):
+ self.is_connected.wait(self.WAIT_TIMEOUT)
- try:
- return self._send(data)
- except (OSError, TimeoutError):
- (log.warning if i > 2 else log.info)(
- "{}: {} Retrying {}".format(i, datetime.now(), type(data))
- )
- time.sleep(1)
- continue
- else:
- return None
+ try:
+ return self._send(data)
+ except (OSError, TimeoutError, InternalServerError) as e:
+ if retries == 0:
+ raise e from None
+
+ (log.warning if retries < 3 else log.info)(
+ "{}: {} Retrying {}".format(
+ Session.MAX_RETRIES - retries,
+ datetime.now(), type(data)))
+
+ time.sleep(0.5)
+ self.send(data, retries - 1)