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

Merge branch 'develop' into layer-85

This commit is contained in:
Dan 2018-12-13 15:13:24 +01:00
commit 85519f9caf
153 changed files with 6449 additions and 5484 deletions

View File

@ -26,7 +26,7 @@ Features
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away. - **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. - **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. - **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 81 on top of MTProto 2.0. - **Updated** to the latest Telegram API version, currently Layer 82 on top of MTProto 2.0.
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API. - **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
- **Full API**, allowing to execute any advanced action an official client is able to do, and more. - **Full API**, allowing to execute any advanced action an official client is able to do, and more.
@ -78,8 +78,7 @@ Copyright & License
<h1 align="center"> <h1 align="center">
<a href="https://github.com/pyrogram/pyrogram"> <a href="https://github.com/pyrogram/pyrogram">
<div><img src="https://media.pyrogram.ml/images/icon.png" alt="Pyrogram Icon"></div> <div><img src="https://raw.githubusercontent.com/pyrogram/logos/master/logos/pyrogram_logo2.png" alt="Pyrogram Logo"></div>
<div><img src="https://media.pyrogram.ml/images/label.png" alt="Pyrogram Label"></div>
</a> </a>
</h1> </h1>
@ -98,27 +97,27 @@ Copyright & License
<a href="https://t.me/PyrogramChat"> <a href="https://t.me/PyrogramChat">
Community Community
</a> </a>
<br><br> <br>
<a href="compiler/api/source/main_api.tl"> <a href="compiler/api/source/main_api.tl">
<img src="https://img.shields.io/badge/SCHEME-LAYER%2081-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30" <img src="https://img.shields.io/badge/schema-layer%2082-eda738.svg?longCache=true&colorA=262b30"
alt="Scheme Layer"> alt="Schema Layer">
</a> </a>
<a href="https://github.com/pyrogram/tgcrypto"> <a href="https://github.com/pyrogram/tgcrypto">
<img src="https://img.shields.io/badge/TGCRYPTO-V1.0.4-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30" <img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
alt="TgCrypto"> alt="TgCrypto">
</a> </a>
</p> </p>
.. |logo| image:: https://pyrogram.ml/images/logo.png .. |logo| image:: https://raw.githubusercontent.com/pyrogram/logos/master/logos/pyrogram_logo2.png
:target: https://pyrogram.ml :target: https://pyrogram.ml
:alt: Pyrogram :alt: Pyrogram
.. |description| replace:: **Telegram MTProto API Client Library for Python** .. |description| replace:: **Telegram MTProto API Client Library for Python**
.. |scheme| image:: "https://img.shields.io/badge/SCHEME-LAYER%2081-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30" .. |scheme| image:: "https://img.shields.io/badge/SCHEME-LAYER%2082-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
:target: compiler/api/source/main_api.tl :target: compiler/api/source/main_api.tl
:alt: Scheme Layer :alt: Scheme Layer
.. |tgcrypto| image:: "https://img.shields.io/badge/TGCRYPTO-V1.0.4-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30" .. |tgcrypto| image:: "https://img.shields.io/badge/TGCRYPTO-V1.1.1-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
:target: https://github.com/pyrogram/tgcrypto :target: https://github.com/pyrogram/tgcrypto
:alt: TgCrypto :alt: TgCrypto

View File

@ -172,9 +172,8 @@ def start():
with open("{}/source/auth_key.tl".format(HOME), encoding="utf-8") as auth, \ with open("{}/source/auth_key.tl".format(HOME), encoding="utf-8") as auth, \
open("{}/source/sys_msgs.tl".format(HOME), encoding="utf-8") as system, \ open("{}/source/sys_msgs.tl".format(HOME), encoding="utf-8") as system, \
open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api, \ open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api:
open("{}/source/pyrogram.tl".format(HOME), encoding="utf-8") as pyrogram: schema = (auth.read() + system.read() + api.read()).splitlines()
schema = (auth.read() + system.read() + api.read() + pyrogram.read()).splitlines()
with open("{}/template/mtproto.txt".format(HOME), encoding="utf-8") as f: with open("{}/template/mtproto.txt".format(HOME), encoding="utf-8") as f:
mtproto_template = f.read() mtproto_template = f.read()
@ -507,6 +506,7 @@ def start():
f.write("\n 0xb0700028: \"pyrogram.client.types.Dialog\",") f.write("\n 0xb0700028: \"pyrogram.client.types.Dialog\",")
f.write("\n 0xb0700029: \"pyrogram.client.types.Dialogs\",") f.write("\n 0xb0700029: \"pyrogram.client.types.Dialogs\",")
f.write("\n 0xb0700030: \"pyrogram.client.types.ChatMembers\",") f.write("\n 0xb0700030: \"pyrogram.client.types.ChatMembers\",")
f.write("\n 0xb0700031: \"pyrogram.client.types.UserStatus\"")
f.write("\n}\n") f.write("\n}\n")

View File

@ -1,22 +0,0 @@
// 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<MessageEntity> caption_entities:flags.12?Vector<MessageEntity> audio:flags.13?Audio document:flags.14?Document game:flags.15?Game photo:flags.16?Vector<PhotoSize> sticker:flags.17?Sticker video:flags.18?Video voice:flags.19?Voice video_note:flags.20?VideoNote caption:flags.21?string contact:flags.22?Contact location:flags.23?Location venue:flags.24?Venue new_chat_members:flags.25?Vector<User> left_chat_member:flags.26?User new_chat_title:flags.27?string new_chat_photo:flags.28?Vector<PhotoSize> delete_chat_photo:flags.29?true group_chat_created:flags.30?true supergroup_chat_created:flags.31?true channel_chat_created:flags.32?true migrate_to_chat_id:flags.33?int migrate_from_chat_id:flags.34?int pinned_message:flags.35?Message invoice:flags.36?Invoice successful_payment:flags.37?SuccessfulPayment connected_website:flags.38?string views:flags.39?int via_bot:flags.40?User = pyrogram.Message;
//pyrogram.messageEntity#b0700004 flags:# type:string offset:int length:int url:flags.0?string user:flags.1?User = pyrogram.MessageEntity;
//pyrogram.photoSize#b0700005 flags:# file_id:string 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<Vector<PhotoSize>> = pyrogram.UserProfilePhotos;
//pyrogram.chatPhoto#b0700015 small_file_id:string big_file_id:string = pyrogram.ChatPhoto;
//pyrogram.chatMember#b0700016 flags:# user:User status:string until_date:flags.0?int can_be_edited:flags.1?Bool can_change_info:flags.2?Bool can_post_messages:flags.3?Bool can_edit_messages:flags.4?Bool can_delete_messages:flags.5?Bool can_invite_users:flags.6?Bool can_restrict_members:flags.7?Bool can_pin_messages:flags.8?Bool can_promote_members:flags.9?Bool can_send_messages:flags.10?Bool can_send_media_messages:flags.11?Bool can_send_other_messages:flags.12?Bool can_add_web_page_previews:flags.13?Bool = pyrogram.ChatMember;
//pyrogram.sticker#b0700017 flags:# file_id:string 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;

View File

@ -88,7 +88,7 @@ def generate(source_path, base):
inner_path = base + "/" + k + "/index" + ".rst" inner_path = base + "/" + k + "/index" + ".rst"
module = "pyrogram.api.{}.{}".format(base, k) module = "pyrogram.api.{}.{}".format(base, k)
else: else:
for i in list(all_entities)[::-1]: for i in sorted(list(all_entities), reverse=True):
if i != base: if i != base:
entities.insert(0, "{0}/index".format(i)) entities.insert(0, "{0}/index".format(i))

View File

@ -67,3 +67,5 @@ USER_NOT_MUTUAL_CONTACT The user is not a mutual contact
USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups
API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side
USER_NOT_PARTICIPANT The user is not a member of this chat USER_NOT_PARTICIPANT The user is not a member of this chat
CHANNEL_PRIVATE The channel/supergroup is not accessible
MESSAGE_IDS_EMPTY The requested message doesn't exist
1 id message
67 USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups
68 API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side
69 USER_NOT_PARTICIPANT The user is not a member of this chat
70 CHANNEL_PRIVATE The channel/supergroup is not accessible
71 MESSAGE_IDS_EMPTY The requested message doesn't exist

View File

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

View File

@ -3,7 +3,7 @@
# You can set these variables from the command line. # You can set these variables from the command line.
SPHINXOPTS = SPHINXOPTS =
SPHINXBUILD = ~/PycharmProjects/pyrogram/venv3.6/bin/sphinx-build SPHINXBUILD = sphinx-build
SPHINXPROJ = Pyrogram SPHINXPROJ = Pyrogram
SOURCEDIR = source SOURCEDIR = source
BUILDDIR = build BUILDDIR = build

View File

@ -1,20 +0,0 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = Pyrogram
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@ -5,8 +5,7 @@ Welcome to Pyrogram
<div align="center"> <div align="center">
<a href="https://docs.pyrogram.ml"> <a href="https://docs.pyrogram.ml">
<div><img src="https://media.pyrogram.ml/images/icon.png" alt="Pyrogram Icon"></div> <div><img src="_static/logo.png" alt="Pyrogram Logo"></div>
<div><img src="https://media.pyrogram.ml/images/label.png" alt="Pyrogram Label"></div>
</a> </a>
</div> </div>
@ -24,13 +23,13 @@ Welcome to Pyrogram
<a href="https://t.me/PyrogramChat"> <a href="https://t.me/PyrogramChat">
Community Community
</a> </a>
<br><br> <br>
<a href="https://github.com/pyrogram/pyrogram/blob/master/compiler/api/source/main_api.tl"> <a href="https://github.com/pyrogram/pyrogram/blob/master/compiler/api/source/main_api.tl">
<img src="https://img.shields.io/badge/SCHEME-LAYER%2081-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30" <img src="https://img.shields.io/badge/schema-layer%2082-eda738.svg?longCache=true&colorA=262b30"
alt="Scheme Layer"> alt="Scheme Layer">
</a> </a>
<a href="https://github.com/pyrogram/tgcrypto"> <a href="https://github.com/pyrogram/tgcrypto">
<img src="https://img.shields.io/badge/TGCRYPTO-V1.0.4-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30" <img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
alt="TgCrypto"> alt="TgCrypto">
</a> </a>
</p> </p>
@ -50,14 +49,14 @@ Welcome to Pyrogram
app.run() app.run()
Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the library. 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 Contents are organized into self-contained topics and can be accessed from the sidebar, or by following them in order
button at the end of each page. But first, here's a brief overview of what is this all about. using the Next button at the end of each page. But first, here's a brief overview of what is this all about.
About About
----- -----
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for building **Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for
custom Telegram applications that interact with the MTProto API as both User and Bot. building custom Telegram applications that interact with the MTProto API as both User and Bot.
Features Features
-------- --------
@ -65,7 +64,7 @@ Features
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away. - **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. - **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. - **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 81 on top of MTProto 2.0. - **Updated** to the latest Telegram API version, currently Layer 82 on top of MTProto 2.0.
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API. - **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
- **Full API**, allowing to execute any advanced action an official client is able to do, and more. - **Full API**, allowing to execute any advanced action an official client is able to do, and more.
@ -85,6 +84,7 @@ To get started, press the Next button.
resources/UpdateHandling resources/UpdateHandling
resources/UsingFilters resources/UsingFilters
resources/SmartPlugins
resources/AutoAuthorization resources/AutoAuthorization
resources/CustomizeSessions resources/CustomizeSessions
resources/TgCrypto resources/TgCrypto

View File

@ -30,6 +30,7 @@ Decorators
on_message on_message
on_callback_query on_callback_query
on_deleted_messages on_deleted_messages
on_user_status
on_disconnect on_disconnect
on_raw_update on_raw_update
@ -57,10 +58,10 @@ Messages
edit_message_text edit_message_text
edit_message_caption edit_message_caption
edit_message_reply_markup edit_message_reply_markup
edit_message_media
delete_messages delete_messages
get_messages get_messages
get_history get_history
get_dialogs
Chats Chats
----- -----
@ -84,6 +85,8 @@ Chats
get_chat get_chat
get_chat_member get_chat_member
get_chat_members get_chat_members
get_chat_members_count
get_dialogs
Users Users
----- -----
@ -94,7 +97,8 @@ Users
get_me get_me
get_users get_users
get_user_profile_photos get_user_profile_photos
delete_profile_photos set_user_profile_photo
delete_user_profile_photos
Contacts Contacts
-------- --------

View File

@ -3,4 +3,3 @@ Filters
.. autoclass:: pyrogram.Filters .. autoclass:: pyrogram.Filters
:members: :members:
:undoc-members:

View File

@ -9,6 +9,7 @@ Handlers
MessageHandler MessageHandler
DeletedMessagesHandler DeletedMessagesHandler
CallbackQueryHandler CallbackQueryHandler
UserStatusHandler
DisconnectHandler DisconnectHandler
RawUpdateHandler RawUpdateHandler
@ -21,6 +22,9 @@ Handlers
.. autoclass:: CallbackQueryHandler .. autoclass:: CallbackQueryHandler
:members: :members:
.. autoclass:: UserStatusHandler
:members:
.. autoclass:: DisconnectHandler .. autoclass:: DisconnectHandler
:members: :members:

View File

@ -10,6 +10,7 @@ Users & Chats
:nosignatures: :nosignatures:
User User
UserStatus
Chat Chat
ChatPhoto ChatPhoto
ChatMember ChatMember
@ -62,6 +63,9 @@ Input Media
InputMediaPhoto InputMediaPhoto
InputMediaVideo InputMediaVideo
InputMediaAudio
InputMediaAnimation
InputMediaDocument
InputPhoneContact InputPhoneContact
.. User & Chats .. User & Chats
@ -70,6 +74,9 @@ Input Media
.. autoclass:: User .. autoclass:: User
:members: :members:
.. autoclass:: UserStatus
:members:
.. autoclass:: Chat .. autoclass:: Chat
:members: :members:
@ -172,5 +179,14 @@ Input Media
.. autoclass:: InputMediaVideo .. autoclass:: InputMediaVideo
:members: :members:
.. autoclass:: InputMediaAudio
:members:
.. autoclass:: InputMediaAnimation
:members:
.. autoclass:: InputMediaDocument
:members:
.. autoclass:: InputPhoneContact .. autoclass:: InputPhoneContact
:members: :members:

View File

@ -35,12 +35,9 @@ Example:
password="password" # (if you have one) password="password" # (if you have one)
) )
app.start() with app:
print(app.get_me()) print(app.get_me())
app.stop()
Sign Up Sign Up
------- -------
@ -67,8 +64,5 @@ Example:
last_name="" # Can be an empty string last_name="" # Can be an empty string
) )
app.start() with app:
print(app.get_me()) print(app.get_me())
app.stop()

View File

