2
0
mirror of https://github.com/pyrogram/pyrogram synced 2025-08-29 05:18:10 +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.
- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
- **Updated** to the latest Telegram API version, currently Layer 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.
- **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">
<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://media.pyrogram.ml/images/label.png" alt="Pyrogram Label"></div>
<div><img src="https://raw.githubusercontent.com/pyrogram/logos/master/logos/pyrogram_logo2.png" alt="Pyrogram Logo"></div>
</a>
</h1>
@ -98,27 +97,27 @@ Copyright & License
<a href="https://t.me/PyrogramChat">
Community
</a>
<br><br>
<br>
<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"
alt="Scheme Layer">
<img src="https://img.shields.io/badge/schema-layer%2082-eda738.svg?longCache=true&colorA=262b30"
alt="Schema Layer">
</a>
<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">
</a>
</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
:alt: Pyrogram
.. |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
: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
:alt: TgCrypto

View File

@ -172,9 +172,8 @@ def start():
with open("{}/source/auth_key.tl".format(HOME), encoding="utf-8") as auth, \
open("{}/source/sys_msgs.tl".format(HOME), encoding="utf-8") as system, \
open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api, \
open("{}/source/pyrogram.tl".format(HOME), encoding="utf-8") as pyrogram:
schema = (auth.read() + system.read() + api.read() + pyrogram.read()).splitlines()
open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api:
schema = (auth.read() + system.read() + api.read()).splitlines()
with open("{}/template/mtproto.txt".format(HOME), encoding="utf-8") as f:
mtproto_template = f.read()
@ -507,6 +506,7 @@ def start():
f.write("\n 0xb0700028: \"pyrogram.client.types.Dialog\",")
f.write("\n 0xb0700029: \"pyrogram.client.types.Dialogs\",")
f.write("\n 0xb0700030: \"pyrogram.client.types.ChatMembers\",")
f.write("\n 0xb0700031: \"pyrogram.client.types.UserStatus\"")
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"
module = "pyrogram.api.{}.{}".format(base, k)
else:
for i in list(all_entities)[::-1]:
for i in sorted(list(all_entities), reverse=True):
if i != base:
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
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
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.
SPHINXOPTS =
SPHINXBUILD = ~/PycharmProjects/pyrogram/venv3.6/bin/sphinx-build
SPHINXBUILD = sphinx-build
SPHINXPROJ = Pyrogram
SOURCEDIR = source
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">
<a href="https://docs.pyrogram.ml">
<div><img src="https://media.pyrogram.ml/images/icon.png" alt="Pyrogram Icon"></div>
<div><img src="https://media.pyrogram.ml/images/label.png" alt="Pyrogram Label"></div>
<div><img src="_static/logo.png" alt="Pyrogram Logo"></div>
</a>
</div>
@ -24,13 +23,13 @@ Welcome to Pyrogram
<a href="https://t.me/PyrogramChat">
Community
</a>
<br><br>
<br>
<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">
</a>
<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">
</a>
</p>
@ -50,14 +49,14 @@ Welcome to Pyrogram
app.run()
Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the library.
Contents are organized by topic and can be accessed from the sidebar, or by following them one by one using the Next
button at the end of each page. But first, here's a brief overview of what is this all about.
Contents are organized into self-contained topics and can be accessed from the sidebar, or by following them in order
using the Next button at the end of each page. But first, here's a brief overview of what is this all about.
About
-----
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for building
custom Telegram applications that interact with the MTProto API as both User and Bot.
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for
building custom Telegram applications that interact with the MTProto API as both User and Bot.
Features
--------
@ -65,7 +64,7 @@ Features
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
- **Updated** to the latest Telegram API version, currently Layer 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.
- **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/UsingFilters
resources/SmartPlugins
resources/AutoAuthorization
resources/CustomizeSessions
resources/TgCrypto

View File

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

View File

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

View File

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

View File

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

