mirror of
https://github.com/pyrogram/pyrogram
synced 2025-08-29 13:27:47 +00:00
Merge branch 'develop' into layer-85
This commit is contained in:
commit
85519f9caf
19
README.rst
19
README.rst
@ -26,7 +26,7 @@ Features
|
|||||||
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
|
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
|
||||||
- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
|
- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
|
||||||
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
|
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
|
||||||
- **Updated** to the latest Telegram API version, currently Layer 81 on top of MTProto 2.0.
|
- **Updated** to the latest Telegram API version, currently Layer 82 on top of MTProto 2.0.
|
||||||
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
|
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
|
||||||
- **Full API**, allowing to execute any advanced action an official client is able to do, and more.
|
- **Full API**, allowing to execute any advanced action an official client is able to do, and more.
|
||||||
|
|
||||||
@ -78,8 +78,7 @@ Copyright & License
|
|||||||
|
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
<a href="https://github.com/pyrogram/pyrogram">
|
<a href="https://github.com/pyrogram/pyrogram">
|
||||||
<div><img src="https://media.pyrogram.ml/images/icon.png" alt="Pyrogram Icon"></div>
|
<div><img src="https://raw.githubusercontent.com/pyrogram/logos/master/logos/pyrogram_logo2.png" alt="Pyrogram Logo"></div>
|
||||||
<div><img src="https://media.pyrogram.ml/images/label.png" alt="Pyrogram Label"></div>
|
|
||||||
</a>
|
</a>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
@ -98,27 +97,27 @@ Copyright & License
|
|||||||
<a href="https://t.me/PyrogramChat">
|
<a href="https://t.me/PyrogramChat">
|
||||||
Community
|
Community
|
||||||
</a>
|
</a>
|
||||||
<br><br>
|
<br>
|
||||||
<a href="compiler/api/source/main_api.tl">
|
<a href="compiler/api/source/main_api.tl">
|
||||||
<img src="https://img.shields.io/badge/SCHEME-LAYER%2081-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
|
<img src="https://img.shields.io/badge/schema-layer%2082-eda738.svg?longCache=true&colorA=262b30"
|
||||||
alt="Scheme Layer">
|
alt="Schema Layer">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/pyrogram/tgcrypto">
|
<a href="https://github.com/pyrogram/tgcrypto">
|
||||||
<img src="https://img.shields.io/badge/TGCRYPTO-V1.0.4-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
|
<img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
|
||||||
alt="TgCrypto">
|
alt="TgCrypto">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
.. |logo| image:: https://pyrogram.ml/images/logo.png
|
.. |logo| image:: https://raw.githubusercontent.com/pyrogram/logos/master/logos/pyrogram_logo2.png
|
||||||
:target: https://pyrogram.ml
|
:target: https://pyrogram.ml
|
||||||
:alt: Pyrogram
|
:alt: Pyrogram
|
||||||
|
|
||||||
.. |description| replace:: **Telegram MTProto API Client Library for Python**
|
.. |description| replace:: **Telegram MTProto API Client Library for Python**
|
||||||
|
|
||||||
.. |scheme| image:: "https://img.shields.io/badge/SCHEME-LAYER%2081-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
|
.. |scheme| image:: "https://img.shields.io/badge/SCHEME-LAYER%2082-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
|
||||||
:target: compiler/api/source/main_api.tl
|
:target: compiler/api/source/main_api.tl
|
||||||
:alt: Scheme Layer
|
:alt: Scheme Layer
|
||||||
|
|
||||||
.. |tgcrypto| image:: "https://img.shields.io/badge/TGCRYPTO-V1.0.4-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
|
.. |tgcrypto| image:: "https://img.shields.io/badge/TGCRYPTO-V1.1.1-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
|
||||||
:target: https://github.com/pyrogram/tgcrypto
|
:target: https://github.com/pyrogram/tgcrypto
|
||||||
:alt: TgCrypto
|
:alt: TgCrypto
|
||||||
|
@ -172,9 +172,8 @@ def start():
|
|||||||
|
|
||||||
with open("{}/source/auth_key.tl".format(HOME), encoding="utf-8") as auth, \
|
with open("{}/source/auth_key.tl".format(HOME), encoding="utf-8") as auth, \
|
||||||
open("{}/source/sys_msgs.tl".format(HOME), encoding="utf-8") as system, \
|
open("{}/source/sys_msgs.tl".format(HOME), encoding="utf-8") as system, \
|
||||||
open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api, \
|
open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api:
|
||||||
open("{}/source/pyrogram.tl".format(HOME), encoding="utf-8") as pyrogram:
|
schema = (auth.read() + system.read() + api.read()).splitlines()
|
||||||
schema = (auth.read() + system.read() + api.read() + pyrogram.read()).splitlines()
|
|
||||||
|
|
||||||
with open("{}/template/mtproto.txt".format(HOME), encoding="utf-8") as f:
|
with open("{}/template/mtproto.txt".format(HOME), encoding="utf-8") as f:
|
||||||
mtproto_template = f.read()
|
mtproto_template = f.read()
|
||||||
@ -507,6 +506,7 @@ def start():
|
|||||||
f.write("\n 0xb0700028: \"pyrogram.client.types.Dialog\",")
|
f.write("\n 0xb0700028: \"pyrogram.client.types.Dialog\",")
|
||||||
f.write("\n 0xb0700029: \"pyrogram.client.types.Dialogs\",")
|
f.write("\n 0xb0700029: \"pyrogram.client.types.Dialogs\",")
|
||||||
f.write("\n 0xb0700030: \"pyrogram.client.types.ChatMembers\",")
|
f.write("\n 0xb0700030: \"pyrogram.client.types.ChatMembers\",")
|
||||||
|
f.write("\n 0xb0700031: \"pyrogram.client.types.UserStatus\"")
|
||||||
|
|
||||||
f.write("\n}\n")
|
f.write("\n}\n")
|
||||||
|
|
||||||
|
@ -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;
|
|
@ -88,7 +88,7 @@ def generate(source_path, base):
|
|||||||
inner_path = base + "/" + k + "/index" + ".rst"
|
inner_path = base + "/" + k + "/index" + ".rst"
|
||||||
module = "pyrogram.api.{}.{}".format(base, k)
|
module = "pyrogram.api.{}.{}".format(base, k)
|
||||||
else:
|
else:
|
||||||
for i in list(all_entities)[::-1]:
|
for i in sorted(list(all_entities), reverse=True):
|
||||||
if i != base:
|
if i != base:
|
||||||
entities.insert(0, "{0}/index".format(i))
|
entities.insert(0, "{0}/index".format(i))
|
||||||
|
|
||||||
|
@ -67,3 +67,5 @@ USER_NOT_MUTUAL_CONTACT The user is not a mutual contact
|
|||||||
USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups
|
USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups
|
||||||
API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side
|
API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side
|
||||||
USER_NOT_PARTICIPANT The user is not a member of this chat
|
USER_NOT_PARTICIPANT The user is not a member of this chat
|
||||||
|
CHANNEL_PRIVATE The channel/supergroup is not accessible
|
||||||
|
MESSAGE_IDS_EMPTY The requested message doesn't exist
|
|
5
compiler/error/source/403_FORBIDDEN.tsv
Normal file
5
compiler/error/source/403_FORBIDDEN.tsv
Normal 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
|
|
@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
# You can set these variables from the command line.
|
# You can set these variables from the command line.
|
||||||
SPHINXOPTS =
|
SPHINXOPTS =
|
||||||
SPHINXBUILD = ~/PycharmProjects/pyrogram/venv3.6/bin/sphinx-build
|
SPHINXBUILD = sphinx-build
|
||||||
SPHINXPROJ = Pyrogram
|
SPHINXPROJ = Pyrogram
|
||||||
SOURCEDIR = source
|
SOURCEDIR = source
|
||||||
BUILDDIR = build
|
BUILDDIR = build
|
||||||
|
@ -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)
|
|
@ -5,8 +5,7 @@ Welcome to Pyrogram
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
<a href="https://docs.pyrogram.ml">
|
<a href="https://docs.pyrogram.ml">
|
||||||
<div><img src="https://media.pyrogram.ml/images/icon.png" alt="Pyrogram Icon"></div>
|
<div><img src="_static/logo.png" alt="Pyrogram Logo"></div>
|
||||||
<div><img src="https://media.pyrogram.ml/images/label.png" alt="Pyrogram Label"></div>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -24,13 +23,13 @@ Welcome to Pyrogram
|
|||||||
<a href="https://t.me/PyrogramChat">
|
<a href="https://t.me/PyrogramChat">
|
||||||
Community
|
Community
|
||||||
</a>
|
</a>
|
||||||
<br><br>
|
<br>
|
||||||
<a href="https://github.com/pyrogram/pyrogram/blob/master/compiler/api/source/main_api.tl">
|
<a href="https://github.com/pyrogram/pyrogram/blob/master/compiler/api/source/main_api.tl">
|
||||||
<img src="https://img.shields.io/badge/SCHEME-LAYER%2081-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
|
<img src="https://img.shields.io/badge/schema-layer%2082-eda738.svg?longCache=true&colorA=262b30"
|
||||||
alt="Scheme Layer">
|
alt="Scheme Layer">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/pyrogram/tgcrypto">
|
<a href="https://github.com/pyrogram/tgcrypto">
|
||||||
<img src="https://img.shields.io/badge/TGCRYPTO-V1.0.4-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
|
<img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
|
||||||
alt="TgCrypto">
|
alt="TgCrypto">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
@ -50,14 +49,14 @@ Welcome to Pyrogram
|
|||||||
app.run()
|
app.run()
|
||||||
|
|
||||||
Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the library.
|
Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the library.
|
||||||
Contents are organized by topic and can be accessed from the sidebar, or by following them one by one using the Next
|
Contents are organized into self-contained topics and can be accessed from the sidebar, or by following them in order
|
||||||
button at the end of each page. But first, here's a brief overview of what is this all about.
|
using the Next button at the end of each page. But first, here's a brief overview of what is this all about.
|
||||||
|
|
||||||
About
|
About
|
||||||
-----
|
-----
|
||||||
|
|
||||||
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for building
|
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for
|
||||||
custom Telegram applications that interact with the MTProto API as both User and Bot.
|
building custom Telegram applications that interact with the MTProto API as both User and Bot.
|
||||||
|
|
||||||
Features
|
Features
|
||||||
--------
|
--------
|
||||||
@ -65,7 +64,7 @@ Features
|
|||||||
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
|
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
|
||||||
- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
|
- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
|
||||||
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
|
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
|
||||||
- **Updated** to the latest Telegram API version, currently Layer 81 on top of MTProto 2.0.
|
- **Updated** to the latest Telegram API version, currently Layer 82 on top of MTProto 2.0.
|
||||||
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
|
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
|
||||||
- **Full API**, allowing to execute any advanced action an official client is able to do, and more.
|
- **Full API**, allowing to execute any advanced action an official client is able to do, and more.
|
||||||
|
|
||||||
@ -85,6 +84,7 @@ To get started, press the Next button.
|
|||||||
|
|
||||||
resources/UpdateHandling
|
resources/UpdateHandling
|
||||||
resources/UsingFilters
|
resources/UsingFilters
|
||||||
|
resources/SmartPlugins
|
||||||
resources/AutoAuthorization
|
resources/AutoAuthorization
|
||||||
resources/CustomizeSessions
|
resources/CustomizeSessions
|
||||||
resources/TgCrypto
|
resources/TgCrypto
|
||||||
|
@ -30,6 +30,7 @@ Decorators
|
|||||||
on_message
|
on_message
|
||||||
on_callback_query
|
on_callback_query
|
||||||
on_deleted_messages
|
on_deleted_messages
|
||||||
|
on_user_status
|
||||||
on_disconnect
|
on_disconnect
|
||||||
on_raw_update
|
on_raw_update
|
||||||
|
|
||||||
@ -57,10 +58,10 @@ Messages
|
|||||||
edit_message_text
|
edit_message_text
|
||||||
edit_message_caption
|
edit_message_caption
|
||||||
edit_message_reply_markup
|
edit_message_reply_markup
|
||||||
|
edit_message_media
|
||||||
delete_messages
|
delete_messages
|
||||||
get_messages
|
get_messages
|
||||||
get_history
|
get_history
|
||||||
get_dialogs
|
|
||||||
|
|
||||||
Chats
|
Chats
|
||||||
-----
|
-----
|
||||||
@ -84,6 +85,8 @@ Chats
|
|||||||
get_chat
|
get_chat
|
||||||
get_chat_member
|
get_chat_member
|
||||||
get_chat_members
|
get_chat_members
|
||||||
|
get_chat_members_count
|
||||||
|
get_dialogs
|
||||||
|
|
||||||
Users
|
Users
|
||||||
-----
|
-----
|
||||||
@ -94,7 +97,8 @@ Users
|
|||||||
get_me
|
get_me
|
||||||
get_users
|
get_users
|
||||||
get_user_profile_photos
|
get_user_profile_photos
|
||||||
delete_profile_photos
|
set_user_profile_photo
|
||||||
|
delete_user_profile_photos
|
||||||
|
|
||||||
Contacts
|
Contacts
|
||||||
--------
|
--------
|
||||||
|
@ -3,4 +3,3 @@ Filters
|
|||||||
|
|
||||||
.. autoclass:: pyrogram.Filters
|
.. autoclass:: pyrogram.Filters
|
||||||
:members:
|
:members:
|
||||||
:undoc-members:
|
|
||||||
|
@ -9,6 +9,7 @@ Handlers
|
|||||||
MessageHandler
|
MessageHandler
|
||||||
DeletedMessagesHandler
|
DeletedMessagesHandler
|
||||||
CallbackQueryHandler
|
CallbackQueryHandler
|
||||||
|
UserStatusHandler
|
||||||
DisconnectHandler
|
DisconnectHandler
|
||||||
RawUpdateHandler
|
RawUpdateHandler
|
||||||
|
|
||||||
@ -21,6 +22,9 @@ Handlers
|
|||||||
.. autoclass:: CallbackQueryHandler
|
.. autoclass:: CallbackQueryHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: UserStatusHandler
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoclass:: DisconnectHandler
|
.. autoclass:: DisconnectHandler
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ Users & Chats
|
|||||||
:nosignatures:
|
:nosignatures:
|
||||||
|
|
||||||
User
|
User
|
||||||
|
UserStatus
|
||||||
Chat
|
Chat
|
||||||
ChatPhoto
|
ChatPhoto
|
||||||
ChatMember
|
ChatMember
|
||||||
@ -62,6 +63,9 @@ Input Media
|
|||||||
|
|
||||||
InputMediaPhoto
|
InputMediaPhoto
|
||||||
InputMediaVideo
|
InputMediaVideo
|
||||||
|
InputMediaAudio
|
||||||
|
InputMediaAnimation
|
||||||
|
InputMediaDocument
|
||||||
InputPhoneContact
|
InputPhoneContact
|
||||||
|
|
||||||
.. User & Chats
|
.. User & Chats
|
||||||
@ -70,6 +74,9 @@ Input Media
|
|||||||
.. autoclass:: User
|
.. autoclass:: User
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: UserStatus
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoclass:: Chat
|
.. autoclass:: Chat
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
@ -172,5 +179,14 @@ Input Media
|
|||||||
.. autoclass:: InputMediaVideo
|
.. autoclass:: InputMediaVideo
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: InputMediaAudio
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: InputMediaAnimation
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: InputMediaDocument
|
||||||
|
:members:
|
||||||
|
|
||||||
.. autoclass:: InputPhoneContact
|
.. autoclass:: InputPhoneContact
|
||||||
:members:
|
:members:
|
||||||
|
@ -35,12 +35,9 @@ Example:
|
|||||||
password="password" # (if you have one)
|
password="password" # (if you have one)
|
||||||
)
|
)
|
||||||
|
|
||||||
app.start()
|
with app:
|
||||||
|
|
||||||
print(app.get_me())
|
print(app.get_me())
|
||||||
|
|
||||||
app.stop()
|
|
||||||
|
|
||||||
Sign Up
|
Sign Up
|
||||||
-------
|
-------
|
||||||
|
|
||||||
@ -67,8 +64,5 @@ Example:
|
|||||||
last_name="" # Can be an empty string
|
last_name="" # Can be an empty string
|
||||||
)
|
)
|
||||||
|
|
||||||
app.start()
|
with app:
|
||||||
|
|
||||||
print(app.get_me())
|
print(app.get_me())
|
||||||
|
|
||||||
app.stop()
|
|
126
docs/source/resources/SmartPlugins.rst
Normal file
126
docs/source/resources/SmartPlugins.rst
Normal 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.
|
@ -2,15 +2,15 @@ Update Handling
|
|||||||
===============
|
===============
|
||||||
|
|
||||||
Updates are events that happen in your Telegram account (incoming messages, new channel posts, new members join, ...)
|
Updates are events that happen in your Telegram account (incoming messages, new channel posts, new members join, ...)
|
||||||
and can be handled by registering one or more callback functions in your app by using an `Handler <../pyrogram/Handlers.html>`_.
|
and can be handled by registering one or more callback functions in your app by using `Handlers <../pyrogram/Handlers.html>`_.
|
||||||
|
|
||||||
To put it simply, whenever an update is received from Telegram it will be dispatched and your previously defined callback
|
To put it simply, whenever an update is received from Telegram it will be dispatched and your previously defined callback
|
||||||
function(s) will be called back with the update itself as argument.
|
function(s) matching it will be called back with the update itself as argument.
|
||||||
|
|
||||||
Registering an Handler
|
Registering an Handler
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
To explain how `Handlers <../pyrogram/Handlers.html>`_ work let's have a look at the most used one, the
|
To explain how handlers work let's have a look at the most used one, the
|
||||||
:obj:`MessageHandler <pyrogram.MessageHandler>`, which will be in charge for handling :obj:`Message <pyrogram.Message>`
|
:obj:`MessageHandler <pyrogram.MessageHandler>`, which will be in charge for handling :obj:`Message <pyrogram.Message>`
|
||||||
updates coming from all around your chats. Every other handler shares the same setup logic; you should not have troubles
|
updates coming from all around your chats. Every other handler shares the same setup logic; you should not have troubles
|
||||||
settings them up once you learn from this section.
|
settings them up once you learn from this section.
|
||||||
|
@ -4,48 +4,85 @@ Installation
|
|||||||
Being a Python library, Pyrogram requires Python to be installed in your system.
|
Being a Python library, Pyrogram requires Python to be installed in your system.
|
||||||
We recommend using the latest version of Python 3 and pip.
|
We recommend using the latest version of Python 3 and pip.
|
||||||
|
|
||||||
Get Python 3 from https://www.python.org/downloads/ or with your package manager and pip
|
Get Python 3 from https://www.python.org/downloads/ (or with your package manager) and pip
|
||||||
by following the instructions at https://pip.pypa.io/en/latest/installing/.
|
by following the instructions at https://pip.pypa.io/en/latest/installing/.
|
||||||
|
|
||||||
.. note::
|
.. important::
|
||||||
Pyrogram supports Python 3 only, starting from version 3.4 and PyPy.
|
|
||||||
|
Pyrogram supports **Python 3** only, starting from version 3.4. **PyPy** is supported too.
|
||||||
|
|
||||||
Install Pyrogram
|
Install Pyrogram
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
- The easiest way to install and upgrade Pyrogram is by using **pip**:
|
- The easiest way to install and upgrade Pyrogram to its latest stable version is by using **pip**:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: text
|
||||||
|
|
||||||
$ pip3 install --upgrade pyrogram
|
$ pip3 install --upgrade pyrogram
|
||||||
|
|
||||||
- or, with TgCrypto_ (recommended):
|
- or, with TgCrypto_ as extra requirement (recommended):
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: text
|
||||||
|
|
||||||
$ pip3 install --upgrade pyrogram[tgcrypto]
|
$ pip3 install --upgrade pyrogram[fast]
|
||||||
|
|
||||||
Bleeding Edge
|
Bleeding Edge
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
If you want the latest development version of Pyrogram, you can install it straight from the develop_
|
If you want the latest development version of Pyrogram, you can install it straight from the develop_
|
||||||
branch using this command:
|
branch using this command (you might need to install **git** first):
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: text
|
||||||
|
|
||||||
$ pip3 install --upgrade git+https://github.com/pyrogram/pyrogram.git
|
$ pip3 install --upgrade git+https://github.com/pyrogram/pyrogram.git
|
||||||
|
|
||||||
|
Asynchronous
|
||||||
|
------------
|
||||||
|
|
||||||
|
Pyrogram heavily depends on IO-bound network code (it's a cloud-based messaging client library after all), and here's
|
||||||
|
where asyncio shines the most by providing extra performance while running on a single OS-level thread only.
|
||||||
|
|
||||||
|
**A fully asynchronous variant of Pyrogram is therefore available** (Python 3.5+ required).
|
||||||
|
Use this command to install:
|
||||||
|
|
||||||
|
.. code-block:: text
|
||||||
|
|
||||||
|
$ pip3 install --upgrade git+https://github.com/pyrogram/pyrogram.git@asyncio
|
||||||
|
|
||||||
|
|
||||||
|
Pyrogram API remains the same and features are kept up to date from the non-async, default develop branch, but you
|
||||||
|
are obviously required Python asyncio knowledge in order to take full advantage of it.
|
||||||
|
|
||||||
|
|
||||||
|
.. tip::
|
||||||
|
|
||||||
|
The idea to turn Pyrogram fully asynchronous is still under consideration, but is wise to expect that in future this
|
||||||
|
would be the one and only way to work with Pyrogram.
|
||||||
|
|
||||||
|
You can start using Pyrogram Async variant right now as an excuse to learn more about asynchronous programming and
|
||||||
|
do experiments with it!
|
||||||
|
|
||||||
|
.. raw:: html
|
||||||
|
|
||||||
|
<script async
|
||||||
|
src="https://telegram.org/js/telegram-widget.js?4"
|
||||||
|
data-telegram-post="Pyrogram/4"
|
||||||
|
data-width="100%">
|
||||||
|
</script>
|
||||||
|
|
||||||
|
.. centered:: Subscribe to `@Pyrogram <https://t.me/Pyrogram>`_ for news and announcements
|
||||||
|
|
||||||
Verifying
|
Verifying
|
||||||
---------
|
---------
|
||||||
|
|
||||||
To verify that Pyrogram is correctly installed, open a Python shell and import it.
|
To verify that Pyrogram is correctly installed, open a Python shell and import it.
|
||||||
If no error shows up you are good to go.
|
If no error shows up you are good to go.
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: python
|
||||||
|
|
||||||
>>> import pyrogram
|
>>> import pyrogram
|
||||||
>>> pyrogram.__version__
|
>>> pyrogram.__version__
|
||||||
'0.7.5'
|
'0.9.3'
|
||||||
|
|
||||||
.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto
|
.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto
|
||||||
.. _develop: http://github.com/pyrogram/pyrogram
|
.. _develop: http://github.com/pyrogram/pyrogram
|
@ -53,6 +53,7 @@ fits better for you:
|
|||||||
)
|
)
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
The examples below assume you have created a ``config.ini`` file, thus they won't show the *api_id*
|
The examples below assume you have created a ``config.ini`` file, thus they won't show the *api_id*
|
||||||
and *api_hash* parameters usage.
|
and *api_hash* parameters usage.
|
||||||
|
|
||||||
@ -74,7 +75,7 @@ the :class:`Client <pyrogram.Client>` class by passing to it a ``session_name``
|
|||||||
This starts an interactive shell asking you to input your **phone number** (including your `Country Code`_)
|
This starts an interactive shell asking you to input your **phone number** (including your `Country Code`_)
|
||||||
and the **phone code** you will receive:
|
and the **phone code** you will receive:
|
||||||
|
|
||||||
.. code::
|
.. code-block:: text
|
||||||
|
|
||||||
Enter phone number: +39**********
|
Enter phone number: +39**********
|
||||||
Is "+39**********" correct? (y/n): y
|
Is "+39**********" correct? (y/n): y
|
||||||
@ -84,7 +85,9 @@ After successfully authorizing yourself, a new file called ``my_account.session`
|
|||||||
Pyrogram executing API calls with your identity. This file will be loaded again when you restart your app,
|
Pyrogram executing API calls with your identity. This file will be loaded again when you restart your app,
|
||||||
and as long as you keep the session alive, Pyrogram won't ask you again to enter your phone number.
|
and as long as you keep the session alive, Pyrogram won't ask you again to enter your phone number.
|
||||||
|
|
||||||
.. important:: Your ``*.session`` files are personal and must be kept secret.
|
.. important::
|
||||||
|
|
||||||
|
Your ``*.session`` files are personal and must be kept secret.
|
||||||
|
|
||||||
Bot Authorization
|
Bot Authorization
|
||||||
-----------------
|
-----------------
|
||||||
|
@ -10,35 +10,50 @@ High-level API
|
|||||||
The easiest and recommended way to interact with Telegram is via the high-level Pyrogram methods_ and types_, which are
|
The easiest and recommended way to interact with Telegram is via the high-level Pyrogram methods_ and types_, which are
|
||||||
named after the `Telegram Bot API`_.
|
named after the `Telegram Bot API`_.
|
||||||
|
|
||||||
Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_):
|
Here's a simple example:
|
||||||
|
|
||||||
- Get information about the authorized user:
|
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
|
app = Client("my_account")
|
||||||
|
|
||||||
|
app.start()
|
||||||
|
|
||||||
print(app.get_me())
|
print(app.get_me())
|
||||||
|
app.send_message("me", "Hi there! I'm using **Pyrogram**")
|
||||||
|
app.send_location("me", 51.500729, -0.124583)
|
||||||
|
|
||||||
- Send a message to yourself (Saved Messages):
|
app.stop()
|
||||||
|
|
||||||
|
You can also use Pyrogram in a context manager with the ``with`` statement. The Client will automatically
|
||||||
|
:meth:`start <pyrogram.Client.start>` and :meth:`stop <pyrogram.Client.stop>` gracefully, even in case of unhandled
|
||||||
|
exceptions in your code:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
app.send_message("me", "Hi there! I'm using Pyrogram")
|
from pyrogram import Client
|
||||||
|
|
||||||
- Upload a new photo (with caption):
|
app = Client("my_account")
|
||||||
|
|
||||||
.. code-block:: python
|
with app:
|
||||||
|
print(app.get_me())
|
||||||
|
app.send_message("me", "Hi there! I'm using **Pyrogram**")
|
||||||
|
app.send_location("me", 51.500729, -0.124583)
|
||||||
|
|
||||||
app.send_photo("me", "/home/dan/perla.jpg", "Cute!")
|
More examples on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_.
|
||||||
|
|
||||||
Raw Functions
|
Raw Functions
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
If you can't find a high-level method for your needs or if want complete, low-level access to the whole Telegram API,
|
If you can't find a high-level method for your needs or if you want complete, low-level access to the whole Telegram API,
|
||||||
you have to use the raw :mod:`functions <pyrogram.api.functions>` and :mod:`types <pyrogram.api.types>` exposed by the
|
you have to use the raw :mod:`functions <pyrogram.api.functions>` and :mod:`types <pyrogram.api.types>` exposed by the
|
||||||
``pyrogram.api`` package and call any Telegram API method you wish using the :meth:`send() <pyrogram.Client.send>`
|
``pyrogram.api`` package and call any Telegram API method you wish using the :meth:`send() <pyrogram.Client.send>`
|
||||||
method provided by the Client class.
|
method provided by the Client class.
|
||||||
|
|
||||||
.. hint:: Every high-level method mentioned in the section above is built on top of these raw functions.
|
.. hint::
|
||||||
|
|
||||||
|
Every high-level method mentioned in the section above is built on top of these raw functions.
|
||||||
|
|
||||||
Nothing stops you from using the raw functions only, but they are rather complex and `plenty of them`_ are already
|
Nothing stops you from using the raw functions only, but they are rather complex and `plenty of them`_ are already
|
||||||
re-implemented by providing a much simpler and cleaner interface which is very similar to the Bot API.
|
re-implemented by providing a much simpler and cleaner interface which is very similar to the Bot API.
|
||||||
@ -54,9 +69,7 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
|
|||||||
from pyrogram import Client
|
from pyrogram import Client
|
||||||
from pyrogram.api import functions
|
from pyrogram.api import functions
|
||||||
|
|
||||||
app = Client("my_account")
|
with Client("my_account") as app:
|
||||||
app.start()
|
|
||||||
|
|
||||||
app.send(
|
app.send(
|
||||||
functions.account.UpdateProfile(
|
functions.account.UpdateProfile(
|
||||||
first_name="Dan", last_name="Tès",
|
first_name="Dan", last_name="Tès",
|
||||||
@ -64,8 +77,6 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
app.stop()
|
|
||||||
|
|
||||||
- Share your Last Seen time only with your contacts:
|
- Share your Last Seen time only with your contacts:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
@ -73,9 +84,7 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
|
|||||||
from pyrogram import Client
|
from pyrogram import Client
|
||||||
from pyrogram.api import functions, types
|
from pyrogram.api import functions, types
|
||||||
|
|
||||||
app = Client("my_account")
|
with Client("my_account") as app:
|
||||||
app.start()
|
|
||||||
|
|
||||||
app.send(
|
app.send(
|
||||||
functions.account.SetPrivacy(
|
functions.account.SetPrivacy(
|
||||||
key=types.InputPrivacyKeyStatusTimestamp(),
|
key=types.InputPrivacyKeyStatusTimestamp(),
|
||||||
@ -83,8 +92,6 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
app.stop()
|
|
||||||
|
|
||||||
- Invite users to your channel/supergroup:
|
- Invite users to your channel/supergroup:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
@ -92,9 +99,7 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
|
|||||||
from pyrogram import Client
|
from pyrogram import Client
|
||||||
from pyrogram.api import functions, types
|
from pyrogram.api import functions, types
|
||||||
|
|
||||||
app = Client("my_account")
|
with Client("my_account") as app:
|
||||||
app.start()
|
|
||||||
|
|
||||||
app.send(
|
app.send(
|
||||||
functions.channels.InviteToChannel(
|
functions.channels.InviteToChannel(
|
||||||
channel=app.resolve_peer(123456789), # ID or Username
|
channel=app.resolve_peer(123456789), # ID or Username
|
||||||
@ -106,8 +111,6 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
app.stop()
|
|
||||||
|
|
||||||
.. _methods: ../pyrogram/Client.html#messages
|
.. _methods: ../pyrogram/Client.html#messages
|
||||||
.. _plenty of them: ../pyrogram/Client.html#messages
|
.. _plenty of them: ../pyrogram/Client.html#messages
|
||||||
.. _types: ../pyrogram/Types.html
|
.. _types: ../pyrogram/Types.html
|
||||||
|
121
examples/LICENSE
Normal file
121
examples/LICENSE
Normal 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.
|
@ -5,14 +5,17 @@ This folder contains example scripts to show you how **Pyrogram** looks like.
|
|||||||
Every script is working right away (provided you correctly set up your credentials), meaning
|
Every script is working right away (provided you correctly set up your credentials), meaning
|
||||||
you can simply copy-paste and run. The only things you have to change are session names and target chats.
|
you can simply copy-paste and run. The only things you have to change are session names and target chats.
|
||||||
|
|
||||||
|
All the examples listed in this directory are licensed under the terms of the [CC0 1.0 Universal](LICENSE) license and
|
||||||
|
can be freely used as basic building blocks for your own applications without worrying about copyrights.
|
||||||
|
|
||||||
Example | Description
|
Example | Description
|
||||||
---: | :---
|
---: | :---
|
||||||
[**hello_world**](hello_world.py) | Demonstration of basic API usages
|
[**hello_world**](hello_world.py) | Demonstration of basic API usages
|
||||||
[**echo_bot**](echo_bot.py) | Echo bot that replies to every private text message
|
[**echo_bot**](echo_bot.py) | Echo bot that replies to every private text message
|
||||||
[**welcome_bot**](welcome_bot.py) | The Welcome Bot source code in [@PyrogramChat](https://t.me/pyrogramchat)
|
[**welcome_bot**](welcome_bot.py) | The Welcome Bot source code in [@PyrogramChat](https://t.me/pyrogramchat)
|
||||||
[**get_history**](get_history.py) | How to retrieve the full message history of a chat
|
[**get_history**](get_history.py) | How to retrieve the full message history of a chat
|
||||||
[**get_participants**](get_participants.py) | How to get the first 10.000 members of a supergroup/channel
|
[**get_chat_members**](get_chat_members.py) | How to get the first 10.000 members of a supergroup/channel
|
||||||
[**get_participants2**](get_participants2.py) | Improved version to get more than 10.000 users
|
[**get_chat_members2**](get_chat_members2.py) | Improved version to get more than 10.000 members
|
||||||
[**query_inline_bots**](query_inline_bots.py) | How to query an inline bot and send a result to a chat
|
[**query_inline_bots**](query_inline_bots.py) | How to query an inline bot and send a result to a chat
|
||||||
[**send_bot_keyboards**](send_bot_keyboards.py) | How to send normal and inline keyboards using regular bots
|
[**send_bot_keyboards**](send_bot_keyboards.py) | How to send normal and inline keyboards using regular bots
|
||||||
[**callback_query_handler**](callback_query_handler.py) | How to handle queries coming from inline button presses
|
[**callback_query_handler**](callback_query_handler.py) | How to handle queries coming from inline button presses
|
||||||
|
@ -1,37 +1,16 @@
|
|||||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
|
||||||
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
|
||||||
#
|
|
||||||
# This file is part of Pyrogram.
|
|
||||||
#
|
|
||||||
# Pyrogram is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Lesser General Public License as published
|
|
||||||
# by the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Pyrogram is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public License
|
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from pyrogram import Client
|
|
||||||
|
|
||||||
"""This example shows how to handle callback queries, i.e.: queries coming from inline button presses.
|
"""This example shows how to handle callback queries, i.e.: queries coming from inline button presses.
|
||||||
|
|
||||||
It uses the @on_callback_query decorator to register a CallbackQueryHandler."""
|
It uses the @on_callback_query decorator to register a CallbackQueryHandler.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pyrogram import Client
|
||||||
|
|
||||||
app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
|
app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
|
||||||
|
|
||||||
|
|
||||||
@app.on_callback_query()
|
@app.on_callback_query()
|
||||||
def answer(client, callback_query):
|
def answer(client, callback_query):
|
||||||
client.answer_callback_query(
|
callback_query.answer('Button contains: "{}"'.format(callback_query.data), show_alert=True)
|
||||||
callback_query.id,
|
|
||||||
text='Button contains: "{}"'.format(callback_query.data),
|
|
||||||
show_alert=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
app.run() # Automatically start() and idle()
|
app.run() # Automatically start() and idle()
|
||||||
|
@ -1,39 +1,17 @@
|
|||||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
|
||||||
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
|
||||||
#
|
|
||||||
# This file is part of Pyrogram.
|
|
||||||
#
|
|
||||||
# Pyrogram is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Lesser General Public License as published
|
|
||||||
# by the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Pyrogram is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public License
|
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from pyrogram import Client, Filters
|
|
||||||
|
|
||||||
"""This simple echo bot replies to every private text message.
|
"""This simple echo bot replies to every private text message.
|
||||||
|
|
||||||
It uses the @on_message decorator to register a MessageHandler
|
It uses the @on_message decorator to register a MessageHandler and applies two filters on it:
|
||||||
and applies two filters on it, Filters.text and Filters.private to make
|
Filters.text and Filters.private to make sure it will reply to private text messages only.
|
||||||
sure it will only reply to private text messages.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from pyrogram import Client, Filters
|
||||||
|
|
||||||
app = Client("my_account")
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(Filters.text & Filters.private)
|
@app.on_message(Filters.text & Filters.private)
|
||||||
def echo(client, message):
|
def echo(client, message):
|
||||||
client.send_message(
|
message.reply(message.text, quote=True)
|
||||||
message.chat.id, message.text,
|
|
||||||
reply_to_message_id=message.message_id
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
app.run() # Automatically start() and idle()
|
app.run() # Automatically start() and idle()
|
||||||
|
31
examples/get_chat_members.py
Normal file
31
examples/get_chat_members.py
Normal 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
|
50
examples/get_chat_members2.py
Normal file
50
examples/get_chat_members2.py
Normal 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
|
@ -1,40 +1,20 @@
|
|||||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
"""This example shows how to retrieve the full message history of a chat"""
|
||||||
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
|
||||||
#
|
|
||||||
# This file is part of Pyrogram.
|
|
||||||
#
|
|
||||||
# Pyrogram is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Lesser General Public License as published
|
|
||||||
# by the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Pyrogram is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public License
|
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from pyrogram import Client
|
from pyrogram import Client
|
||||||
from pyrogram.api.errors import FloodWait
|
from pyrogram.api.errors import FloodWait
|
||||||
|
|
||||||
"""This example shows how to retrieve the full message history of a chat"""
|
|
||||||
|
|
||||||
app = Client("my_account")
|
app = Client("my_account")
|
||||||
target = "me" # "me" refers to your own chat (Saved Messages)
|
target = "me" # "me" refers to your own chat (Saved Messages)
|
||||||
messages = [] # List that will contain all the messages of the target chat
|
messages = [] # List that will contain all the messages of the target chat
|
||||||
offset_id = 0 # ID of the last message of the chunk
|
offset_id = 0 # ID of the last message of the chunk
|
||||||
|
|
||||||
app.start()
|
with app:
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
m = app.get_history(target, offset_id=offset_id)
|
m = app.get_history(target, offset_id=offset_id)
|
||||||
except FloodWait as e:
|
except FloodWait as e: # For very large chats the method call can raise a FloodWait
|
||||||
# For very large chats the method call can raise a FloodWait
|
|
||||||
print("waiting {}".format(e.x))
|
print("waiting {}".format(e.x))
|
||||||
time.sleep(e.x) # Sleep X seconds before continuing
|
time.sleep(e.x) # Sleep X seconds before continuing
|
||||||
continue
|
continue
|
||||||
@ -47,7 +27,5 @@ while True:
|
|||||||
|
|
||||||
print("Messages: {}".format(len(messages)))
|
print("Messages: {}".format(len(messages)))
|
||||||
|
|
||||||
app.stop()
|
|
||||||
|
|
||||||
# Now the "messages" list contains all the messages sorted by date in
|
# Now the "messages" list contains all the messages sorted by date in
|
||||||
# descending order (from the most recent to the oldest one)
|
# descending order (from the most recent to the oldest one)
|
||||||
|
@ -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
|
|
@ -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()
|
|
@ -1,25 +1,7 @@
|
|||||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
"""This example demonstrates a basic API usage"""
|
||||||
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
|
||||||
#
|
|
||||||
# This file is part of Pyrogram.
|
|
||||||
#
|
|
||||||
# Pyrogram is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Lesser General Public License as published
|
|
||||||
# by the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Pyrogram is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public License
|
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from pyrogram import Client
|
from pyrogram import Client
|
||||||
|
|
||||||
"""This example demonstrates a basic API usage"""
|
|
||||||
|
|
||||||
# Create a new Client instance
|
# Create a new Client instance
|
||||||
app = Client("my_account")
|
app = Client("my_account")
|
||||||
|
|
||||||
|
@ -1,25 +1,7 @@
|
|||||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
"""This example shows how to query an inline bot"""
|
||||||
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
|
||||||
#
|
|
||||||
# This file is part of Pyrogram.
|
|
||||||
#
|
|
||||||
# Pyrogram is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Lesser General Public License as published
|
|
||||||
# by the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Pyrogram is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public License
|
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from pyrogram import Client
|
from pyrogram import Client
|
||||||
|
|
||||||
"""This example shows how to query an inline bot"""
|
|
||||||
|
|
||||||
# Create a new Client
|
# Create a new Client
|
||||||
app = Client("my_account")
|
app = Client("my_account")
|
||||||
|
|
||||||
|
@ -1,25 +1,7 @@
|
|||||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
"""This example shows how to handle raw updates"""
|
||||||
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
|
||||||
#
|
|
||||||
# This file is part of Pyrogram.
|
|
||||||
#
|
|
||||||
# Pyrogram is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Lesser General Public License as published
|
|
||||||
# by the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Pyrogram is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public License
|
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from pyrogram import Client
|
from pyrogram import Client
|
||||||
|
|
||||||
"""This example shows how to handle raw updates"""
|
|
||||||
|
|
||||||
app = Client("my_account")
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,23 +1,3 @@
|
|||||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
|
||||||
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
|
||||||
#
|
|
||||||
# This file is part of Pyrogram.
|
|
||||||
#
|
|
||||||
# Pyrogram is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Lesser General Public License as published
|
|
||||||
# by the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Pyrogram is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public License
|
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
|
|
||||||
|
|
||||||
"""This example will show you how to send normal and inline keyboards.
|
"""This example will show you how to send normal and inline keyboards.
|
||||||
|
|
||||||
You must log-in as a regular bot in order to send keyboards (use the token from @BotFather).
|
You must log-in as a regular bot in order to send keyboards (use the token from @BotFather).
|
||||||
@ -27,6 +7,8 @@ send_message() is used as example, but a keyboard can be sent with any other sen
|
|||||||
like send_audio(), send_document(), send_location(), etc...
|
like send_audio(), send_document(), send_location(), etc...
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
|
||||||
# Create a client using your bot token
|
# Create a client using your bot token
|
||||||
app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
|
app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
|
||||||
app.start()
|
app.start()
|
||||||
|
@ -1,52 +1,45 @@
|
|||||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
|
||||||
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
|
||||||
#
|
|
||||||
# This file is part of Pyrogram.
|
|
||||||
#
|
|
||||||
# Pyrogram is free software: you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU Lesser General Public License as published
|
|
||||||
# by the Free Software Foundation, either version 3 of the License, or
|
|
||||||
# (at your option) any later version.
|
|
||||||
#
|
|
||||||
# Pyrogram is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public License
|
|
||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
from pyrogram import Client, Emoji, Filters
|
|
||||||
|
|
||||||
"""This is the Welcome Bot in @PyrogramChat.
|
"""This is the Welcome Bot in @PyrogramChat.
|
||||||
|
|
||||||
It uses the Emoji module to easily add emojis in your text messages and Filters
|
It uses the Emoji module to easily add emojis in your text messages and Filters
|
||||||
to make it only work for specific messages in a specific chat
|
to make it only work for specific messages in a specific chat.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from pyrogram import Client, Emoji, Filters
|
||||||
|
|
||||||
|
USER = "**{}**"
|
||||||
|
MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {{}}!".format(Emoji.SPARKLES)
|
||||||
|
|
||||||
|
enabled_groups = Filters.chat("PyrogramChat")
|
||||||
|
last_welcomes = {}
|
||||||
|
|
||||||
app = Client("my_account")
|
app = Client("my_account")
|
||||||
|
|
||||||
|
|
||||||
@app.on_message(Filters.chat("PyrogramChat") & Filters.new_chat_members)
|
@app.on_message(enabled_groups & Filters.new_chat_members)
|
||||||
def welcome(client, message):
|
def welcome(client, message):
|
||||||
# Build the new members list (with mentions) by using their first_name
|
chat_id = message.chat.id
|
||||||
new_members = ", ".join([
|
|
||||||
"[{}](tg://user?id={})".format(i.first_name, i.id)
|
|
||||||
for i in message.new_chat_members
|
|
||||||
])
|
|
||||||
|
|
||||||
# Build the welcome message by using an emoji and the list we built above
|
# Get the previous welcome message and members, if any
|
||||||
text = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!".format(
|
previous_welcome, previous_members = last_welcomes.pop(chat_id, (None, []))
|
||||||
Emoji.SPARKLES,
|
|
||||||
new_members
|
|
||||||
)
|
|
||||||
|
|
||||||
# Send the welcome message
|
# Delete the previous message, if exists
|
||||||
client.send_message(
|
if previous_welcome:
|
||||||
message.chat.id, text,
|
previous_welcome.delete()
|
||||||
reply_to_message_id=message.message_id,
|
|
||||||
disable_web_page_preview=True
|
# Build the new members list by using their first_name. Also append the previous members, if any
|
||||||
)
|
new_members = [USER.format(i.first_name) for i in message.new_chat_members] + previous_members
|
||||||
|
|
||||||
|
# Build the welcome message by using an emoji and the list we created above
|
||||||
|
text = MESSAGE.format(", ".join(new_members))
|
||||||
|
|
||||||
|
# Actually send the welcome and save the new message and the new members list
|
||||||
|
last_welcomes[message.chat.id] = message.reply(text, disable_web_page_preview=True), new_members
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_message(enabled_groups)
|
||||||
|
def reset(client, message):
|
||||||
|
# Don't make the bot delete the previous welcome in case someone talks in the middle
|
||||||
|
last_welcomes.pop(message.chat.id, None)
|
||||||
|
|
||||||
|
|
||||||
app.run() # Automatically start() and idle()
|
app.run() # Automatically start() and idle()
|
||||||
|
@ -23,18 +23,18 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance
|
|||||||
"e" if sys.getfilesystemencoding() != "utf-8" else "\xe8"
|
"e" if sys.getfilesystemencoding() != "utf-8" else "\xe8"
|
||||||
)
|
)
|
||||||
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
|
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
|
||||||
__version__ = "0.8.0dev1"
|
__version__ = "0.9.3"
|
||||||
|
|
||||||
from .api.errors import Error
|
from .api.errors import Error
|
||||||
from .client.types import (
|
from .client.types import (
|
||||||
Audio, Chat, ChatMember, ChatMembers, ChatPhoto, Contact, Document, InputMediaPhoto,
|
Audio, Chat, ChatMember, ChatMembers, ChatPhoto, Contact, Document, InputMediaPhoto,
|
||||||
InputMediaVideo, InputMediaDocument, InputMediaAudio, InputMediaAnimation, InputPhoneContact,
|
InputMediaVideo, InputMediaDocument, InputMediaAudio, InputMediaAnimation, InputPhoneContact,
|
||||||
Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, Update, User,
|
Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, Update, User, UserStatus,
|
||||||
UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply,
|
UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply,
|
||||||
InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove
|
||||||
)
|
)
|
||||||
from .client import (
|
from .client import (
|
||||||
Client, ChatAction, ParseMode, Emoji,
|
Client, ChatAction, ParseMode, Emoji,
|
||||||
MessageHandler, DeletedMessagesHandler, CallbackQueryHandler,
|
MessageHandler, DeletedMessagesHandler, CallbackQueryHandler,
|
||||||
RawUpdateHandler, DisconnectHandler, Filters
|
RawUpdateHandler, DisconnectHandler, UserStatusHandler, Filters
|
||||||
)
|
)
|
||||||
|
@ -30,7 +30,7 @@ class BoolFalse(Object):
|
|||||||
return cls.value
|
return cls.value
|
||||||
|
|
||||||
def __new__(cls) -> bytes:
|
def __new__(cls) -> bytes:
|
||||||
return int.to_bytes(cls.ID, 4, "little")
|
return cls.ID.to_bytes(4, "little")
|
||||||
|
|
||||||
|
|
||||||
class BoolTrue(BoolFalse):
|
class BoolTrue(BoolFalse):
|
||||||
|
@ -48,7 +48,7 @@ class Bytes(Object):
|
|||||||
else:
|
else:
|
||||||
return (
|
return (
|
||||||
bytes([254])
|
bytes([254])
|
||||||
+ int.to_bytes(length, 3, "little")
|
+ length.to_bytes(3, "little")
|
||||||
+ value
|
+ value
|
||||||
+ bytes(-length % 4)
|
+ bytes(-length % 4)
|
||||||
)
|
)
|
||||||
|
@ -29,15 +29,12 @@ class Int(Object):
|
|||||||
return int.from_bytes(b.read(cls.SIZE), "little", signed=signed)
|
return int.from_bytes(b.read(cls.SIZE), "little", signed=signed)
|
||||||
|
|
||||||
def __new__(cls, value: int, signed: bool = True) -> bytes:
|
def __new__(cls, value: int, signed: bool = True) -> bytes:
|
||||||
return int.to_bytes(value, cls.SIZE, "little", signed=signed)
|
return value.to_bytes(cls.SIZE, "little", signed=signed)
|
||||||
|
|
||||||
|
|
||||||
class Long(Int):
|
class Long(Int):
|
||||||
SIZE = 8
|
SIZE = 8
|
||||||
|
|
||||||
def __new__(cls, *args):
|
|
||||||
return super().__new__(cls, *args)
|
|
||||||
|
|
||||||
|
|
||||||
class Int128(Int):
|
class Int128(Int):
|
||||||
SIZE = 16
|
SIZE = 16
|
||||||
|
@ -29,4 +29,4 @@ class Null(Object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def __new__(cls) -> bytes:
|
def __new__(cls) -> bytes:
|
||||||
return int.to_bytes(cls.ID, 4, "little")
|
return cls.ID.to_bytes(4, "little")
|
||||||
|
@ -24,7 +24,7 @@ from . import Bytes
|
|||||||
class String(Bytes):
|
class String(Bytes):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def read(b: BytesIO, *args) -> str:
|
def read(b: BytesIO, *args) -> str:
|
||||||
return super(String, String).read(b).decode()
|
return super(String, String).read(b).decode(errors="replace")
|
||||||
|
|
||||||
def __new__(cls, value: str) -> bytes:
|
def __new__(cls, value: str) -> bytes:
|
||||||
return super().__new__(cls, value.encode())
|
return super().__new__(cls, value.encode())
|
||||||
|
@ -22,5 +22,5 @@ from .filters import Filters
|
|||||||
from .handlers import (
|
from .handlers import (
|
||||||
MessageHandler, DeletedMessagesHandler,
|
MessageHandler, DeletedMessagesHandler,
|
||||||
CallbackQueryHandler, RawUpdateHandler,
|
CallbackQueryHandler, RawUpdateHandler,
|
||||||
DisconnectHandler
|
DisconnectHandler, UserStatusHandler
|
||||||
)
|
)
|
||||||
|
@ -33,6 +33,8 @@ import time
|
|||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from hashlib import sha256, md5
|
from hashlib import sha256, md5
|
||||||
|
from importlib import import_module
|
||||||
|
from pathlib import Path
|
||||||
from signal import signal, SIGINT, SIGTERM, SIGABRT
|
from signal import signal, SIGINT, SIGTERM, SIGABRT
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
@ -43,8 +45,9 @@ from pyrogram.api.errors import (
|
|||||||
PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty,
|
PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty,
|
||||||
PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded,
|
PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded,
|
||||||
PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned,
|
PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned,
|
||||||
VolumeLocNotFound, UserMigrate, FileIdInvalid)
|
VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate)
|
||||||
from pyrogram.client.handlers import DisconnectHandler
|
from pyrogram.client.handlers import DisconnectHandler
|
||||||
|
from pyrogram.client.handlers.handler import Handler
|
||||||
from pyrogram.crypto import AES
|
from pyrogram.crypto import AES
|
||||||
from pyrogram.session import Auth, Session
|
from pyrogram.session import Auth, Session
|
||||||
from .dispatcher import Dispatcher
|
from .dispatcher import Dispatcher
|
||||||
@ -90,6 +93,10 @@ class Client(Methods, BaseClient):
|
|||||||
Code of the language used on the client, in ISO 639-1 standard. Defaults to "en".
|
Code of the language used on the client, in ISO 639-1 standard. Defaults to "en".
|
||||||
This is an alternative way to set it if you don't want to use the *config.ini* file.
|
This is an alternative way to set it if you don't want to use the *config.ini* file.
|
||||||
|
|
||||||
|
ipv6 (``bool``, *optional*):
|
||||||
|
Pass True to connect to Telegram using IPv6.
|
||||||
|
Defaults to False (IPv4).
|
||||||
|
|
||||||
proxy (``dict``, *optional*):
|
proxy (``dict``, *optional*):
|
||||||
Your SOCKS5 Proxy settings as dict,
|
Your SOCKS5 Proxy settings as dict,
|
||||||
e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*.
|
e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*.
|
||||||
@ -136,6 +143,11 @@ class Client(Methods, BaseClient):
|
|||||||
|
|
||||||
config_file (``str``, *optional*):
|
config_file (``str``, *optional*):
|
||||||
Path of the configuration file. Defaults to ./config.ini
|
Path of the configuration file. Defaults to ./config.ini
|
||||||
|
|
||||||
|
plugins_dir (``str``, *optional*):
|
||||||
|
Define a custom directory for your plugins. The plugins directory is the location in your
|
||||||
|
filesystem where Pyrogram will automatically load your update handlers.
|
||||||
|
Defaults to None (plugins disabled).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self,
|
def __init__(self,
|
||||||
@ -146,6 +158,7 @@ class Client(Methods, BaseClient):
|
|||||||
device_model: str = None,
|
device_model: str = None,
|
||||||
system_version: str = None,
|
system_version: str = None,
|
||||||
lang_code: str = None,
|
lang_code: str = None,
|
||||||
|
ipv6: bool = False,
|
||||||
proxy: dict = None,
|
proxy: dict = None,
|
||||||
test_mode: bool = False,
|
test_mode: bool = False,
|
||||||
phone_number: str = None,
|
phone_number: str = None,
|
||||||
@ -154,9 +167,10 @@ class Client(Methods, BaseClient):
|
|||||||
force_sms: bool = False,
|
force_sms: bool = False,
|
||||||
first_name: str = None,
|
first_name: str = None,
|
||||||
last_name: str = None,
|
last_name: str = None,
|
||||||
workers: int = 4,
|
workers: int = BaseClient.WORKERS,
|
||||||
workdir: str = ".",
|
workdir: str = BaseClient.WORKDIR,
|
||||||
config_file: str = "./config.ini"):
|
config_file: str = BaseClient.CONFIG_FILE,
|
||||||
|
plugins_dir: str = None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.session_name = session_name
|
self.session_name = session_name
|
||||||
@ -166,6 +180,7 @@ class Client(Methods, BaseClient):
|
|||||||
self.device_model = device_model
|
self.device_model = device_model
|
||||||
self.system_version = system_version
|
self.system_version = system_version
|
||||||
self.lang_code = lang_code
|
self.lang_code = lang_code
|
||||||
|
self.ipv6 = ipv6
|
||||||
# TODO: Make code consistent, use underscore for private/protected fields
|
# TODO: Make code consistent, use underscore for private/protected fields
|
||||||
self._proxy = proxy
|
self._proxy = proxy
|
||||||
self.test_mode = test_mode
|
self.test_mode = test_mode
|
||||||
@ -178,9 +193,17 @@ class Client(Methods, BaseClient):
|
|||||||
self.workers = workers
|
self.workers = workers
|
||||||
self.workdir = workdir
|
self.workdir = workdir
|
||||||
self.config_file = config_file
|
self.config_file = config_file
|
||||||
|
self.plugins_dir = plugins_dir
|
||||||
|
|
||||||
self.dispatcher = Dispatcher(self, workers)
|
self.dispatcher = Dispatcher(self, workers)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
self.start()
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
self.stop()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def proxy(self):
|
def proxy(self):
|
||||||
return self._proxy
|
return self._proxy
|
||||||
@ -195,17 +218,19 @@ class Client(Methods, BaseClient):
|
|||||||
Requires no parameters.
|
Requires no parameters.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
|
``ConnectionError`` in case you try to start an already started Client.
|
||||||
"""
|
"""
|
||||||
if self.is_started:
|
if self.is_started:
|
||||||
raise ConnectionError("Client has already been started")
|
raise ConnectionError("Client has already been started")
|
||||||
|
|
||||||
if self.BOT_TOKEN_RE.match(self.session_name):
|
if self.BOT_TOKEN_RE.match(self.session_name):
|
||||||
self.token = self.session_name
|
self.bot_token = self.session_name
|
||||||
self.session_name = self.session_name.split(":")[0]
|
self.session_name = self.session_name.split(":")[0]
|
||||||
|
|
||||||
self.load_config()
|
self.load_config()
|
||||||
self.load_session()
|
self.load_session()
|
||||||
|
self.load_plugins()
|
||||||
|
|
||||||
self.session = Session(
|
self.session = Session(
|
||||||
self,
|
self,
|
||||||
@ -216,15 +241,16 @@ class Client(Methods, BaseClient):
|
|||||||
self.session.start()
|
self.session.start()
|
||||||
self.is_started = True
|
self.is_started = True
|
||||||
|
|
||||||
|
try:
|
||||||
if self.user_id is None:
|
if self.user_id is None:
|
||||||
if self.token is None:
|
if self.bot_token is None:
|
||||||
self.authorize_user()
|
self.authorize_user()
|
||||||
else:
|
else:
|
||||||
self.authorize_bot()
|
self.authorize_bot()
|
||||||
|
|
||||||
self.save_session()
|
self.save_session()
|
||||||
|
|
||||||
if self.token is None:
|
if self.bot_token is None:
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
if abs(now - self.date) > Client.OFFLINE_SLEEP:
|
if abs(now - self.date) > Client.OFFLINE_SLEEP:
|
||||||
@ -238,6 +264,10 @@ class Client(Methods, BaseClient):
|
|||||||
self.get_initial_dialogs_chunk()
|
self.get_initial_dialogs_chunk()
|
||||||
else:
|
else:
|
||||||
self.send(functions.updates.GetState())
|
self.send(functions.updates.GetState())
|
||||||
|
except Exception as e:
|
||||||
|
self.is_started = False
|
||||||
|
self.session.stop()
|
||||||
|
raise e
|
||||||
|
|
||||||
for i in range(self.UPDATES_WORKERS):
|
for i in range(self.UPDATES_WORKERS):
|
||||||
self.updates_workers_list.append(
|
self.updates_workers_list.append(
|
||||||
@ -267,6 +297,9 @@ class Client(Methods, BaseClient):
|
|||||||
def stop(self):
|
def stop(self):
|
||||||
"""Use this method to manually stop the Client.
|
"""Use this method to manually stop the Client.
|
||||||
Requires no parameters.
|
Requires no parameters.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
``ConnectionError`` in case you try to stop an already stopped Client.
|
||||||
"""
|
"""
|
||||||
if not self.is_started:
|
if not self.is_started:
|
||||||
raise ConnectionError("Client is already stopped")
|
raise ConnectionError("Client is already stopped")
|
||||||
@ -326,7 +359,7 @@ class Client(Methods, BaseClient):
|
|||||||
Requires no parameters.
|
Requires no parameters.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
self.start()
|
self.start()
|
||||||
self.idle()
|
self.idle()
|
||||||
@ -381,14 +414,14 @@ class Client(Methods, BaseClient):
|
|||||||
flags=0,
|
flags=0,
|
||||||
api_id=self.api_id,
|
api_id=self.api_id,
|
||||||
api_hash=self.api_hash,
|
api_hash=self.api_hash,
|
||||||
bot_auth_token=self.token
|
bot_auth_token=self.bot_token
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
except UserMigrate as e:
|
except UserMigrate as e:
|
||||||
self.session.stop()
|
self.session.stop()
|
||||||
|
|
||||||
self.dc_id = e.x
|
self.dc_id = e.x
|
||||||
self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create()
|
self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
|
||||||
|
|
||||||
self.session = Session(
|
self.session = Session(
|
||||||
self,
|
self,
|
||||||
@ -433,7 +466,7 @@ class Client(Methods, BaseClient):
|
|||||||
self.session.stop()
|
self.session.stop()
|
||||||
|
|
||||||
self.dc_id = e.x
|
self.dc_id = e.x
|
||||||
self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create()
|
self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
|
||||||
|
|
||||||
self.session = Session(
|
self.session = Session(
|
||||||
self,
|
self,
|
||||||
@ -613,7 +646,7 @@ class Client(Methods, BaseClient):
|
|||||||
if phone is not None:
|
if phone is not None:
|
||||||
self.peers_by_phone[phone] = input_peer
|
self.peers_by_phone[phone] = input_peer
|
||||||
|
|
||||||
if isinstance(entity, types.Chat):
|
if isinstance(entity, (types.Chat, types.ChatForbidden)):
|
||||||
chat_id = entity.id
|
chat_id = entity.id
|
||||||
peer_id = -chat_id
|
peer_id = -chat_id
|
||||||
|
|
||||||
@ -623,7 +656,7 @@ class Client(Methods, BaseClient):
|
|||||||
|
|
||||||
self.peers_by_id[peer_id] = input_peer
|
self.peers_by_id[peer_id] = input_peer
|
||||||
|
|
||||||
if isinstance(entity, types.Channel):
|
if isinstance(entity, (types.Channel, types.ChannelForbidden)):
|
||||||
channel_id = entity.id
|
channel_id = entity.id
|
||||||
peer_id = int("-100" + str(channel_id))
|
peer_id = int("-100" + str(channel_id))
|
||||||
|
|
||||||
@ -632,7 +665,7 @@ class Client(Methods, BaseClient):
|
|||||||
if access_hash is None:
|
if access_hash is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
username = entity.username
|
username = getattr(entity, "username", None)
|
||||||
|
|
||||||
input_peer = types.InputPeerChannel(
|
input_peer = types.InputPeerChannel(
|
||||||
channel_id=channel_id,
|
channel_id=channel_id,
|
||||||
@ -785,6 +818,7 @@ class Client(Methods, BaseClient):
|
|||||||
message = update.message
|
message = update.message
|
||||||
|
|
||||||
if not isinstance(message, types.MessageEmpty):
|
if not isinstance(message, types.MessageEmpty):
|
||||||
|
try:
|
||||||
diff = self.send(
|
diff = self.send(
|
||||||
functions.updates.GetChannelDifference(
|
functions.updates.GetChannelDifference(
|
||||||
channel=self.resolve_peer(int("-100" + str(channel_id))),
|
channel=self.resolve_peer(int("-100" + str(channel_id))),
|
||||||
@ -798,7 +832,9 @@ class Client(Methods, BaseClient):
|
|||||||
limit=pts
|
limit=pts
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
except ChannelPrivate:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
if not isinstance(diff, types.updates.ChannelDifferenceEmpty):
|
if not isinstance(diff, types.updates.ChannelDifferenceEmpty):
|
||||||
updates.users += diff.users
|
updates.users += diff.users
|
||||||
updates.chats += diff.chats
|
updates.chats += diff.chats
|
||||||
@ -815,7 +851,7 @@ class Client(Methods, BaseClient):
|
|||||||
if len(self.channels_pts[channel_id]) > 50:
|
if len(self.channels_pts[channel_id]) > 50:
|
||||||
self.channels_pts[channel_id] = self.channels_pts[channel_id][25:]
|
self.channels_pts[channel_id] = self.channels_pts[channel_id][25:]
|
||||||
|
|
||||||
self.dispatcher.updates.put((update, updates.users, updates.chats))
|
self.dispatcher.updates_queue.put((update, updates.users, updates.chats))
|
||||||
elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)):
|
elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)):
|
||||||
diff = self.send(
|
diff = self.send(
|
||||||
functions.updates.GetDifference(
|
functions.updates.GetDifference(
|
||||||
@ -826,7 +862,7 @@ class Client(Methods, BaseClient):
|
|||||||
)
|
)
|
||||||
|
|
||||||
if diff.new_messages:
|
if diff.new_messages:
|
||||||
self.dispatcher.updates.put((
|
self.dispatcher.updates_queue.put((
|
||||||
types.UpdateNewMessage(
|
types.UpdateNewMessage(
|
||||||
message=diff.new_messages[0],
|
message=diff.new_messages[0],
|
||||||
pts=updates.pts,
|
pts=updates.pts,
|
||||||
@ -836,9 +872,9 @@ class Client(Methods, BaseClient):
|
|||||||
diff.chats
|
diff.chats
|
||||||
))
|
))
|
||||||
else:
|
else:
|
||||||
self.dispatcher.updates.put((diff.other_updates[0], [], []))
|
self.dispatcher.updates_queue.put((diff.other_updates[0], [], []))
|
||||||
elif isinstance(updates, types.UpdateShort):
|
elif isinstance(updates, types.UpdateShort):
|
||||||
self.dispatcher.updates.put((updates.update, [], []))
|
self.dispatcher.updates_queue.put((updates.update, [], []))
|
||||||
elif isinstance(updates, types.UpdatesTooLong):
|
elif isinstance(updates, types.UpdatesTooLong):
|
||||||
log.warning(updates)
|
log.warning(updates)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -855,7 +891,7 @@ class Client(Methods, BaseClient):
|
|||||||
|
|
||||||
Args:
|
Args:
|
||||||
data (``Object``):
|
data (``Object``):
|
||||||
The API Scheme function filled with proper arguments.
|
The API Schema function filled with proper arguments.
|
||||||
|
|
||||||
retries (``int``):
|
retries (``int``):
|
||||||
Number of retries.
|
Number of retries.
|
||||||
@ -864,7 +900,7 @@ class Client(Methods, BaseClient):
|
|||||||
Timeout in seconds.
|
Timeout in seconds.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
if not self.is_started:
|
if not self.is_started:
|
||||||
raise ConnectionError("Client has not been started")
|
raise ConnectionError("Client has not been started")
|
||||||
@ -892,30 +928,18 @@ class Client(Methods, BaseClient):
|
|||||||
"More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration"
|
"More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration"
|
||||||
)
|
)
|
||||||
|
|
||||||
for option in {"app_version", "device_model", "system_version", "lang_code"}:
|
for option in ["app_version", "device_model", "system_version", "lang_code"]:
|
||||||
if getattr(self, option):
|
if getattr(self, option):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
setattr(self, option, Client.APP_VERSION)
|
|
||||||
|
|
||||||
if parser.has_section("pyrogram"):
|
if parser.has_section("pyrogram"):
|
||||||
setattr(self, option, parser.get(
|
setattr(self, option, parser.get(
|
||||||
"pyrogram",
|
"pyrogram",
|
||||||
option,
|
option,
|
||||||
fallback=getattr(Client, option.upper())
|
fallback=getattr(Client, option.upper())
|
||||||
))
|
))
|
||||||
|
|
||||||
if self.lang_code:
|
|
||||||
pass
|
|
||||||
else:
|
else:
|
||||||
self.lang_code = Client.LANG_CODE
|
setattr(self, option, getattr(Client, option.upper()))
|
||||||
|
|
||||||
if parser.has_section("pyrogram"):
|
|
||||||
self.lang_code = parser.get(
|
|
||||||
"pyrogram",
|
|
||||||
"lang_code",
|
|
||||||
fallback=Client.LANG_CODE
|
|
||||||
)
|
|
||||||
|
|
||||||
if self._proxy:
|
if self._proxy:
|
||||||
self._proxy["enabled"] = True
|
self._proxy["enabled"] = True
|
||||||
@ -936,7 +960,7 @@ class Client(Methods, BaseClient):
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
self.dc_id = 1
|
self.dc_id = 1
|
||||||
self.date = 0
|
self.date = 0
|
||||||
self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create()
|
self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
|
||||||
else:
|
else:
|
||||||
self.dc_id = s["dc_id"]
|
self.dc_id = s["dc_id"]
|
||||||
self.test_mode = s["test_mode"]
|
self.test_mode = s["test_mode"]
|
||||||
@ -959,6 +983,45 @@ class Client(Methods, BaseClient):
|
|||||||
if peer:
|
if peer:
|
||||||
self.peers_by_phone[k] = peer
|
self.peers_by_phone[k] = peer
|
||||||
|
|
||||||
|
def load_plugins(self):
|
||||||
|
if self.plugins_dir is not None:
|
||||||
|
plugins_count = 0
|
||||||
|
|
||||||
|
for path in Path(self.plugins_dir).rglob("*.py"):
|
||||||
|
file_path = os.path.splitext(str(path))[0]
|
||||||
|
import_path = []
|
||||||
|
|
||||||
|
while file_path:
|
||||||
|
file_path, tail = os.path.split(file_path)
|
||||||
|
import_path.insert(0, tail)
|
||||||
|
|
||||||
|
import_path = ".".join(import_path)
|
||||||
|
module = import_module(import_path)
|
||||||
|
|
||||||
|
for name in dir(module):
|
||||||
|
# noinspection PyBroadException
|
||||||
|
try:
|
||||||
|
handler, group = getattr(module, name)
|
||||||
|
|
||||||
|
if isinstance(handler, Handler) and isinstance(group, int):
|
||||||
|
self.add_handler(handler, group)
|
||||||
|
|
||||||
|
log.info('{}("{}") from "{}" loaded in group {}'.format(
|
||||||
|
type(handler).__name__, name, import_path, group))
|
||||||
|
|
||||||
|
plugins_count += 1
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if plugins_count > 0:
|
||||||
|
log.warning('Successfully loaded {} plugin{} from "{}"'.format(
|
||||||
|
plugins_count,
|
||||||
|
"s" if plugins_count > 1 else "",
|
||||||
|
self.plugins_dir
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
log.warning('No plugin loaded: "{}" doesn\'t contain any valid plugin'.format(self.plugins_dir))
|
||||||
|
|
||||||
def save_session(self):
|
def save_session(self):
|
||||||
auth_key = base64.b64encode(self.auth_key).decode()
|
auth_key = base64.b64encode(self.auth_key).decode()
|
||||||
auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)]
|
auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)]
|
||||||
@ -1026,7 +1089,8 @@ class Client(Methods, BaseClient):
|
|||||||
On success, the resolved peer id is returned in form of an InputPeer object.
|
On success, the resolved peer id is returned in form of an InputPeer object.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
|
``KeyError`` in case the peer doesn't exist in the internal database.
|
||||||
"""
|
"""
|
||||||
if type(peer_id) is str:
|
if type(peer_id) is str:
|
||||||
if peer_id in ("self", "me"):
|
if peer_id in ("self", "me"):
|
||||||
@ -1066,6 +1130,13 @@ class Client(Methods, BaseClient):
|
|||||||
progress_args: tuple = ()):
|
progress_args: tuple = ()):
|
||||||
part_size = 512 * 1024
|
part_size = 512 * 1024
|
||||||
file_size = os.path.getsize(path)
|
file_size = os.path.getsize(path)
|
||||||
|
|
||||||
|
if file_size == 0:
|
||||||
|
raise ValueError("File size equals to 0 B")
|
||||||
|
|
||||||
|
if file_size > 1500 * 1024 * 1024:
|
||||||
|
raise ValueError("Telegram doesn't support uploading files bigger than 1500 MiB")
|
||||||
|
|
||||||
file_total_parts = int(math.ceil(file_size / part_size))
|
file_total_parts = int(math.ceil(file_size / part_size))
|
||||||
is_big = True if file_size > 10 * 1024 * 1024 else False
|
is_big = True if file_size > 10 * 1024 * 1024 else False
|
||||||
is_missing_part = True if file_id is not None else False
|
is_missing_part = True if file_id is not None else False
|
||||||
@ -1158,7 +1229,7 @@ class Client(Methods, BaseClient):
|
|||||||
session = Session(
|
session = Session(
|
||||||
self,
|
self,
|
||||||
dc_id,
|
dc_id,
|
||||||
Auth(dc_id, self.test_mode, self._proxy).create(),
|
Auth(dc_id, self.test_mode, self.ipv6, self._proxy).create(),
|
||||||
is_media=True
|
is_media=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1243,7 +1314,7 @@ class Client(Methods, BaseClient):
|
|||||||
cdn_session = Session(
|
cdn_session = Session(
|
||||||
self,
|
self,
|
||||||
r.dc_id,
|
r.dc_id,
|
||||||
Auth(r.dc_id, self.test_mode, self._proxy).create(),
|
Auth(r.dc_id, self.test_mode, self.ipv6, self._proxy).create(),
|
||||||
is_media=True,
|
is_media=True,
|
||||||
is_cdn=True
|
is_cdn=True
|
||||||
)
|
)
|
||||||
|
@ -22,10 +22,9 @@ from collections import OrderedDict
|
|||||||
from queue import Queue
|
from queue import Queue
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
|
|
||||||
import pyrogram
|
|
||||||
from pyrogram.api import types
|
from pyrogram.api import types
|
||||||
from ..ext import utils
|
from ..ext import utils
|
||||||
from ..handlers import RawUpdateHandler, CallbackQueryHandler, MessageHandler, DeletedMessagesHandler
|
from ..handlers import CallbackQueryHandler, MessageHandler, DeletedMessagesHandler, UserStatusHandler, RawUpdateHandler
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -46,15 +45,37 @@ class Dispatcher:
|
|||||||
types.UpdateDeleteChannelMessages
|
types.UpdateDeleteChannelMessages
|
||||||
)
|
)
|
||||||
|
|
||||||
|
CALLBACK_QUERY_UPDATES = (
|
||||||
|
types.UpdateBotCallbackQuery,
|
||||||
|
types.UpdateInlineBotCallbackQuery
|
||||||
|
)
|
||||||
|
|
||||||
MESSAGE_UPDATES = NEW_MESSAGE_UPDATES + EDIT_MESSAGE_UPDATES
|
MESSAGE_UPDATES = NEW_MESSAGE_UPDATES + EDIT_MESSAGE_UPDATES
|
||||||
|
|
||||||
def __init__(self, client, workers):
|
def __init__(self, client, workers: int):
|
||||||
self.client = client
|
self.client = client
|
||||||
self.workers = workers
|
self.workers = workers
|
||||||
|
|
||||||
self.workers_list = []
|
self.workers_list = []
|
||||||
self.updates = Queue()
|
self.updates_queue = Queue()
|
||||||
self.groups = OrderedDict()
|
self.groups = OrderedDict()
|
||||||
|
|
||||||
|
self.update_parsers = {
|
||||||
|
Dispatcher.MESSAGE_UPDATES:
|
||||||
|
lambda upd, usr, cht: (utils.parse_messages(self.client, upd.message, usr, cht), MessageHandler),
|
||||||
|
|
||||||
|
Dispatcher.DELETE_MESSAGE_UPDATES:
|
||||||
|
lambda upd, usr, cht: (utils.parse_deleted_messages(upd), DeletedMessagesHandler),
|
||||||
|
|
||||||
|
Dispatcher.CALLBACK_QUERY_UPDATES:
|
||||||
|
lambda upd, usr, cht: (utils.parse_callback_query(self.client, upd, usr), CallbackQueryHandler),
|
||||||
|
|
||||||
|
(types.UpdateUserStatus,):
|
||||||
|
lambda upd, usr, cht: (utils.parse_user_status(upd.status, upd.user_id), UserStatusHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.update_parsers = {key: value for key_tuple, value in self.update_parsers.items() for key in key_tuple}
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
for i in range(self.workers):
|
for i in range(self.workers):
|
||||||
self.workers_list.append(
|
self.workers_list.append(
|
||||||
@ -68,10 +89,10 @@ class Dispatcher:
|
|||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
for _ in range(self.workers):
|
for _ in range(self.workers):
|
||||||
self.updates.put(None)
|
self.updates_queue.put(None)
|
||||||
|
|
||||||
for i in self.workers_list:
|
for worker in self.workers_list:
|
||||||
i.join()
|
worker.join()
|
||||||
|
|
||||||
self.workers_list.clear()
|
self.workers_list.clear()
|
||||||
|
|
||||||
@ -84,56 +105,16 @@ class Dispatcher:
|
|||||||
|
|
||||||
def remove_handler(self, handler, group: int):
|
def remove_handler(self, handler, group: int):
|
||||||
if group not in self.groups:
|
if group not in self.groups:
|
||||||
raise ValueError("Group {} does not exist. "
|
raise ValueError("Group {} does not exist. Handler was not removed.".format(group))
|
||||||
"Handler was not removed.".format(group))
|
|
||||||
self.groups[group].remove(handler)
|
self.groups[group].remove(handler)
|
||||||
|
|
||||||
def dispatch(self, update, users: dict = None, chats: dict = None, is_raw: bool = False):
|
|
||||||
for group in self.groups.values():
|
|
||||||
for handler in group:
|
|
||||||
if is_raw:
|
|
||||||
if not isinstance(handler, RawUpdateHandler):
|
|
||||||
continue
|
|
||||||
|
|
||||||
args = (self.client, update, users, chats)
|
|
||||||
else:
|
|
||||||
message = (update.message
|
|
||||||
or update.channel_post
|
|
||||||
or update.edited_message
|
|
||||||
or update.edited_channel_post)
|
|
||||||
|
|
||||||
deleted_messages = (update.deleted_channel_posts
|
|
||||||
or update.deleted_messages)
|
|
||||||
|
|
||||||
callback_query = update.callback_query
|
|
||||||
|
|
||||||
if message and isinstance(handler, MessageHandler):
|
|
||||||
if not handler.check(message):
|
|
||||||
continue
|
|
||||||
|
|
||||||
args = (self.client, message)
|
|
||||||
elif deleted_messages and isinstance(handler, DeletedMessagesHandler):
|
|
||||||
if not handler.check(deleted_messages):
|
|
||||||
continue
|
|
||||||
|
|
||||||
args = (self.client, deleted_messages)
|
|
||||||
elif callback_query and isinstance(handler, CallbackQueryHandler):
|
|
||||||
if not handler.check(callback_query):
|
|
||||||
continue
|
|
||||||
|
|
||||||
args = (self.client, callback_query)
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
|
|
||||||
handler.callback(*args)
|
|
||||||
break
|
|
||||||
|
|
||||||
def update_worker(self):
|
def update_worker(self):
|
||||||
name = threading.current_thread().name
|
name = threading.current_thread().name
|
||||||
log.debug("{} started".format(name))
|
log.debug("{} started".format(name))
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
update = self.updates.get()
|
update = self.updates_queue.get()
|
||||||
|
|
||||||
if update is None:
|
if update is None:
|
||||||
break
|
break
|
||||||
@ -143,71 +124,32 @@ class Dispatcher:
|
|||||||
chats = {i.id: i for i in update[2]}
|
chats = {i.id: i for i in update[2]}
|
||||||
update = update[0]
|
update = update[0]
|
||||||
|
|
||||||
self.dispatch(update, users=users, chats=chats, is_raw=True)
|
parser = self.update_parsers.get(type(update), None)
|
||||||
|
|
||||||
if isinstance(update, Dispatcher.MESSAGE_UPDATES):
|
if parser is None:
|
||||||
if isinstance(update.message, types.MessageEmpty):
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
message = utils.parse_messages(
|
parsed_update, handler_type = parser(update, users, chats)
|
||||||
self.client,
|
|
||||||
update.message,
|
|
||||||
users,
|
|
||||||
chats
|
|
||||||
)
|
|
||||||
|
|
||||||
is_edited_message = isinstance(update, Dispatcher.EDIT_MESSAGE_UPDATES)
|
for group in self.groups.values():
|
||||||
|
for handler in group:
|
||||||
|
args = None
|
||||||
|
|
||||||
self.dispatch(
|
if isinstance(handler, RawUpdateHandler):
|
||||||
pyrogram.Update(
|
args = (update, users, chats)
|
||||||
message=((message if message.chat.type != "channel"
|
elif isinstance(handler, handler_type):
|
||||||
else None) if not is_edited_message
|
if handler.check(parsed_update):
|
||||||
else None),
|
args = (parsed_update,)
|
||||||
edited_message=((message if message.chat.type != "channel"
|
|
||||||
else None) if is_edited_message
|
|
||||||
else None),
|
|
||||||
channel_post=((message if message.chat.type == "channel"
|
|
||||||
else None) if not is_edited_message
|
|
||||||
else None),
|
|
||||||
edited_channel_post=((message if message.chat.type == "channel"
|
|
||||||
else None) if is_edited_message
|
|
||||||
else None)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif isinstance(update, Dispatcher.DELETE_MESSAGE_UPDATES):
|
if args is None:
|
||||||
is_channel = hasattr(update, 'channel_id')
|
|
||||||
|
|
||||||
messages = utils.parse_deleted_messages(
|
|
||||||
update.messages,
|
|
||||||
(update.channel_id if is_channel else None)
|
|
||||||
)
|
|
||||||
|
|
||||||
self.dispatch(
|
|
||||||
pyrogram.Update(
|
|
||||||
deleted_messages=(messages if not is_channel else None),
|
|
||||||
deleted_channel_posts=(messages if is_channel else None)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif isinstance(update, types.UpdateBotCallbackQuery):
|
|
||||||
self.dispatch(
|
|
||||||
pyrogram.Update(
|
|
||||||
callback_query=utils.parse_callback_query(
|
|
||||||
self.client, update, users
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif isinstance(update, types.UpdateInlineBotCallbackQuery):
|
|
||||||
self.dispatch(
|
|
||||||
pyrogram.Update(
|
|
||||||
callback_query=utils.parse_inline_callback_query(
|
|
||||||
update, users
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
handler.callback(self.client, *args)
|
||||||
|
except Exception as e:
|
||||||
|
log.error(e, exc_info=True)
|
||||||
|
finally:
|
||||||
|
break
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.error(e, exc_info=True)
|
log.error(e, exc_info=True)
|
||||||
|
|
||||||
|
@ -49,6 +49,9 @@ class BaseClient:
|
|||||||
UPDATES_WORKERS = 1
|
UPDATES_WORKERS = 1
|
||||||
DOWNLOAD_WORKERS = 1
|
DOWNLOAD_WORKERS = 1
|
||||||
OFFLINE_SLEEP = 300
|
OFFLINE_SLEEP = 300
|
||||||
|
WORKERS = 4
|
||||||
|
WORKDIR = "."
|
||||||
|
CONFIG_FILE = "./config.ini"
|
||||||
|
|
||||||
MEDIA_TYPE_ID = {
|
MEDIA_TYPE_ID = {
|
||||||
0: "thumbnail",
|
0: "thumbnail",
|
||||||
@ -64,7 +67,7 @@ class BaseClient:
|
|||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.token = None
|
self.bot_token = None
|
||||||
self.dc_id = None
|
self.dc_id = None
|
||||||
self.auth_key = None
|
self.auth_key = None
|
||||||
self.user_id = None
|
self.user_id = None
|
||||||
@ -116,7 +119,8 @@ class BaseClient:
|
|||||||
def get_messages(
|
def get_messages(
|
||||||
self,
|
self,
|
||||||
chat_id: int or str,
|
chat_id: int or str,
|
||||||
message_ids,
|
message_ids: int or list = None,
|
||||||
|
reply_to_message_ids: int or list = None,
|
||||||
replies: int = 1
|
replies: int = 1
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -17,15 +17,12 @@
|
|||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import time
|
|
||||||
from base64 import b64decode, b64encode
|
from base64 import b64decode, b64encode
|
||||||
from struct import pack
|
from struct import pack
|
||||||
from weakref import proxy
|
|
||||||
|
|
||||||
from pyrogram.api.errors import FloodWait
|
|
||||||
from pyrogram.client import types as pyrogram_types
|
from pyrogram.client import types as pyrogram_types
|
||||||
from ...api import types, functions
|
from ...api import types, functions
|
||||||
from ...api.errors import StickersetInvalid
|
from ...api.errors import StickersetInvalid, MessageIdsEmpty
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -129,6 +126,30 @@ def parse_chat_photo(photo):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def parse_user_status(user_status, user_id: int = None, is_bot: bool = False) -> pyrogram_types.UserStatus or None:
|
||||||
|
if is_bot:
|
||||||
|
return None
|
||||||
|
|
||||||
|
status = pyrogram_types.UserStatus(user_id)
|
||||||
|
|
||||||
|
if isinstance(user_status, types.UserStatusOnline):
|
||||||
|
status.online = True
|
||||||
|
status.date = user_status.expires
|
||||||
|
elif isinstance(user_status, types.UserStatusOffline):
|
||||||
|
status.offline = True
|
||||||
|
status.date = user_status.was_online
|
||||||
|
elif isinstance(user_status, types.UserStatusRecently):
|
||||||
|
status.recently = True
|
||||||
|
elif isinstance(user_status, types.UserStatusLastWeek):
|
||||||
|
status.within_week = True
|
||||||
|
elif isinstance(user_status, types.UserStatusLastMonth):
|
||||||
|
status.within_month = True
|
||||||
|
else:
|
||||||
|
status.long_time_ago = True
|
||||||
|
|
||||||
|
return status
|
||||||
|
|
||||||
|
|
||||||
def parse_user(user: types.User) -> pyrogram_types.User or None:
|
def parse_user(user: types.User) -> pyrogram_types.User or None:
|
||||||
return pyrogram_types.User(
|
return pyrogram_types.User(
|
||||||
id=user.id,
|
id=user.id,
|
||||||
@ -142,7 +163,9 @@ def parse_user(user: types.User) -> pyrogram_types.User or None:
|
|||||||
username=user.username,
|
username=user.username,
|
||||||
language_code=user.lang_code,
|
language_code=user.lang_code,
|
||||||
phone_number=user.phone,
|
phone_number=user.phone,
|
||||||
photo=parse_chat_photo(user.photo)
|
photo=parse_chat_photo(user.photo),
|
||||||
|
status=parse_user_status(user.status, is_bot=user.bot),
|
||||||
|
restriction_reason=user.restriction_reason
|
||||||
) if user else None
|
) if user else None
|
||||||
|
|
||||||
|
|
||||||
@ -162,7 +185,8 @@ def parse_user_chat(user: types.User) -> pyrogram_types.Chat:
|
|||||||
username=user.username,
|
username=user.username,
|
||||||
first_name=user.first_name,
|
first_name=user.first_name,
|
||||||
last_name=user.last_name,
|
last_name=user.last_name,
|
||||||
photo=parse_chat_photo(user.photo)
|
photo=parse_chat_photo(user.photo),
|
||||||
|
restriction_reason=user.restriction_reason
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -187,7 +211,8 @@ def parse_channel_chat(channel: types.Channel) -> pyrogram_types.Chat:
|
|||||||
type="supergroup" if channel.megagroup else "channel",
|
type="supergroup" if channel.megagroup else "channel",
|
||||||
title=channel.title,
|
title=channel.title,
|
||||||
username=getattr(channel, "username", None),
|
username=getattr(channel, "username", None),
|
||||||
photo=parse_chat_photo(getattr(channel, "photo", None))
|
photo=parse_chat_photo(getattr(channel, "photo", None)),
|
||||||
|
restriction_reason=getattr(channel, "restriction_reason", None)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -301,6 +326,7 @@ def parse_messages(
|
|||||||
video_note = None
|
video_note = None
|
||||||
sticker = None
|
sticker = None
|
||||||
document = None
|
document = None
|
||||||
|
web_page = None
|
||||||
|
|
||||||
media = message.media
|
media = message.media
|
||||||
|
|
||||||
@ -548,6 +574,8 @@ def parse_messages(
|
|||||||
file_size=doc.size,
|
file_size=doc.size,
|
||||||
date=doc.date
|
date=doc.date
|
||||||
)
|
)
|
||||||
|
elif isinstance(media, types.MessageMediaWebPage):
|
||||||
|
web_page = True
|
||||||
else:
|
else:
|
||||||
media = None
|
media = None
|
||||||
|
|
||||||
@ -580,6 +608,8 @@ def parse_messages(
|
|||||||
forward_from_message_id=forward_from_message_id,
|
forward_from_message_id=forward_from_message_id,
|
||||||
forward_signature=forward_signature,
|
forward_signature=forward_signature,
|
||||||
forward_date=forward_date,
|
forward_date=forward_date,
|
||||||
|
mentioned=message.mentioned,
|
||||||
|
media=bool(media) or None,
|
||||||
edit_date=message.edit_date,
|
edit_date=message.edit_date,
|
||||||
media_group_id=message.grouped_id,
|
media_group_id=message.grouped_id,
|
||||||
photo=photo,
|
photo=photo,
|
||||||
@ -593,10 +623,11 @@ def parse_messages(
|
|||||||
video_note=video_note,
|
video_note=video_note,
|
||||||
sticker=sticker,
|
sticker=sticker,
|
||||||
document=document,
|
document=document,
|
||||||
|
web_page=web_page,
|
||||||
views=message.views,
|
views=message.views,
|
||||||
via_bot=parse_user(users.get(message.via_bot_id, None)),
|
via_bot=parse_user(users.get(message.via_bot_id, None)),
|
||||||
outgoing=message.out,
|
outgoing=message.out,
|
||||||
client=proxy(client),
|
client=client,
|
||||||
reply_markup=reply_markup
|
reply_markup=reply_markup
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -607,18 +638,14 @@ def parse_messages(
|
|||||||
m.caption.init(m._client, m.caption_entities or [])
|
m.caption.init(m._client, m.caption_entities or [])
|
||||||
|
|
||||||
if message.reply_to_msg_id and replies:
|
if message.reply_to_msg_id and replies:
|
||||||
while True:
|
|
||||||
try:
|
try:
|
||||||
m.reply_to_message = client.get_messages(
|
m.reply_to_message = client.get_messages(
|
||||||
m.chat.id, message.reply_to_msg_id,
|
m.chat.id,
|
||||||
|
reply_to_message_ids=message.id,
|
||||||
replies=replies - 1
|
replies=replies - 1
|
||||||
)
|
)
|
||||||
except FloodWait as e:
|
except MessageIdsEmpty:
|
||||||
log.warning("get_messages flood: waiting {} seconds".format(e.x))
|
pass
|
||||||
time.sleep(e.x)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
elif isinstance(message, types.MessageService):
|
elif isinstance(message, types.MessageService):
|
||||||
action = message.action
|
action = message.action
|
||||||
|
|
||||||
@ -705,6 +732,7 @@ def parse_messages(
|
|||||||
date=message.date,
|
date=message.date,
|
||||||
chat=parse_chat(message, users, chats),
|
chat=parse_chat(message, users, chats),
|
||||||
from_user=parse_user(users.get(message.from_id, None)),
|
from_user=parse_user(users.get(message.from_id, None)),
|
||||||
|
service=True,
|
||||||
new_chat_members=new_chat_members,
|
new_chat_members=new_chat_members,
|
||||||
left_chat_member=left_chat_member,
|
left_chat_member=left_chat_member,
|
||||||
new_chat_title=new_chat_title,
|
new_chat_title=new_chat_title,
|
||||||
@ -714,35 +742,31 @@ def parse_messages(
|
|||||||
migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None,
|
migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None,
|
||||||
group_chat_created=group_chat_created,
|
group_chat_created=group_chat_created,
|
||||||
channel_chat_created=channel_chat_created,
|
channel_chat_created=channel_chat_created,
|
||||||
client=proxy(client)
|
client=client
|
||||||
# TODO: supergroup_chat_created
|
# TODO: supergroup_chat_created
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(action, types.MessageActionPinMessage):
|
if isinstance(action, types.MessageActionPinMessage):
|
||||||
while True:
|
|
||||||
try:
|
try:
|
||||||
m.pinned_message = client.get_messages(
|
m.pinned_message = client.get_messages(
|
||||||
m.chat.id, message.reply_to_msg_id,
|
m.chat.id,
|
||||||
|
reply_to_message_ids=message.id,
|
||||||
replies=0
|
replies=0
|
||||||
)
|
)
|
||||||
except FloodWait as e:
|
except MessageIdsEmpty:
|
||||||
log.warning("get_messages flood: waiting {} seconds".format(e.x))
|
pass
|
||||||
time.sleep(e.x)
|
|
||||||
continue
|
|
||||||
else:
|
else:
|
||||||
break
|
m = pyrogram_types.Message(message_id=message.id, client=client, empty=True)
|
||||||
else:
|
|
||||||
m = pyrogram_types.Message(message_id=message.id, client=proxy(client))
|
|
||||||
|
|
||||||
parsed_messages.append(m)
|
parsed_messages.append(m)
|
||||||
|
|
||||||
return parsed_messages if is_list else parsed_messages[0]
|
return parsed_messages if is_list else parsed_messages[0]
|
||||||
|
|
||||||
|
|
||||||
def parse_deleted_messages(
|
def parse_deleted_messages(update) -> pyrogram_types.Messages:
|
||||||
messages: list,
|
messages = update.messages
|
||||||
channel_id: int
|
channel_id = getattr(update, "channel_id", None)
|
||||||
) -> pyrogram_types.Messages:
|
|
||||||
parsed_messages = []
|
parsed_messages = []
|
||||||
|
|
||||||
for message in messages:
|
for message in messages:
|
||||||
@ -849,8 +873,12 @@ def parse_profile_photos(photos):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def parse_callback_query(client, callback_query, users):
|
def parse_callback_query(client, update, users):
|
||||||
peer = callback_query.peer
|
message = None
|
||||||
|
inline_message_id = None
|
||||||
|
|
||||||
|
if isinstance(update, types.UpdateBotCallbackQuery):
|
||||||
|
peer = update.peer
|
||||||
|
|
||||||
if isinstance(peer, types.PeerUser):
|
if isinstance(peer, types.PeerUser):
|
||||||
peer_id = peer.user_id
|
peer_id = peer.user_id
|
||||||
@ -859,31 +887,27 @@ def parse_callback_query(client, callback_query, users):
|
|||||||
else:
|
else:
|
||||||
peer_id = int("-100" + str(peer.channel_id))
|
peer_id = int("-100" + str(peer.channel_id))
|
||||||
|
|
||||||
return pyrogram_types.CallbackQuery(
|
message = client.get_messages(peer_id, update.msg_id)
|
||||||
id=str(callback_query.query_id),
|
elif isinstance(update, types.UpdateInlineBotCallbackQuery):
|
||||||
from_user=parse_user(users[callback_query.user_id]),
|
|
||||||
message=client.get_messages(peer_id, callback_query.msg_id),
|
|
||||||
chat_instance=str(callback_query.chat_instance),
|
|
||||||
data=callback_query.data.decode(),
|
|
||||||
game_short_name=callback_query.game_short_name
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_inline_callback_query(callback_query, users):
|
|
||||||
return pyrogram_types.CallbackQuery(
|
|
||||||
id=str(callback_query.query_id),
|
|
||||||
from_user=parse_user(users[callback_query.user_id]),
|
|
||||||
chat_instance=str(callback_query.chat_instance),
|
|
||||||
inline_message_id = b64encode(
|
inline_message_id = b64encode(
|
||||||
pack(
|
pack(
|
||||||
"<iqq",
|
"<iqq",
|
||||||
callback_query.msg_id.dc_id,
|
update.msg_id.dc_id,
|
||||||
callback_query.msg_id.id,
|
update.msg_id.id,
|
||||||
callback_query.msg_id.access_hash
|
update.msg_id.access_hash
|
||||||
),
|
),
|
||||||
b"-_"
|
b"-_"
|
||||||
).decode().rstrip("="),
|
).decode().rstrip("=")
|
||||||
game_short_name=callback_query.game_short_name
|
|
||||||
|
return pyrogram_types.CallbackQuery(
|
||||||
|
id=str(update.query_id),
|
||||||
|
from_user=parse_user(users[update.user_id]),
|
||||||
|
message=message,
|
||||||
|
inline_message_id=inline_message_id,
|
||||||
|
chat_instance=str(update.chat_instance),
|
||||||
|
data=update.data.decode(),
|
||||||
|
game_short_name=update.game_short_name,
|
||||||
|
client=client
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -918,7 +942,7 @@ def parse_chat_full(
|
|||||||
if full_chat.pinned_msg_id:
|
if full_chat.pinned_msg_id:
|
||||||
parsed_chat.pinned_message = client.get_messages(
|
parsed_chat.pinned_message = client.get_messages(
|
||||||
parsed_chat.id,
|
parsed_chat.id,
|
||||||
full_chat.pinned_msg_id
|
message_ids=full_chat.pinned_msg_id
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(full_chat.exported_invite, types.ChatInviteExported):
|
if isinstance(full_chat.exported_invite, types.ChatInviteExported):
|
||||||
@ -941,6 +965,7 @@ def parse_chat_members(members: types.channels.ChannelParticipants or types.mess
|
|||||||
parsed_members = []
|
parsed_members = []
|
||||||
|
|
||||||
if isinstance(members, types.channels.ChannelParticipants):
|
if isinstance(members, types.channels.ChannelParticipants):
|
||||||
|
count = members.count
|
||||||
members = members.participants
|
members = members.participants
|
||||||
|
|
||||||
for member in members:
|
for member in members:
|
||||||
@ -999,7 +1024,7 @@ def parse_chat_members(members: types.channels.ChannelParticipants or types.mess
|
|||||||
parsed_members.append(chat_member)
|
parsed_members.append(chat_member)
|
||||||
|
|
||||||
return pyrogram_types.ChatMembers(
|
return pyrogram_types.ChatMembers(
|
||||||
total_count=members.count,
|
total_count=count,
|
||||||
chat_members=parsed_members
|
chat_members=parsed_members
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
@ -118,6 +118,9 @@ class Filters:
|
|||||||
venue = create("Venue", lambda _, m: bool(m.venue))
|
venue = create("Venue", lambda _, m: bool(m.venue))
|
||||||
"""Filter messages that contain :obj:`Venue <pyrogram.api.types.pyrogram.Venue>` objects."""
|
"""Filter messages that contain :obj:`Venue <pyrogram.api.types.pyrogram.Venue>` objects."""
|
||||||
|
|
||||||
|
web_page = create("WebPage", lambda _, m: m.web_page)
|
||||||
|
"""Filter messages sent with a webpage preview."""
|
||||||
|
|
||||||
private = create("Private", lambda _, m: bool(m.chat and m.chat.type == "private"))
|
private = create("Private", lambda _, m: bool(m.chat and m.chat.type == "private"))
|
||||||
"""Filter messages sent in private chats."""
|
"""Filter messages sent in private chats."""
|
||||||
|
|
||||||
@ -166,9 +169,41 @@ class Filters:
|
|||||||
inline_keyboard = create("InlineKeyboard", lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup))
|
inline_keyboard = create("InlineKeyboard", lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup))
|
||||||
"""Filter messages containing inline keyboard markups"""
|
"""Filter messages containing inline keyboard markups"""
|
||||||
|
|
||||||
|
mentioned = create("Mentioned", lambda _, m: bool(m.mentioned))
|
||||||
|
"""Filter messages containing mentions"""
|
||||||
|
|
||||||
|
service = create("Service", lambda _, m: bool(m.service))
|
||||||
|
"""Filter service messages. A service message contains any of the following fields set
|
||||||
|
|
||||||
|
- left_chat_member
|
||||||
|
- new_chat_title
|
||||||
|
- new_chat_photo
|
||||||
|
- delete_chat_photo
|
||||||
|
- group_chat_created
|
||||||
|
- supergroup_chat_created
|
||||||
|
- channel_chat_created
|
||||||
|
- migrate_to_chat_id
|
||||||
|
- migrate_from_chat_id
|
||||||
|
- pinned_message"""
|
||||||
|
|
||||||
|
media = create("Media", lambda _, m: bool(m.media))
|
||||||
|
"""Filter media messages. A media message contains any of the following fields set
|
||||||
|
|
||||||
|
- audio
|
||||||
|
- document
|
||||||
|
- photo
|
||||||
|
- sticker
|
||||||
|
- video
|
||||||
|
- animation
|
||||||
|
- voice
|
||||||
|
- video_note
|
||||||
|
- contact
|
||||||
|
- location
|
||||||
|
- venue"""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def command(command: str or list,
|
def command(command: str or list,
|
||||||
prefix: str = "/",
|
prefix: str or list = "/",
|
||||||
separator: str = " ",
|
separator: str = " ",
|
||||||
case_sensitive: bool = False):
|
case_sensitive: bool = False):
|
||||||
"""Filter commands, i.e.: text messages starting with "/" or any other custom prefix.
|
"""Filter commands, i.e.: text messages starting with "/" or any other custom prefix.
|
||||||
@ -180,9 +215,10 @@ class Filters:
|
|||||||
a command arrives, the command itself and its arguments will be stored in the *command*
|
a command arrives, the command itself and its arguments will be stored in the *command*
|
||||||
field of the :class:`Message <pyrogram.Message>`.
|
field of the :class:`Message <pyrogram.Message>`.
|
||||||
|
|
||||||
prefix (``str``, *optional*):
|
prefix (``str`` | ``list``, *optional*):
|
||||||
The command prefix. Defaults to "/" (slash).
|
A prefix or a list of prefixes as string the filter should look for.
|
||||||
Examples: /start, .help, !settings.
|
Defaults to "/" (slash). Examples: ".", "!", ["/", "!", "."].
|
||||||
|
Can be None or "" (empty string) to allow commands with no prefix at all.
|
||||||
|
|
||||||
separator (``str``, *optional*):
|
separator (``str``, *optional*):
|
||||||
The command arguments separator. Defaults to " " (white space).
|
The command arguments separator. Defaults to " " (white space).
|
||||||
@ -194,11 +230,14 @@ class Filters:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def f(_, m):
|
def f(_, m):
|
||||||
if m.text and m.text.startswith(_.p):
|
if m.text:
|
||||||
|
for i in _.p:
|
||||||
|
if m.text.startswith(i):
|
||||||
t = m.text.split(_.s)
|
t = m.text.split(_.s)
|
||||||
c, a = t[0][len(_.p):], t[1:]
|
c, a = t[0][len(i):], t[1:]
|
||||||
c = c if _.cs else c.lower()
|
c = c if _.cs else c.lower()
|
||||||
m.command = ([c] + a) if c in _.c else None
|
m.command = ([c] + a) if c in _.c else None
|
||||||
|
break
|
||||||
|
|
||||||
return bool(m.command)
|
return bool(m.command)
|
||||||
|
|
||||||
@ -211,7 +250,7 @@ class Filters:
|
|||||||
else {c if case_sensitive
|
else {c if case_sensitive
|
||||||
else c.lower()
|
else c.lower()
|
||||||
for c in command},
|
for c in command},
|
||||||
p=prefix,
|
p=set(prefix) if prefix else {""},
|
||||||
s=separator,
|
s=separator,
|
||||||
cs=case_sensitive
|
cs=case_sensitive
|
||||||
)
|
)
|
||||||
@ -236,80 +275,69 @@ class Filters:
|
|||||||
|
|
||||||
return create("Regex", f, p=re.compile(pattern, flags))
|
return create("Regex", f, p=re.compile(pattern, flags))
|
||||||
|
|
||||||
@staticmethod
|
# noinspection PyPep8Naming
|
||||||
def user(user: int or str or list):
|
class user(Filter, set):
|
||||||
"""Filter messages coming from specific users.
|
"""Filter messages coming from one or more users.
|
||||||
|
|
||||||
|
You can use `set bound methods <https://docs.python.org/3/library/stdtypes.html#set>`_ to manipulate the
|
||||||
|
users container.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
user (``int`` | ``str`` | ``list``):
|
users (``int`` | ``str`` | ``list``):
|
||||||
The user or list of user IDs (int) or usernames (str) the filter should look for.
|
Pass one or more user ids/usernames to filter users.
|
||||||
|
For you yourself, "me" or "self" can be used as well.
|
||||||
|
Defaults to None (no users).
|
||||||
"""
|
"""
|
||||||
return create(
|
|
||||||
"User",
|
def __init__(self, users: int or str or list = None):
|
||||||
lambda _, m: bool(m.from_user
|
users = [] if users is None else users if type(users) is list else [users]
|
||||||
and (m.from_user.id in _.u
|
super().__init__(
|
||||||
or (m.from_user.username
|
{"me" if i in ["me", "self"] else i.lower().strip("@") if type(i) is str else i for i in users}
|
||||||
and m.from_user.username.lower() in _.u))),
|
if type(users) is list else
|
||||||
u=(
|
{"me" if users in ["me", "self"] else users.lower().strip("@") if type(users) is str else users}
|
||||||
{user.lower().strip("@") if type(user) is str else user}
|
|
||||||
if not isinstance(user, list)
|
|
||||||
else {i.lower().strip("@") if type(i) is str else i for i in user}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
def __call__(self, message):
|
||||||
def chat(chat: int or str or list):
|
return bool(
|
||||||
"""Filter messages coming from specific chats.
|
message.from_user
|
||||||
|
and (message.from_user.id in self
|
||||||
|
or (message.from_user.username
|
||||||
|
and message.from_user.username.lower() in self)
|
||||||
|
or ("me" in self
|
||||||
|
and message.from_user.is_self))
|
||||||
|
)
|
||||||
|
|
||||||
|
# noinspection PyPep8Naming
|
||||||
|
class chat(Filter, set):
|
||||||
|
"""Filter messages coming from one or more chats.
|
||||||
|
|
||||||
|
You can use `set bound methods <https://docs.python.org/3/library/stdtypes.html#set>`_ to manipulate the
|
||||||
|
chats container.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chat (``int`` | ``str`` | ``list``):
|
chats (``int`` | ``str`` | ``list``):
|
||||||
The chat or list of chat IDs (int) or usernames (str) the filter should look for.
|
Pass one or more chat ids/usernames to filter chats.
|
||||||
|
For your personal cloud (Saved Messages) you can simply use "me" or "self".
|
||||||
|
Defaults to None (no chats).
|
||||||
"""
|
"""
|
||||||
return create(
|
|
||||||
"Chat",
|
def __init__(self, chats: int or str or list = None):
|
||||||
lambda _, m: bool(m.chat
|
chats = [] if chats is None else chats if type(chats) is list else [chats]
|
||||||
and (m.chat.id in _.c
|
super().__init__(
|
||||||
or (m.chat.username
|
{"me" if i in ["me", "self"] else i.lower().strip("@") if type(i) is str else i for i in chats}
|
||||||
and m.chat.username.lower() in _.c))),
|
if type(chats) is list else
|
||||||
c=(
|
{"me" if chats in ["me", "self"] else chats.lower().strip("@") if type(chats) is str else chats}
|
||||||
{chat.lower().strip("@") if type(chat) is str else chat}
|
|
||||||
if not isinstance(chat, list)
|
|
||||||
else {i.lower().strip("@") if type(i) is str else i for i in chat}
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
service = create(
|
def __call__(self, message):
|
||||||
"Service",
|
return bool(
|
||||||
lambda _, m: bool(
|
message.chat
|
||||||
Filters.new_chat_members(m)
|
and (message.chat.id in self
|
||||||
or Filters.left_chat_member(m)
|
or (message.chat.username
|
||||||
or Filters.new_chat_title(m)
|
and message.chat.username.lower() in self)
|
||||||
or Filters.new_chat_photo(m)
|
or ("me" in self and message.from_user
|
||||||
or Filters.delete_chat_photo(m)
|
and message.from_user.is_self
|
||||||
or Filters.group_chat_created(m)
|
and not message.outgoing))
|
||||||
or Filters.supergroup_chat_created(m)
|
|
||||||
or Filters.channel_chat_created(m)
|
|
||||||
or Filters.migrate_to_chat_id(m)
|
|
||||||
or Filters.migrate_from_chat_id(m)
|
|
||||||
or Filters.pinned_message(m)
|
|
||||||
)
|
)
|
||||||
)
|
|
||||||
"""Filter all service messages."""
|
|
||||||
|
|
||||||
media = create(
|
dan = create("Dan", lambda _, m: bool(m.from_user and m.from_user.id == 23122162))
|
||||||
"Media",
|
|
||||||
lambda _, m: bool(
|
|
||||||
Filters.audio(m)
|
|
||||||
or Filters.document(m)
|
|
||||||
or Filters.photo(m)
|
|
||||||
or Filters.sticker(m)
|
|
||||||
or Filters.video(m)
|
|
||||||
or Filters.animation(m)
|
|
||||||
or Filters.voice(m)
|
|
||||||
or Filters.video_note(m)
|
|
||||||
or Filters.contact(m)
|
|
||||||
or Filters.location(m)
|
|
||||||
or Filters.venue(m)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
"""Filter all media messages."""
|
|
||||||
|
@ -17,7 +17,8 @@
|
|||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from .callback_query_handler import CallbackQueryHandler
|
from .callback_query_handler import CallbackQueryHandler
|
||||||
|
from .deleted_messages_handler import DeletedMessagesHandler
|
||||||
from .disconnect_handler import DisconnectHandler
|
from .disconnect_handler import DisconnectHandler
|
||||||
from .message_handler import MessageHandler
|
from .message_handler import MessageHandler
|
||||||
from .deleted_messages_handler import DeletedMessagesHandler
|
|
||||||
from .raw_update_handler import RawUpdateHandler
|
from .raw_update_handler import RawUpdateHandler
|
||||||
|
from .user_status_handler import UserStatusHandler
|
||||||
|
@ -49,6 +49,6 @@ class CallbackQueryHandler(Handler):
|
|||||||
def check(self, callback_query):
|
def check(self, callback_query):
|
||||||
return (
|
return (
|
||||||
self.filters(callback_query)
|
self.filters(callback_query)
|
||||||
if self.filters
|
if callable(self.filters)
|
||||||
else True
|
else True
|
||||||
)
|
)
|
||||||
|
@ -50,6 +50,6 @@ class DeletedMessagesHandler(Handler):
|
|||||||
def check(self, messages):
|
def check(self, messages):
|
||||||
return (
|
return (
|
||||||
self.filters(messages.messages[0])
|
self.filters(messages.messages[0])
|
||||||
if self.filters
|
if callable(self.filters)
|
||||||
else True
|
else True
|
||||||
)
|
)
|
||||||
|
@ -50,6 +50,6 @@ class MessageHandler(Handler):
|
|||||||
def check(self, message):
|
def check(self, message):
|
||||||
return (
|
return (
|
||||||
self.filters(message)
|
self.filters(message)
|
||||||
if self.filters
|
if callable(self.filters)
|
||||||
else True
|
else True
|
||||||
)
|
)
|
||||||
|
54
pyrogram/client/handlers/user_status_handler.py
Normal file
54
pyrogram/client/handlers/user_status_handler.py
Normal 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
|
||||||
|
)
|
@ -50,6 +50,12 @@ class AnswerCallbackQuery(BaseClient):
|
|||||||
cache_time (``int``):
|
cache_time (``int``):
|
||||||
The maximum amount of time in seconds that the result of the callback query may be cached client-side.
|
The maximum amount of time in seconds that the result of the callback query may be cached client-side.
|
||||||
Telegram apps will support caching starting in version 3.14. Defaults to 0.
|
Telegram apps will support caching starting in version 3.14. Defaults to 0.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True, on success.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
return self.send(
|
return self.send(
|
||||||
functions.messages.SetBotCallbackAnswer(
|
functions.messages.SetBotCallbackAnswer(
|
||||||
|
@ -54,8 +54,8 @@ class GetInlineBotResults(BaseClient):
|
|||||||
On Success, :obj:`BotResults <pyrogram.api.types.messages.BotResults>` is returned.
|
On Success, :obj:`BotResults <pyrogram.api.types.messages.BotResults>` is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
``TimeoutError``: If the bot fails to answer within 10 seconds
|
``TimeoutError`` if the bot fails to answer within 10 seconds
|
||||||
"""
|
"""
|
||||||
# TODO: Don't return the raw type
|
# TODO: Don't return the raw type
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ class RequestCallbackAnswer(BaseClient):
|
|||||||
def request_callback_answer(self,
|
def request_callback_answer(self,
|
||||||
chat_id: int or str,
|
chat_id: int or str,
|
||||||
message_id: int,
|
message_id: int,
|
||||||
callback_data: str):
|
callback_data: bytes):
|
||||||
"""Use this method to request a callback answer from bots. This is the equivalent of clicking an
|
"""Use this method to request a callback answer from bots. This is the equivalent of clicking an
|
||||||
inline button containing callback data.
|
inline button containing callback data.
|
||||||
|
|
||||||
@ -37,7 +37,7 @@ class RequestCallbackAnswer(BaseClient):
|
|||||||
message_id (``int``):
|
message_id (``int``):
|
||||||
The message id the inline keyboard is attached on.
|
The message id the inline keyboard is attached on.
|
||||||
|
|
||||||
callback_data (``str``):
|
callback_data (``bytes``):
|
||||||
Callback data associated with the inline button you want to get the answer from.
|
Callback data associated with the inline button you want to get the answer from.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@ -45,14 +45,14 @@ class RequestCallbackAnswer(BaseClient):
|
|||||||
or as an alert.
|
or as an alert.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
``TimeoutError``: If the bot fails to answer within 10 seconds
|
``TimeoutError`` if the bot fails to answer within 10 seconds.
|
||||||
"""
|
"""
|
||||||
return self.send(
|
return self.send(
|
||||||
functions.messages.GetBotCallbackAnswer(
|
functions.messages.GetBotCallbackAnswer(
|
||||||
peer=self.resolve_peer(chat_id),
|
peer=self.resolve_peer(chat_id),
|
||||||
msg_id=message_id,
|
msg_id=message_id,
|
||||||
data=callback_data.encode()
|
data=callback_data
|
||||||
),
|
),
|
||||||
retries=0,
|
retries=0,
|
||||||
timeout=10
|
timeout=10
|
||||||
|
@ -53,7 +53,7 @@ class SendInlineBotResult(BaseClient):
|
|||||||
On success, the sent Message is returned.
|
On success, the sent Message is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
return self.send(
|
return self.send(
|
||||||
functions.messages.SendInlineBotResult(
|
functions.messages.SendInlineBotResult(
|
||||||
|
@ -21,6 +21,7 @@ from .export_chat_invite_link import ExportChatInviteLink
|
|||||||
from .get_chat import GetChat
|
from .get_chat import GetChat
|
||||||
from .get_chat_member import GetChatMember
|
from .get_chat_member import GetChatMember
|
||||||
from .get_chat_members import GetChatMembers
|
from .get_chat_members import GetChatMembers
|
||||||
|
from .get_chat_members_count import GetChatMembersCount
|
||||||
from .get_dialogs import GetDialogs
|
from .get_dialogs import GetDialogs
|
||||||
from .join_chat import JoinChat
|
from .join_chat import JoinChat
|
||||||
from .kick_chat_member import KickChatMember
|
from .kick_chat_member import KickChatMember
|
||||||
@ -52,6 +53,7 @@ class Chats(
|
|||||||
SetChatDescription,
|
SetChatDescription,
|
||||||
PinChatMessage,
|
PinChatMessage,
|
||||||
UnpinChatMessage,
|
UnpinChatMessage,
|
||||||
GetDialogs
|
GetDialogs,
|
||||||
|
GetChatMembersCount
|
||||||
):
|
):
|
||||||
pass
|
pass
|
||||||
|
@ -38,8 +38,8 @@ class DeleteChatPhoto(BaseClient):
|
|||||||
True on success.
|
True on success.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
``ValueError``: If a chat_id belongs to user.
|
``ValueError`` if a chat_id belongs to user.
|
||||||
"""
|
"""
|
||||||
peer = self.resolve_peer(chat_id)
|
peer = self.resolve_peer(chat_id)
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ class ExportChatInviteLink(BaseClient):
|
|||||||
On success, the exported invite link as string is returned.
|
On success, the exported invite link as string is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
peer = self.resolve_peer(chat_id)
|
peer = self.resolve_peer(chat_id)
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ class GetChat(BaseClient):
|
|||||||
On success, a :obj:`Chat <pyrogram.Chat>` object is returned.
|
On success, a :obj:`Chat <pyrogram.Chat>` object is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
peer = self.resolve_peer(chat_id)
|
peer = self.resolve_peer(chat_id)
|
||||||
|
|
||||||
|
@ -39,7 +39,7 @@ class GetChatMember(BaseClient):
|
|||||||
On success, a :obj:`ChatMember <pyrogram.ChatMember>` object is returned.
|
On success, a :obj:`ChatMember <pyrogram.ChatMember>` object is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
chat_id = self.resolve_peer(chat_id)
|
chat_id = self.resolve_peer(chat_id)
|
||||||
user_id = self.resolve_peer(user_id)
|
user_id = self.resolve_peer(user_id)
|
||||||
|
@ -39,7 +39,7 @@ class GetChatMembers(BaseClient):
|
|||||||
"""Use this method to get the members list of a chat.
|
"""Use this method to get the members list of a chat.
|
||||||
|
|
||||||
A chat can be either a basic group, a supergroup or a channel.
|
A chat can be either a basic group, a supergroup or a channel.
|
||||||
You must be admin to retrieve the members (also known as "subscribers") list of a channel.
|
You must be admin to retrieve the members list of a channel (also known as "subscribers").
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
chat_id (``int`` | ``str``):
|
chat_id (``int`` | ``str``):
|
||||||
@ -51,7 +51,7 @@ class GetChatMembers(BaseClient):
|
|||||||
|
|
||||||
limit (``int``, *optional*):
|
limit (``int``, *optional*):
|
||||||
Limits the number of members to be retrieved.
|
Limits the number of members to be retrieved.
|
||||||
Defaults to 200, which is also the maximum limit allowed per method call.
|
Defaults to 200, which is also the maximum server limit allowed per method call.
|
||||||
|
|
||||||
query (``str``, *optional*):
|
query (``str``, *optional*):
|
||||||
Query string to filter members based on their display names and usernames.
|
Query string to filter members based on their display names and usernames.
|
||||||
@ -68,9 +68,17 @@ class GetChatMembers(BaseClient):
|
|||||||
*"administrators"* - chat administrators only.
|
*"administrators"* - chat administrators only.
|
||||||
Defaults to *"all"*.
|
Defaults to *"all"*.
|
||||||
|
|
||||||
.. [1] On supergroups and channels you can get up to 10,000 members for a single query string.
|
.. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members
|
||||||
|
on channels.
|
||||||
|
|
||||||
.. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only.
|
.. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
On success, a :obj:`ChatMembers` object is returned.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
|
``ValueError`` if you used an invalid filter or a chat_id that belongs to a user.
|
||||||
"""
|
"""
|
||||||
peer = self.resolve_peer(chat_id)
|
peer = self.resolve_peer(chat_id)
|
||||||
|
|
||||||
|
53
pyrogram/client/methods/chats/get_chat_members_count.py
Normal file
53
pyrogram/client/methods/chats/get_chat_members_count.py
Normal 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))
|
@ -47,7 +47,7 @@ class GetDialogs(BaseClient):
|
|||||||
On success, a :obj:`Dialogs` object is returned.
|
On success, a :obj:`Dialogs` object is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if pinned_only:
|
if pinned_only:
|
||||||
|
@ -30,7 +30,7 @@ class JoinChat(BaseClient):
|
|||||||
channel/supergroup (in the format @username).
|
channel/supergroup (in the format @username).
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
match = self.INVITE_LINK_RE.match(chat_id)
|
match = self.INVITE_LINK_RE.match(chat_id)
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from pyrogram.api import functions, types
|
from pyrogram.api import functions, types
|
||||||
|
from pyrogram.client.ext import utils
|
||||||
from ...ext import BaseClient
|
from ...ext import BaseClient
|
||||||
|
|
||||||
|
|
||||||
@ -52,13 +53,13 @@ class KickChatMember(BaseClient):
|
|||||||
True on success.
|
True on success.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
chat_peer = self.resolve_peer(chat_id)
|
chat_peer = self.resolve_peer(chat_id)
|
||||||
user_peer = self.resolve_peer(user_id)
|
user_peer = self.resolve_peer(user_id)
|
||||||
|
|
||||||
if isinstance(chat_peer, types.InputPeerChannel):
|
if isinstance(chat_peer, types.InputPeerChannel):
|
||||||
self.send(
|
r = self.send(
|
||||||
functions.channels.EditBanned(
|
functions.channels.EditBanned(
|
||||||
channel=chat_peer,
|
channel=chat_peer,
|
||||||
user_id=user_peer,
|
user_id=user_peer,
|
||||||
@ -76,11 +77,17 @@ class KickChatMember(BaseClient):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.send(
|
r = self.send(
|
||||||
functions.messages.DeleteChatUser(
|
functions.messages.DeleteChatUser(
|
||||||
chat_id=abs(chat_id),
|
chat_id=abs(chat_id),
|
||||||
user_id=user_peer
|
user_id=user_peer
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
return True
|
for i in r.updates:
|
||||||
|
if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)):
|
||||||
|
return utils.parse_messages(
|
||||||
|
self, i.message,
|
||||||
|
{i.id: i for i in r.users},
|
||||||
|
{i.id: i for i in r.chats}
|
||||||
|
)
|
||||||
|
@ -33,7 +33,7 @@ class LeaveChat(BaseClient):
|
|||||||
Deletes the group chat dialog after leaving (for simple group chats, not supergroups).
|
Deletes the group chat dialog after leaving (for simple group chats, not supergroups).
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
peer = self.resolve_peer(chat_id)
|
peer = self.resolve_peer(chat_id)
|
||||||
|
|
||||||
|
@ -41,8 +41,8 @@ class PinChatMessage(BaseClient):
|
|||||||
True on success.
|
True on success.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
``ValueError``: If a chat_id doesn't belong to a supergroup or a channel.
|
``ValueError`` if a chat_id doesn't belong to a supergroup or a channel.
|
||||||
"""
|
"""
|
||||||
peer = self.resolve_peer(chat_id)
|
peer = self.resolve_peer(chat_id)
|
||||||
|
|
||||||
|
@ -25,12 +25,12 @@ class PromoteChatMember(BaseClient):
|
|||||||
chat_id: int or str,
|
chat_id: int or str,
|
||||||
user_id: int or str,
|
user_id: int or str,
|
||||||
can_change_info: bool = True,
|
can_change_info: bool = True,
|
||||||
can_post_messages: bool = True,
|
can_post_messages: bool = False,
|
||||||
can_edit_messages: bool = True,
|
can_edit_messages: bool = False,
|
||||||
can_delete_messages: bool = True,
|
can_delete_messages: bool = True,
|
||||||
can_invite_users: bool = True,
|
can_invite_users: bool = True,
|
||||||
can_restrict_members: bool = True,
|
can_restrict_members: bool = True,
|
||||||
can_pin_messages: bool = True,
|
can_pin_messages: bool = False,
|
||||||
can_promote_members: bool = False):
|
can_promote_members: bool = False):
|
||||||
"""Use this method to promote or demote a user in a supergroup or a channel.
|
"""Use this method to promote or demote a user in a supergroup or a channel.
|
||||||
You must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
You must be an administrator in the chat for this to work and must have the appropriate admin rights.
|
||||||
@ -74,7 +74,7 @@ class PromoteChatMember(BaseClient):
|
|||||||
True on success.
|
True on success.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
self.send(
|
self.send(
|
||||||
functions.channels.EditAdmin(
|
functions.channels.EditAdmin(
|
||||||
|
@ -64,7 +64,7 @@ class RestrictChatMember(BaseClient):
|
|||||||
True on success.
|
True on success.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
send_messages = True
|
send_messages = True
|
||||||
send_media = True
|
send_media = True
|
||||||
|
@ -36,8 +36,8 @@ class SetChatDescription(BaseClient):
|
|||||||
True on success.
|
True on success.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
``ValueError``: If a chat_id doesn't belong to a supergroup or a channel.
|
``ValueError`` if a chat_id doesn't belong to a supergroup or a channel.
|
||||||
"""
|
"""
|
||||||
peer = self.resolve_peer(chat_id)
|
peer = self.resolve_peer(chat_id)
|
||||||
|
|
||||||
|
@ -45,8 +45,8 @@ class SetChatPhoto(BaseClient):
|
|||||||
True on success.
|
True on success.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
``ValueError``: If a chat_id belongs to user.
|
``ValueError`` if a chat_id belongs to user.
|
||||||
"""
|
"""
|
||||||
peer = self.resolve_peer(chat_id)
|
peer = self.resolve_peer(chat_id)
|
||||||
|
|
||||||
|
@ -41,8 +41,8 @@ class SetChatTitle(BaseClient):
|
|||||||
True on success.
|
True on success.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
``ValueError``: If a chat_id belongs to user.
|
``ValueError`` if a chat_id belongs to user.
|
||||||
"""
|
"""
|
||||||
peer = self.resolve_peer(chat_id)
|
peer = self.resolve_peer(chat_id)
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ class UnbanChatMember(BaseClient):
|
|||||||
True on success.
|
True on success.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
self.send(
|
self.send(
|
||||||
functions.channels.EditBanned(
|
functions.channels.EditBanned(
|
||||||
|
@ -34,8 +34,8 @@ class UnpinChatMessage(BaseClient):
|
|||||||
True on success.
|
True on success.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
``ValueError``: If a chat_id doesn't belong to a supergroup or a channel.
|
``ValueError`` if a chat_id doesn't belong to a supergroup or a channel.
|
||||||
"""
|
"""
|
||||||
peer = self.resolve_peer(chat_id)
|
peer = self.resolve_peer(chat_id)
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ class AddContacts(BaseClient):
|
|||||||
On success, the added contacts are returned.
|
On success, the added contacts are returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
imported_contacts = self.send(
|
imported_contacts = self.send(
|
||||||
functions.contacts.ImportContacts(
|
functions.contacts.ImportContacts(
|
||||||
|
@ -34,7 +34,7 @@ class DeleteContacts(BaseClient):
|
|||||||
True on success.
|
True on success.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
contacts = []
|
contacts = []
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class GetContacts(BaseClient):
|
|||||||
On success, the user's contacts are returned
|
On success, the user's contacts are returned
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
|
@ -17,11 +17,19 @@
|
|||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from .on_callback_query import OnCallbackQuery
|
from .on_callback_query import OnCallbackQuery
|
||||||
|
from .on_deleted_messages import OnDeletedMessages
|
||||||
from .on_disconnect import OnDisconnect
|
from .on_disconnect import OnDisconnect
|
||||||
from .on_message import OnMessage
|
from .on_message import OnMessage
|
||||||
from .on_deleted_messages import OnDeletedMessages
|
|
||||||
from .on_raw_update import OnRawUpdate
|
from .on_raw_update import OnRawUpdate
|
||||||
|
from .on_user_status import OnUserStatus
|
||||||
|
|
||||||
|
|
||||||
class Decorators(OnMessage, OnDeletedMessages, OnCallbackQuery, OnRawUpdate, OnDisconnect):
|
class Decorators(
|
||||||
|
OnMessage,
|
||||||
|
OnDeletedMessages,
|
||||||
|
OnCallbackQuery,
|
||||||
|
OnRawUpdate,
|
||||||
|
OnDisconnect,
|
||||||
|
OnUserStatus
|
||||||
|
):
|
||||||
pass
|
pass
|
||||||
|
@ -17,11 +17,12 @@
|
|||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import pyrogram
|
import pyrogram
|
||||||
|
from pyrogram.client.filters.filter import Filter
|
||||||
from ...ext import BaseClient
|
from ...ext import BaseClient
|
||||||
|
|
||||||
|
|
||||||
class OnCallbackQuery(BaseClient):
|
class OnCallbackQuery(BaseClient):
|
||||||
def on_callback_query(self, filters=None, group: int = 0):
|
def on_callback_query(self=None, filters=None, group: int = 0):
|
||||||
"""Use this decorator to automatically register a function for handling
|
"""Use this decorator to automatically register a function for handling
|
||||||
callback queries. This does the same thing as :meth:`add_handler` using the
|
callback queries. This does the same thing as :meth:`add_handler` using the
|
||||||
:class:`CallbackQueryHandler`.
|
:class:`CallbackQueryHandler`.
|
||||||
@ -36,7 +37,17 @@ class OnCallbackQuery(BaseClient):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
self.add_handler(pyrogram.CallbackQueryHandler(func, filters), group)
|
if isinstance(func, tuple):
|
||||||
return func
|
func = func[0].callback
|
||||||
|
|
||||||
|
handler = pyrogram.CallbackQueryHandler(func, filters)
|
||||||
|
|
||||||
|
if isinstance(self, Filter):
|
||||||
|
return pyrogram.CallbackQueryHandler(func, self), group if filters is None else filters
|
||||||
|
|
||||||
|
if self is not None:
|
||||||
|
self.add_handler(handler, group)
|
||||||
|
|
||||||
|
return handler, group
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -17,11 +17,12 @@
|
|||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import pyrogram
|
import pyrogram
|
||||||
|
from pyrogram.client.filters.filter import Filter
|
||||||
from ...ext import BaseClient
|
from ...ext import BaseClient
|
||||||
|
|
||||||
|
|
||||||
class OnDeletedMessages(BaseClient):
|
class OnDeletedMessages(BaseClient):
|
||||||
def on_deleted_messages(self, filters=None, group: int = 0):
|
def on_deleted_messages(self=None, filters=None, group: int = 0):
|
||||||
"""Use this decorator to automatically register a function for handling
|
"""Use this decorator to automatically register a function for handling
|
||||||
deleted messages. This does the same thing as :meth:`add_handler` using the
|
deleted messages. This does the same thing as :meth:`add_handler` using the
|
||||||
:class:`DeletedMessagesHandler`.
|
:class:`DeletedMessagesHandler`.
|
||||||
@ -36,7 +37,17 @@ class OnDeletedMessages(BaseClient):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
self.add_handler(pyrogram.DeletedMessagesHandler(func, filters), group)
|
if isinstance(func, tuple):
|
||||||
return func
|
func = func[0].callback
|
||||||
|
|
||||||
|
handler = pyrogram.DeletedMessagesHandler(func, filters)
|
||||||
|
|
||||||
|
if isinstance(self, Filter):
|
||||||
|
return pyrogram.DeletedMessagesHandler(func, self), group if filters is None else filters
|
||||||
|
|
||||||
|
if self is not None:
|
||||||
|
self.add_handler(handler, group)
|
||||||
|
|
||||||
|
return handler, group
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -21,14 +21,18 @@ from ...ext import BaseClient
|
|||||||
|
|
||||||
|
|
||||||
class OnDisconnect(BaseClient):
|
class OnDisconnect(BaseClient):
|
||||||
def on_disconnect(self):
|
def on_disconnect(self=None):
|
||||||
"""Use this decorator to automatically register a function for handling
|
"""Use this decorator to automatically register a function for handling
|
||||||
disconnections. This does the same thing as :meth:`add_handler` using the
|
disconnections. This does the same thing as :meth:`add_handler` using the
|
||||||
:class:`DisconnectHandler`.
|
:class:`DisconnectHandler`.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
self.add_handler(pyrogram.DisconnectHandler(func))
|
handler = pyrogram.DisconnectHandler(func)
|
||||||
return func
|
|
||||||
|
if self is not None:
|
||||||
|
self.add_handler(handler)
|
||||||
|
|
||||||
|
return handler
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -17,11 +17,12 @@
|
|||||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import pyrogram
|
import pyrogram
|
||||||
|
from pyrogram.client.filters.filter import Filter
|
||||||
from ...ext import BaseClient
|
from ...ext import BaseClient
|
||||||
|
|
||||||
|
|
||||||
class OnMessage(BaseClient):
|
class OnMessage(BaseClient):
|
||||||
def on_message(self, filters=None, group: int = 0):
|
def on_message(self=None, filters=None, group: int = 0):
|
||||||
"""Use this decorator to automatically register a function for handling
|
"""Use this decorator to automatically register a function for handling
|
||||||
messages. This does the same thing as :meth:`add_handler` using the
|
messages. This does the same thing as :meth:`add_handler` using the
|
||||||
:class:`MessageHandler`.
|
:class:`MessageHandler`.
|
||||||
@ -36,7 +37,17 @@ class OnMessage(BaseClient):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
self.add_handler(pyrogram.MessageHandler(func, filters), group)
|
if isinstance(func, tuple):
|
||||||
return func
|
func = func[0].callback
|
||||||
|
|
||||||
|
handler = pyrogram.MessageHandler(func, filters)
|
||||||
|
|
||||||
|
if isinstance(self, Filter):
|
||||||
|
return pyrogram.MessageHandler(func, self), group if filters is None else filters
|
||||||
|
|
||||||
|
if self is not None:
|
||||||
|
self.add_handler(handler, group)
|
||||||
|
|
||||||
|
return handler, group
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -21,7 +21,7 @@ from ...ext import BaseClient
|
|||||||
|
|
||||||
|
|
||||||
class OnRawUpdate(BaseClient):
|
class OnRawUpdate(BaseClient):
|
||||||
def on_raw_update(self, group: int = 0):
|
def on_raw_update(self=None, group: int = 0):
|
||||||
"""Use this decorator to automatically register a function for handling
|
"""Use this decorator to automatically register a function for handling
|
||||||
raw updates. This does the same thing as :meth:`add_handler` using the
|
raw updates. This does the same thing as :meth:`add_handler` using the
|
||||||
:class:`RawUpdateHandler`.
|
:class:`RawUpdateHandler`.
|
||||||
@ -32,7 +32,17 @@ class OnRawUpdate(BaseClient):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def decorator(func):
|
def decorator(func):
|
||||||
self.add_handler(pyrogram.RawUpdateHandler(func), group)
|
if isinstance(func, tuple):
|
||||||
return func
|
func = func[0].callback
|
||||||
|
|
||||||
|
handler = pyrogram.RawUpdateHandler(func)
|
||||||
|
|
||||||
|
if isinstance(self, int):
|
||||||
|
return handler, group if self is None else group
|
||||||
|
|
||||||
|
if self is not None:
|
||||||
|
self.add_handler(handler, group)
|
||||||
|
|
||||||
|
return handler, group
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
52
pyrogram/client/methods/decorators/on_user_status.py
Normal file
52
pyrogram/client/methods/decorators/on_user_status.py
Normal 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
|
@ -53,7 +53,7 @@ class DeleteMessages(BaseClient):
|
|||||||
True on success.
|
True on success.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
peer = self.resolve_peer(chat_id)
|
peer = self.resolve_peer(chat_id)
|
||||||
message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids]
|
message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids]
|
||||||
|
@ -53,7 +53,7 @@ class EditMessageCaption(BaseClient):
|
|||||||
On success, the edited :obj:`Message <pyrogram.Message>` is returned.
|
On success, the edited :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ from pyrogram.api.errors import FileIdInvalid
|
|||||||
from pyrogram.client.ext import BaseClient, utils
|
from pyrogram.client.ext import BaseClient, utils
|
||||||
from pyrogram.client.types import (
|
from pyrogram.client.types import (
|
||||||
InputMediaPhoto, InputMediaVideo, InputMediaAudio,
|
InputMediaPhoto, InputMediaVideo, InputMediaAudio,
|
||||||
InputMediaAnimation
|
InputMediaAnimation, InputMediaDocument
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -36,6 +36,34 @@ class EditMessageMedia(BaseClient):
|
|||||||
message_id: int,
|
message_id: int,
|
||||||
media,
|
media,
|
||||||
reply_markup=None):
|
reply_markup=None):
|
||||||
|
"""Use this method to edit audio, document, photo, or video messages.
|
||||||
|
|
||||||
|
If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise,
|
||||||
|
message type can be changed arbitrarily. When inline message is edited, new file can't be uploaded.
|
||||||
|
Use previously uploaded file via its file_id or specify a URL. On success, if the edited message was sent
|
||||||
|
by the bot, the edited Message is returned, otherwise True is returned.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id (``int`` | ``str``):
|
||||||
|
Unique identifier (int) or username (str) of the target chat.
|
||||||
|
For your personal cloud (Saved Messages) you can simply use "me" or "self".
|
||||||
|
For a contact that exists in your Telegram address book you can use his phone number (str).
|
||||||
|
|
||||||
|
message_id (``int``):
|
||||||
|
Message identifier in the chat specified in chat_id.
|
||||||
|
|
||||||
|
media (:obj:`InputMediaAnimation` | :obj:`InputMediaAudio` | :obj:`InputMediaDocument` | :obj:`InputMediaPhoto` | :obj:`InputMediaVideo`)
|
||||||
|
One of the InputMedia objects describing an animation, audio, document, photo or video.
|
||||||
|
|
||||||
|
reply_markup (:obj:`InlineKeyboardMarkup`, *optional*):
|
||||||
|
An InlineKeyboardMarkup object.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
On success, the edited :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
|
"""
|
||||||
style = self.html if media.parse_mode.lower() == "html" else self.markdown
|
style = self.html if media.parse_mode.lower() == "html" else self.markdown
|
||||||
caption = media.caption
|
caption = media.caption
|
||||||
|
|
||||||
@ -245,6 +273,54 @@ class EditMessageMedia(BaseClient):
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if isinstance(media, InputMediaDocument):
|
||||||
|
if os.path.exists(media.media):
|
||||||
|
media = self.send(
|
||||||
|
functions.messages.UploadMedia(
|
||||||
|
peer=self.resolve_peer(chat_id),
|
||||||
|
media=types.InputMediaUploadedDocument(
|
||||||
|
mime_type=mimetypes.types_map.get("." + media.media.split(".")[-1], "text/plain"),
|
||||||
|
file=self.save_file(media.media),
|
||||||
|
attributes=[
|
||||||
|
types.DocumentAttributeFilename(os.path.basename(media.media))
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
media = types.InputMediaDocument(
|
||||||
|
id=types.InputDocument(
|
||||||
|
id=media.document.id,
|
||||||
|
access_hash=media.document.access_hash
|
||||||
|
)
|
||||||
|
)
|
||||||
|
elif media.media.startswith("http"):
|
||||||
|
media = types.InputMediaDocumentExternal(
|
||||||
|
url=media.media
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
decoded = utils.decode(media.media)
|
||||||
|
fmt = "<iiqqqqi" if len(decoded) > 24 else "<iiqq"
|
||||||
|
unpacked = struct.unpack(fmt, decoded)
|
||||||
|
except (AssertionError, binascii.Error, struct.error):
|
||||||
|
raise FileIdInvalid from None
|
||||||
|
else:
|
||||||
|
if unpacked[0] not in (5, 10):
|
||||||
|
media_type = BaseClient.MEDIA_TYPE_ID.get(unpacked[0], None)
|
||||||
|
|
||||||
|
if media_type:
|
||||||
|
raise FileIdInvalid("The file_id belongs to a {}".format(media_type))
|
||||||
|
else:
|
||||||
|
raise FileIdInvalid("Unknown media type: {}".format(unpacked[0]))
|
||||||
|
|
||||||
|
media = types.InputMediaDocument(
|
||||||
|
id=types.InputDocument(
|
||||||
|
id=unpacked[2],
|
||||||
|
access_hash=unpacked[3]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
r = self.send(
|
r = self.send(
|
||||||
functions.messages.EditMessage(
|
functions.messages.EditMessage(
|
||||||
peer=self.resolve_peer(chat_id),
|
peer=self.resolve_peer(chat_id),
|
||||||
|
@ -44,7 +44,7 @@ class EditMessageReplyMarkup(BaseClient):
|
|||||||
:obj:`Message <pyrogram.Message>` is returned, otherwise True is returned.
|
:obj:`Message <pyrogram.Message>` is returned, otherwise True is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
r = self.send(
|
r = self.send(
|
||||||
|
@ -57,7 +57,7 @@ class EditMessageText(BaseClient):
|
|||||||
On success, the edited :obj:`Message <pyrogram.Message>` is returned.
|
On success, the edited :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ class ForwardMessages(BaseClient):
|
|||||||
is returned.
|
is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
is_iterable = not isinstance(message_ids, int)
|
is_iterable = not isinstance(message_ids, int)
|
||||||
message_ids = list(message_ids) if is_iterable else [message_ids]
|
message_ids = list(message_ids) if is_iterable else [message_ids]
|
||||||
|
@ -56,7 +56,7 @@ class GetHistory(BaseClient):
|
|||||||
On success, a :obj:`Messages <pyrogram.Messages>` object is returned.
|
On success, a :obj:`Messages <pyrogram.Messages>` object is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
r = self.send(
|
r = self.send(
|
||||||
|
@ -23,9 +23,10 @@ from ...ext import BaseClient, utils
|
|||||||
class GetMessages(BaseClient):
|
class GetMessages(BaseClient):
|
||||||
def get_messages(self,
|
def get_messages(self,
|
||||||
chat_id: int or str,
|
chat_id: int or str,
|
||||||
message_ids,
|
message_ids: int or list = None,
|
||||||
|
reply_to_message_ids: int or list = None,
|
||||||
replies: int = 1):
|
replies: int = 1):
|
||||||
"""Use this method to get messages that belong to a specific chat.
|
"""Use this method to get one or more messages that belong to a specific chat.
|
||||||
You can retrieve up to 200 messages at once.
|
You can retrieve up to 200 messages at once.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -34,36 +35,46 @@ class GetMessages(BaseClient):
|
|||||||
For your personal cloud (Saved Messages) you can simply use "me" or "self".
|
For your personal cloud (Saved Messages) you can simply use "me" or "self".
|
||||||
For a contact that exists in your Telegram address book you can use his phone number (str).
|
For a contact that exists in your Telegram address book you can use his phone number (str).
|
||||||
|
|
||||||
message_ids (``iterable``):
|
message_ids (``iterable``, *optional*):
|
||||||
A list of Message identifiers in the chat specified in *chat_id* or a single message id, as integer.
|
Pass a single message identifier or a list of message ids (as integers) to get the content of the
|
||||||
Iterators and Generators are also accepted.
|
message themselves. Iterators and Generators are also accepted.
|
||||||
|
|
||||||
|
reply_to_message_ids (``iterable``, *optional*):
|
||||||
|
Pass a single message identifier or a list of message ids (as integers) to get the content of
|
||||||
|
the previous message you replied to using this message. Iterators and Generators are also accepted.
|
||||||
|
If *message_ids* is set, this argument will be ignored.
|
||||||
|
|
||||||
replies (``int``, *optional*):
|
replies (``int``, *optional*):
|
||||||
The number of subsequent replies to get for each message. Defaults to 1.
|
The number of subsequent replies to get for each message. Defaults to 1.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
On success and in case *message_ids* was a list, the returned value will be a list of the requested
|
On success and in case *message_ids* or *reply_to_message_ids* was a list, the returned value will be a
|
||||||
:obj:`Messages <pyrogram.Messages>` even if a list contains just one element, otherwise if
|
list of the requested :obj:`Messages <pyrogram.Messages>` even if a list contains just one element,
|
||||||
*message_ids* was an integer, the single requested :obj:`Message <pyrogram.Message>`
|
otherwise if *message_ids* or *reply_to_message_ids* was an integer, the single requested
|
||||||
is returned.
|
:obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
|
ids, ids_type = (
|
||||||
|
(message_ids, types.InputMessageID) if message_ids
|
||||||
|
else (reply_to_message_ids, types.InputMessageReplyTo) if reply_to_message_ids
|
||||||
|
else (None, None)
|
||||||
|
)
|
||||||
|
|
||||||
|
if ids is None:
|
||||||
|
raise ValueError("No argument supplied")
|
||||||
|
|
||||||
peer = self.resolve_peer(chat_id)
|
peer = self.resolve_peer(chat_id)
|
||||||
is_iterable = not isinstance(message_ids, int)
|
|
||||||
message_ids = list(message_ids) if is_iterable else [message_ids]
|
is_iterable = not isinstance(ids, int)
|
||||||
message_ids = [types.InputMessageID(i) for i in message_ids]
|
ids = list(ids) if is_iterable else [ids]
|
||||||
|
ids = [ids_type(i) for i in ids]
|
||||||
|
|
||||||
if isinstance(peer, types.InputPeerChannel):
|
if isinstance(peer, types.InputPeerChannel):
|
||||||
rpc = functions.channels.GetMessages(
|
rpc = functions.channels.GetMessages(channel=peer, id=ids)
|
||||||
channel=peer,
|
|
||||||
id=message_ids
|
|
||||||
)
|
|
||||||
else:
|
else:
|
||||||
rpc = functions.messages.GetMessages(
|
rpc = functions.messages.GetMessages(id=ids)
|
||||||
id=message_ids
|
|
||||||
)
|
|
||||||
|
|
||||||
r = self.send(rpc)
|
r = self.send(rpc)
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ class SendAnimation(BaseClient):
|
|||||||
pass a file path as string to upload a new animation that exists on your local machine.
|
pass a file path as string to upload a new animation that exists on your local machine.
|
||||||
|
|
||||||
caption (``str``, *optional*):
|
caption (``str``, *optional*):
|
||||||
Animation caption, 0-200 characters.
|
Animation caption, 0-1024 characters.
|
||||||
|
|
||||||
parse_mode (``str``, *optional*):
|
parse_mode (``str``, *optional*):
|
||||||
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
||||||
@ -73,9 +73,10 @@ class SendAnimation(BaseClient):
|
|||||||
Animation height.
|
Animation height.
|
||||||
|
|
||||||
thumb (``str``, *optional*):
|
thumb (``str``, *optional*):
|
||||||
Animation thumbnail.
|
Thumbnail of the animation file sent.
|
||||||
Pass a file path as string to send an image that exists on your local machine.
|
The thumbnail should be in JPEG format and less than 200 KB in size.
|
||||||
Thumbnail should have 90 or less pixels of width and 90 or less pixels of height.
|
A thumbnail's width and height should not exceed 90 pixels.
|
||||||
|
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||||
|
|
||||||
disable_notification (``bool``, *optional*):
|
disable_notification (``bool``, *optional*):
|
||||||
Sends the message silently.
|
Sends the message silently.
|
||||||
@ -115,7 +116,7 @@ class SendAnimation(BaseClient):
|
|||||||
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
file = None
|
file = None
|
||||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||||
|
@ -35,6 +35,7 @@ class SendAudio(BaseClient):
|
|||||||
duration: int = 0,
|
duration: int = 0,
|
||||||
performer: str = None,
|
performer: str = None,
|
||||||
title: str = None,
|
title: str = None,
|
||||||
|
thumb: str = None,
|
||||||
disable_notification: bool = None,
|
disable_notification: bool = None,
|
||||||
reply_to_message_id: int = None,
|
reply_to_message_id: int = None,
|
||||||
reply_markup=None,
|
reply_markup=None,
|
||||||
@ -57,7 +58,7 @@ class SendAudio(BaseClient):
|
|||||||
pass a file path as string to upload a new audio file that exists on your local machine.
|
pass a file path as string to upload a new audio file that exists on your local machine.
|
||||||
|
|
||||||
caption (``str``, *optional*):
|
caption (``str``, *optional*):
|
||||||
Audio caption, 0-200 characters.
|
Audio caption, 0-1024 characters.
|
||||||
|
|
||||||
parse_mode (``str``, *optional*):
|
parse_mode (``str``, *optional*):
|
||||||
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
||||||
@ -73,6 +74,12 @@ class SendAudio(BaseClient):
|
|||||||
title (``str``, *optional*):
|
title (``str``, *optional*):
|
||||||
Track name.
|
Track name.
|
||||||
|
|
||||||
|
thumb (``str``, *optional*):
|
||||||
|
Thumbnail of the music file album cover.
|
||||||
|
The thumbnail should be in JPEG format and less than 200 KB in size.
|
||||||
|
A thumbnail's width and height should not exceed 90 pixels.
|
||||||
|
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||||
|
|
||||||
disable_notification (``bool``, *optional*):
|
disable_notification (``bool``, *optional*):
|
||||||
Sends the message silently.
|
Sends the message silently.
|
||||||
Users will receive a notification with no sound.
|
Users will receive a notification with no sound.
|
||||||
@ -111,16 +118,18 @@ class SendAudio(BaseClient):
|
|||||||
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
file = None
|
file = None
|
||||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||||
|
|
||||||
if os.path.exists(audio):
|
if os.path.exists(audio):
|
||||||
|
thumb = None if thumb is None else self.save_file(thumb)
|
||||||
file = self.save_file(audio, progress=progress, progress_args=progress_args)
|
file = self.save_file(audio, progress=progress, progress_args=progress_args)
|
||||||
media = types.InputMediaUploadedDocument(
|
media = types.InputMediaUploadedDocument(
|
||||||
mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"),
|
mime_type=mimetypes.types_map.get("." + audio.split(".")[-1], "audio/mpeg"),
|
||||||
file=file,
|
file=file,
|
||||||
|
thumb=thumb,
|
||||||
attributes=[
|
attributes=[
|
||||||
types.DocumentAttributeAudio(
|
types.DocumentAttributeAudio(
|
||||||
duration=duration,
|
duration=duration,
|
||||||
|
@ -47,8 +47,8 @@ class SendChatAction(BaseClient):
|
|||||||
On success, True is returned.
|
On success, True is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
``ValueError``: If the provided string is not a valid ChatAction
|
``ValueError`` if the provided string is not a valid ChatAction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Resolve Enum type
|
# Resolve Enum type
|
||||||
|
@ -65,7 +65,7 @@ class SendContact(BaseClient):
|
|||||||
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
r = self.send(
|
r = self.send(
|
||||||
functions.messages.SendMedia(
|
functions.messages.SendMedia(
|
||||||
|
@ -30,6 +30,7 @@ class SendDocument(BaseClient):
|
|||||||
def send_document(self,
|
def send_document(self,
|
||||||
chat_id: int or str,
|
chat_id: int or str,
|
||||||
document: str,
|
document: str,
|
||||||
|
thumb: str = None,
|
||||||
caption: str = "",
|
caption: str = "",
|
||||||
parse_mode: str = "",
|
parse_mode: str = "",
|
||||||
disable_notification: bool = None,
|
disable_notification: bool = None,
|
||||||
@ -51,8 +52,14 @@ class SendDocument(BaseClient):
|
|||||||
pass an HTTP URL as a string for Telegram to get a file from the Internet, or
|
pass an HTTP URL as a string for Telegram to get a file from the Internet, or
|
||||||
pass a file path as string to upload a new file that exists on your local machine.
|
pass a file path as string to upload a new file that exists on your local machine.
|
||||||
|
|
||||||
|
thumb (``str``):
|
||||||
|
Thumbnail of the file sent.
|
||||||
|
The thumbnail should be in JPEG format and less than 200 KB in size.
|
||||||
|
A thumbnail's width and height should not exceed 90 pixels.
|
||||||
|
Thumbnails can't be reused and can be only uploaded as a new file.
|
||||||
|
|
||||||
caption (``str``, *optional*):
|
caption (``str``, *optional*):
|
||||||
Document caption, 0-200 characters.
|
Document caption, 0-1024 characters.
|
||||||
|
|
||||||
parse_mode (``str``, *optional*):
|
parse_mode (``str``, *optional*):
|
||||||
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
||||||
@ -97,16 +104,18 @@ class SendDocument(BaseClient):
|
|||||||
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
file = None
|
file = None
|
||||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||||
|
|
||||||
if os.path.exists(document):
|
if os.path.exists(document):
|
||||||
|
thumb = None if thumb is None else self.save_file(thumb)
|
||||||
file = self.save_file(document, progress=progress, progress_args=progress_args)
|
file = self.save_file(document, progress=progress, progress_args=progress_args)
|
||||||
media = types.InputMediaUploadedDocument(
|
media = types.InputMediaUploadedDocument(
|
||||||
mime_type=mimetypes.types_map.get("." + document.split(".")[-1], "text/plain"),
|
mime_type=mimetypes.types_map.get("." + document.split(".")[-1], "text/plain"),
|
||||||
file=file,
|
file=file,
|
||||||
|
thumb=thumb,
|
||||||
attributes=[
|
attributes=[
|
||||||
types.DocumentAttributeFilename(os.path.basename(document))
|
types.DocumentAttributeFilename(os.path.basename(document))
|
||||||
]
|
]
|
||||||
|
@ -57,7 +57,7 @@ class SendLocation(BaseClient):
|
|||||||
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
r = self.send(
|
r = self.send(
|
||||||
functions.messages.SendMedia(
|
functions.messages.SendMedia(
|
||||||
|
@ -64,9 +64,10 @@ class SendMessage(BaseClient):
|
|||||||
On success, the sent :obj:`Message` is returned.
|
On success, the sent :obj:`Message` is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||||
|
message, entities = style.parse(text).values()
|
||||||
|
|
||||||
r = self.send(
|
r = self.send(
|
||||||
functions.messages.SendMessage(
|
functions.messages.SendMessage(
|
||||||
@ -76,16 +77,20 @@ class SendMessage(BaseClient):
|
|||||||
reply_to_msg_id=reply_to_message_id,
|
reply_to_msg_id=reply_to_message_id,
|
||||||
random_id=self.rnd_id(),
|
random_id=self.rnd_id(),
|
||||||
reply_markup=reply_markup.write() if reply_markup else None,
|
reply_markup=reply_markup.write() if reply_markup else None,
|
||||||
**style.parse(text)
|
message=message,
|
||||||
|
entities=entities
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if isinstance(r, types.UpdateShortSentMessage):
|
if isinstance(r, types.UpdateShortSentMessage):
|
||||||
return pyrogram_types.Message(
|
return pyrogram_types.Message(
|
||||||
message_id=r.id,
|
message_id=r.id,
|
||||||
|
chat=pyrogram_types.Chat(id=list(self.resolve_peer(chat_id).__dict__.values())[0], type="private"),
|
||||||
|
text=message,
|
||||||
date=r.date,
|
date=r.date,
|
||||||
outgoing=r.out,
|
outgoing=r.out,
|
||||||
entities=utils.parse_entities(r.entities, {}) or None
|
entities=entities,
|
||||||
|
client=self
|
||||||
)
|
)
|
||||||
|
|
||||||
for i in r.updates:
|
for i in r.updates:
|
||||||
|
@ -52,7 +52,7 @@ class SendPhoto(BaseClient):
|
|||||||
pass a file path as string to upload a new photo that exists on your local machine.
|
pass a file path as string to upload a new photo that exists on your local machine.
|
||||||
|
|
||||||
caption (``bool``, *optional*):
|
caption (``bool``, *optional*):
|
||||||
Photo caption, 0-200 characters.
|
Photo caption, 0-1024 characters.
|
||||||
|
|
||||||
parse_mode (``str``, *optional*):
|
parse_mode (``str``, *optional*):
|
||||||
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
|
||||||
@ -102,7 +102,7 @@ class SendPhoto(BaseClient):
|
|||||||
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
On success, the sent :obj:`Message <pyrogram.Message>` is returned.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
:class:`Error <pyrogram.Error>`
|
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
|
||||||
"""
|
"""
|
||||||
file = None
|
file = None
|
||||||
style = self.html if parse_mode.lower() == "html" else self.markdown
|
style = self.html if parse_mode.lower() == "html" else self.markdown
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user