@ -0,0 +1,126 @@
Smart Plugins
=============
Pyrogram embeds a **smart** (automatic) and lightweight plugin system that is meant to further simplify the organization
of large projects and to provide a way for creating pluggable components that can be **easily shared** across different
Pyrogram applications with **minimal boilerplate code**.
.. tip::
Smart Plugins are completely optional and disabled by default.
Introduction
------------
Prior to the Smart Plugin system, pluggable handlers were already possible. For example, if you wanted to modularize
your applications, you had to do something like this...
.. note::
This is an example application that replies in private chats with two messages: one containing the same
text message you sent and the other containing the reversed text message.
Example: *"Pyrogram"* replies with *"Pyrogram"* and *"margoryP"*
.. code-block:: text
myproject/
config.ini
handlers.py
main.py
- ``handlers.py``
.. code-block:: python
def echo(client, message):
message.reply(message.text)
def echo_reversed(client, message):
message.reply(message.text[::-1])
- ``main.py``
.. code-block:: python
from pyrogram import Client, MessageHandler, Filters
from handlers import echo, echo_reversed
app = Client("my_account")
app.add_handler(
MessageHandler(
echo,
Filters.text & Filters.private))
app.add_handler(
MessageHandler(
echo_reversed,
Filters.text & Filters.private),
group=1)
app.run()
...which is already nice and doesn't add *too much* boilerplate code, but things can get boring still; you have to
manually ``import``, manually :meth:`add_handler <pyrogram.Client.add_handler>` and manually instantiate each
:obj:`MessageHandler <pyrogram.MessageHandler>` object because **you can't use those cool decorators** for your
functions. So... What if you could?
Using Smart Plugins
-------------------
Setting up your Pyrogram project to accommodate Smart Plugins is pretty straightforward:
#. Create a new folder to store all the plugins (e.g.: "plugins").
#. Put your files full of plugins inside.
#. Enable plugins in your Client.
.. note::
This is the same example application `as shown above <#introduction>`_, written using the Smart Plugin system.
.. code-block:: text
:emphasize-lines: 2, 3
myproject/
plugins/
handlers.py
config.ini
main.py
- ``plugins/handlers.py``
.. code-block:: python
:emphasize-lines: 4, 9
from pyrogram import Client, Filters
@Client.on_message(Filters.text & Filters.private)
def echo(client, message):
message.reply(message.text)
@Client.on_message(Filters.text & Filters.private, group=1)
def echo_reversed(client, message):
message.reply(message.text[::-1])
- ``main.py``
.. code-block:: python
from pyrogram import Client
Client("my_account", plugins_dir="plugins").run()
The first important thing to note is the new ``plugins`` folder, whose name is passed to the the ``plugins_dir``
parameter when creating a :obj:`Client <pyrogram.Client>` in the ``main.py`` file — you can put *any python file* in
there and each file can contain *any decorated function* (handlers) with only one limitation: within a single plugin
file you must use different names for each decorated function. Your Pyrogram Client instance will **automatically**
scan the folder upon creation to search for valid handlers and register them for you.
Then you'll notice you can now use decorators. That's right, you can apply the usual decorators to your callback
functions in a static way, i.e. **without having the Client instance around**: simply use ``@Client`` (Client class)
instead of the usual ``@app`` (Client instance) namespace and things will work just the same.

View File

@ -2,15 +2,15 @@ Update Handling
=============== ===============
Updates are events that happen in your Telegram account (incoming messages, new channel posts, new members join, ...) Updates are events that happen in your Telegram account (incoming messages, new channel posts, new members join, ...)
and can be handled by registering one or more callback functions in your app by using an `Handler <../pyrogram/Handlers.html>`_. and can be handled by registering one or more callback functions in your app by using `Handlers <../pyrogram/Handlers.html>`_.
To put it simply, whenever an update is received from Telegram it will be dispatched and your previously defined callback To put it simply, whenever an update is received from Telegram it will be dispatched and your previously defined callback
function(s) will be called back with the update itself as argument. function(s) matching it will be called back with the update itself as argument.
Registering an Handler Registering an Handler
---------------------- ----------------------
To explain how `Handlers <../pyrogram/Handlers.html>`_ work let's have a look at the most used one, the To explain how handlers work let's have a look at the most used one, the
:obj:`MessageHandler <pyrogram.MessageHandler>`, which will be in charge for handling :obj:`Message <pyrogram.Message>` :obj:`MessageHandler <pyrogram.MessageHandler>`, which will be in charge for handling :obj:`Message <pyrogram.Message>`
updates coming from all around your chats. Every other handler shares the same setup logic; you should not have troubles updates coming from all around your chats. Every other handler shares the same setup logic; you should not have troubles
settings them up once you learn from this section. settings them up once you learn from this section.

View File