View File

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

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, ...)
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
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
----------------------
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>`
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.

View File

@ -4,48 +4,85 @@ Installation
Being a Python library, Pyrogram requires Python to be installed in your system.
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/.
.. note::
Pyrogram supports Python 3 only, starting from version 3.4 and PyPy.
.. important::
Pyrogram supports **Python 3** only, starting from version 3.4. **PyPy** is supported too.
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
- 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
-------------
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
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
---------
To verify that Pyrogram is correctly installed, open a Python shell and import it.
If no error shows up you are good to go.
.. code-block:: bash
.. code-block:: python
>>> import pyrogram
>>> pyrogram.__version__
'0.7.5'
'0.9.3'
.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto
.. _develop: http://github.com/pyrogram/pyrogram

View File

@ -53,6 +53,7 @@ fits better for you:
)
.. note::
The examples below assume you have created a ``config.ini`` file, thus they won't show the *api_id*
and *api_hash* parameters usage.
@ -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`_)
and the **phone code** you will receive:
.. code::
.. code-block:: text
Enter phone number: +39**********
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,
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
-----------------

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
named after the `Telegram Bot API`_.
Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_):
- Get information about the authorized user:
Here's a simple example:
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
app.start()
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
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
-------------
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
``pyrogram.api`` package and call any Telegram API method you wish using the :meth:`send() <pyrogram.Client.send>`
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
re-implemented by providing a much simpler and cleaner interface which is very similar to the Bot API.
@ -54,17 +69,13 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
from pyrogram import Client
from pyrogram.api import functions
app = Client("my_account")
app.start()
app.send(
functions.account.UpdateProfile(
first_name="Dan", last_name="Tès",
about="Bio written from Pyrogram"
with Client("my_account") as app:
app.send(
functions.account.UpdateProfile(
first_name="Dan", last_name="Tès",
about="Bio written from Pyrogram"
)
)
)
app.stop()
- Share your Last Seen time only with your contacts:
@ -73,17 +84,13 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
from pyrogram import Client
from pyrogram.api import functions, types
app = Client("my_account")
app.start()
app.send(
functions.account.SetPrivacy(
key=types.InputPrivacyKeyStatusTimestamp(),
rules=[types.InputPrivacyValueAllowContacts()]
with Client("my_account") as app:
app.send(
functions.account.SetPrivacy(
key=types.InputPrivacyKeyStatusTimestamp(),
rules=[types.InputPrivacyValueAllowContacts()]
)
)
)
app.stop()
- Invite users to your channel/supergroup:
@ -92,21 +99,17 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
from pyrogram import Client
from pyrogram.api import functions, types
app = Client("my_account")
app.start()
app.send(
functions.channels.InviteToChannel(
channel=app.resolve_peer(123456789), # ID or Username
users=[ # The users you want to invite
app.resolve_peer(23456789), # By ID
app.resolve_peer("username"), # By username
app.resolve_peer("393281234567"), # By phone number
]
with Client("my_account") as app:
app.send(
functions.channels.InviteToChannel(
channel=app.resolve_peer(123456789), # ID or Username
users=[ # The users you want to invite
app.resolve_peer(23456789), # By ID
app.resolve_peer("username"), # By username
app.resolve_peer("393281234567"), # By phone number
]
)
)
)
app.stop()
.. _methods: ../pyrogram/Client.html#messages
.. _plenty of them: ../pyrogram/Client.html#messages

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
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
---: | :---
[**hello_world**](hello_world.py) | Demonstration of basic API usages
[**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)
[**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_participants2**](get_participants2.py) | Improved version to get more than 10.000 users
[**get_chat_members**](get_chat_members.py) | How to get the first 10.000 members of a supergroup/channel
[**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
[**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

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.
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.on_callback_query()
def answer(client, callback_query):
client.answer_callback_query(
callback_query.id,
text='Button contains: "{}"'.format(callback_query.data),
show_alert=True
)
callback_query.answer('Button contains: "{}"'.format(callback_query.data), show_alert=True)
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.
It uses the @on_message decorator to register a MessageHandler
and applies two filters on it, Filters.text and Filters.private to make
sure it will only reply to private text messages.
It uses the @on_message decorator to register a MessageHandler and applies two filters on it:
Filters.text and Filters.private to make sure it will reply to private text messages only.
"""
from pyrogram import Client, Filters
app = Client("my_account")
@app.on_message(Filters.text & Filters.private)
def echo(client, message):
client.send_message(
message.chat.id, message.text,
reply_to_message_id=message.message_id
)
message.reply(message.text, quote=True)
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,53 +1,31 @@
# 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/>.
"""This example shows how to retrieve the full message history of a chat"""
import time
from pyrogram import Client
from pyrogram.api.errors import FloodWait
"""This example shows how to retrieve the full message history of a chat"""
app = Client("my_account")
target = "me" # "me" refers to your own chat (Saved Messages)
messages = [] # List that will contain all the messages of the target chat
offset_id = 0 # ID of the last message of the chunk
app.start()
with app:
while True:
try:
m = app.get_history(target, offset_id=offset_id)
except FloodWait as e: # For very large chats the method call can raise a FloodWait
print("waiting {}".format(e.x))
time.sleep(e.x) # Sleep X seconds before continuing
continue
while True:
try:
m = app.get_history(target, offset_id=offset_id)
except FloodWait as e:
# For very large chats the method call can raise a FloodWait
print("waiting {}".format(e.x))
time.sleep(e.x) # Sleep X seconds before continuing
continue
if not m.messages:
break
if not m.messages:
break
messages += m.messages
offset_id = m.messages[-1].message_id
messages += m.messages
offset_id = m.messages[-1].message_id
print("Messages: {}".format(len(messages)))
app.stop()
print("Messages: {}".format(len(messages)))
# Now the "messages" list contains all the messages sorted by date in
# 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
# 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/>.
"""This example demonstrates a basic API usage"""
from pyrogram import Client
"""This example demonstrates a basic API usage"""
# Create a new Client instance
app = Client("my_account")

View File

@ -1,25 +1,7 @@
# 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/>.
"""This example shows how to query an inline bot"""
from pyrogram import Client
"""This example shows how to query an inline bot"""
# Create a new Client
app = Client("my_account")

View File

@ -1,25 +1,7 @@
# 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/>.
"""This example shows how to handle raw updates"""
from pyrogram import Client
"""This example shows how to handle raw updates"""
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.
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...
"""
from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
# Create a client using your bot token
app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
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.
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.on_message(Filters.chat("PyrogramChat") & Filters.new_chat_members)
@app.on_message(enabled_groups & Filters.new_chat_members)
def welcome(client, message):
# Build the new members list (with mentions) by using their first_name
new_members = ", ".join([
"[{}](tg://user?id={})".format(i.first_name, i.id)
for i in message.new_chat_members
])
chat_id = message.chat.id
# Build the welcome message by using an emoji and the list we built above
text = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!".format(
Emoji.SPARKLES,
new_members
)
# Get the previous welcome message and members, if any
previous_welcome, previous_members = last_welcomes.pop(chat_id, (None, []))
# Send the welcome message
client.send_message(
message.chat.id, text,
reply_to_message_id=message.message_id,
disable_web_page_preview=True
)
# Delete the previous message, if exists
if previous_welcome:
previous_welcome.delete()
# 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()

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"
)
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
__version__ = "0.8.0dev1"
__version__ = "0.9.3"
from .api.errors import Error
from .client.types import (
Audio, Chat, ChatMember, ChatMembers, ChatPhoto, Contact, Document, InputMediaPhoto,
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,
InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove
)
from .client import (
Client, ChatAction, ParseMode, Emoji,
MessageHandler, DeletedMessagesHandler, CallbackQueryHandler,
RawUpdateHandler, DisconnectHandler, Filters
RawUpdateHandler, DisconnectHandler, UserStatusHandler, Filters
)

View File

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

View File

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

View File

@ -29,15 +29,12 @@ class Int(Object):
return int.from_bytes(b.read(cls.SIZE), "little", signed=signed)
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):
SIZE = 8
def __new__(cls, *args):
return super().__new__(cls, *args)
class Int128(Int):
SIZE = 16

View File

@ -29,4 +29,4 @@ class Null(Object):
return None
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):
@staticmethod
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:
return super().__new__(cls, value.encode())

View File

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

View File

@ -33,6 +33,8 @@ import time
from configparser import ConfigParser
from datetime import datetime
from hashlib import sha256, md5
from importlib import import_module
from pathlib import Path
from signal import signal, SIGINT, SIGTERM, SIGABRT
from threading import Thread
@ -43,8 +45,9 @@ from pyrogram.api.errors import (
PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty,
PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded,
PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned,
VolumeLocNotFound, UserMigrate, FileIdInvalid)
VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate)
from pyrogram.client.handlers import DisconnectHandler
from pyrogram.client.handlers.handler import Handler
from pyrogram.crypto import AES
from pyrogram.session import Auth, Session
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".
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*):
Your SOCKS5 Proxy settings as dict,
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*):
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,
@ -146,6 +158,7 @@ class Client(Methods, BaseClient):
device_model: str = None,
system_version: str = None,
lang_code: str = None,
ipv6: bool = False,
proxy: dict = None,
test_mode: bool = False,
phone_number: str = None,
@ -154,9 +167,10 @@ class Client(Methods, BaseClient):
force_sms: bool = False,
first_name: str = None,
last_name: str = None,
workers: int = 4,
workdir: str = ".",
config_file: str = "./config.ini"):
workers: int = BaseClient.WORKERS,
workdir: str = BaseClient.WORKDIR,
config_file: str = BaseClient.CONFIG_FILE,
plugins_dir: str = None):
super().__init__()
self.session_name = session_name
@ -166,6 +180,7 @@ class Client(Methods, BaseClient):
self.device_model = device_model
self.system_version = system_version
self.lang_code = lang_code
self.ipv6 = ipv6
# TODO: Make code consistent, use underscore for private/protected fields
self._proxy = proxy
self.test_mode = test_mode
@ -178,9 +193,17 @@ class Client(Methods, BaseClient):
self.workers = workers
self.workdir = workdir
self.config_file = config_file
self.plugins_dir = plugins_dir
self.dispatcher = Dispatcher(self, workers)
def __enter__(self):
self.start()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.stop()
@property
def proxy(self):
return self._proxy
@ -195,17 +218,19 @@ class Client(Methods, BaseClient):
Requires no parameters.
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:
raise ConnectionError("Client has already been started")
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.load_config()
self.load_session()
self.load_plugins()
self.session = Session(
self,
@ -216,28 +241,33 @@ class Client(Methods, BaseClient):
self.session.start()
self.is_started = True
if self.user_id is None:
if self.token is None:
self.authorize_user()
try:
if self.user_id is None:
if self.bot_token is None:
self.authorize_user()
else:
self.authorize_bot()
self.save_session()
if self.bot_token is None:
now = time.time()
if abs(now - self.date) > Client.OFFLINE_SLEEP:
self.peers_by_username = {}
self.peers_by_phone = {}
self.get_initial_dialogs()
self.get_contacts()
else:
self.send(functions.messages.GetPinnedDialogs())
self.get_initial_dialogs_chunk()
else:
self.authorize_bot()
self.save_session()
if self.token is None:
now = time.time()
if abs(now - self.date) > Client.OFFLINE_SLEEP:
self.peers_by_username = {}
self.peers_by_phone = {}
self.get_initial_dialogs()
self.get_contacts()
else:
self.send(functions.messages.GetPinnedDialogs())
self.get_initial_dialogs_chunk()
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):
self.updates_workers_list.append(
@ -267,6 +297,9 @@ class Client(Methods, BaseClient):
def stop(self):
"""Use this method to manually stop the Client.
Requires no parameters.
Raises:
``ConnectionError`` in case you try to stop an already stopped Client.
"""
if not self.is_started:
raise ConnectionError("Client is already stopped")
@ -326,7 +359,7 @@ class Client(Methods, BaseClient):
Requires no parameters.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
self.start()
self.idle()
@ -381,14 +414,14 @@ class Client(Methods, BaseClient):
flags=0,
api_id=self.api_id,
api_hash=self.api_hash,
bot_auth_token=self.token
bot_auth_token=self.bot_token
)
)
except UserMigrate as e:
self.session.stop()
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,
@ -433,7 +466,7 @@ class Client(Methods, BaseClient):
self.session.stop()
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,
@ -613,7 +646,7 @@ class Client(Methods, BaseClient):
if phone is not None:
self.peers_by_phone[phone] = input_peer
if isinstance(entity, types.Chat):
if isinstance(entity, (types.Chat, types.ChatForbidden)):
chat_id = entity.id
peer_id = -chat_id
@ -623,7 +656,7 @@ class Client(Methods, BaseClient):
self.peers_by_id[peer_id] = input_peer
if isinstance(entity, types.Channel):
if isinstance(entity, (types.Channel, types.ChannelForbidden)):
channel_id = entity.id
peer_id = int("-100" + str(channel_id))
@ -632,7 +665,7 @@ class Client(Methods, BaseClient):
if access_hash is None:
continue
username = entity.username
username = getattr(entity, "username", None)
input_peer = types.InputPeerChannel(
channel_id=channel_id,
@ -785,23 +818,26 @@ class Client(Methods, BaseClient):
message = update.message
if not isinstance(message, types.MessageEmpty):
diff = self.send(
functions.updates.GetChannelDifference(
channel=self.resolve_peer(int("-100" + str(channel_id))),
filter=types.ChannelMessagesFilter(
ranges=[types.MessageRange(
min_id=update.message.id,
max_id=update.message.id
)]
),
pts=pts - pts_count,
limit=pts
try:
diff = self.send(
functions.updates.GetChannelDifference(
channel=self.resolve_peer(int("-100" + str(channel_id))),
filter=types.ChannelMessagesFilter(
ranges=[types.MessageRange(
min_id=update.message.id,
max_id=update.message.id
)]
),
pts=pts - pts_count,
limit=pts
)
)
)
if not isinstance(diff, types.updates.ChannelDifferenceEmpty):
updates.users += diff.users
updates.chats += diff.chats
except ChannelPrivate:
pass
else:
if not isinstance(diff, types.updates.ChannelDifferenceEmpty):
updates.users += diff.users
updates.chats += diff.chats
if channel_id and pts:
if channel_id not in self.channels_pts:
@ -815,7 +851,7 @@ class Client(Methods, BaseClient):
if len(self.channels_pts[channel_id]) > 50:
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)):
diff = self.send(
functions.updates.GetDifference(
@ -826,7 +862,7 @@ class Client(Methods, BaseClient):
)
if diff.new_messages:
self.dispatcher.updates.put((
self.dispatcher.updates_queue.put((
types.UpdateNewMessage(
message=diff.new_messages[0],
pts=updates.pts,
@ -836,9 +872,9 @@ class Client(Methods, BaseClient):
diff.chats
))
else:
self.dispatcher.updates.put((diff.other_updates[0], [], []))
self.dispatcher.updates_queue.put((diff.other_updates[0], [], []))
elif isinstance(updates, types.UpdateShort):
self.dispatcher.updates.put((updates.update, [], []))
self.dispatcher.updates_queue.put((updates.update, [], []))
elif isinstance(updates, types.UpdatesTooLong):
log.warning(updates)
except Exception as e:
@ -855,7 +891,7 @@ class Client(Methods, BaseClient):
Args:
data (``Object``):
The API Scheme function filled with proper arguments.
The API Schema function filled with proper arguments.
retries (``int``):
Number of retries.
@ -864,7 +900,7 @@ class Client(Methods, BaseClient):
Timeout in seconds.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
if not self.is_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"
)
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):
pass
else:
setattr(self, option, Client.APP_VERSION)
if parser.has_section("pyrogram"):
setattr(self, option, parser.get(
"pyrogram",
option,
fallback=getattr(Client, option.upper())
))
if self.lang_code:
pass
else:
self.lang_code = Client.LANG_CODE
if parser.has_section("pyrogram"):
self.lang_code = parser.get(
"pyrogram",
"lang_code",
fallback=Client.LANG_CODE
)
else:
setattr(self, option, getattr(Client, option.upper()))
if self._proxy:
self._proxy["enabled"] = True
@ -936,7 +960,7 @@ class Client(Methods, BaseClient):
except FileNotFoundError:
self.dc_id = 1
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:
self.dc_id = s["dc_id"]
self.test_mode = s["test_mode"]
@ -959,6 +983,45 @@ class Client(Methods, BaseClient):
if 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):
auth_key = base64.b64encode(self.auth_key).decode()
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.
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 peer_id in ("self", "me"):
@ -1066,6 +1130,13 @@ class Client(Methods, BaseClient):
progress_args: tuple = ()):
part_size = 512 * 1024
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))
is_big = True if file_size > 10 * 1024 * 1024 else False
is_missing_part = True if file_id is not None else False
@ -1158,7 +1229,7 @@ class Client(Methods, BaseClient):
session = Session(
self,
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
)
@ -1243,7 +1314,7 @@ class Client(Methods, BaseClient):
cdn_session = Session(
self,
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_cdn=True
)

View File

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

View File

@ -49,6 +49,9 @@ class BaseClient:
UPDATES_WORKERS = 1
DOWNLOAD_WORKERS = 1
OFFLINE_SLEEP = 300
WORKERS = 4
WORKDIR = "."
CONFIG_FILE = "./config.ini"
MEDIA_TYPE_ID = {
0: "thumbnail",
@ -64,7 +67,7 @@ class BaseClient:
}
def __init__(self):
self.token = None
self.bot_token = None
self.dc_id = None
self.auth_key = None
self.user_id = None
@ -116,7 +119,8 @@ class BaseClient:
def get_messages(
self,
chat_id: int or str,
message_ids,
message_ids: int or list = None,
reply_to_message_ids: int or list = None,
replies: int = 1
):
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/>.
import logging
import time
from base64 import b64decode, b64encode
from struct import pack
from weakref import proxy
from pyrogram.api.errors import FloodWait
from pyrogram.client import types as pyrogram_types
from ...api import types, functions
from ...api.errors import StickersetInvalid
from ...api.errors import StickersetInvalid, MessageIdsEmpty
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:
return pyrogram_types.User(
id=user.id,
@ -142,7 +163,9 @@ def parse_user(user: types.User) -> pyrogram_types.User or None:
username=user.username,
language_code=user.lang_code,
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
@ -162,7 +185,8 @@ def parse_user_chat(user: types.User) -> pyrogram_types.Chat:
username=user.username,
first_name=user.first_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",
title=channel.title,
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
sticker = None
document = None
web_page = None
media = message.media
@ -548,6 +574,8 @@ def parse_messages(
file_size=doc.size,
date=doc.date
)
elif isinstance(media, types.MessageMediaWebPage):
web_page = True
else:
media = None
@ -580,6 +608,8 @@ def parse_messages(
forward_from_message_id=forward_from_message_id,
forward_signature=forward_signature,
forward_date=forward_date,
mentioned=message.mentioned,
media=bool(media) or None,
edit_date=message.edit_date,
media_group_id=message.grouped_id,
photo=photo,
@ -593,10 +623,11 @@ def parse_messages(
video_note=video_note,
sticker=sticker,
document=document,
web_page=web_page,
views=message.views,
via_bot=parse_user(users.get(message.via_bot_id, None)),
outgoing=message.out,
client=proxy(client),
client=client,
reply_markup=reply_markup
)
@ -607,18 +638,14 @@ def parse_messages(
m.caption.init(m._client, m.caption_entities or [])
if message.reply_to_msg_id and replies:
while True:
try:
m.reply_to_message = client.get_messages(
m.chat.id, message.reply_to_msg_id,
replies=replies - 1
)
except FloodWait as e:
log.warning("get_messages flood: waiting {} seconds".format(e.x))
time.sleep(e.x)
continue
else:
break
try:
m.reply_to_message = client.get_messages(
m.chat.id,
reply_to_message_ids=message.id,
replies=replies - 1
)
except MessageIdsEmpty:
pass
elif isinstance(message, types.MessageService):
action = message.action
@ -705,6 +732,7 @@ def parse_messages(
date=message.date,
chat=parse_chat(message, users, chats),
from_user=parse_user(users.get(message.from_id, None)),
service=True,
new_chat_members=new_chat_members,
left_chat_member=left_chat_member,
new_chat_title=new_chat_title,
@ -714,35 +742,31 @@ def parse_messages(
migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None,
group_chat_created=group_chat_created,
channel_chat_created=channel_chat_created,
client=proxy(client)
client=client
# TODO: supergroup_chat_created
)
if isinstance(action, types.MessageActionPinMessage):
while True:
try:
m.pinned_message = client.get_messages(
m.chat.id, message.reply_to_msg_id,
replies=0
)
except FloodWait as e:
log.warning("get_messages flood: waiting {} seconds".format(e.x))
time.sleep(e.x)
continue
else:
break
try:
m.pinned_message = client.get_messages(
m.chat.id,
reply_to_message_ids=message.id,
replies=0
)
except MessageIdsEmpty:
pass
else:
m = pyrogram_types.Message(message_id=message.id, client=proxy(client))
m = pyrogram_types.Message(message_id=message.id, client=client, empty=True)
parsed_messages.append(m)
return parsed_messages if is_list else parsed_messages[0]
def parse_deleted_messages(
messages: list,
channel_id: int
) -> pyrogram_types.Messages:
def parse_deleted_messages(update) -> pyrogram_types.Messages:
messages = update.messages
channel_id = getattr(update, "channel_id", None)
parsed_messages = []
for message in messages:
@ -849,41 +873,41 @@ def parse_profile_photos(photos):
)
def parse_callback_query(client, callback_query, users):
peer = callback_query.peer
def parse_callback_query(client, update, users):
message = None
inline_message_id = None
if isinstance(peer, types.PeerUser):
peer_id = peer.user_id
elif isinstance(peer, types.PeerChat):
peer_id = -peer.chat_id
else:
peer_id = int("-100" + str(peer.channel_id))
if isinstance(update, types.UpdateBotCallbackQuery):
peer = update.peer
return pyrogram_types.CallbackQuery(
id=str(callback_query.query_id),
from_user=parse_user(users[callback_query.user_id]),
message=client.get_messages(peer_id, callback_query.msg_id),
chat_instance=str(callback_query.chat_instance),
data=callback_query.data.decode(),
game_short_name=callback_query.game_short_name
)
if isinstance(peer, types.PeerUser):
peer_id = peer.user_id
elif isinstance(peer, types.PeerChat):
peer_id = -peer.chat_id
else:
peer_id = int("-100" + str(peer.channel_id))
def parse_inline_callback_query(callback_query, users):
return pyrogram_types.CallbackQuery(
id=str(callback_query.query_id),
from_user=parse_user(users[callback_query.user_id]),
chat_instance=str(callback_query.chat_instance),
inline_message_id=b64encode(
message = client.get_messages(peer_id, update.msg_id)
elif isinstance(update, types.UpdateInlineBotCallbackQuery):
inline_message_id = b64encode(
pack(
"<iqq",
callback_query.msg_id.dc_id,
callback_query.msg_id.id,
callback_query.msg_id.access_hash
update.msg_id.dc_id,
update.msg_id.id,
update.msg_id.access_hash
),
b"-_"
).decode().rstrip("="),
game_short_name=callback_query.game_short_name
).decode().rstrip("=")
return pyrogram_types.CallbackQuery(
id=str(update.query_id),
from_user=parse_user(users[update.user_id]),
message=message,
inline_message_id=inline_message_id,
chat_instance=str(update.chat_instance),
data=update.data.decode(),
game_short_name=update.game_short_name,
client=client
)
@ -918,7 +942,7 @@ def parse_chat_full(
if full_chat.pinned_msg_id:
parsed_chat.pinned_message = client.get_messages(
parsed_chat.id,
full_chat.pinned_msg_id
message_ids=full_chat.pinned_msg_id
)
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 = []
if isinstance(members, types.channels.ChannelParticipants):
count = members.count
members = members.participants
for member in members:
@ -999,7 +1024,7 @@ def parse_chat_members(members: types.channels.ChannelParticipants or types.mess
parsed_members.append(chat_member)
return pyrogram_types.ChatMembers(
total_count=members.count,
total_count=count,
chat_members=parsed_members
)
else:

View File

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

View File

@ -17,7 +17,8 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .callback_query_handler import CallbackQueryHandler
from .deleted_messages_handler import DeletedMessagesHandler
from .disconnect_handler import DisconnectHandler
from .message_handler import MessageHandler
from .deleted_messages_handler import DeletedMessagesHandler
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):
return (
self.filters(callback_query)
if self.filters
if callable(self.filters)
else True
)

View File

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

View File

@ -50,6 +50,6 @@ class MessageHandler(Handler):
def check(self, message):
return (
self.filters(message)
if self.filters
if callable(self.filters)
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``):
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.
Returns:
True, on success.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
return self.send(
functions.messages.SetBotCallbackAnswer(

View File

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

View File

@ -24,7 +24,7 @@ class RequestCallbackAnswer(BaseClient):
def request_callback_answer(self,
chat_id: int or str,
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
inline button containing callback data.
@ -37,7 +37,7 @@ class RequestCallbackAnswer(BaseClient):
message_id (``int``):
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.
Returns:
@ -45,14 +45,14 @@ class RequestCallbackAnswer(BaseClient):
or as an alert.
Raises:
:class:`Error <pyrogram.Error>`
``TimeoutError``: If the bot fails to answer within 10 seconds
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``TimeoutError`` if the bot fails to answer within 10 seconds.
"""
return self.send(
functions.messages.GetBotCallbackAnswer(
peer=self.resolve_peer(chat_id),
msg_id=message_id,
data=callback_data.encode()
data=callback_data
),
retries=0,
timeout=10

View File

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

View File

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

View File

@ -38,8 +38,8 @@ class DeleteChatPhoto(BaseClient):
True on success.
Raises:
:class:`Error <pyrogram.Error>`
``ValueError``: If a chat_id belongs to user.
: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)

View File

@ -35,7 +35,7 @@ class ExportChatInviteLink(BaseClient):
On success, the exported invite link as string is returned.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
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.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
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.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
chat_id = self.resolve_peer(chat_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.
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:
chat_id (``int`` | ``str``):
@ -51,7 +51,7 @@ class GetChatMembers(BaseClient):
limit (``int``, *optional*):
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 string to filter members based on their display names and usernames.
@ -68,9 +68,17 @@ class GetChatMembers(BaseClient):
*"administrators"* - chat administrators only.
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.
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)

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.
Raises:
:class:`Error`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
if pinned_only:

View File

@ -30,7 +30,7 @@ class JoinChat(BaseClient):
channel/supergroup (in the format @username).
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
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/>.
from pyrogram.api import functions, types
from pyrogram.client.ext import utils
from ...ext import BaseClient
@ -52,13 +53,13 @@ class KickChatMember(BaseClient):
True on success.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
chat_peer = self.resolve_peer(chat_id)
user_peer = self.resolve_peer(user_id)
if isinstance(chat_peer, types.InputPeerChannel):
self.send(
r = self.send(
functions.channels.EditBanned(
channel=chat_peer,
user_id=user_peer,
@ -76,11 +77,17 @@ class KickChatMember(BaseClient):
)
)
else:
self.send(
r = self.send(
functions.messages.DeleteChatUser(
chat_id=abs(chat_id),
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).
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
peer = self.resolve_peer(chat_id)

View File

@ -41,8 +41,8 @@ class PinChatMessage(BaseClient):
True on success.
Raises:
:class:`Error <pyrogram.Error>`
``ValueError``: If a chat_id doesn't belong to a supergroup or a channel.
: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.
"""
peer = self.resolve_peer(chat_id)

View File

@ -25,12 +25,12 @@ class PromoteChatMember(BaseClient):
chat_id: int or str,
user_id: int or str,
can_change_info: bool = True,
can_post_messages: bool = True,
can_edit_messages: bool = True,
can_post_messages: bool = False,
can_edit_messages: bool = False,
can_delete_messages: bool = True,
can_invite_users: bool = True,
can_restrict_members: bool = True,
can_pin_messages: bool = True,
can_pin_messages: bool = False,
can_promote_members: bool = False):
"""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.
@ -74,7 +74,7 @@ class PromoteChatMember(BaseClient):
True on success.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
self.send(
functions.channels.EditAdmin(

View File

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

View File

@ -36,8 +36,8 @@ class SetChatDescription(BaseClient):
True on success.
Raises:
:class:`Error <pyrogram.Error>`
``ValueError``: If a chat_id doesn't belong to a supergroup or a channel.
: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.
"""
peer = self.resolve_peer(chat_id)

View File

@ -45,8 +45,8 @@ class SetChatPhoto(BaseClient):
True on success.
Raises:
:class:`Error <pyrogram.Error>`
``ValueError``: If a chat_id belongs to user.
: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)

View File

@ -41,8 +41,8 @@ class SetChatTitle(BaseClient):
True on success.
Raises:
:class:`Error <pyrogram.Error>`
``ValueError``: If a chat_id belongs to user.
: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)

View File

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

View File

@ -34,8 +34,8 @@ class UnpinChatMessage(BaseClient):
True on success.
Raises:
:class:`Error <pyrogram.Error>`
``ValueError``: If a chat_id doesn't belong to a supergroup or a channel.
: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.
"""
peer = self.resolve_peer(chat_id)

View File

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

View File

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

View File

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

View File

@ -17,11 +17,19 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .on_callback_query import OnCallbackQuery
from .on_deleted_messages import OnDeletedMessages
from .on_disconnect import OnDisconnect
from .on_message import OnMessage
from .on_deleted_messages import OnDeletedMessages
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

View File

@ -17,11 +17,12 @@
# 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 OnCallbackQuery(BaseClient):
def on_callback_query(self, filters=None, group: int = 0):
def on_callback_query(self=None, filters=None, group: int = 0):
"""Use this decorator to automatically register a function for handling
callback queries. This does the same thing as :meth:`add_handler` using the
:class:`CallbackQueryHandler`.
@ -36,7 +37,17 @@ class OnCallbackQuery(BaseClient):
"""
def decorator(func):
self.add_handler(pyrogram.CallbackQueryHandler(func, filters), group)
return func
if isinstance(func, tuple):
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

View File

@ -17,11 +17,12 @@
# 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 OnDeletedMessages(BaseClient):
def on_deleted_messages(self, filters=None, group: int = 0):
def on_deleted_messages(self=None, filters=None, group: int = 0):
"""Use this decorator to automatically register a function for handling
deleted messages. This does the same thing as :meth:`add_handler` using the
:class:`DeletedMessagesHandler`.
@ -36,7 +37,17 @@ class OnDeletedMessages(BaseClient):
"""
def decorator(func):
self.add_handler(pyrogram.DeletedMessagesHandler(func, filters), group)
return func
if isinstance(func, tuple):
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

View File

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

View File

@ -17,11 +17,12 @@
# 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 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
messages. This does the same thing as :meth:`add_handler` using the
:class:`MessageHandler`.
@ -36,7 +37,17 @@ class OnMessage(BaseClient):
"""
def decorator(func):
self.add_handler(pyrogram.MessageHandler(func, filters), group)
return func
if isinstance(func, tuple):
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

View File

@ -21,7 +21,7 @@ from ...ext import 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
raw updates. This does the same thing as :meth:`add_handler` using the
:class:`RawUpdateHandler`.
@ -32,7 +32,17 @@ class OnRawUpdate(BaseClient):
"""
def decorator(func):
self.add_handler(pyrogram.RawUpdateHandler(func), group)
return func
if isinstance(func, tuple):
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

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.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
peer = self.resolve_peer(chat_id)
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.
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

View File

@ -26,7 +26,7 @@ from pyrogram.api.errors import FileIdInvalid
from pyrogram.client.ext import BaseClient, utils
from pyrogram.client.types import (
InputMediaPhoto, InputMediaVideo, InputMediaAudio,
InputMediaAnimation
InputMediaAnimation, InputMediaDocument
)
@ -36,6 +36,34 @@ class EditMessageMedia(BaseClient):
message_id: int,
media,
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
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(
functions.messages.EditMessage(
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.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
r = self.send(

View File

@ -57,7 +57,7 @@ class EditMessageText(BaseClient):
On success, the edited :obj:`Message <pyrogram.Message>` is returned.
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

View File

@ -54,7 +54,7 @@ class ForwardMessages(BaseClient):
is returned.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
is_iterable = not isinstance(message_ids, int)
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.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
r = self.send(

View File

@ -23,9 +23,10 @@ from ...ext import BaseClient, utils
class GetMessages(BaseClient):
def get_messages(self,
chat_id: int or str,
message_ids,
message_ids: int or list = None,
reply_to_message_ids: int or list = None,
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.
Args:
@ -34,36 +35,46 @@ class GetMessages(BaseClient):
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_ids (``iterable``):
A list of Message identifiers in the chat specified in *chat_id* or a single message id, as integer.
Iterators and Generators are also accepted.
message_ids (``iterable``, *optional*):
Pass a single message identifier or a list of message ids (as integers) to get the content of the
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*):
The number of subsequent replies to get for each message. Defaults to 1.
Returns:
On success and in case *message_ids* was a list, the returned value will be a list of the requested
:obj:`Messages <pyrogram.Messages>` even if a list contains just one element, otherwise if
*message_ids* was an integer, the single requested :obj:`Message <pyrogram.Message>`
is returned.
On success and in case *message_ids* or *reply_to_message_ids* was a list, the returned value will be a
list of the requested :obj:`Messages <pyrogram.Messages>` even if a list contains just one element,
otherwise if *message_ids* or *reply_to_message_ids* was an integer, the single requested
:obj:`Message <pyrogram.Message>` is returned.
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)
is_iterable = not isinstance(message_ids, int)
message_ids = list(message_ids) if is_iterable else [message_ids]
message_ids = [types.InputMessageID(i) for i in message_ids]
is_iterable = not isinstance(ids, int)
ids = list(ids) if is_iterable else [ids]
ids = [ids_type(i) for i in ids]
if isinstance(peer, types.InputPeerChannel):
rpc = functions.channels.GetMessages(
channel=peer,
id=message_ids
)
rpc = functions.channels.GetMessages(channel=peer, id=ids)
else:
rpc = functions.messages.GetMessages(
id=message_ids
)
rpc = functions.messages.GetMessages(id=ids)
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.
caption (``str``, *optional*):
Animation caption, 0-200 characters.
Animation caption, 0-1024 characters.
parse_mode (``str``, *optional*):
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
@ -73,9 +73,10 @@ class SendAnimation(BaseClient):
Animation height.
thumb (``str``, *optional*):
Animation thumbnail.
Pass a file path as string to send an image that exists on your local machine.
Thumbnail should have 90 or less pixels of width and 90 or less pixels of height.
Thumbnail of the animation 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.
disable_notification (``bool``, *optional*):
Sends the message silently.
@ -115,7 +116,7 @@ class SendAnimation(BaseClient):
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
file = None
style = self.html if parse_mode.lower() == "html" else self.markdown

View File

@ -35,6 +35,7 @@ class SendAudio(BaseClient):
duration: int = 0,
performer: str = None,
title: str = None,
thumb: str = None,
disable_notification: bool = None,
reply_to_message_id: int = 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.
caption (``str``, *optional*):
Audio caption, 0-200 characters.
Audio caption, 0-1024 characters.
parse_mode (``str``, *optional*):
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
@ -73,6 +74,12 @@ class SendAudio(BaseClient):
title (``str``, *optional*):
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*):
Sends the message silently.
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.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
file = None
style = self.html if parse_mode.lower() == "html" else self.markdown
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)
media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"),
file=file,
thumb=thumb,
attributes=[
types.DocumentAttributeAudio(
duration=duration,

View File

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

View File

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

View File

@ -30,6 +30,7 @@ class SendDocument(BaseClient):
def send_document(self,
chat_id: int or str,
document: str,
thumb: str = None,
caption: str = "",
parse_mode: str = "",
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 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*):
Document caption, 0-200 characters.
Document caption, 0-1024 characters.
parse_mode (``str``, *optional*):
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.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
file = None
style = self.html if parse_mode.lower() == "html" else self.markdown
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)
media = types.InputMediaUploadedDocument(
mime_type=mimetypes.types_map.get("." + document.split(".")[-1], "text/plain"),
file=file,
thumb=thumb,
attributes=[
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.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
r = self.send(
functions.messages.SendMedia(

View File

@ -64,9 +64,10 @@ class SendMessage(BaseClient):
On success, the sent :obj:`Message` is returned.
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
message, entities = style.parse(text).values()
r = self.send(
functions.messages.SendMessage(
@ -76,16 +77,20 @@ class SendMessage(BaseClient):
reply_to_msg_id=reply_to_message_id,
random_id=self.rnd_id(),
reply_markup=reply_markup.write() if reply_markup else None,
**style.parse(text)
message=message,
entities=entities
)
)
if isinstance(r, types.UpdateShortSentMessage):
return pyrogram_types.Message(
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,
outgoing=r.out,
entities=utils.parse_entities(r.entities, {}) or None
entities=entities,
client=self
)
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.
caption (``bool``, *optional*):
Photo caption, 0-200 characters.
Photo caption, 0-1024 characters.
parse_mode (``str``, *optional*):
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.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
file = None
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