@ -4,48 +4,85 @@ Installation
Being a Python library, Pyrogram requires Python to be installed in your system. Being a Python library, Pyrogram requires Python to be installed in your system.
We recommend using the latest version of Python 3 and pip. We recommend using the latest version of Python 3 and pip.
Get Python 3 from https://www.python.org/downloads/ or with your package manager and pip Get Python 3 from https://www.python.org/downloads/ (or with your package manager) and pip
by following the instructions at https://pip.pypa.io/en/latest/installing/. by following the instructions at https://pip.pypa.io/en/latest/installing/.
.. note:: .. important::
Pyrogram supports Python 3 only, starting from version 3.4 and PyPy.
Pyrogram supports **Python 3** only, starting from version 3.4. **PyPy** is supported too.
Install Pyrogram Install Pyrogram
---------------- ----------------
- The easiest way to install and upgrade Pyrogram is by using **pip**: - The easiest way to install and upgrade Pyrogram to its latest stable version is by using **pip**:
.. code-block:: bash .. code-block:: text
$ pip3 install --upgrade pyrogram $ pip3 install --upgrade pyrogram
- or, with TgCrypto_ (recommended): - or, with TgCrypto_ as extra requirement (recommended):
.. code-block:: bash .. code-block:: text
$ pip3 install --upgrade pyrogram[tgcrypto] $ pip3 install --upgrade pyrogram[fast]
Bleeding Edge Bleeding Edge
------------- -------------
If you want the latest development version of Pyrogram, you can install it straight from the develop_ If you want the latest development version of Pyrogram, you can install it straight from the develop_
branch using this command: branch using this command (you might need to install **git** first):
.. code-block:: bash .. code-block:: text
$ pip3 install --upgrade git+https://github.com/pyrogram/pyrogram.git $ pip3 install --upgrade git+https://github.com/pyrogram/pyrogram.git
Asynchronous
------------
Pyrogram heavily depends on IO-bound network code (it's a cloud-based messaging client library after all), and here's
where asyncio shines the most by providing extra performance while running on a single OS-level thread only.
**A fully asynchronous variant of Pyrogram is therefore available** (Python 3.5+ required).
Use this command to install:
.. code-block:: text
$ pip3 install --upgrade git+https://github.com/pyrogram/pyrogram.git@asyncio
Pyrogram API remains the same and features are kept up to date from the non-async, default develop branch, but you
are obviously required Python asyncio knowledge in order to take full advantage of it.
.. tip::
The idea to turn Pyrogram fully asynchronous is still under consideration, but is wise to expect that in future this
would be the one and only way to work with Pyrogram.
You can start using Pyrogram Async variant right now as an excuse to learn more about asynchronous programming and
do experiments with it!
.. raw:: html
<script async
src="https://telegram.org/js/telegram-widget.js?4"
data-telegram-post="Pyrogram/4"
data-width="100%">
</script>
.. centered:: Subscribe to `@Pyrogram <https://t.me/Pyrogram>`_ for news and announcements
Verifying Verifying
--------- ---------
To verify that Pyrogram is correctly installed, open a Python shell and import it. To verify that Pyrogram is correctly installed, open a Python shell and import it.
If no error shows up you are good to go. If no error shows up you are good to go.
.. code-block:: bash .. code-block:: python
>>> import pyrogram >>> import pyrogram
>>> pyrogram.__version__ >>> pyrogram.__version__
'0.7.5' '0.9.3'
.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto .. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto
.. _develop: http://github.com/pyrogram/pyrogram .. _develop: http://github.com/pyrogram/pyrogram

View File

@ -53,6 +53,7 @@ fits better for you:
) )
.. note:: .. note::
The examples below assume you have created a ``config.ini`` file, thus they won't show the *api_id* The examples below assume you have created a ``config.ini`` file, thus they won't show the *api_id*
and *api_hash* parameters usage. and *api_hash* parameters usage.
@ -74,7 +75,7 @@ the :class:`Client <pyrogram.Client>` class by passing to it a ``session_name``
This starts an interactive shell asking you to input your **phone number** (including your `Country Code`_) This starts an interactive shell asking you to input your **phone number** (including your `Country Code`_)
and the **phone code** you will receive: and the **phone code** you will receive:
.. code:: .. code-block:: text
Enter phone number: +39********** Enter phone number: +39**********
Is "+39**********" correct? (y/n): y Is "+39**********" correct? (y/n): y
@ -84,7 +85,9 @@ After successfully authorizing yourself, a new file called ``my_account.session`
Pyrogram executing API calls with your identity. This file will be loaded again when you restart your app, 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. and as long as you keep the session alive, Pyrogram won't ask you again to enter your phone number.
.. important:: Your ``*.session`` files are personal and must be kept secret. .. important::
Your ``*.session`` files are personal and must be kept secret.
Bot Authorization Bot Authorization
----------------- -----------------

View File

@ -10,35 +10,50 @@ High-level API
The easiest and recommended way to interact with Telegram is via the high-level Pyrogram methods_ and types_, which are The easiest and recommended way to interact with Telegram is via the high-level Pyrogram methods_ and types_, which are
named after the `Telegram Bot API`_. named after the `Telegram Bot API`_.
Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_): Here's a simple example:
- Get information about the authorized user:
.. code-block:: python .. code-block:: python
from pyrogram import Client
app = Client("my_account")
app.start()
print(app.get_me()) print(app.get_me())
app.send_message("me", "Hi there! I'm using **Pyrogram**")
app.send_location("me", 51.500729, -0.124583)
- Send a message to yourself (Saved Messages): app.stop()
You can also use Pyrogram in a context manager with the ``with`` statement. The Client will automatically
:meth:`start <pyrogram.Client.start>` and :meth:`stop <pyrogram.Client.stop>` gracefully, even in case of unhandled
exceptions in your code:
.. code-block:: python .. code-block:: python
app.send_message("me", "Hi there! I'm using Pyrogram") from pyrogram import Client
- Upload a new photo (with caption): app = Client("my_account")
.. code-block:: python with app:
print(app.get_me())
app.send_message("me", "Hi there! I'm using **Pyrogram**")
app.send_location("me", 51.500729, -0.124583)
app.send_photo("me", "/home/dan/perla.jpg", "Cute!") More examples on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_.
Raw Functions Raw Functions
------------- -------------
If you can't find a high-level method for your needs or if want complete, low-level access to the whole Telegram API, If you can't find a high-level method for your needs or if you want complete, low-level access to the whole Telegram API,
you have to use the raw :mod:`functions <pyrogram.api.functions>` and :mod:`types <pyrogram.api.types>` exposed by the you have to use the raw :mod:`functions <pyrogram.api.functions>` and :mod:`types <pyrogram.api.types>` exposed by the
``pyrogram.api`` package and call any Telegram API method you wish using the :meth:`send() <pyrogram.Client.send>` ``pyrogram.api`` package and call any Telegram API method you wish using the :meth:`send() <pyrogram.Client.send>`
method provided by the Client class. method provided by the Client class.
.. hint:: Every high-level method mentioned in the section above is built on top of these raw functions. .. hint::
Every high-level method mentioned in the section above is built on top of these raw functions.
Nothing stops you from using the raw functions only, but they are rather complex and `plenty of them`_ are already Nothing stops you from using the raw functions only, but they are rather complex and `plenty of them`_ are already
re-implemented by providing a much simpler and cleaner interface which is very similar to the Bot API. re-implemented by providing a much simpler and cleaner interface which is very similar to the Bot API.
@ -54,9 +69,7 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
from pyrogram import Client from pyrogram import Client
from pyrogram.api import functions from pyrogram.api import functions
app = Client("my_account") with Client("my_account") as app:
app.start()
app.send( app.send(
functions.account.UpdateProfile( functions.account.UpdateProfile(
first_name="Dan", last_name="Tès", first_name="Dan", last_name="Tès",
@ -64,8 +77,6 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
) )
) )
app.stop()
- Share your Last Seen time only with your contacts: - Share your Last Seen time only with your contacts:
.. code-block:: python .. code-block:: python
@ -73,9 +84,7 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
from pyrogram import Client from pyrogram import Client
from pyrogram.api import functions, types from pyrogram.api import functions, types
app = Client("my_account") with Client("my_account") as app:
app.start()
app.send( app.send(
functions.account.SetPrivacy( functions.account.SetPrivacy(
key=types.InputPrivacyKeyStatusTimestamp(), key=types.InputPrivacyKeyStatusTimestamp(),
@ -83,8 +92,6 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
) )
) )
app.stop()
- Invite users to your channel/supergroup: - Invite users to your channel/supergroup:
.. code-block:: python .. code-block:: python
@ -92,9 +99,7 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
from pyrogram import Client from pyrogram import Client
from pyrogram.api import functions, types from pyrogram.api import functions, types
app = Client("my_account") with Client("my_account") as app:
app.start()
app.send( app.send(
functions.channels.InviteToChannel( functions.channels.InviteToChannel(
channel=app.resolve_peer(123456789), # ID or Username channel=app.resolve_peer(123456789), # ID or Username
@ -106,8 +111,6 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
) )
) )
app.stop()
.. _methods: ../pyrogram/Client.html#messages .. _methods: ../pyrogram/Client.html#messages
.. _plenty of them: ../pyrogram/Client.html#messages .. _plenty of them: ../pyrogram/Client.html#messages
.. _types: ../pyrogram/Types.html .. _types: ../pyrogram/Types.html

121
examples/LICENSE Normal file
View File

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

View File

@ -5,14 +5,17 @@ This folder contains example scripts to show you how **Pyrogram** looks like.
Every script is working right away (provided you correctly set up your credentials), meaning 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 session names and target chats. you can simply copy-paste and run. The only things you have to change are session names and target chats.
All the examples listed in this directory are licensed under the terms of the [CC0 1.0 Universal](LICENSE) license and
can be freely used as basic building blocks for your own applications without worrying about copyrights.
Example | Description Example | Description
---: | :--- ---: | :---
[**hello_world**](hello_world.py) | Demonstration of basic API usages [**hello_world**](hello_world.py) | Demonstration of basic API usages
[**echo_bot**](echo_bot.py) | Echo bot that replies to every private text message [**echo_bot**](echo_bot.py) | Echo bot that replies to every private text message
[**welcome_bot**](welcome_bot.py) | The Welcome Bot source code in [@PyrogramChat](https://t.me/pyrogramchat) [**welcome_bot**](welcome_bot.py) | The Welcome Bot source code in [@PyrogramChat](https://t.me/pyrogramchat)
[**get_history**](get_history.py) | How to retrieve the full message history of a chat [**get_history**](get_history.py) | How to retrieve the full message history of a chat
[**get_participants**](get_participants.py) | How to get the first 10.000 members of a supergroup/channel [**get_chat_members**](get_chat_members.py) | How to get the first 10.000 members of a supergroup/channel
[**get_participants2**](get_participants2.py) | Improved version to get more than 10.000 users [**get_chat_members2**](get_chat_members2.py) | Improved version to get more than 10.000 members
[**query_inline_bots**](query_inline_bots.py) | How to query an inline bot and send a result to a chat [**query_inline_bots**](query_inline_bots.py) | How to query an inline bot and send a result to a chat
[**send_bot_keyboards**](send_bot_keyboards.py) | How to send normal and inline keyboards using regular bots [**send_bot_keyboards**](send_bot_keyboards.py) | How to send normal and inline keyboards using regular bots
[**callback_query_handler**](callback_query_handler.py) | How to handle queries coming from inline button presses [**callback_query_handler**](callback_query_handler.py) | How to handle queries coming from inline button presses

View File

@ -1,37 +1,16 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from pyrogram import Client
"""This example shows how to handle callback queries, i.e.: queries coming from inline button presses. """This example shows how to handle callback queries, i.e.: queries coming from inline button presses.
It uses the @on_callback_query decorator to register a CallbackQueryHandler.""" It uses the @on_callback_query decorator to register a CallbackQueryHandler.
"""
from pyrogram import Client
app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
@app.on_callback_query() @app.on_callback_query()
def answer(client, callback_query): def answer(client, callback_query):
client.answer_callback_query( callback_query.answer('Button contains: "{}"'.format(callback_query.data), show_alert=True)
callback_query.id,
text='Button contains: "{}"'.format(callback_query.data),
show_alert=True
)
app.run() # Automatically start() and idle() app.run() # Automatically start() and idle()

View File

@ -1,39 +1,17 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from pyrogram import Client, Filters
"""This simple echo bot replies to every private text message. """This simple echo bot replies to every private text message.
It uses the @on_message decorator to register a MessageHandler It uses the @on_message decorator to register a MessageHandler and applies two filters on it:
and applies two filters on it, Filters.text and Filters.private to make Filters.text and Filters.private to make sure it will reply to private text messages only.
sure it will only reply to private text messages.
""" """
from pyrogram import Client, Filters
app = Client("my_account") app = Client("my_account")
@app.on_message(Filters.text & Filters.private) @app.on_message(Filters.text & Filters.private)
def echo(client, message): def echo(client, message):
client.send_message( message.reply(message.text, quote=True)
message.chat.id, message.text,
reply_to_message_id=message.message_id
)
app.run() # Automatically start() and idle() app.run() # Automatically start() and idle()

View File

@ -0,0 +1,31 @@
"""This example shows you how to get the first 10.000 members of a chat.
Refer to get_chat_members2.py for more than 10.000 members.
"""
import time
from pyrogram import Client
from pyrogram.api.errors import FloodWait
app = Client("my_account")
target = "pyrogramchat" # Target channel/supergroup
members = [] # List that will contain all the members of the target chat
offset = 0 # Offset starts at 0
limit = 200 # Amount of users to retrieve for each API call (max 200)
with app:
while True:
try:
chunk = app.get_chat_members(target, offset)
except FloodWait as e: # Very large chats could trigger FloodWait
time.sleep(e.x) # When it happens, wait X seconds and try again
continue
if not chunk.chat_members:
break # No more members left
members.extend(chunk.chat_members)
offset += len(chunk.chat_members)
# Now the "members" list contains all the members of the target chat

View File

@ -0,0 +1,50 @@
"""This is an improved version of get_chat_members.py
Since Telegram will return at most 10.000 members for a single query, this script
repeats the search using numbers ("0" to "9") and all the available ascii letters ("a" to "z").
This can be further improved by also searching for non-ascii characters (e.g.: Japanese script),
as some user names may not contain ascii letters at all.
"""
import time
from string import ascii_lowercase
from pyrogram import Client
from pyrogram.api.errors import FloodWait
app = Client("my_account")
target = "pyrogramchat" # Target channel/supergroup
members = {} # List that will contain all the members of the target chat
limit = 200 # Amount of users to retrieve for each API call (max 200)
# "" + "0123456789" + "abcdefghijklmnopqrstuvwxyz" (as list)
queries = [""] + [str(i) for i in range(10)] + list(ascii_lowercase)
with app:
for q in queries:
print('Searching for "{}"'.format(q))
offset = 0 # For each query, offset restarts from 0
while True:
try:
chunk = app.get_chat_members(target, offset, query=q)
except FloodWait as e: # Very large chats could trigger FloodWait
print("Flood wait: {} seconds".format(e.x))
time.sleep(e.x) # When it happens, wait X seconds and try again
continue
if not chunk.chat_members:
print('Done searching for "{}"'.format(q))
print()
break # No more members left
members.update({i.user.id: i for i in chunk.chat_members})
offset += len(chunk.chat_members)
print("Total members: {}".format(len(members)))
print("Grand total: {}".format(len(members)))
# Now the "members" list contains all the members of the target chat

View File

@ -1,40 +1,20 @@
# Pyrogram - Telegram MTProto API Client Library for Python """This example shows how to retrieve the full message history of a chat"""
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import time import time
from pyrogram import Client from pyrogram import Client
from pyrogram.api.errors import FloodWait from pyrogram.api.errors import FloodWait
"""This example shows how to retrieve the full message history of a chat"""
app = Client("my_account") app = Client("my_account")
target = "me" # "me" refers to your own chat (Saved Messages) target = "me" # "me" refers to your own chat (Saved Messages)
messages = [] # List that will contain all the messages of the target chat messages = [] # List that will contain all the messages of the target chat
offset_id = 0 # ID of the last message of the chunk offset_id = 0 # ID of the last message of the chunk
app.start() with app:
while True:
while True:
try: try:
m = app.get_history(target, offset_id=offset_id) m = app.get_history(target, offset_id=offset_id)
except FloodWait as e: except FloodWait as e: # For very large chats the method call can raise a FloodWait
# For very large chats the method call can raise a FloodWait
print("waiting {}".format(e.x)) print("waiting {}".format(e.x))
time.sleep(e.x) # Sleep X seconds before continuing time.sleep(e.x) # Sleep X seconds before continuing
continue continue
@ -47,7 +27,5 @@ while True:
print("Messages: {}".format(len(messages))) print("Messages: {}".format(len(messages)))
app.stop()
# Now the "messages" list contains all the messages sorted by date in # Now the "messages" list contains all the messages sorted by date in
# descending order (from the most recent to the oldest one) # descending order (from the most recent to the oldest one)

View File

@ -1,63 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import time
from pyrogram import Client
from pyrogram.api import functions, types
from pyrogram.api.errors import FloodWait
"""This simple GetParticipants method usage shows you how to get the first 10.000 users of a chat.
Refer to get_participants2.py for more than 10.000 users.
"""
app = Client("my_account")
target = "pyrogramchat" # Target channel/supergroup
users = [] # List that will contain all the users of the target chat
limit = 200 # Amount of users to retrieve for each API call
offset = 0 # Offset starts at 0
app.start()
while True:
try:
participants = app.send(
functions.channels.GetParticipants(
channel=app.resolve_peer(target),
filter=types.ChannelParticipantsSearch(""), # Filter by empty string (search for all)
offset=offset,
limit=limit,
hash=0
)
)
except FloodWait as e:
# Very large channels will trigger FloodWait.
# When happens, wait X seconds before continuing
time.sleep(e.x)
continue
if not participants.participants:
break # No more participants left
users.extend(participants.users)
offset += limit
app.stop()
# Now the "users" list contains all the members of the target chat

View File

@ -1,79 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import time
from string import ascii_lowercase
from pyrogram import Client
from pyrogram.api import functions, types
from pyrogram.api.errors import FloodWait
"""This is an improved version of get_participants.py
Since Telegram will return at most 10.000 users for a single query, this script
repeats the search using numbers ("0" to "9") and all the available ascii letters ("a" to "z").
This can be further improved by also searching for non-ascii characters (e.g.: Japanese script),
as some user names may not contain ascii letters at all.
"""
app = Client("my_account")
target = "pyrogramchat" # Target channel/supergroup username or id
users = {} # To ensure uniqueness, users will be stored in a dictionary with user_id as key
limit = 200 # Amount of users to retrieve for each API call (200 is the maximum)
# "" + "0123456789" + "abcdefghijklmnopqrstuvwxyz" (as list)
queries = [""] + [str(i) for i in range(10)] + list(ascii_lowercase)
app.start()
for q in queries:
print("Searching for '{}'".format(q))
offset = 0 # For each query, offset restarts from 0
while True:
try:
participants = app.send(
functions.channels.GetParticipants(
channel=app.resolve_peer(target),
filter=types.ChannelParticipantsSearch(q),
offset=offset,
limit=limit,
hash=0
)
)
except FloodWait as e:
# Very large chats could trigger FloodWait.
# When happens, wait X seconds before continuing
print("Flood wait: {} seconds".format(e.x))
time.sleep(e.x)
continue
if not participants.participants:
print("Done searching for '{}'".format(q))
print()
break # No more participants left
# User information are stored in the participants.users list.
# Add those users to the dictionary
users.update({i.id: i for i in participants.users})
offset += len(participants.participants)
print("Total users: {}".format(len(users)))
app.stop()

View File

@ -1,25 +1,7 @@
# Pyrogram - Telegram MTProto API Client Library for Python """This example demonstrates a basic API usage"""
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from pyrogram import Client from pyrogram import Client
"""This example demonstrates a basic API usage"""
# Create a new Client instance # Create a new Client instance
app = Client("my_account") app = Client("my_account")

View File

@ -1,25 +1,7 @@
# Pyrogram - Telegram MTProto API Client Library for Python """This example shows how to query an inline bot"""
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from pyrogram import Client from pyrogram import Client
"""This example shows how to query an inline bot"""
# Create a new Client # Create a new Client
app = Client("my_account") app = Client("my_account")

View File

@ -1,25 +1,7 @@
# Pyrogram - Telegram MTProto API Client Library for Python """This example shows how to handle raw updates"""
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from pyrogram import Client from pyrogram import Client
"""This example shows how to handle raw updates"""
app = Client("my_account") app = Client("my_account")

View File

@ -1,23 +1,3 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
"""This example will show you how to send normal and inline keyboards. """This example will show you how to send normal and inline keyboards.
You must log-in as a regular bot in order to send keyboards (use the token from @BotFather). You must log-in as a regular bot in order to send keyboards (use the token from @BotFather).
@ -27,6 +7,8 @@ send_message() is used as example, but a keyboard can be sent with any other sen
like send_audio(), send_document(), send_location(), etc... like send_audio(), send_document(), send_location(), etc...
""" """
from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
# Create a client using your bot token # Create a client using your bot token
app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
app.start() app.start()

View File

@ -1,52 +1,45 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from pyrogram import Client, Emoji, Filters
"""This is the Welcome Bot in @PyrogramChat. """This is the Welcome Bot in @PyrogramChat.
It uses the Emoji module to easily add emojis in your text messages and Filters 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 to make it only work for specific messages in a specific chat.
""" """
from pyrogram import Client, Emoji, Filters
USER = "**{}**"
MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {{}}!".format(Emoji.SPARKLES)
enabled_groups = Filters.chat("PyrogramChat")
last_welcomes = {}
app = Client("my_account") app = Client("my_account")
@app.on_message(Filters.chat("PyrogramChat") & Filters.new_chat_members) @app.on_message(enabled_groups & Filters.new_chat_members)
def welcome(client, message): def welcome(client, message):
# Build the new members list (with mentions) by using their first_name chat_id = message.chat.id
new_members = ", ".join([
"[{}](tg://user?id={})".format(i.first_name, i.id)
for i in message.new_chat_members
])
# Build the welcome message by using an emoji and the list we built above # Get the previous welcome message and members, if any
text = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!".format( previous_welcome, previous_members = last_welcomes.pop(chat_id, (None, []))
Emoji.SPARKLES,
new_members
)
# Send the welcome message # Delete the previous message, if exists
client.send_message( if previous_welcome:
message.chat.id, text, previous_welcome.delete()
reply_to_message_id=message.message_id,
disable_web_page_preview=True # Build the new members list by using their first_name. Also append the previous members, if any
) new_members = [USER.format(i.first_name) for i in message.new_chat_members] + previous_members
# Build the welcome message by using an emoji and the list we created above
text = MESSAGE.format(", ".join(new_members))
# Actually send the welcome and save the new message and the new members list
last_welcomes[message.chat.id] = message.reply(text, disable_web_page_preview=True), new_members
@app.on_message(enabled_groups)
def reset(client, message):
# Don't make the bot delete the previous welcome in case someone talks in the middle
last_welcomes.pop(message.chat.id, None)
app.run() # Automatically start() and idle() app.run() # Automatically start() and idle()

View File

@ -23,18 +23,18 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance
"e" if sys.getfilesystemencoding() != "utf-8" else "\xe8" "e" if sys.getfilesystemencoding() != "utf-8" else "\xe8"
) )
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
__version__ = "0.8.0dev1" __version__ = "0.9.3"
from .api.errors import Error from .api.errors import Error
from .client.types import ( from .client.types import (
Audio, Chat, ChatMember, ChatMembers, ChatPhoto, Contact, Document, InputMediaPhoto, Audio, Chat, ChatMember, ChatMembers, ChatPhoto, Contact, Document, InputMediaPhoto,
InputMediaVideo, InputMediaDocument, InputMediaAudio, InputMediaAnimation, InputPhoneContact, InputMediaVideo, InputMediaDocument, InputMediaAudio, InputMediaAnimation, InputPhoneContact,
Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, Update, User, Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, Update, User, UserStatus,
UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply, UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply,
InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove
) )
from .client import ( from .client import (
Client, ChatAction, ParseMode, Emoji, Client, ChatAction, ParseMode, Emoji,
MessageHandler, DeletedMessagesHandler, CallbackQueryHandler, MessageHandler, DeletedMessagesHandler, CallbackQueryHandler,
RawUpdateHandler, DisconnectHandler, Filters RawUpdateHandler, DisconnectHandler, UserStatusHandler, Filters
) )

View File

@ -30,7 +30,7 @@ class BoolFalse(Object):
return cls.value return cls.value
def __new__(cls) -> bytes: def __new__(cls) -> bytes:
return int.to_bytes(cls.ID, 4, "little") return cls.ID.to_bytes(4, "little")
class BoolTrue(BoolFalse): class BoolTrue(BoolFalse):

View File

@ -48,7 +48,7 @@ class Bytes(Object):
else: else:
return ( return (
bytes([254]) bytes([254])
+ int.to_bytes(length, 3, "little") + length.to_bytes(3, "little")
+ value + value
+ bytes(-length % 4) + bytes(-length % 4)
) )

View File

@ -29,15 +29,12 @@ class Int(Object):
return int.from_bytes(b.read(cls.SIZE), "little", signed=signed) return int.from_bytes(b.read(cls.SIZE), "little", signed=signed)
def __new__(cls, value: int, signed: bool = True) -> bytes: def __new__(cls, value: int, signed: bool = True) -> bytes:
return int.to_bytes(value, cls.SIZE, "little", signed=signed) return value.to_bytes(cls.SIZE, "little", signed=signed)
class Long(Int): class Long(Int):
SIZE = 8 SIZE = 8
def __new__(cls, *args):
return super().__new__(cls, *args)
class Int128(Int): class Int128(Int):
SIZE = 16 SIZE = 16

View File

@ -29,4 +29,4 @@ class Null(Object):
return None return None
def __new__(cls) -> bytes: def __new__(cls) -> bytes:
return int.to_bytes(cls.ID, 4, "little") return cls.ID.to_bytes(4, "little")

View File

@ -24,7 +24,7 @@ from . import Bytes
class String(Bytes): class String(Bytes):
@staticmethod @staticmethod
def read(b: BytesIO, *args) -> str: def read(b: BytesIO, *args) -> str:
return super(String, String).read(b).decode() return super(String, String).read(b).decode(errors="replace")
def __new__(cls, value: str) -> bytes: def __new__(cls, value: str) -> bytes:
return super().__new__(cls, value.encode()) return super().__new__(cls, value.encode())

View File

@ -22,5 +22,5 @@ from .filters import Filters
from .handlers import ( from .handlers import (
MessageHandler, DeletedMessagesHandler, MessageHandler, DeletedMessagesHandler,
CallbackQueryHandler, RawUpdateHandler, CallbackQueryHandler, RawUpdateHandler,
DisconnectHandler DisconnectHandler, UserStatusHandler
) )

View File

@ -33,6 +33,8 @@ import time
from configparser import ConfigParser from configparser import ConfigParser
from datetime import datetime from datetime import datetime
from hashlib import sha256, md5 from hashlib import sha256, md5
from importlib import import_module
from pathlib import Path
from signal import signal, SIGINT, SIGTERM, SIGABRT from signal import signal, SIGINT, SIGTERM, SIGABRT
from threading import Thread from threading import Thread
@ -43,8 +45,9 @@ from pyrogram.api.errors import (
PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty, PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty,
PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded, PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded,
PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned, PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned,
VolumeLocNotFound, UserMigrate, FileIdInvalid) VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate)
from pyrogram.client.handlers import DisconnectHandler from pyrogram.client.handlers import DisconnectHandler
from pyrogram.client.handlers.handler import Handler
from pyrogram.crypto import AES from pyrogram.crypto import AES
from pyrogram.session import Auth, Session from pyrogram.session import Auth, Session
from .dispatcher import Dispatcher from .dispatcher import Dispatcher
@ -90,6 +93,10 @@ class Client(Methods, BaseClient):
Code of the language used on the client, in ISO 639-1 standard. Defaults to "en". Code of the language used on the client, in ISO 639-1 standard. Defaults to "en".
This is an alternative way to set it if you don't want to use the *config.ini* file. This is an alternative way to set it if you don't want to use the *config.ini* file.
ipv6 (``bool``, *optional*):
Pass True to connect to Telegram using IPv6.
Defaults to False (IPv4).
proxy (``dict``, *optional*): proxy (``dict``, *optional*):
Your SOCKS5 Proxy settings as dict, Your SOCKS5 Proxy settings as dict,
e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*. e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*.
@ -136,6 +143,11 @@ class Client(Methods, BaseClient):
config_file (``str``, *optional*): config_file (``str``, *optional*):
Path of the configuration file. Defaults to ./config.ini Path of the configuration file. Defaults to ./config.ini
plugins_dir (``str``, *optional*):
Define a custom directory for your plugins. The plugins directory is the location in your
filesystem where Pyrogram will automatically load your update handlers.
Defaults to None (plugins disabled).
""" """
def __init__(self, def __init__(self,
@ -146,6 +158,7 @@ class Client(Methods, BaseClient):
device_model: str = None, device_model: str = None,
system_version: str = None, system_version: str = None,
lang_code: str = None, lang_code: str = None,
ipv6: bool = False,
proxy: dict = None, proxy: dict = None,
test_mode: bool = False, test_mode: bool = False,
phone_number: str = None, phone_number: str = None,
@ -154,9 +167,10 @@ class Client(Methods, BaseClient):
force_sms: bool = False, force_sms: bool = False,
first_name: str = None, first_name: str = None,
last_name: str = None, last_name: str = None,
workers: int = 4, workers: int = BaseClient.WORKERS,
workdir: str = ".", workdir: str = BaseClient.WORKDIR,
config_file: str = "./config.ini"): config_file: str = BaseClient.CONFIG_FILE,
plugins_dir: str = None):
super().__init__() super().__init__()
self.session_name = session_name self.session_name = session_name
@ -166,6 +180,7 @@ class Client(Methods, BaseClient):
self.device_model = device_model self.device_model = device_model
self.system_version = system_version self.system_version = system_version
self.lang_code = lang_code self.lang_code = lang_code
self.ipv6 = ipv6
# TODO: Make code consistent, use underscore for private/protected fields # TODO: Make code consistent, use underscore for private/protected fields
self._proxy = proxy self._proxy = proxy
self.test_mode = test_mode self.test_mode = test_mode
@ -178,9 +193,17 @@ class Client(Methods, BaseClient):
self.workers = workers self.workers = workers
self.workdir = workdir self.workdir = workdir
self.config_file = config_file self.config_file = config_file
self.plugins_dir = plugins_dir
self.dispatcher = Dispatcher(self, workers) self.dispatcher = Dispatcher(self, workers)
def __enter__(self):
self.start()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.stop()
@property @property
def proxy(self): def proxy(self):
return self._proxy return self._proxy
@ -195,17 +218,19 @@ class Client(Methods, BaseClient):
Requires no parameters. Requires no parameters.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ConnectionError`` in case you try to start an already started Client.
""" """
if self.is_started: if self.is_started:
raise ConnectionError("Client has already been started") raise ConnectionError("Client has already been started")
if self.BOT_TOKEN_RE.match(self.session_name): if self.BOT_TOKEN_RE.match(self.session_name):
self.token = self.session_name self.bot_token = self.session_name
self.session_name = self.session_name.split(":")[0] self.session_name = self.session_name.split(":")[0]
self.load_config() self.load_config()
self.load_session() self.load_session()
self.load_plugins()
self.session = Session( self.session = Session(
self, self,
@ -216,15 +241,16 @@ class Client(Methods, BaseClient):
self.session.start() self.session.start()
self.is_started = True self.is_started = True
try:
if self.user_id is None: if self.user_id is None:
if self.token is None: if self.bot_token is None:
self.authorize_user() self.authorize_user()
else: else:
self.authorize_bot() self.authorize_bot()
self.save_session() self.save_session()
if self.token is None: if self.bot_token is None:
now = time.time() now = time.time()
if abs(now - self.date) > Client.OFFLINE_SLEEP: if abs(now - self.date) > Client.OFFLINE_SLEEP:
@ -238,6 +264,10 @@ class Client(Methods, BaseClient):
self.get_initial_dialogs_chunk() self.get_initial_dialogs_chunk()
else: else:
self.send(functions.updates.GetState()) self.send(functions.updates.GetState())
except Exception as e:
self.is_started = False
self.session.stop()
raise e
for i in range(self.UPDATES_WORKERS): for i in range(self.UPDATES_WORKERS):
self.updates_workers_list.append( self.updates_workers_list.append(
@ -267,6 +297,9 @@ class Client(Methods, BaseClient):
def stop(self): def stop(self):
"""Use this method to manually stop the Client. """Use this method to manually stop the Client.
Requires no parameters. Requires no parameters.
Raises:
``ConnectionError`` in case you try to stop an already stopped Client.
""" """
if not self.is_started: if not self.is_started:
raise ConnectionError("Client is already stopped") raise ConnectionError("Client is already stopped")
@ -326,7 +359,7 @@ class Client(Methods, BaseClient):
Requires no parameters. Requires no parameters.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
self.start() self.start()
self.idle() self.idle()
@ -381,14 +414,14 @@ class Client(Methods, BaseClient):
flags=0, flags=0,
api_id=self.api_id, api_id=self.api_id,
api_hash=self.api_hash, api_hash=self.api_hash,
bot_auth_token=self.token bot_auth_token=self.bot_token
) )
) )
except UserMigrate as e: except UserMigrate as e:
self.session.stop() self.session.stop()
self.dc_id = e.x self.dc_id = e.x
self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create() self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
self.session = Session( self.session = Session(
self, self,
@ -433,7 +466,7 @@ class Client(Methods, BaseClient):
self.session.stop() self.session.stop()
self.dc_id = e.x self.dc_id = e.x
self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create() self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
self.session = Session( self.session = Session(
self, self,
@ -613,7 +646,7 @@ class Client(Methods, BaseClient):
if phone is not None: if phone is not None:
self.peers_by_phone[phone] = input_peer self.peers_by_phone[phone] = input_peer
if isinstance(entity, types.Chat): if isinstance(entity, (types.Chat, types.ChatForbidden)):
chat_id = entity.id chat_id = entity.id
peer_id = -chat_id peer_id = -chat_id
@ -623,7 +656,7 @@ class Client(Methods, BaseClient):
self.peers_by_id[peer_id] = input_peer self.peers_by_id[peer_id] = input_peer
if isinstance(entity, types.Channel): if isinstance(entity, (types.Channel, types.ChannelForbidden)):
channel_id = entity.id channel_id = entity.id
peer_id = int("-100" + str(channel_id)) peer_id = int("-100" + str(channel_id))
@ -632,7 +665,7 @@ class Client(Methods, BaseClient):
if access_hash is None: if access_hash is None:
continue continue
username = entity.username username = getattr(entity, "username", None)
input_peer = types.InputPeerChannel( input_peer = types.InputPeerChannel(
channel_id=channel_id, channel_id=channel_id,
@ -785,6 +818,7 @@ class Client(Methods, BaseClient):
message = update.message message = update.message
if not isinstance(message, types.MessageEmpty): if not isinstance(message, types.MessageEmpty):
try:
diff = self.send( diff = self.send(
functions.updates.GetChannelDifference( functions.updates.GetChannelDifference(
channel=self.resolve_peer(int("-100" + str(channel_id))), channel=self.resolve_peer(int("-100" + str(channel_id))),
@ -798,7 +832,9 @@ class Client(Methods, BaseClient):
limit=pts limit=pts
) )
) )
except ChannelPrivate:
pass
else:
if not isinstance(diff, types.updates.ChannelDifferenceEmpty): if not isinstance(diff, types.updates.ChannelDifferenceEmpty):
updates.users += diff.users updates.users += diff.users
updates.chats += diff.chats updates.chats += diff.chats
@ -815,7 +851,7 @@ class Client(Methods, BaseClient):
if len(self.channels_pts[channel_id]) > 50: if len(self.channels_pts[channel_id]) > 50:
self.channels_pts[channel_id] = self.channels_pts[channel_id][25:] self.channels_pts[channel_id] = self.channels_pts[channel_id][25:]
self.dispatcher.updates.put((update, updates.users, updates.chats)) self.dispatcher.updates_queue.put((update, updates.users, updates.chats))
elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)): elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)):
diff = self.send( diff = self.send(
functions.updates.GetDifference( functions.updates.GetDifference(
@ -826,7 +862,7 @@ class Client(Methods, BaseClient):
) )
if diff.new_messages: if diff.new_messages:
self.dispatcher.updates.put(( self.dispatcher.updates_queue.put((
types.UpdateNewMessage( types.UpdateNewMessage(
message=diff.new_messages[0], message=diff.new_messages[0],
pts=updates.pts, pts=updates.pts,
@ -836,9 +872,9 @@ class Client(Methods, BaseClient):
diff.chats diff.chats
)) ))
else: else:
self.dispatcher.updates.put((diff.other_updates[0], [], [])) self.dispatcher.updates_queue.put((diff.other_updates[0], [], []))
elif isinstance(updates, types.UpdateShort): elif isinstance(updates, types.UpdateShort):
self.dispatcher.updates.put((updates.update, [], [])) self.dispatcher.updates_queue.put((updates.update, [], []))
elif isinstance(updates, types.UpdatesTooLong): elif isinstance(updates, types.UpdatesTooLong):
log.warning(updates) log.warning(updates)
except Exception as e: except Exception as e:
@ -855,7 +891,7 @@ class Client(Methods, BaseClient):
Args: Args:
data (``Object``): data (``Object``):
The API Scheme function filled with proper arguments. The API Schema function filled with proper arguments.
retries (``int``): retries (``int``):
Number of retries. Number of retries.
@ -864,7 +900,7 @@ class Client(Methods, BaseClient):
Timeout in seconds. Timeout in seconds.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
if not self.is_started: if not self.is_started:
raise ConnectionError("Client has not been started") raise ConnectionError("Client has not been started")
@ -892,30 +928,18 @@ class Client(Methods, BaseClient):
"More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration" "More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration"
) )
for option in {"app_version", "device_model", "system_version", "lang_code"}: for option in ["app_version", "device_model", "system_version", "lang_code"]:
if getattr(self, option): if getattr(self, option):
pass pass
else: else:
setattr(self, option, Client.APP_VERSION)
if parser.has_section("pyrogram"): if parser.has_section("pyrogram"):
setattr(self, option, parser.get( setattr(self, option, parser.get(
"pyrogram", "pyrogram",
option, option,
fallback=getattr(Client, option.upper()) fallback=getattr(Client, option.upper())
)) ))
if self.lang_code:
pass
else: else:
self.lang_code = Client.LANG_CODE setattr(self, option, getattr(Client, option.upper()))
if parser.has_section("pyrogram"):
self.lang_code = parser.get(
"pyrogram",
"lang_code",
fallback=Client.LANG_CODE
)
if self._proxy: if self._proxy:
self._proxy["enabled"] = True self._proxy["enabled"] = True
@ -936,7 +960,7 @@ class Client(Methods, BaseClient):
except FileNotFoundError: except FileNotFoundError:
self.dc_id = 1 self.dc_id = 1
self.date = 0 self.date = 0
self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create() self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
else: else:
self.dc_id = s["dc_id"] self.dc_id = s["dc_id"]
self.test_mode = s["test_mode"] self.test_mode = s["test_mode"]
@ -959,6 +983,45 @@ class Client(Methods, BaseClient):
if peer: if peer:
self.peers_by_phone[k] = peer self.peers_by_phone[k] = peer
def load_plugins(self):
if self.plugins_dir is not None:
plugins_count = 0
for path in Path(self.plugins_dir).rglob("*.py"):
file_path = os.path.splitext(str(path))[0]
import_path = []
while file_path:
file_path, tail = os.path.split(file_path)
import_path.insert(0, tail)
import_path = ".".join(import_path)
module = import_module(import_path)
for name in dir(module):
# noinspection PyBroadException
try:
handler, group = getattr(module, name)
if isinstance(handler, Handler) and isinstance(group, int):
self.add_handler(handler, group)
log.info('{}("{}") from "{}" loaded in group {}'.format(
type(handler).__name__, name, import_path, group))
plugins_count += 1
except Exception:
pass
if plugins_count > 0:
log.warning('Successfully loaded {} plugin{} from "{}"'.format(
plugins_count,
"s" if plugins_count > 1 else "",
self.plugins_dir
))
else:
log.warning('No plugin loaded: "{}" doesn\'t contain any valid plugin'.format(self.plugins_dir))
def save_session(self): def save_session(self):
auth_key = base64.b64encode(self.auth_key).decode() auth_key = base64.b64encode(self.auth_key).decode()
auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)]
@ -1026,7 +1089,8 @@ class Client(Methods, BaseClient):
On success, the resolved peer id is returned in form of an InputPeer object. On success, the resolved peer id is returned in form of an InputPeer object.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``KeyError`` in case the peer doesn't exist in the internal database.
""" """
if type(peer_id) is str: if type(peer_id) is str:
if peer_id in ("self", "me"): if peer_id in ("self", "me"):
@ -1066,6 +1130,13 @@ class Client(Methods, BaseClient):
progress_args: tuple = ()): progress_args: tuple = ()):
part_size = 512 * 1024 part_size = 512 * 1024
file_size = os.path.getsize(path) file_size = os.path.getsize(path)
if file_size == 0:
raise ValueError("File size equals to 0 B")
if file_size > 1500 * 1024 * 1024:
raise ValueError("Telegram doesn't support uploading files bigger than 1500 MiB")
file_total_parts = int(math.ceil(file_size / part_size)) file_total_parts = int(math.ceil(file_size / part_size))
is_big = True if file_size > 10 * 1024 * 1024 else False is_big = True if file_size > 10 * 1024 * 1024 else False
is_missing_part = True if file_id is not None else False is_missing_part = True if file_id is not None else False
@ -1158,7 +1229,7 @@ class Client(Methods, BaseClient):
session = Session( session = Session(
self, self,
dc_id, dc_id,
Auth(dc_id, self.test_mode, self._proxy).create(), Auth(dc_id, self.test_mode, self.ipv6, self._proxy).create(),
is_media=True is_media=True
) )
@ -1243,7 +1314,7 @@ class Client(Methods, BaseClient):
cdn_session = Session( cdn_session = Session(
self, self,
r.dc_id, r.dc_id,
Auth(r.dc_id, self.test_mode, self._proxy).create(), Auth(r.dc_id, self.test_mode, self.ipv6, self._proxy).create(),
is_media=True, is_media=True,
is_cdn=True is_cdn=True
) )

View File

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

View File

@ -49,6 +49,9 @@ class BaseClient:
UPDATES_WORKERS = 1 UPDATES_WORKERS = 1
DOWNLOAD_WORKERS = 1 DOWNLOAD_WORKERS = 1
OFFLINE_SLEEP = 300 OFFLINE_SLEEP = 300
WORKERS = 4
WORKDIR = "."
CONFIG_FILE = "./config.ini"
MEDIA_TYPE_ID = { MEDIA_TYPE_ID = {
0: "thumbnail", 0: "thumbnail",
@ -64,7 +67,7 @@ class BaseClient:
} }
def __init__(self): def __init__(self):
self.token = None self.bot_token = None
self.dc_id = None self.dc_id = None
self.auth_key = None self.auth_key = None
self.user_id = None self.user_id = None
@ -116,7 +119,8 @@ class BaseClient:
def get_messages( def get_messages(
self, self,
chat_id: int or str, chat_id: int or str,
message_ids, message_ids: int or list = None,
reply_to_message_ids: int or list = None,
replies: int = 1 replies: int = 1
): ):
pass pass

File diff suppressed because it is too large Load Diff

View File

@ -17,15 +17,12 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import logging import logging
import time
from base64 import b64decode, b64encode from base64 import b64decode, b64encode
from struct import pack from struct import pack
from weakref import proxy
from pyrogram.api.errors import FloodWait
from pyrogram.client import types as pyrogram_types from pyrogram.client import types as pyrogram_types
from ...api import types, functions from ...api import types, functions
from ...api.errors import StickersetInvalid from ...api.errors import StickersetInvalid, MessageIdsEmpty
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -129,6 +126,30 @@ def parse_chat_photo(photo):
) )
def parse_user_status(user_status, user_id: int = None, is_bot: bool = False) -> pyrogram_types.UserStatus or None:
if is_bot:
return None
status = pyrogram_types.UserStatus(user_id)
if isinstance(user_status, types.UserStatusOnline):
status.online = True
status.date = user_status.expires
elif isinstance(user_status, types.UserStatusOffline):
status.offline = True
status.date = user_status.was_online
elif isinstance(user_status, types.UserStatusRecently):
status.recently = True
elif isinstance(user_status, types.UserStatusLastWeek):
status.within_week = True
elif isinstance(user_status, types.UserStatusLastMonth):
status.within_month = True
else:
status.long_time_ago = True
return status
def parse_user(user: types.User) -> pyrogram_types.User or None: def parse_user(user: types.User) -> pyrogram_types.User or None:
return pyrogram_types.User( return pyrogram_types.User(
id=user.id, id=user.id,
@ -142,7 +163,9 @@ def parse_user(user: types.User) -> pyrogram_types.User or None:
username=user.username, username=user.username,
language_code=user.lang_code, language_code=user.lang_code,
phone_number=user.phone, phone_number=user.phone,
photo=parse_chat_photo(user.photo) photo=parse_chat_photo(user.photo),
status=parse_user_status(user.status, is_bot=user.bot),
restriction_reason=user.restriction_reason
) if user else None ) if user else None
@ -162,7 +185,8 @@ def parse_user_chat(user: types.User) -> pyrogram_types.Chat:
username=user.username, username=user.username,
first_name=user.first_name, first_name=user.first_name,
last_name=user.last_name, last_name=user.last_name,
photo=parse_chat_photo(user.photo) photo=parse_chat_photo(user.photo),
restriction_reason=user.restriction_reason
) )
@ -187,7 +211,8 @@ def parse_channel_chat(channel: types.Channel) -> pyrogram_types.Chat:
type="supergroup" if channel.megagroup else "channel", type="supergroup" if channel.megagroup else "channel",
title=channel.title, title=channel.title,
username=getattr(channel, "username", None), username=getattr(channel, "username", None),
photo=parse_chat_photo(getattr(channel, "photo", None)) photo=parse_chat_photo(getattr(channel, "photo", None)),
restriction_reason=getattr(channel, "restriction_reason", None)
) )
@ -301,6 +326,7 @@ def parse_messages(
video_note = None video_note = None
sticker = None sticker = None
document = None document = None
web_page = None
media = message.media media = message.media
@ -548,6 +574,8 @@ def parse_messages(
file_size=doc.size, file_size=doc.size,
date=doc.date date=doc.date
) )
elif isinstance(media, types.MessageMediaWebPage):
web_page = True
else: else:
media = None media = None
@ -580,6 +608,8 @@ def parse_messages(
forward_from_message_id=forward_from_message_id, forward_from_message_id=forward_from_message_id,
forward_signature=forward_signature, forward_signature=forward_signature,
forward_date=forward_date, forward_date=forward_date,
mentioned=message.mentioned,
media=bool(media) or None,
edit_date=message.edit_date, edit_date=message.edit_date,
media_group_id=message.grouped_id, media_group_id=message.grouped_id,
photo=photo, photo=photo,
@ -593,10 +623,11 @@ def parse_messages(
video_note=video_note, video_note=video_note,
sticker=sticker, sticker=sticker,
document=document, document=document,
web_page=web_page,
views=message.views, views=message.views,
via_bot=parse_user(users.get(message.via_bot_id, None)), via_bot=parse_user(users.get(message.via_bot_id, None)),
outgoing=message.out, outgoing=message.out,
client=proxy(client), client=client,
reply_markup=reply_markup reply_markup=reply_markup
) )
@ -607,18 +638,14 @@ def parse_messages(
m.caption.init(m._client, m.caption_entities or []) m.caption.init(m._client, m.caption_entities or [])
if message.reply_to_msg_id and replies: if message.reply_to_msg_id and replies:
while True:
try: try:
m.reply_to_message = client.get_messages( m.reply_to_message = client.get_messages(
m.chat.id, message.reply_to_msg_id, m.chat.id,
reply_to_message_ids=message.id,
replies=replies - 1 replies=replies - 1
) )
except FloodWait as e: except MessageIdsEmpty:
log.warning("get_messages flood: waiting {} seconds".format(e.x)) pass
time.sleep(e.x)
continue
else:
break
elif isinstance(message, types.MessageService): elif isinstance(message, types.MessageService):
action = message.action action = message.action
@ -705,6 +732,7 @@ def parse_messages(
date=message.date, date=message.date,
chat=parse_chat(message, users, chats), chat=parse_chat(message, users, chats),
from_user=parse_user(users.get(message.from_id, None)), from_user=parse_user(users.get(message.from_id, None)),
service=True,
new_chat_members=new_chat_members, new_chat_members=new_chat_members,
left_chat_member=left_chat_member, left_chat_member=left_chat_member,
new_chat_title=new_chat_title, new_chat_title=new_chat_title,
@ -714,35 +742,31 @@ def parse_messages(
migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None, migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None,
group_chat_created=group_chat_created, group_chat_created=group_chat_created,
channel_chat_created=channel_chat_created, channel_chat_created=channel_chat_created,
client=proxy(client) client=client
# TODO: supergroup_chat_created # TODO: supergroup_chat_created
) )
if isinstance(action, types.MessageActionPinMessage): if isinstance(action, types.MessageActionPinMessage):
while True:
try: try:
m.pinned_message = client.get_messages( m.pinned_message = client.get_messages(
m.chat.id, message.reply_to_msg_id, m.chat.id,
reply_to_message_ids=message.id,
replies=0 replies=0
) )
except FloodWait as e: except MessageIdsEmpty:
log.warning("get_messages flood: waiting {} seconds".format(e.x)) pass
time.sleep(e.x)
continue
else: else:
break m = pyrogram_types.Message(message_id=message.id, client=client, empty=True)
else:
m = pyrogram_types.Message(message_id=message.id, client=proxy(client))
parsed_messages.append(m) parsed_messages.append(m)
return parsed_messages if is_list else parsed_messages[0] return parsed_messages if is_list else parsed_messages[0]
def parse_deleted_messages( def parse_deleted_messages(update) -> pyrogram_types.Messages:
messages: list, messages = update.messages
channel_id: int channel_id = getattr(update, "channel_id", None)
) -> pyrogram_types.Messages:
parsed_messages = [] parsed_messages = []
for message in messages: for message in messages:
@ -849,8 +873,12 @@ def parse_profile_photos(photos):
) )
def parse_callback_query(client, callback_query, users): def parse_callback_query(client, update, users):
peer = callback_query.peer message = None
inline_message_id = None
if isinstance(update, types.UpdateBotCallbackQuery):
peer = update.peer
if isinstance(peer, types.PeerUser): if isinstance(peer, types.PeerUser):
peer_id = peer.user_id peer_id = peer.user_id
@ -859,31 +887,27 @@ def parse_callback_query(client, callback_query, users):
else: else:
peer_id = int("-100" + str(peer.channel_id)) peer_id = int("-100" + str(peer.channel_id))
return pyrogram_types.CallbackQuery( message = client.get_messages(peer_id, update.msg_id)
id=str(callback_query.query_id), elif isinstance(update, types.UpdateInlineBotCallbackQuery):
from_user=parse_user(users[callback_query.user_id]), inline_message_id = b64encode(
message=client.get_messages(peer_id, callback_query.msg_id),
chat_instance=str(callback_query.chat_instance),
data=callback_query.data.decode(),
game_short_name=callback_query.game_short_name
)
def parse_inline_callback_query(callback_query, users):
return pyrogram_types.CallbackQuery(
id=str(callback_query.query_id),
from_user=parse_user(users[callback_query.user_id]),
chat_instance=str(callback_query.chat_instance),
inline_message_id=b64encode(
pack( pack(
"<iqq", "<iqq",
callback_query.msg_id.dc_id, update.msg_id.dc_id,
callback_query.msg_id.id, update.msg_id.id,
callback_query.msg_id.access_hash update.msg_id.access_hash
), ),
b"-_" b"-_"
).decode().rstrip("="), ).decode().rstrip("=")
game_short_name=callback_query.game_short_name
return pyrogram_types.CallbackQuery(
id=str(update.query_id),
from_user=parse_user(users[update.user_id]),
message=message,
inline_message_id=inline_message_id,
chat_instance=str(update.chat_instance),
data=update.data.decode(),
game_short_name=update.game_short_name,
client=client
) )
@ -918,7 +942,7 @@ def parse_chat_full(
if full_chat.pinned_msg_id: if full_chat.pinned_msg_id:
parsed_chat.pinned_message = client.get_messages( parsed_chat.pinned_message = client.get_messages(
parsed_chat.id, parsed_chat.id,
full_chat.pinned_msg_id message_ids=full_chat.pinned_msg_id
) )
if isinstance(full_chat.exported_invite, types.ChatInviteExported): if isinstance(full_chat.exported_invite, types.ChatInviteExported):
@ -941,6 +965,7 @@ def parse_chat_members(members: types.channels.ChannelParticipants or types.mess
parsed_members = [] parsed_members = []
if isinstance(members, types.channels.ChannelParticipants): if isinstance(members, types.channels.ChannelParticipants):
count = members.count
members = members.participants members = members.participants
for member in members: for member in members:
@ -999,7 +1024,7 @@ def parse_chat_members(members: types.channels.ChannelParticipants or types.mess
parsed_members.append(chat_member) parsed_members.append(chat_member)
return pyrogram_types.ChatMembers( return pyrogram_types.ChatMembers(
total_count=members.count, total_count=count,
chat_members=parsed_members chat_members=parsed_members
) )
else: else:

View File

@ -118,6 +118,9 @@ class Filters:
venue = create("Venue", lambda _, m: bool(m.venue)) venue = create("Venue", lambda _, m: bool(m.venue))
"""Filter messages that contain :obj:`Venue <pyrogram.api.types.pyrogram.Venue>` objects.""" """Filter messages that contain :obj:`Venue <pyrogram.api.types.pyrogram.Venue>` objects."""
web_page = create("WebPage", lambda _, m: m.web_page)
"""Filter messages sent with a webpage preview."""
private = create("Private", lambda _, m: bool(m.chat and m.chat.type == "private")) private = create("Private", lambda _, m: bool(m.chat and m.chat.type == "private"))
"""Filter messages sent in private chats.""" """Filter messages sent in private chats."""
@ -166,9 +169,41 @@ class Filters:
inline_keyboard = create("InlineKeyboard", lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup)) inline_keyboard = create("InlineKeyboard", lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup))
"""Filter messages containing inline keyboard markups""" """Filter messages containing inline keyboard markups"""
mentioned = create("Mentioned", lambda _, m: bool(m.mentioned))
"""Filter messages containing mentions"""
service = create("Service", lambda _, m: bool(m.service))
"""Filter service messages. A service message contains any of the following fields set
- left_chat_member
- new_chat_title
- new_chat_photo
- delete_chat_photo
- group_chat_created
- supergroup_chat_created
- channel_chat_created
- migrate_to_chat_id
- migrate_from_chat_id
- pinned_message"""
media = create("Media", lambda _, m: bool(m.media))
"""Filter media messages. A media message contains any of the following fields set
- audio
- document
- photo
- sticker
- video
- animation
- voice
- video_note
- contact
- location
- venue"""
@staticmethod @staticmethod
def command(command: str or list, def command(command: str or list,
prefix: str = "/", prefix: str or list = "/",
separator: str = " ", separator: str = " ",
case_sensitive: bool = False): case_sensitive: bool = False):
"""Filter commands, i.e.: text messages starting with "/" or any other custom prefix. """Filter commands, i.e.: text messages starting with "/" or any other custom prefix.
@ -180,9 +215,10 @@ class Filters:
a command arrives, the command itself and its arguments will be stored in the *command* a command arrives, the command itself and its arguments will be stored in the *command*
field of the :class:`Message <pyrogram.Message>`. field of the :class:`Message <pyrogram.Message>`.
prefix (``str``, *optional*): prefix (``str`` | ``list``, *optional*):
The command prefix. Defaults to "/" (slash). A prefix or a list of prefixes as string the filter should look for.
Examples: /start, .help, !settings. Defaults to "/" (slash). Examples: ".", "!", ["/", "!", "."].
Can be None or "" (empty string) to allow commands with no prefix at all.
separator (``str``, *optional*): separator (``str``, *optional*):
The command arguments separator. Defaults to " " (white space). The command arguments separator. Defaults to " " (white space).
@ -194,11 +230,14 @@ class Filters:
""" """
def f(_, m): def f(_, m):
if m.text and m.text.startswith(_.p): if m.text:
for i in _.p:
if m.text.startswith(i):
t = m.text.split(_.s) t = m.text.split(_.s)
c, a = t[0][len(_.p):], t[1:] c, a = t[0][len(i):], t[1:]
c = c if _.cs else c.lower() c = c if _.cs else c.lower()
m.command = ([c] + a) if c in _.c else None m.command = ([c] + a) if c in _.c else None
break
return bool(m.command) return bool(m.command)
@ -211,7 +250,7 @@ class Filters:
else {c if case_sensitive else {c if case_sensitive
else c.lower() else c.lower()
for c in command}, for c in command},
p=prefix, p=set(prefix) if prefix else {""},
s=separator, s=separator,
cs=case_sensitive cs=case_sensitive
) )
@ -236,80 +275,69 @@ class Filters:
return create("Regex", f, p=re.compile(pattern, flags)) return create("Regex", f, p=re.compile(pattern, flags))
@staticmethod # noinspection PyPep8Naming
def user(user: int or str or list): class user(Filter, set):
"""Filter messages coming from specific users. """Filter messages coming from one or more users.
You can use `set bound methods <https://docs.python.org/3/library/stdtypes.html#set>`_ to manipulate the
users container.
Args: Args:
user (``int`` | ``str`` | ``list``): users (``int`` | ``str`` | ``list``):
The user or list of user IDs (int) or usernames (str) the filter should look for. Pass one or more user ids/usernames to filter users.
For you yourself, "me" or "self" can be used as well.
Defaults to None (no users).
""" """
return create(
"User", def __init__(self, users: int or str or list = None):
lambda _, m: bool(m.from_user users = [] if users is None else users if type(users) is list else [users]
and (m.from_user.id in _.u super().__init__(
or (m.from_user.username {"me" if i in ["me", "self"] else i.lower().strip("@") if type(i) is str else i for i in users}
and m.from_user.username.lower() in _.u))), if type(users) is list else
u=( {"me" if users in ["me", "self"] else users.lower().strip("@") if type(users) is str else users}
{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 __call__(self, message):
def chat(chat: int or str or list): return bool(
"""Filter messages coming from specific chats. message.from_user
and (message.from_user.id in self
or (message.from_user.username
and message.from_user.username.lower() in self)
or ("me" in self
and message.from_user.is_self))
)
# noinspection PyPep8Naming
class chat(Filter, set):
"""Filter messages coming from one or more chats.
You can use `set bound methods <https://docs.python.org/3/library/stdtypes.html#set>`_ to manipulate the
chats container.
Args: Args:
chat (``int`` | ``str`` | ``list``): chats (``int`` | ``str`` | ``list``):
The chat or list of chat IDs (int) or usernames (str) the filter should look for. Pass one or more chat ids/usernames to filter chats.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
Defaults to None (no chats).
""" """
return create(
"Chat", def __init__(self, chats: int or str or list = None):
lambda _, m: bool(m.chat chats = [] if chats is None else chats if type(chats) is list else [chats]
and (m.chat.id in _.c super().__init__(
or (m.chat.username {"me" if i in ["me", "self"] else i.lower().strip("@") if type(i) is str else i for i in chats}
and m.chat.username.lower() in _.c))), if type(chats) is list else
c=( {"me" if chats in ["me", "self"] else chats.lower().strip("@") if type(chats) is str else chats}
{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 = create( def __call__(self, message):
"Service", return bool(
lambda _, m: bool( message.chat
Filters.new_chat_members(m) and (message.chat.id in self
or Filters.left_chat_member(m) or (message.chat.username
or Filters.new_chat_title(m) and message.chat.username.lower() in self)
or Filters.new_chat_photo(m) or ("me" in self and message.from_user
or Filters.delete_chat_photo(m) and message.from_user.is_self
or Filters.group_chat_created(m) and not message.outgoing))
or Filters.supergroup_chat_created(m)
or Filters.channel_chat_created(m)
or Filters.migrate_to_chat_id(m)
or Filters.migrate_from_chat_id(m)
or Filters.pinned_message(m)
) )
)
"""Filter all service messages."""
media = create( dan = create("Dan", lambda _, m: bool(m.from_user and m.from_user.id == 23122162))
"Media",
lambda _, m: bool(
Filters.audio(m)
or Filters.document(m)
or Filters.photo(m)
or Filters.sticker(m)
or Filters.video(m)
or Filters.animation(m)
or Filters.voice(m)
or Filters.video_note(m)
or Filters.contact(m)
or Filters.location(m)
or Filters.venue(m)
)
)
"""Filter all media messages."""

View File

@ -17,7 +17,8 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .callback_query_handler import CallbackQueryHandler from .callback_query_handler import CallbackQueryHandler
from .deleted_messages_handler import DeletedMessagesHandler
from .disconnect_handler import DisconnectHandler from .disconnect_handler import DisconnectHandler
from .message_handler import MessageHandler from .message_handler import MessageHandler
from .deleted_messages_handler import DeletedMessagesHandler
from .raw_update_handler import RawUpdateHandler from .raw_update_handler import RawUpdateHandler
from .user_status_handler import UserStatusHandler

View File

@ -49,6 +49,6 @@ class CallbackQueryHandler(Handler):
def check(self, callback_query): def check(self, callback_query):
return ( return (
self.filters(callback_query) self.filters(callback_query)
if self.filters if callable(self.filters)
else True else True
) )

View File

@ -50,6 +50,6 @@ class DeletedMessagesHandler(Handler):
def check(self, messages): def check(self, messages):
return ( return (
self.filters(messages.messages[0]) self.filters(messages.messages[0])
if self.filters if callable(self.filters)
else True else True
) )

View File

@ -50,6 +50,6 @@ class MessageHandler(Handler):
def check(self, message): def check(self, message):
return ( return (
self.filters(message) self.filters(message)
if self.filters if callable(self.filters)
else True else True
) )

View File

@ -0,0 +1,54 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .handler import Handler
class UserStatusHandler(Handler):
"""The UserStatus handler class. Used to handle user status updates (user going online or offline).
It is intended to be used with :meth:`add_handler() <pyrogram.Client.add_handler>`
For a nicer way to register this handler, have a look at the
:meth:`on_user_status() <pyrogram.Client.on_user_status>` decorator.
Args:
callback (``callable``):
Pass a function that will be called when a new UserStatus update arrives. It takes *(client, user_status)*
as positional arguments (look at the section below for a detailed description).
filters (:obj:`Filters <pyrogram.Filters>`):
Pass one or more filters to allow only a subset of messages to be passed
in your callback function.
Other parameters:
client (:obj:`Client <pyrogram.Client>`):
The Client itself, useful when you want to call other API methods inside the user status handler.
user_status (:obj:`UserStatus <pyrogram.UserStatus>`):
The received UserStatus update.
"""
def __init__(self, callback: callable, filters=None):
super().__init__(callback, filters)
def check(self, user_status):
return (
self.filters(user_status)
if callable(self.filters)
else True
)

View File

@ -50,6 +50,12 @@ class AnswerCallbackQuery(BaseClient):
cache_time (``int``): cache_time (``int``):
The maximum amount of time in seconds that the result of the callback query may be cached client-side. The maximum amount of time in seconds that the result of the callback query may be cached client-side.
Telegram apps will support caching starting in version 3.14. Defaults to 0. Telegram apps will support caching starting in version 3.14. Defaults to 0.
Returns:
True, on success.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
return self.send( return self.send(
functions.messages.SetBotCallbackAnswer( functions.messages.SetBotCallbackAnswer(

View File

@ -54,8 +54,8 @@ class GetInlineBotResults(BaseClient):
On Success, :obj:`BotResults <pyrogram.api.types.messages.BotResults>` is returned. On Success, :obj:`BotResults <pyrogram.api.types.messages.BotResults>` is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``TimeoutError``: If the bot fails to answer within 10 seconds ``TimeoutError`` if the bot fails to answer within 10 seconds
""" """
# TODO: Don't return the raw type # TODO: Don't return the raw type

View File

@ -24,7 +24,7 @@ class RequestCallbackAnswer(BaseClient):
def request_callback_answer(self, def request_callback_answer(self,
chat_id: int or str, chat_id: int or str,
message_id: int, message_id: int,
callback_data: str): callback_data: bytes):
"""Use this method to request a callback answer from bots. This is the equivalent of clicking an """Use this method to request a callback answer from bots. This is the equivalent of clicking an
inline button containing callback data. inline button containing callback data.
@ -37,7 +37,7 @@ class RequestCallbackAnswer(BaseClient):
message_id (``int``): message_id (``int``):
The message id the inline keyboard is attached on. The message id the inline keyboard is attached on.
callback_data (``str``): callback_data (``bytes``):
Callback data associated with the inline button you want to get the answer from. Callback data associated with the inline button you want to get the answer from.
Returns: Returns:
@ -45,14 +45,14 @@ class RequestCallbackAnswer(BaseClient):
or as an alert. or as an alert.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``TimeoutError``: If the bot fails to answer within 10 seconds ``TimeoutError`` if the bot fails to answer within 10 seconds.
""" """
return self.send( return self.send(
functions.messages.GetBotCallbackAnswer( functions.messages.GetBotCallbackAnswer(
peer=self.resolve_peer(chat_id), peer=self.resolve_peer(chat_id),
msg_id=message_id, msg_id=message_id,
data=callback_data.encode() data=callback_data
), ),
retries=0, retries=0,
timeout=10 timeout=10

View File

@ -53,7 +53,7 @@ class SendInlineBotResult(BaseClient):
On success, the sent Message is returned. On success, the sent Message is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
return self.send( return self.send(
functions.messages.SendInlineBotResult( functions.messages.SendInlineBotResult(

View File

@ -21,6 +21,7 @@ from .export_chat_invite_link import ExportChatInviteLink
from .get_chat import GetChat from .get_chat import GetChat
from .get_chat_member import GetChatMember from .get_chat_member import GetChatMember
from .get_chat_members import GetChatMembers from .get_chat_members import GetChatMembers
from .get_chat_members_count import GetChatMembersCount
from .get_dialogs import GetDialogs from .get_dialogs import GetDialogs
from .join_chat import JoinChat from .join_chat import JoinChat
from .kick_chat_member import KickChatMember from .kick_chat_member import KickChatMember
@ -52,6 +53,7 @@ class Chats(
SetChatDescription, SetChatDescription,
PinChatMessage, PinChatMessage,
UnpinChatMessage, UnpinChatMessage,
GetDialogs GetDialogs,
GetChatMembersCount
): ):
pass pass

View File

@ -38,8 +38,8 @@ class DeleteChatPhoto(BaseClient):
True on success. True on success.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError``: If a chat_id belongs to user. ``ValueError`` if a chat_id belongs to user.
""" """
peer = self.resolve_peer(chat_id) peer = self.resolve_peer(chat_id)

View File

@ -35,7 +35,7 @@ class ExportChatInviteLink(BaseClient):
On success, the exported invite link as string is returned. On success, the exported invite link as string is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
peer = self.resolve_peer(chat_id) peer = self.resolve_peer(chat_id)

View File

@ -33,7 +33,7 @@ class GetChat(BaseClient):
On success, a :obj:`Chat <pyrogram.Chat>` object is returned. On success, a :obj:`Chat <pyrogram.Chat>` object is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
peer = self.resolve_peer(chat_id) peer = self.resolve_peer(chat_id)

View File

@ -39,7 +39,7 @@ class GetChatMember(BaseClient):
On success, a :obj:`ChatMember <pyrogram.ChatMember>` object is returned. On success, a :obj:`ChatMember <pyrogram.ChatMember>` object is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
chat_id = self.resolve_peer(chat_id) chat_id = self.resolve_peer(chat_id)
user_id = self.resolve_peer(user_id) user_id = self.resolve_peer(user_id)

View File

@ -39,7 +39,7 @@ class GetChatMembers(BaseClient):
"""Use this method to get the members list of a chat. """Use this method to get the members list of a chat.
A chat can be either a basic group, a supergroup or a channel. A chat can be either a basic group, a supergroup or a channel.
You must be admin to retrieve the members (also known as "subscribers") list of a channel. You must be admin to retrieve the members list of a channel (also known as "subscribers").
Args: Args:
chat_id (``int`` | ``str``): chat_id (``int`` | ``str``):
@ -51,7 +51,7 @@ class GetChatMembers(BaseClient):
limit (``int``, *optional*): limit (``int``, *optional*):
Limits the number of members to be retrieved. Limits the number of members to be retrieved.
Defaults to 200, which is also the maximum limit allowed per method call. Defaults to 200, which is also the maximum server limit allowed per method call.
query (``str``, *optional*): query (``str``, *optional*):
Query string to filter members based on their display names and usernames. Query string to filter members based on their display names and usernames.
@ -68,9 +68,17 @@ class GetChatMembers(BaseClient):
*"administrators"* - chat administrators only. *"administrators"* - chat administrators only.
Defaults to *"all"*. Defaults to *"all"*.
.. [1] On supergroups and channels you can get up to 10,000 members for a single query string. .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members
on channels.
.. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only. .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only.
Returns:
On success, a :obj:`ChatMembers` object is returned.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError`` if you used an invalid filter or a chat_id that belongs to a user.
""" """
peer = self.resolve_peer(chat_id) peer = self.resolve_peer(chat_id)

View File

@ -0,0 +1,53 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from pyrogram.api import functions, types
from ...ext import BaseClient
class GetChatMembersCount(BaseClient):
def get_chat_members_count(self, chat_id: int or str):
"""Use this method to get the number of members in a chat.
Args:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
Returns:
On success, an integer is returned.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError`` if a chat_id belongs to user.
"""
peer = self.resolve_peer(chat_id)
if isinstance(peer, types.InputPeerChat):
return self.send(
functions.messages.GetChats(
id=[peer.chat_id]
)
).chats[0].participants_count
elif isinstance(peer, types.InputPeerChannel):
return self.send(
functions.channels.GetFullChannel(
channel=peer
)
).full_chat.participants_count
else:
raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id))

View File

@ -47,7 +47,7 @@ class GetDialogs(BaseClient):
On success, a :obj:`Dialogs` object is returned. On success, a :obj:`Dialogs` object is returned.
Raises: Raises:
:class:`Error` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
if pinned_only: if pinned_only:

View File

@ -30,7 +30,7 @@ class JoinChat(BaseClient):
channel/supergroup (in the format @username). channel/supergroup (in the format @username).
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
match = self.INVITE_LINK_RE.match(chat_id) match = self.INVITE_LINK_RE.match(chat_id)

View File

@ -17,6 +17,7 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from pyrogram.api import functions, types from pyrogram.api import functions, types
from pyrogram.client.ext import utils
from ...ext import BaseClient from ...ext import BaseClient
@ -52,13 +53,13 @@ class KickChatMember(BaseClient):
True on success. True on success.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
chat_peer = self.resolve_peer(chat_id) chat_peer = self.resolve_peer(chat_id)
user_peer = self.resolve_peer(user_id) user_peer = self.resolve_peer(user_id)
if isinstance(chat_peer, types.InputPeerChannel): if isinstance(chat_peer, types.InputPeerChannel):
self.send( r = self.send(
functions.channels.EditBanned( functions.channels.EditBanned(
channel=chat_peer, channel=chat_peer,
user_id=user_peer, user_id=user_peer,
@ -76,11 +77,17 @@ class KickChatMember(BaseClient):
) )
) )
else: else:
self.send( r = self.send(
functions.messages.DeleteChatUser( functions.messages.DeleteChatUser(
chat_id=abs(chat_id), chat_id=abs(chat_id),
user_id=user_peer user_id=user_peer
) )
) )
return True for i in r.updates:
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
return utils.parse_messages(
self, i.message,
{i.id: i for i in r.users},
{i.id: i for i in r.chats}
)

View File

@ -33,7 +33,7 @@ class LeaveChat(BaseClient):
Deletes the group chat dialog after leaving (for simple group chats, not supergroups). Deletes the group chat dialog after leaving (for simple group chats, not supergroups).
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
peer = self.resolve_peer(chat_id) peer = self.resolve_peer(chat_id)

View File

@ -41,8 +41,8 @@ class PinChatMessage(BaseClient):
True on success. True on success.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError``: If a chat_id doesn't belong to a supergroup or a channel. ``ValueError`` if a chat_id doesn't belong to a supergroup or a channel.
""" """
peer = self.resolve_peer(chat_id) peer = self.resolve_peer(chat_id)

View File

@ -25,12 +25,12 @@ class PromoteChatMember(BaseClient):
chat_id: int or str, chat_id: int or str,
user_id: int or str, user_id: int or str,
can_change_info: bool = True, can_change_info: bool = True,
can_post_messages: bool = True, can_post_messages: bool = False,
can_edit_messages: bool = True, can_edit_messages: bool = False,
can_delete_messages: bool = True, can_delete_messages: bool = True,
can_invite_users: bool = True, can_invite_users: bool = True,
can_restrict_members: bool = True, can_restrict_members: bool = True,
can_pin_messages: bool = True, can_pin_messages: bool = False,
can_promote_members: bool = False): can_promote_members: bool = False):
"""Use this method to promote or demote a user in a supergroup or a channel. """Use this method to promote or demote a user in a supergroup or a channel.
You must be an administrator in the chat for this to work and must have the appropriate admin rights. You must be an administrator in the chat for this to work and must have the appropriate admin rights.
@ -74,7 +74,7 @@ class PromoteChatMember(BaseClient):
True on success. True on success.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
self.send( self.send(
functions.channels.EditAdmin( functions.channels.EditAdmin(

View File

@ -64,7 +64,7 @@ class RestrictChatMember(BaseClient):
True on success. True on success.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
send_messages = True send_messages = True
send_media = True send_media = True

View File

@ -36,8 +36,8 @@ class SetChatDescription(BaseClient):
True on success. True on success.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError``: If a chat_id doesn't belong to a supergroup or a channel. ``ValueError`` if a chat_id doesn't belong to a supergroup or a channel.
""" """
peer = self.resolve_peer(chat_id) peer = self.resolve_peer(chat_id)

View File

@ -45,8 +45,8 @@ class SetChatPhoto(BaseClient):
True on success. True on success.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError``: If a chat_id belongs to user. ``ValueError`` if a chat_id belongs to user.
""" """
peer = self.resolve_peer(chat_id) peer = self.resolve_peer(chat_id)

View File

@ -41,8 +41,8 @@ class SetChatTitle(BaseClient):
True on success. True on success.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError``: If a chat_id belongs to user. ``ValueError`` if a chat_id belongs to user.
""" """
peer = self.resolve_peer(chat_id) peer = self.resolve_peer(chat_id)

View File

@ -40,7 +40,7 @@ class UnbanChatMember(BaseClient):
True on success. True on success.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
self.send( self.send(
functions.channels.EditBanned( functions.channels.EditBanned(

View File

@ -34,8 +34,8 @@ class UnpinChatMessage(BaseClient):
True on success. True on success.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError``: If a chat_id doesn't belong to a supergroup or a channel. ``ValueError`` if a chat_id doesn't belong to a supergroup or a channel.
""" """
peer = self.resolve_peer(chat_id) peer = self.resolve_peer(chat_id)

View File

@ -32,7 +32,7 @@ class AddContacts(BaseClient):
On success, the added contacts are returned. On success, the added contacts are returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
imported_contacts = self.send( imported_contacts = self.send(
functions.contacts.ImportContacts( functions.contacts.ImportContacts(

View File

@ -34,7 +34,7 @@ class DeleteContacts(BaseClient):
True on success. True on success.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
contacts = [] contacts = []

View File

@ -36,7 +36,7 @@ class GetContacts(BaseClient):
On success, the user's contacts are returned On success, the user's contacts are returned
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
while True: while True:
try: try:

View File

@ -17,11 +17,19 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .on_callback_query import OnCallbackQuery from .on_callback_query import OnCallbackQuery
from .on_deleted_messages import OnDeletedMessages
from .on_disconnect import OnDisconnect from .on_disconnect import OnDisconnect
from .on_message import OnMessage from .on_message import OnMessage
from .on_deleted_messages import OnDeletedMessages
from .on_raw_update import OnRawUpdate from .on_raw_update import OnRawUpdate
from .on_user_status import OnUserStatus
class Decorators(OnMessage, OnDeletedMessages, OnCallbackQuery, OnRawUpdate, OnDisconnect): class Decorators(
OnMessage,
OnDeletedMessages,
OnCallbackQuery,
OnRawUpdate,
OnDisconnect,
OnUserStatus
):
pass pass

View File

@ -17,11 +17,12 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import pyrogram import pyrogram
from pyrogram.client.filters.filter import Filter
from ...ext import BaseClient from ...ext import BaseClient
class OnCallbackQuery(BaseClient): class OnCallbackQuery(BaseClient):
def on_callback_query(self, filters=None, group: int = 0): def on_callback_query(self=None, filters=None, group: int = 0):
"""Use this decorator to automatically register a function for handling """Use this decorator to automatically register a function for handling
callback queries. This does the same thing as :meth:`add_handler` using the callback queries. This does the same thing as :meth:`add_handler` using the
:class:`CallbackQueryHandler`. :class:`CallbackQueryHandler`.
@ -36,7 +37,17 @@ class OnCallbackQuery(BaseClient):
""" """
def decorator(func): def decorator(func):
self.add_handler(pyrogram.CallbackQueryHandler(func, filters), group) if isinstance(func, tuple):
return func func = func[0].callback
handler = pyrogram.CallbackQueryHandler(func, filters)
if isinstance(self, Filter):
return pyrogram.CallbackQueryHandler(func, self), group if filters is None else filters
if self is not None:
self.add_handler(handler, group)
return handler, group
return decorator return decorator

View File

@ -17,11 +17,12 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import pyrogram import pyrogram
from pyrogram.client.filters.filter import Filter
from ...ext import BaseClient from ...ext import BaseClient
class OnDeletedMessages(BaseClient): class OnDeletedMessages(BaseClient):
def on_deleted_messages(self, filters=None, group: int = 0): def on_deleted_messages(self=None, filters=None, group: int = 0):
"""Use this decorator to automatically register a function for handling """Use this decorator to automatically register a function for handling
deleted messages. This does the same thing as :meth:`add_handler` using the deleted messages. This does the same thing as :meth:`add_handler` using the
:class:`DeletedMessagesHandler`. :class:`DeletedMessagesHandler`.
@ -36,7 +37,17 @@ class OnDeletedMessages(BaseClient):
""" """
def decorator(func): def decorator(func):
self.add_handler(pyrogram.DeletedMessagesHandler(func, filters), group) if isinstance(func, tuple):
return func func = func[0].callback
handler = pyrogram.DeletedMessagesHandler(func, filters)
if isinstance(self, Filter):
return pyrogram.DeletedMessagesHandler(func, self), group if filters is None else filters
if self is not None:
self.add_handler(handler, group)
return handler, group
return decorator return decorator

View File

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

View File

@ -17,11 +17,12 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import pyrogram import pyrogram
from pyrogram.client.filters.filter import Filter
from ...ext import BaseClient from ...ext import BaseClient
class OnMessage(BaseClient): class OnMessage(BaseClient):
def on_message(self, filters=None, group: int = 0): def on_message(self=None, filters=None, group: int = 0):
"""Use this decorator to automatically register a function for handling """Use this decorator to automatically register a function for handling
messages. This does the same thing as :meth:`add_handler` using the messages. This does the same thing as :meth:`add_handler` using the
:class:`MessageHandler`. :class:`MessageHandler`.
@ -36,7 +37,17 @@ class OnMessage(BaseClient):
""" """
def decorator(func): def decorator(func):
self.add_handler(pyrogram.MessageHandler(func, filters), group) if isinstance(func, tuple):
return func func = func[0].callback
handler = pyrogram.MessageHandler(func, filters)
if isinstance(self, Filter):
return pyrogram.MessageHandler(func, self), group if filters is None else filters
if self is not None:
self.add_handler(handler, group)
return handler, group
return decorator return decorator

View File

@ -21,7 +21,7 @@ from ...ext import BaseClient
class OnRawUpdate(BaseClient): class OnRawUpdate(BaseClient):
def on_raw_update(self, group: int = 0): def on_raw_update(self=None, group: int = 0):
"""Use this decorator to automatically register a function for handling """Use this decorator to automatically register a function for handling
raw updates. This does the same thing as :meth:`add_handler` using the raw updates. This does the same thing as :meth:`add_handler` using the
:class:`RawUpdateHandler`. :class:`RawUpdateHandler`.
@ -32,7 +32,17 @@ class OnRawUpdate(BaseClient):
""" """
def decorator(func): def decorator(func):
self.add_handler(pyrogram.RawUpdateHandler(func), group) if isinstance(func, tuple):
return func func = func[0].callback
handler = pyrogram.RawUpdateHandler(func)
if isinstance(self, int):
return handler, group if self is None else group
if self is not None:
self.add_handler(handler, group)
return handler, group
return decorator return decorator

View File

@ -0,0 +1,52 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import pyrogram
from pyrogram.client.filters.filter import Filter
from ...ext import BaseClient
class OnUserStatus(BaseClient):
def on_user_status(self=None, filters=None, group: int = 0):
"""Use this decorator to automatically register a function for handling
user status updates. This does the same thing as :meth:`add_handler` using the
:class:`UserStatusHandler`.
Args:
filters (:obj:`Filters <pyrogram.Filters>`):
Pass one or more filters to allow only a subset of UserStatus updated to be passed in your function.
group (``int``, *optional*):
The group identifier, defaults to 0.
"""
def decorator(func):
if isinstance(func, tuple):
func = func[0].callback
handler = pyrogram.UserStatusHandler(func, filters)
if isinstance(self, Filter):
return pyrogram.UserStatusHandler(func, self), group if filters is None else filters
if self is not None:
self.add_handler(handler, group)
return handler, group
return decorator

View File

@ -53,7 +53,7 @@ class DeleteMessages(BaseClient):
True on success. True on success.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
peer = self.resolve_peer(chat_id) peer = self.resolve_peer(chat_id)
message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids] message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids]

View File

@ -53,7 +53,7 @@ class EditMessageCaption(BaseClient):
On success, the edited :obj:`Message <pyrogram.Message>` is returned. On success, the edited :obj:`Message <pyrogram.Message>` is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
style = self.html if parse_mode.lower() == "html" else self.markdown style = self.html if parse_mode.lower() == "html" else self.markdown

View File

@ -26,7 +26,7 @@ from pyrogram.api.errors import FileIdInvalid
from pyrogram.client.ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
from pyrogram.client.types import ( from pyrogram.client.types import (
InputMediaPhoto, InputMediaVideo, InputMediaAudio, InputMediaPhoto, InputMediaVideo, InputMediaAudio,
InputMediaAnimation InputMediaAnimation, InputMediaDocument
) )
@ -36,6 +36,34 @@ class EditMessageMedia(BaseClient):
message_id: int, message_id: int,
media, media,
reply_markup=None): reply_markup=None):
"""Use this method to edit audio, document, photo, or video messages.
If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise,
message type can be changed arbitrarily. When inline message is edited, new file can't be uploaded.
Use previously uploaded file via its file_id or specify a URL. On success, if the edited message was sent
by the bot, the edited Message is returned, otherwise True is returned.
Args:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str).
message_id (``int``):
Message identifier in the chat specified in chat_id.
media (:obj:`InputMediaAnimation` | :obj:`InputMediaAudio` | :obj:`InputMediaDocument` | :obj:`InputMediaPhoto` | :obj:`InputMediaVideo`)
One of the InputMedia objects describing an animation, audio, document, photo or video.
reply_markup (:obj:`InlineKeyboardMarkup`, *optional*):
An InlineKeyboardMarkup object.
Returns:
On success, the edited :obj:`Message <pyrogram.Message>` is returned.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
style = self.html if media.parse_mode.lower() == "html" else self.markdown style = self.html if media.parse_mode.lower() == "html" else self.markdown
caption = media.caption caption = media.caption
@ -245,6 +273,54 @@ class EditMessageMedia(BaseClient):
) )
) )
if isinstance(media, InputMediaDocument):
if os.path.exists(media.media):
media = self.send(
functions.messages.UploadMedia(
peer=self.resolve_peer(chat_id),
media=types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map.get("." + media.media.split(".")[-1], "text/plain"),
file=self.save_file(media.media),
attributes=[
types.DocumentAttributeFilename(os.path.basename(media.media))
]
)
)
)
media = types.InputMediaDocument(
id=types.InputDocument(
id=media.document.id,
access_hash=media.document.access_hash
)
)
elif media.media.startswith("http"):
media = types.InputMediaDocumentExternal(
url=media.media
)
else:
try:
decoded = utils.decode(media.media)
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
unpacked = struct.unpack(fmt, decoded)
except (AssertionError, binascii.Error, struct.error):
raise FileIdInvalid from None
else:
if unpacked[0] not in (5, 10):
media_type = BaseClient.MEDIA_TYPE_ID.get(unpacked[0], None)
if media_type:
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
else:
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
media = types.InputMediaDocument(
id=types.InputDocument(
id=unpacked[2],
access_hash=unpacked[3]
)
)
r = self.send( r = self.send(
functions.messages.EditMessage( functions.messages.EditMessage(
peer=self.resolve_peer(chat_id), peer=self.resolve_peer(chat_id),

View File

@ -44,7 +44,7 @@ class EditMessageReplyMarkup(BaseClient):
:obj:`Message <pyrogram.Message>` is returned, otherwise True is returned. :obj:`Message <pyrogram.Message>` is returned, otherwise True is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
r = self.send( r = self.send(

View File

@ -57,7 +57,7 @@ class EditMessageText(BaseClient):
On success, the edited :obj:`Message <pyrogram.Message>` is returned. On success, the edited :obj:`Message <pyrogram.Message>` is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
style = self.html if parse_mode.lower() == "html" else self.markdown style = self.html if parse_mode.lower() == "html" else self.markdown

View File

@ -54,7 +54,7 @@ class ForwardMessages(BaseClient):
is returned. is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
is_iterable = not isinstance(message_ids, int) is_iterable = not isinstance(message_ids, int)
message_ids = list(message_ids) if is_iterable else [message_ids] message_ids = list(message_ids) if is_iterable else [message_ids]

View File

@ -56,7 +56,7 @@ class GetHistory(BaseClient):
On success, a :obj:`Messages <pyrogram.Messages>` object is returned. On success, a :obj:`Messages <pyrogram.Messages>` object is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
r = self.send( r = self.send(

View File

@ -23,9 +23,10 @@ from ...ext import BaseClient, utils
class GetMessages(BaseClient): class GetMessages(BaseClient):
def get_messages(self, def get_messages(self,
chat_id: int or str, chat_id: int or str,
message_ids, message_ids: int or list = None,
reply_to_message_ids: int or list = None,
replies: int = 1): replies: int = 1):
"""Use this method to get messages that belong to a specific chat. """Use this method to get one or more messages that belong to a specific chat.
You can retrieve up to 200 messages at once. You can retrieve up to 200 messages at once.
Args: Args:
@ -34,36 +35,46 @@ class GetMessages(BaseClient):
For your personal cloud (Saved Messages) you can simply use "me" or "self". For your personal cloud (Saved Messages) you can simply use "me" or "self".
For a contact that exists in your Telegram address book you can use his phone number (str). For a contact that exists in your Telegram address book you can use his phone number (str).
message_ids (``iterable``): message_ids (``iterable``, *optional*):
A list of Message identifiers in the chat specified in *chat_id* or a single message id, as integer. Pass a single message identifier or a list of message ids (as integers) to get the content of the
Iterators and Generators are also accepted. message themselves. Iterators and Generators are also accepted.
reply_to_message_ids (``iterable``, *optional*):
Pass a single message identifier or a list of message ids (as integers) to get the content of
the previous message you replied to using this message. Iterators and Generators are also accepted.
If *message_ids* is set, this argument will be ignored.
replies (``int``, *optional*): replies (``int``, *optional*):
The number of subsequent replies to get for each message. Defaults to 1. The number of subsequent replies to get for each message. Defaults to 1.
Returns: Returns:
On success and in case *message_ids* was a list, the returned value will be a list of the requested On success and in case *message_ids* or *reply_to_message_ids* was a list, the returned value will be a
:obj:`Messages <pyrogram.Messages>` even if a list contains just one element, otherwise if list of the requested :obj:`Messages <pyrogram.Messages>` even if a list contains just one element,
*message_ids* was an integer, the single requested :obj:`Message <pyrogram.Message>` otherwise if *message_ids* or *reply_to_message_ids* was an integer, the single requested
is returned. :obj:`Message <pyrogram.Message>` is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
ids, ids_type = (
(message_ids, types.InputMessageID) if message_ids
else (reply_to_message_ids, types.InputMessageReplyTo) if reply_to_message_ids
else (None, None)
)
if ids is None:
raise ValueError("No argument supplied")
peer = self.resolve_peer(chat_id) peer = self.resolve_peer(chat_id)
is_iterable = not isinstance(message_ids, int)
message_ids = list(message_ids) if is_iterable else [message_ids] is_iterable = not isinstance(ids, int)
message_ids = [types.InputMessageID(i) for i in message_ids] ids = list(ids) if is_iterable else [ids]
ids = [ids_type(i) for i in ids]
if isinstance(peer, types.InputPeerChannel): if isinstance(peer, types.InputPeerChannel):
rpc = functions.channels.GetMessages( rpc = functions.channels.GetMessages(channel=peer, id=ids)
channel=peer,
id=message_ids
)
else: else:
rpc = functions.messages.GetMessages( rpc = functions.messages.GetMessages(id=ids)
id=message_ids
)
r = self.send(rpc) r = self.send(rpc)

View File

@ -56,7 +56,7 @@ class SendAnimation(BaseClient):
pass a file path as string to upload a new animation that exists on your local machine. pass a file path as string to upload a new animation that exists on your local machine.
caption (``str``, *optional*): caption (``str``, *optional*):
Animation caption, 0-200 characters. Animation caption, 0-1024 characters.
parse_mode (``str``, *optional*): parse_mode (``str``, *optional*):
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>` Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
@ -73,9 +73,10 @@ class SendAnimation(BaseClient):
Animation height. Animation height.
thumb (``str``, *optional*): thumb (``str``, *optional*):
Animation thumbnail. Thumbnail of the animation file sent.
Pass a file path as string to send an image that exists on your local machine. The thumbnail should be in JPEG format and less than 200 KB in size.
Thumbnail should have 90 or less pixels of width and 90 or less pixels of height. A thumbnail's width and height should not exceed 90 pixels.
Thumbnails can't be reused and can be only uploaded as a new file.
disable_notification (``bool``, *optional*): disable_notification (``bool``, *optional*):
Sends the message silently. Sends the message silently.
@ -115,7 +116,7 @@ class SendAnimation(BaseClient):
On success, the sent :obj:`Message <pyrogram.Message>` is returned. On success, the sent :obj:`Message <pyrogram.Message>` is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
file = None file = None
style = self.html if parse_mode.lower() == "html" else self.markdown style = self.html if parse_mode.lower() == "html" else self.markdown

View File

@ -35,6 +35,7 @@ class SendAudio(BaseClient):
duration: int = 0, duration: int = 0,
performer: str = None, performer: str = None,
title: str = None, title: str = None,
thumb: str = None,
disable_notification: bool = None, disable_notification: bool = None,
reply_to_message_id: int = None, reply_to_message_id: int = None,
reply_markup=None, reply_markup=None,
@ -57,7 +58,7 @@ class SendAudio(BaseClient):
pass a file path as string to upload a new audio file that exists on your local machine. pass a file path as string to upload a new audio file that exists on your local machine.
caption (``str``, *optional*): caption (``str``, *optional*):
Audio caption, 0-200 characters. Audio caption, 0-1024 characters.
parse_mode (``str``, *optional*): parse_mode (``str``, *optional*):
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>` Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
@ -73,6 +74,12 @@ class SendAudio(BaseClient):
title (``str``, *optional*): title (``str``, *optional*):
Track name. Track name.
thumb (``str``, *optional*):
Thumbnail of the music file album cover.
The thumbnail should be in JPEG format and less than 200 KB in size.
A thumbnail's width and height should not exceed 90 pixels.
Thumbnails can't be reused and can be only uploaded as a new file.
disable_notification (``bool``, *optional*): disable_notification (``bool``, *optional*):
Sends the message silently. Sends the message silently.
Users will receive a notification with no sound. Users will receive a notification with no sound.
@ -111,16 +118,18 @@ class SendAudio(BaseClient):
On success, the sent :obj:`Message <pyrogram.Message>` is returned. On success, the sent :obj:`Message <pyrogram.Message>` is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
file = None file = None
style = self.html if parse_mode.lower() == "html" else self.markdown style = self.html if parse_mode.lower() == "html" else self.markdown
if os.path.exists(audio): if os.path.exists(audio):
thumb = None if thumb is None else self.save_file(thumb)
file = self.save_file(audio, progress=progress, progress_args=progress_args) file = self.save_file(audio, progress=progress, progress_args=progress_args)
media = types.InputMediaUploadedDocument( media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"), mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"),
file=file, file=file,
thumb=thumb,
attributes=[ attributes=[
types.DocumentAttributeAudio( types.DocumentAttributeAudio(
duration=duration, duration=duration,

View File

@ -47,8 +47,8 @@ class SendChatAction(BaseClient):
On success, True is returned. On success, True is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ValueError``: If the provided string is not a valid ChatAction ``ValueError`` if the provided string is not a valid ChatAction.
""" """
# Resolve Enum type # Resolve Enum type

View File

@ -65,7 +65,7 @@ class SendContact(BaseClient):
On success, the sent :obj:`Message <pyrogram.Message>` is returned. On success, the sent :obj:`Message <pyrogram.Message>` is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
r = self.send( r = self.send(
functions.messages.SendMedia( functions.messages.SendMedia(

View File

@ -30,6 +30,7 @@ class SendDocument(BaseClient):
def send_document(self, def send_document(self,
chat_id: int or str, chat_id: int or str,
document: str, document: str,
thumb: str = None,
caption: str = "", caption: str = "",
parse_mode: str = "", parse_mode: str = "",
disable_notification: bool = None, disable_notification: bool = None,
@ -51,8 +52,14 @@ class SendDocument(BaseClient):
pass an HTTP URL as a string for Telegram to get a file from the Internet, or pass an HTTP URL as a string for Telegram to get a file from the Internet, or
pass a file path as string to upload a new file that exists on your local machine. pass a file path as string to upload a new file that exists on your local machine.
thumb (``str``):
Thumbnail of the file sent.
The thumbnail should be in JPEG format and less than 200 KB in size.
A thumbnail's width and height should not exceed 90 pixels.
Thumbnails can't be reused and can be only uploaded as a new file.
caption (``str``, *optional*): caption (``str``, *optional*):
Document caption, 0-200 characters. Document caption, 0-1024 characters.
parse_mode (``str``, *optional*): parse_mode (``str``, *optional*):
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>` Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
@ -97,16 +104,18 @@ class SendDocument(BaseClient):
On success, the sent :obj:`Message <pyrogram.Message>` is returned. On success, the sent :obj:`Message <pyrogram.Message>` is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
file = None file = None
style = self.html if parse_mode.lower() == "html" else self.markdown style = self.html if parse_mode.lower() == "html" else self.markdown
if os.path.exists(document): if os.path.exists(document):
thumb = None if thumb is None else self.save_file(thumb)
file = self.save_file(document, progress=progress, progress_args=progress_args) file = self.save_file(document, progress=progress, progress_args=progress_args)
media = types.InputMediaUploadedDocument( media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map.get("." + document.split(".")[-1], "text/plain"), mime_type=mimetypes.types_map.get("." + document.split(".")[-1], "text/plain"),
file=file, file=file,
thumb=thumb,
attributes=[ attributes=[
types.DocumentAttributeFilename(os.path.basename(document)) types.DocumentAttributeFilename(os.path.basename(document))
] ]

View File

@ -57,7 +57,7 @@ class SendLocation(BaseClient):
On success, the sent :obj:`Message <pyrogram.Message>` is returned. On success, the sent :obj:`Message <pyrogram.Message>` is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
r = self.send( r = self.send(
functions.messages.SendMedia( functions.messages.SendMedia(

View File

@ -64,9 +64,10 @@ class SendMessage(BaseClient):
On success, the sent :obj:`Message` is returned. On success, the sent :obj:`Message` is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
style = self.html if parse_mode.lower() == "html" else self.markdown style = self.html if parse_mode.lower() == "html" else self.markdown
message, entities = style.parse(text).values()
r = self.send( r = self.send(
functions.messages.SendMessage( functions.messages.SendMessage(
@ -76,16 +77,20 @@ class SendMessage(BaseClient):
reply_to_msg_id=reply_to_message_id, reply_to_msg_id=reply_to_message_id,
random_id=self.rnd_id(), random_id=self.rnd_id(),
reply_markup=reply_markup.write() if reply_markup else None, reply_markup=reply_markup.write() if reply_markup else None,
**style.parse(text) message=message,
entities=entities
) )
) )
if isinstance(r, types.UpdateShortSentMessage): if isinstance(r, types.UpdateShortSentMessage):
return pyrogram_types.Message( return pyrogram_types.Message(
message_id=r.id, message_id=r.id,
chat=pyrogram_types.Chat(id=list(self.resolve_peer(chat_id).__dict__.values())[0], type="private"),
text=message,
date=r.date, date=r.date,
outgoing=r.out, outgoing=r.out,
entities=utils.parse_entities(r.entities, {}) or None entities=entities,
client=self
) )
for i in r.updates: for i in r.updates:

View File

@ -52,7 +52,7 @@ class SendPhoto(BaseClient):
pass a file path as string to upload a new photo that exists on your local machine. pass a file path as string to upload a new photo that exists on your local machine.
caption (``bool``, *optional*): caption (``bool``, *optional*):
Photo caption, 0-200 characters. Photo caption, 0-1024 characters.
parse_mode (``str``, *optional*): parse_mode (``str``, *optional*):
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>` Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
@ -102,7 +102,7 @@ class SendPhoto(BaseClient):
On success, the sent :obj:`Message <pyrogram.Message>` is returned. On success, the sent :obj:`Message <pyrogram.Message>` is returned.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
""" """
file = None file = None
style = self.html if parse_mode.lower() == "html" else self.markdown style = self.html if parse_mode.lower() == "html" else self.markdown

Some files were not shown because too many files have changed in this diff Show More