Merge pull request #92 from pyrogram/develop

Preparing v0.7.5
This commit is contained in:
Dan 2018-06-27 16:39:25 +02:00 committed by GitHub
commit 24013d6528
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 2217 additions and 806 deletions

View File

@ -1,5 +1,5 @@
## Include ## Include
include COPYING COPYING.lesser NOTICE requirements.txt requirements_extras.txt include COPYING COPYING.lesser NOTICE requirements.txt
recursive-include compiler *.py *.tl *.tsv *.txt recursive-include compiler *.py *.tl *.tsv *.txt
## Exclude ## Exclude

View File

@ -1,7 +1,7 @@
|header| |header|
Pyrogram |twitter| Pyrogram
================== ========
.. code-block:: python .. code-block:: python
@ -12,12 +12,10 @@ Pyrogram |twitter|
@app.on_message(Filters.private) @app.on_message(Filters.private)
def hello(client, message): def hello(client, message):
client.send_message( message.reply("Hello {}".format(message.from_user.first_name))
message.chat.id, "Hello {}".format(message.from_user.first_name))
app.start() app.run()
app.idle()
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for building **Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for building
custom Telegram applications that interact with the MTProto API as both User and Bot. custom Telegram applications that interact with the MTProto API as both User and Bot.
@ -28,8 +26,8 @@ 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 76 running on MTProto 2.0. - **Updated** to the latest Telegram API version, currently Layer 81 on top of MTProto 2.0.
- **Documented**: Pyrogram API methods are documented and resemble 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.
Requirements Requirements
@ -56,7 +54,7 @@ Getting Started
Contributing Contributing
------------ ------------
Pyrogram is brand new! **You are welcome to try it and help make it better** by either submitting pull Pyrogram is brand new, and **you are welcome to try it and help make it even better** by either submitting pull
requests or reporting issues/bugs as well as suggesting best practices, ideas, enhancements on both code requests or reporting issues/bugs as well as suggesting best practices, ideas, enhancements on both code
and documentation. Any help is appreciated! and documentation. Any help is appreciated!
@ -102,28 +100,25 @@ Copyright & License
</a> </a>
<br><br> <br><br>
<a href="compiler/api/source/main_api.tl"> <a href="compiler/api/source/main_api.tl">
<img src="https://media.pyrogram.ml/images/scheme.svg" <img src="https://img.shields.io/badge/SCHEME-LAYER%2081-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
alt="Scheme Layer 76"> alt="Scheme Layer">
</a> </a>
<a href="https://github.com/pyrogram/tgcrypto"> <a href="https://github.com/pyrogram/tgcrypto">
<img src="https://media.pyrogram.ml/images/tgcrypto.svg" <img src="https://img.shields.io/badge/TGCRYPTO-V1.0.4-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
alt="TgCrypto"> alt="TgCrypto">
</a> </a>
</p> </p>
.. |twitter| image:: https://media.pyrogram.ml/images/twitter.svg
:target: https://twitter.com/intent/tweet?text=Build%20custom%20Telegram%20applications%20with%20Pyrogram&url=https://github.com/pyrogram/pyrogram&hashtags=Telegram,MTProto,Python
.. |logo| image:: https://pyrogram.ml/images/logo.png .. |logo| image:: https://pyrogram.ml/images/logo.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://www.pyrogram.ml/images/scheme.svg .. |scheme| image:: "https://img.shields.io/badge/SCHEME-LAYER%2081-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 76 :alt: Scheme Layer
.. |tgcrypto| image:: https://www.pyrogram.ml/images/tgcrypto.svg .. |tgcrypto| image:: "https://img.shields.io/badge/TGCRYPTO-V1.0.4-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
:target: https://github.com/pyrogram/tgcrypto :target: https://github.com/pyrogram/tgcrypto
:alt: TgCrypto :alt: TgCrypto

View File

@ -7,7 +7,9 @@
resPQ#05162463 nonce:int128 server_nonce:int128 pq:bytes server_public_key_fingerprints:Vector<long> = ResPQ; resPQ#05162463 nonce:int128 server_nonce:int128 pq:bytes server_public_key_fingerprints:Vector<long> = ResPQ;
p_q_inner_data#83c95aec pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data; p_q_inner_data#83c95aec pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data;
p_q_inner_data_dc#a9f55f95 pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 dc:int = P_Q_inner_data;
p_q_inner_data_temp#3c6a84d4 pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 expires_in:int = P_Q_inner_data; p_q_inner_data_temp#3c6a84d4 pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 expires_in:int = P_Q_inner_data;
p_q_inner_data_temp_dc#56fddf88 pq:bytes p:bytes q:bytes nonce:int128 server_nonce:int128 new_nonce:int256 dc:int expires_in:int = P_Q_inner_data;
bind_auth_key_inner#75a3f765 nonce:long temp_auth_key_id:long perm_auth_key_id:long temp_session_id:long expires_at:int = BindAuthKeyInner; bind_auth_key_inner#75a3f765 nonce:long temp_auth_key_id:long perm_auth_key_id:long temp_session_id:long expires_at:int = BindAuthKeyInner;

View File

@ -60,6 +60,7 @@ inputPhoto#fb95c6c4 id:long access_hash:long = InputPhoto;
inputFileLocation#14637196 volume_id:long local_id:int secret:long = InputFileLocation; inputFileLocation#14637196 volume_id:long local_id:int secret:long = InputFileLocation;
inputEncryptedFileLocation#f5235d55 id:long access_hash:long = InputFileLocation; inputEncryptedFileLocation#f5235d55 id:long access_hash:long = InputFileLocation;
inputDocumentFileLocation#430f0724 id:long access_hash:long version:int = InputFileLocation; inputDocumentFileLocation#430f0724 id:long access_hash:long version:int = InputFileLocation;
inputSecureFileLocation#cbc7ee28 id:long access_hash:long = InputFileLocation;
inputAppEvent#770656a8 time:double type:string peer:long data:string = InputAppEvent; inputAppEvent#770656a8 time:double type:string peer:long data:string = InputAppEvent;
@ -97,7 +98,7 @@ userStatusLastMonth#77ebc742 = UserStatus;
chatEmpty#9ba2d800 id:int = Chat; chatEmpty#9ba2d800 id:int = Chat;
chat#d91cdd54 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true admins_enabled:flags.3?true admin:flags.4?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel = Chat; chat#d91cdd54 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true admins_enabled:flags.3?true admin:flags.4?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel = Chat;
chatForbidden#7328bdb id:int title:string = Chat; chatForbidden#7328bdb id:int title:string = Chat;
channel#450b7115 flags:# creator:flags.0?true left:flags.2?true editor:flags.3?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true democracy:flags.10?true signatures:flags.11?true min:flags.12?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string admin_rights:flags.14?ChannelAdminRights banned_rights:flags.15?ChannelBannedRights participants_count:flags.17?int = Chat; channel#c88974ac flags:# creator:flags.0?true left:flags.2?true editor:flags.3?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true democracy:flags.10?true signatures:flags.11?true min:flags.12?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string admin_rights:flags.14?ChannelAdminRights banned_rights:flags.15?ChannelBannedRights participants_count:flags.17?int = Chat;
channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat;
chatFull#2e02a614 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> = ChatFull; chatFull#2e02a614 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> = ChatFull;
@ -149,6 +150,8 @@ messageActionPhoneCall#80e11a7f flags:# call_id:long reason:flags.0?PhoneCallDis
messageActionScreenshotTaken#4792929b = MessageAction; messageActionScreenshotTaken#4792929b = MessageAction;
messageActionCustomAction#fae69f56 message:string = MessageAction; messageActionCustomAction#fae69f56 message:string = MessageAction;
messageActionBotAllowed#abe9affe domain:string = MessageAction; messageActionBotAllowed#abe9affe domain:string = MessageAction;
messageActionSecureValuesSentMe#1b287353 values:Vector<SecureValue> credentials:SecureCredentialsEncrypted = MessageAction;
messageActionSecureValuesSent#d95c6154 types:Vector<SecureValueType> = MessageAction;
dialog#e4def5db flags:# pinned:flags.2?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage = Dialog; dialog#e4def5db flags:# pinned:flags.2?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage = Dialog;
@ -164,7 +167,7 @@ geoPoint#2049d70c long:double lat:double = GeoPoint;
auth.checkedPhone#811ea28e phone_registered:Bool = auth.CheckedPhone; auth.checkedPhone#811ea28e phone_registered:Bool = auth.CheckedPhone;
auth.sentCode#5e002502 flags:# phone_registered:flags.0?true type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode; auth.sentCode#38faab5f flags:# phone_registered:flags.0?true type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int terms_of_service:flags.3?help.TermsOfService = auth.SentCode;
auth.authorization#cd050916 flags:# tmp_sessions:flags.0?int user:User = auth.Authorization; auth.authorization#cd050916 flags:# tmp_sessions:flags.0?int user:User = auth.Authorization;
@ -173,18 +176,10 @@ auth.exportedAuthorization#df969c2d id:int bytes:bytes = auth.ExportedAuthorizat
inputNotifyPeer#b8bc5b0c peer:InputPeer = InputNotifyPeer; inputNotifyPeer#b8bc5b0c peer:InputPeer = InputNotifyPeer;
inputNotifyUsers#193b4417 = InputNotifyPeer; inputNotifyUsers#193b4417 = InputNotifyPeer;
inputNotifyChats#4a95e84e = InputNotifyPeer; inputNotifyChats#4a95e84e = InputNotifyPeer;
inputNotifyAll#a429b886 = InputNotifyPeer;
inputPeerNotifyEventsEmpty#f03064d8 = InputPeerNotifyEvents; inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = InputPeerNotifySettings;
inputPeerNotifyEventsAll#e86a2c74 = InputPeerNotifyEvents;
inputPeerNotifySettings#38935eb2 flags:# show_previews:flags.0?true silent:flags.1?true mute_until:int sound:string = InputPeerNotifySettings; peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings;
peerNotifyEventsEmpty#add53cb3 = PeerNotifyEvents;
peerNotifyEventsAll#6d1ded88 = PeerNotifyEvents;
peerNotifySettingsEmpty#70a68512 = PeerNotifySettings;
peerNotifySettings#9acda4c0 flags:# show_previews:flags.0?true silent:flags.1?true mute_until:int sound:string = PeerNotifySettings;
peerSettings#818426cd flags:# report_spam:flags.0?true = PeerSettings; peerSettings#818426cd flags:# report_spam:flags.0?true = PeerSettings;
@ -338,9 +333,9 @@ photos.photo#20212ca8 photo:Photo users:Vector<User> = photos.Photo;
upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File; upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File;
upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes encryption_iv:bytes file_hashes:Vector<FileHash> = upload.File; upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes encryption_iv:bytes file_hashes:Vector<FileHash> = upload.File;
dcOption#5d8c6cc flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int = DcOption; dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption;
config#86b5778e flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string suggested_lang_code:flags.2?string lang_pack_version:flags.2?int = Config; config#eb7bb160 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string suggested_lang_code:flags.2?string lang_pack_version:flags.2?int = Config;
nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
@ -385,7 +380,6 @@ help.support#17c6b5f6 phone_number:string user:User = help.Support;
notifyPeer#9fd40bd8 peer:Peer = NotifyPeer; notifyPeer#9fd40bd8 peer:Peer = NotifyPeer;
notifyUsers#b4c83b4c = NotifyPeer; notifyUsers#b4c83b4c = NotifyPeer;
notifyChats#c007cec3 = NotifyPeer; notifyChats#c007cec3 = NotifyPeer;
notifyAll#74d07c60 = NotifyPeer;
sendMessageTypingAction#16bf744e = SendMessageAction; sendMessageTypingAction#16bf744e = SendMessageAction;
sendMessageCancelAction#fd5ec8f5 = SendMessageAction; sendMessageCancelAction#fd5ec8f5 = SendMessageAction;
@ -438,7 +432,7 @@ documentAttributeFilename#15590068 file_name:string = DocumentAttribute;
documentAttributeHasStickers#9801d2f7 = DocumentAttribute; documentAttributeHasStickers#9801d2f7 = DocumentAttribute;
messages.stickersNotModified#f1749a22 = messages.Stickers; messages.stickersNotModified#f1749a22 = messages.Stickers;
messages.stickers#8a8ecd32 hash:string stickers:Vector<Document> = messages.Stickers; messages.stickers#e4599bbd hash:int stickers:Vector<Document> = messages.Stickers;
stickerPack#12b299d4 emoticon:string documents:Vector<long> = StickerPack; stickerPack#12b299d4 emoticon:string documents:Vector<long> = StickerPack;
@ -461,12 +455,12 @@ authorization#7bf2e6f6 hash:long flags:int device_model:string platform:string s
account.authorizations#1250abde authorizations:Vector<Authorization> = account.Authorizations; account.authorizations#1250abde authorizations:Vector<Authorization> = account.Authorizations;
account.noPassword#96dabc18 new_salt:bytes email_unconfirmed_pattern:string = account.Password; account.noPassword#5ea182f6 new_salt:bytes new_secure_salt:bytes secure_random:bytes email_unconfirmed_pattern:string = account.Password;
account.password#7c18141c current_salt:bytes new_salt:bytes hint:string has_recovery:Bool email_unconfirmed_pattern:string = account.Password; account.password#ca39b447 flags:# has_recovery:flags.0?true has_secure_values:flags.1?true current_salt:bytes new_salt:bytes new_secure_salt:bytes secure_random:bytes hint:string email_unconfirmed_pattern:string = account.Password;
account.passwordSettings#b7b72ab3 email:string = account.PasswordSettings; account.passwordSettings#7bd9c3f1 email:string secure_salt:bytes secure_secret:bytes secure_secret_id:long = account.PasswordSettings;
account.passwordInputSettings#86916deb flags:# new_salt:flags.0?bytes new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string = account.PasswordInputSettings; account.passwordInputSettings#21ffa60d flags:# new_salt:flags.0?bytes new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string new_secure_salt:flags.2?bytes new_secure_secret:flags.2?bytes new_secure_secret_id:flags.2?long = account.PasswordInputSettings;
auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery; auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery;
@ -554,7 +548,7 @@ channels.channelParticipantsNotModified#f0173fe9 = channels.ChannelParticipants;
channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector<User> = channels.ChannelParticipant; channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector<User> = channels.ChannelParticipant;
help.termsOfService#f1ee3e90 text:string = help.TermsOfService; help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string entities:Vector<MessageEntity> min_age_confirm:flags.1?int = help.TermsOfService;
foundGif#162ecc1f url:string thumb_url:string content_url:string content_type:string w:int h:int = FoundGif; foundGif#162ecc1f url:string thumb_url:string content_url:string content_type:string w:int h:int = FoundGif;
foundGifCached#9c750409 url:string photo:Photo document:Document = FoundGif; foundGifCached#9c750409 url:string photo:Photo document:Document = FoundGif;
@ -567,7 +561,7 @@ messages.savedGifs#2e0709a5 hash:int gifs:Vector<Document> = messages.SavedGifs;
inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageMediaGeo#c1b15d65 flags:# geo_point:InputGeoPoint period:int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaGeo#c1b15d65 flags:# geo_point:InputGeoPoint period:int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageMediaVenue#aaafadc8 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageMediaContact#2daf01a7 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaContact#2daf01a7 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
@ -579,7 +573,7 @@ inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:Input
botInlineMessageMediaAuto#764cf810 flags:# message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaAuto#764cf810 flags:# message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaGeo#b722de65 flags:# geo:GeoPoint period:int reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaGeo#b722de65 flags:# geo:GeoPoint period:int reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaVenue#4366232e flags:# geo:GeoPoint title:string address:string provider:string venue_id:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaContact#35edb4d4 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaContact#35edb4d4 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult; botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult;
@ -718,6 +712,8 @@ webDocumentNoProxy#f9c8bcc6 url:string size:int mime_type:string attributes:Vect
inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = InputWebDocument; inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = InputWebDocument;
inputWebFileLocation#c239d686 url:string access_hash:long = InputWebFileLocation; inputWebFileLocation#c239d686 url:string access_hash:long = InputWebFileLocation;
inputWebFileGeoPointLocation#66275a62 geo_point:InputGeoPoint w:int h:int zoom:int scale:int = InputWebFileLocation;
inputWebFileGeoMessageLocation#553f32eb peer:InputPeer msg_id:int w:int h:int zoom:int scale:int = InputWebFileLocation;
upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile; upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile;
@ -773,7 +769,7 @@ langPackDifference#f385c1f6 lang_code:string from_version:int version:int string
langPackLanguage#117698f1 name:string native_name:string lang_code:string = LangPackLanguage; langPackLanguage#117698f1 name:string native_name:string lang_code:string = LangPackLanguage;
channelAdminRights#5d7ceba5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true invite_link:flags.6?true pin_messages:flags.7?true add_admins:flags.9?true = ChannelAdminRights; channelAdminRights#5d7ceba5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true invite_link:flags.6?true pin_messages:flags.7?true add_admins:flags.9?true manage_call:flags.10?true = ChannelAdminRights;
channelBannedRights#58cf4249 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true until_date:int = ChannelBannedRights; channelBannedRights#58cf4249 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true until_date:int = ChannelBannedRights;
@ -832,15 +828,69 @@ messages.foundStickerSets#5108d648 hash:int sets:Vector<StickerSetCovered> = mes
fileHash#6242c773 offset:int limit:int hash:bytes = FileHash; fileHash#6242c773 offset:int limit:int hash:bytes = FileHash;
inputClientProxy#75588b3f address:string port:int = InputClientProxy;
help.proxyDataEmpty#e09e1fb8 expires:int = help.ProxyData;
help.proxyDataPromo#2bf7ee23 expires:int peer:Peer chats:Vector<Chat> users:Vector<User> = help.ProxyData;
help.termsOfServiceUpdateEmpty#e3309f7f expires:int = help.TermsOfServiceUpdate;
help.termsOfServiceUpdate#28ecf961 expires:int terms_of_service:help.TermsOfService = help.TermsOfServiceUpdate;
inputSecureFileUploaded#3334b0f0 id:long parts:int md5_checksum:string file_hash:bytes secret:bytes = InputSecureFile;
inputSecureFile#5367e5be id:long access_hash:long = InputSecureFile;
secureFileEmpty#64199744 = SecureFile;
secureFile#e0277a62 id:long access_hash:long size:int dc_id:int date:int file_hash:bytes secret:bytes = SecureFile;
secureData#8aeabec3 data:bytes data_hash:bytes secret:bytes = SecureData;
securePlainPhone#7d6099dd phone:string = SecurePlainData;
securePlainEmail#21ec5a5f email:string = SecurePlainData;
secureValueTypePersonalDetails#9d2a81e3 = SecureValueType;
secureValueTypePassport#3dac6a00 = SecureValueType;
secureValueTypeDriverLicense#6e425c4 = SecureValueType;
secureValueTypeIdentityCard#a0d0744b = SecureValueType;
secureValueTypeInternalPassport#99a48f23 = SecureValueType;
secureValueTypeAddress#cbe31e26 = SecureValueType;
secureValueTypeUtilityBill#fc36954e = SecureValueType;
secureValueTypeBankStatement#89137c0d = SecureValueType;
secureValueTypeRentalAgreement#8b883488 = SecureValueType;
secureValueTypePassportRegistration#99e3806a = SecureValueType;
secureValueTypeTemporaryRegistration#ea02ec33 = SecureValueType;
secureValueTypePhone#b320aadb = SecureValueType;
secureValueTypeEmail#8e3ca7ee = SecureValueType;
secureValue#b4b4b699 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?SecureFile reverse_side:flags.2?SecureFile selfie:flags.3?SecureFile files:flags.4?Vector<SecureFile> plain_data:flags.5?SecurePlainData hash:bytes = SecureValue;
inputSecureValue#67872e8 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?InputSecureFile reverse_side:flags.2?InputSecureFile selfie:flags.3?InputSecureFile files:flags.4?Vector<InputSecureFile> plain_data:flags.5?SecurePlainData = InputSecureValue;
secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash;
secureValueErrorData#e8a40bd9 type:SecureValueType data_hash:bytes field:string text:string = SecureValueError;
secureValueErrorFrontSide#be3dfa type:SecureValueType file_hash:bytes text:string = SecureValueError;
secureValueErrorReverseSide#868a2aa5 type:SecureValueType file_hash:bytes text:string = SecureValueError;
secureValueErrorSelfie#e537ced6 type:SecureValueType file_hash:bytes text:string = SecureValueError;
secureValueErrorFile#7a700873 type:SecureValueType file_hash:bytes text:string = SecureValueError;
secureValueErrorFiles#666220e9 type:SecureValueType file_hash:Vector<bytes> text:string = SecureValueError;
secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted;
account.authorizationForm#cb976d53 flags:# selfie_required:flags.1?true required_types:Vector<SecureValueType> values:Vector<SecureValue> errors:Vector<SecureValueError> users:Vector<User> privacy_policy_url:flags.0?string = account.AuthorizationForm;
account.sentEmailCode#811f854f email_pattern:string length:int = account.SentEmailCode;
help.deepLinkInfoEmpty#66afa166 = help.DeepLinkInfo;
help.deepLinkInfo#6a4ee832 flags:# update_app:flags.0?true message:string entities:flags.1?Vector<MessageEntity> = help.DeepLinkInfo;
---functions--- ---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector<long> query:!X = X; invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector<long> query:!X = X;
initConnection#c7481da6 {X:Type} api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string query:!X = X; initConnection#785188b8 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy query:!X = X;
invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X; invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X;
invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X; invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X;
auth.checkPhone#6fe51dfb phone_number:string = auth.CheckedPhone;
auth.sendCode#86aef0ec flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool api_id:int api_hash:string = auth.SentCode; auth.sendCode#86aef0ec flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool api_id:int api_hash:string = auth.SentCode;
auth.signUp#1b067634 phone_number:string phone_code_hash:string phone_code:string first_name:string last_name:string = auth.Authorization; auth.signUp#1b067634 phone_number:string phone_code_hash:string phone_code:string first_name:string last_name:string = auth.Authorization;
auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization; auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization;
@ -888,9 +938,20 @@ account.getTmpPassword#4a82327e password_hash:bytes period:int = account.TmpPass
account.getWebAuthorizations#182e6d6f = account.WebAuthorizations; account.getWebAuthorizations#182e6d6f = account.WebAuthorizations;
account.resetWebAuthorization#2d01b9ef hash:long = Bool; account.resetWebAuthorization#2d01b9ef hash:long = Bool;
account.resetWebAuthorizations#682d2594 = Bool; account.resetWebAuthorizations#682d2594 = Bool;
account.getAllSecureValues#b288bc7d = Vector<SecureValue>;
account.getSecureValue#73665bc2 types:Vector<SecureValueType> = Vector<SecureValue>;
account.saveSecureValue#899fe31d value:InputSecureValue secure_secret_id:long = SecureValue;
account.deleteSecureValue#b880bc4b types:Vector<SecureValueType> = Bool;
account.getAuthorizationForm#b86ba8e1 bot_id:int scope:string public_key:string = account.AuthorizationForm;
account.acceptAuthorization#e7027c94 bot_id:int scope:string public_key:string value_hashes:Vector<SecureValueHash> credentials:SecureCredentialsEncrypted = Bool;
account.sendVerifyPhoneCode#823380b4 flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool = auth.SentCode;
account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool;
account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode;
account.verifyEmail#ecba39db email:string code:string = Bool;
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>; users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
users.getFullUser#ca30a5b1 id:InputUser = UserFull; users.getFullUser#ca30a5b1 id:InputUser = UserFull;
users.setSecureValueErrors#90c894b5 id:InputUser errors:Vector<SecureValueError> = Bool;
contacts.getStatuses#c4a353ee = Vector<ContactStatus>; contacts.getStatuses#c4a353ee = Vector<ContactStatus>;
contacts.getContacts#c023849f hash:int = contacts.Contacts; contacts.getContacts#c023849f hash:int = contacts.Contacts;
@ -943,7 +1004,7 @@ messages.sendEncryptedService#32d439a4 peer:InputEncryptedChat random_id:long da
messages.receivedQueue#55a5bb66 max_qts:int = Vector<long>; messages.receivedQueue#55a5bb66 max_qts:int = Vector<long>;
messages.reportEncryptedSpam#4b0c8c0f peer:InputEncryptedChat = Bool; messages.reportEncryptedSpam#4b0c8c0f peer:InputEncryptedChat = Bool;
messages.readMessageContents#36a73f77 id:Vector<int> = messages.AffectedMessages; messages.readMessageContents#36a73f77 id:Vector<int> = messages.AffectedMessages;
messages.getStickers#85cb5182 flags:# exclude_featured:flags.0?true emoticon:string hash:string = messages.Stickers; messages.getStickers#43d4f2c emoticon:string hash:int = messages.Stickers;
messages.getAllStickers#1c9618b1 hash:int = messages.AllStickers; messages.getAllStickers#1c9618b1 hash:int = messages.AllStickers;
messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector<MessageEntity> = MessageMedia; messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector<MessageEntity> = MessageMedia;
messages.exportChatInvite#7d885289 chat_id:int = ExportedChatInvite; messages.exportChatInvite#7d885289 chat_id:int = ExportedChatInvite;
@ -967,8 +1028,8 @@ messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_p
messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector<InputBotInlineResult> cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool; messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector<InputBotInlineResult> cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool;
messages.sendInlineBotResult#b16e06fe flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string = Updates; messages.sendInlineBotResult#b16e06fe flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string = Updates;
messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData;
messages.editMessage#5d1b8dd flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true peer:InputPeer id:int message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> geo_point:flags.13?InputGeoPoint = Updates; messages.editMessage#c000e4c8 flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> geo_point:flags.13?InputGeoPoint = Updates;
messages.editInlineBotMessage#b0e08243 flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true id:InputBotInlineMessageID message:flags.11?string reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> geo_point:flags.13?InputGeoPoint = Bool; messages.editInlineBotMessage#adc3e828 flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> geo_point:flags.13?InputGeoPoint = Bool;
messages.getBotCallbackAnswer#810a9fec flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes = messages.BotCallbackAnswer; messages.getBotCallbackAnswer#810a9fec flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes = messages.BotCallbackAnswer;
messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool; messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool;
messages.getPeerDialogs#e470bcfd peers:Vector<InputDialogPeer> = messages.PeerDialogs; messages.getPeerDialogs#e470bcfd peers:Vector<InputDialogPeer> = messages.PeerDialogs;
@ -1030,10 +1091,13 @@ help.saveAppLog#6f02f748 events:Vector<InputAppEvent> = Bool;
help.getInviteText#4d392343 = help.InviteText; help.getInviteText#4d392343 = help.InviteText;
help.getSupport#9cdf08cd = help.Support; help.getSupport#9cdf08cd = help.Support;
help.getAppChangelog#9010ef6f prev_app_version:string = Updates; help.getAppChangelog#9010ef6f prev_app_version:string = Updates;
help.getTermsOfService#350170f3 = help.TermsOfService;
help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool; help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool;
help.getCdnConfig#52029342 = CdnConfig; help.getCdnConfig#52029342 = CdnConfig;
help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls; help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls;
help.getProxyData#3d7758e1 = help.ProxyData;
help.getTermsOfServiceUpdate#2ca51fd1 = help.TermsOfServiceUpdate;
help.acceptTermsOfService#ee72f79a id:DataJSON = Bool;
help.getDeepLinkInfo#3fedc75f path:string = help.DeepLinkInfo;
channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
@ -1097,4 +1161,4 @@ langpack.getStrings#2e1ee318 lang_code:string keys:Vector<string> = Vector<LangP
langpack.getDifference#b2e4d7d from_version:int = LangPackDifference; langpack.getDifference#b2e4d7d from_version:int = LangPackDifference;
langpack.getLanguages#800fd57d = Vector<LangPackLanguage>; langpack.getLanguages#800fd57d = Vector<LangPackLanguage>;
// LAYER 76 // LAYER 81

View File

@ -42,9 +42,16 @@ new_session_created#9ec20908 first_msg_id:long unique_id:long server_salt:long =
// msg_copy#e06046b2 orig_message:Message = MessageCopy; // Not used // msg_copy#e06046b2 orig_message:Message = MessageCopy; // Not used
// gzip_packed#3072cfa1 packed_data:string = Object; // Parsed manually // gzip_packed#3072cfa1 packed_data:string = Object; // Parsed manually
http_wait#9299359f max_delay:int wait_after:int max_wait:int = HttpWait;
//// 0xd433ad73 = hex(crc32(b"ipPort ipv4:int port:int = IpPort")) //// 0xd433ad73 = hex(crc32(b"ipPort ipv4:int port:int = IpPort"))
// ipPort ipv4:int port:int = IpPort;
// help.configSimple#d997c3c5 date:int expires:int dc_id:int ip_port_list:Vector<IpPort> = help.ConfigSimple;
ipPort#d433ad73 ipv4:int port:int = IpPort; ipPort#d433ad73 ipv4:int port:int = IpPort;
help.configSimple#d997c3c5 date:int expires:int dc_id:int ip_port_list:Vector<IpPort> = help.ConfigSimple; ipPortSecret#37982646 ipv4:int port:int secret:bytes = IpPort;
accessPointRule#4679b65f phone_prefix_rules:string dc_id:int ips:vector<IpPort> = AccessPointRule;
help.configSimple#5a592a6c date:int expires:int rules:vector<AccessPointRule> = help.ConfigSimple;
---functions--- ---functions---
@ -58,5 +65,3 @@ ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong;
destroy_session#e7512126 session_id:long = DestroySessionRes; destroy_session#e7512126 session_id:long = DestroySessionRes;
contest.saveDeveloperInfo#9a5f6e95 vk_id:int name:string phone_number:string age:int city:string = Bool; contest.saveDeveloperInfo#9a5f6e95 vk_id:int name:string phone_number:string age:int city:string = Bool;
http_wait#9299359f max_delay:int wait_after:int max_wait:int = HttpWait;

View File

@ -77,7 +77,8 @@ def generate(source_path, base):
build(source_path) build(source_path)
for k, v in all_entities.items(): for k, v in sorted(all_entities.items()):
v = sorted(v)
entities = [] entities = []
for i in v: for i in v:

View File

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

20
docs/Makefile_ Normal file
View File

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

View File

@ -26,11 +26,11 @@ Welcome to Pyrogram
</a> </a>
<br><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://media.pyrogram.ml/images/scheme.svg" <img src="https://img.shields.io/badge/SCHEME-LAYER%2081-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
alt="Scheme Layer 75"> alt="Scheme Layer">
</a> </a>
<a href="https://github.com/pyrogram/tgcrypto"> <a href="https://github.com/pyrogram/tgcrypto">
<img src="https://media.pyrogram.ml/images/tgcrypto.svg" <img src="https://img.shields.io/badge/TGCRYPTO-V1.0.4-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
alt="TgCrypto"> alt="TgCrypto">
</a> </a>
</p> </p>
@ -44,12 +44,10 @@ Welcome to Pyrogram
@app.on_message(Filters.private) @app.on_message(Filters.private)
def hello(client, message): def hello(client, message):
client.send_message( message.reply("Hello {}".format(message.from_user.first_name))
message.chat.id, "Hello {}".format(message.from_user.first_name))
app.start() app.run()
app.idle()
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 by topic and can be accessed from the sidebar, or by following them one by one using the Next
@ -58,7 +56,7 @@ button at the end of each page. But first, here's a brief overview of what is th
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 building
custom Telegram applications that interact with the MTProto API as both User and Bot. custom Telegram applications that interact with the MTProto API as both User and Bot.
Features Features
@ -67,29 +65,30 @@ 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 76 running on MTProto 2.0. - **Updated** to the latest Telegram API version, currently Layer 81 on top of MTProto 2.0.
- **Documented**: Pyrogram API methods are documented and resemble 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.
To get started, press the Next button. To get started, press the Next button.
.. toctree:: .. toctree::
:hidden: :hidden:
:caption: Getting Started :caption: Quick Start
start/QuickInstallation start/Installation
start/ProjectSetup start/Setup
start/BasicUsage start/Usage
.. toctree:: .. toctree::
:hidden: :hidden:
:caption: Resources :caption: Resources
resources/UpdateHandling resources/UpdateHandling
resources/SOCKS5Proxy
resources/TgCrypto
resources/AutoAuthorization resources/AutoAuthorization
resources/CustomizeSessions
resources/TgCrypto
resources/TextFormatting resources/TextFormatting
resources/SOCKS5Proxy
resources/BotsInteraction resources/BotsInteraction
resources/ErrorHandling resources/ErrorHandling
@ -107,5 +106,4 @@ To get started, press the Next button.
types/index types/index
.. _`Telegram`: https://telegram.org/ .. _`Telegram`: https://telegram.org/
.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto/
.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto/

View File

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

View File

@ -7,6 +7,8 @@ Client
:inherited-members: :inherited-members:
:members: :members:
.. _available-methods:
**Available methods** **Available methods**
.. autosummary:: .. autosummary::
@ -15,6 +17,7 @@ Client
start start
stop stop
idle idle
run
on_message on_message
on_callback_query on_callback_query
on_raw_update on_raw_update
@ -49,6 +52,10 @@ Client
enable_cloud_password enable_cloud_password
change_cloud_password change_cloud_password
remove_cloud_password remove_cloud_password
kick_chat_member
unban_chat_member
restrict_chat_member
promote_chat_member
add_contacts add_contacts
get_contacts get_contacts
delete_contacts delete_contacts

View File

@ -0,0 +1,6 @@
DeletedMessagesHandler
======================
.. autoclass:: pyrogram.DeletedMessagesHandler
:members:
:undoc-members:

View File

@ -0,0 +1,6 @@
DisconnectHandler
=================
.. autoclass:: pyrogram.DisconnectHandler
:members:
:undoc-members:

View File

@ -5,5 +5,7 @@ Handlers
.. toctree:: .. toctree::
MessageHandler MessageHandler
DeletedMessagesHandler
CallbackQueryHandler CallbackQueryHandler
RawUpdateHandler DisconnectHandler
RawUpdateHandler

View File

@ -14,28 +14,33 @@ Log In
To automate the **Log In** process, pass your ``phone_number`` and ``password`` (if you have one) in the Client parameters. To automate the **Log In** process, pass your ``phone_number`` and ``password`` (if you have one) in the Client parameters.
If you want to retrieve the phone code programmatically, pass a callback function in the ``phone_code`` field — this If you want to retrieve the phone code programmatically, pass a callback function in the ``phone_code`` field — this
function must return the correct phone code as string (e.g., "12345") — otherwise, ignore this parameter, Pyrogram will function accepts a single positional argument (phone_number) and must return the correct phone code (e.g., "12345")
ask you to input the phone code manually. — otherwise, ignore this parameter, Pyrogram will ask you to input the phone code manually.
Example:
.. code-block:: python .. code-block:: python
from pyrogram import Client from pyrogram import Client
def phone_code_callback(): def phone_code_callback(phone_number):
code = ... # Get your code programmatically code = ... # Get your code programmatically
return code # Must be string, e.g., "12345" return code # e.g., "12345"
app = Client( app = Client(
session_name="example", session_name="example",
phone_number="39**********", phone_number="39**********",
phone_code=phone_code_callback, phone_code=phone_code_callback, # Note the missing parentheses
password="password" # (if you have one) password="password" # (if you have one)
) )
app.start() app.start()
print(app.get_me()) print(app.get_me())
app.stop()
Sign Up Sign Up
------- -------
@ -43,22 +48,27 @@ To automate the **Sign Up** process (i.e., automatically create a new Telegram a
``first_name`` and ``last_name`` fields alongside the other parameters; they will be used to automatically create a new ``first_name`` and ``last_name`` fields alongside the other parameters; they will be used to automatically create a new
Telegram account in case the phone number you passed is not registered yet. Telegram account in case the phone number you passed is not registered yet.
Example:
.. code-block:: python .. code-block:: python
from pyrogram import Client from pyrogram import Client
def phone_code_callback(): def phone_code_callback(phone_number):
code = ... # Get your code programmatically code = ... # Get your code programmatically
return code # Must be string, e.g., "12345" return code # e.g., "12345"
app = Client( app = Client(
session_name="example", session_name="example",
phone_number="39**********", phone_number="39**********",
phone_code=phone_code_callback, phone_code=phone_code_callback, # Note the missing parentheses
first_name="Pyrogram", first_name="Pyrogram",
last_name="" # Can be an empty string last_name="" # Can be an empty string
) )
app.start() app.start()
print(app.get_me())
print(app.get_me())
app.stop()

View File

@ -28,8 +28,12 @@ Inline Bots
.. code-block:: python .. code-block:: python
# Send the first result (bot_results.results[0]) to your own chat (Saved Messages) # Send the first result to your own chat
app.send_inline_bot_result("me", bot_results.query_id, bot_results.results[0].id) app.send_inline_bot_result(
"me",
bot_results.query_id,
bot_results.results[0].id
)
.. figure:: https://i.imgur.com/wwxr7B7.png .. figure:: https://i.imgur.com/wwxr7B7.png
:width: 90% :width: 90%

View File

@ -0,0 +1,66 @@
Customize Sessions
==================
As you may probably know, Telegram allows Users (and Bots) having more than one session (authorizations) registered
in the system at the same time.
Briefly explaining, sessions are simply new logins in your account. They can be reviewed in the settings of an official
app (or by invoking `GetAuthorizations <../functions/account/GetAuthorizations.html>`_ with Pyrogram) and store some useful
information about the client who generated them.
.. figure:: https://i.imgur.com/lzGPCdZ.png
:width: 70%
:align: center
A Pyrogram session running on Linux, Python 3.6.
That's how a session looks like on the Android app, showing the three main pieces of information.
- ``app_version``: **Pyrogram 🔥 0.7.5**
- ``device_model``: **CPython 3.6.5**
- ``system_version``: **Linux 4.15.0-23-generic**
Set Custom Values
-----------------
To set custom values, you can either make use of the ``config.ini`` file, this way:
.. code-block:: ini
[pyrogram]
app_version = 1.2.3
device_model = PC
system_version = Linux
Or, pass the arguments directly in the Client's constructor.
.. code-block:: python
app = Client(
"my_account",
app_version="1.2.3",
device_model="PC",
system_version="Linux"
)
Set Custom Languages
--------------------
To tell Telegram in which language should speak to you (terms of service, bots, service messages, ...) you can
set ``lang_code`` in `ISO 639-1 <https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes>`_ standard (defaults to "en",
English).
With the following code we make Telegram know we want it to speak in Italian (it):
.. code-block:: ini
[pyrogram]
lang_code = it
.. code-block:: python
app = Client(
"my_account",
lang_code="it",
)

View File

@ -6,8 +6,10 @@ and are handled by registering one or more callback functions with an Handler. T
from, one for each kind of update: from, one for each kind of update:
- `MessageHandler <../pyrogram/handlers/MessageHandler.html>`_ - `MessageHandler <../pyrogram/handlers/MessageHandler.html>`_
- `DeletedMessagesHandler <../pyrogram/handlers/DeletedMessagesHandler.html>`_
- `CallbackQueryHandler <../pyrogram/handlers/CallbackQueryHandler.html>`_ - `CallbackQueryHandler <../pyrogram/handlers/CallbackQueryHandler.html>`_
- `RawUpdateHandler <../pyrogram/handlers/RawUpdateHandler.html>`_ - `RawUpdateHandler <../pyrogram/handlers/RawUpdateHandler.html>`_
- `DisconnectHandler <../pyrogram/handlers/DisconnectHandler.html>`_
Registering an Handler Registering an Handler
---------------------- ----------------------
@ -31,8 +33,7 @@ We shall examine the :obj:`MessageHandler <pyrogram.MessageHandler>`, which will
print(message) print(message)
app.start() app.run()
app.idle()
- If you prefer not to use decorators, there is an alternative way for registering Handlers. - If you prefer not to use decorators, there is an alternative way for registering Handlers.
This is useful, for example, when you want to keep your callback functions in separate files. This is useful, for example, when you want to keep your callback functions in separate files.
@ -50,8 +51,7 @@ We shall examine the :obj:`MessageHandler <pyrogram.MessageHandler>`, which will
app.add_handler(MessageHandler(my_handler)) app.add_handler(MessageHandler(my_handler))
app.start() app.run()
app.idle()
Using Filters Using Filters
------------- -------------

View File

@ -1,97 +0,0 @@
Basic Usage
===========
.. note::
All the snippets below assume you have successfully created and started a :class:`Client <pyrogram.Client>`
instance. You also must be authorized, that is, a valid *.session file does exist in your working directory.
Simple API Access
-----------------
The easiest way to interact with the Telegram API is via the :class:`Client <pyrogram.Client>` class, which
exposes `Bot API-like`_ methods:
- Get information about the authorized user:
.. code-block:: python
print(app.get_me())
- Send a message to yourself (Saved Messages):
.. code-block:: python
app.send_message("me", "Hi there! I'm using Pyrogram")
- Upload a new photo (with caption):
.. code-block:: python
app.send_photo("me", "/home/dan/perla.jpg", "Cute!")
.. seealso:: For a complete list of the available methods and an exhaustive description for each of them, have a look
at the :class:`Client <pyrogram.Client>` class.
.. _using-raw-functions:
Using Raw Functions
-------------------
If you want **complete**, low-level access to the Telegram API you have to use the raw
:mod:`functions <pyrogram.api.functions>` and :mod:`types <pyrogram.api.types>` exposed by the ``pyrogram.api``
package and call any Telegram API method you wish using the :meth:`send() <pyrogram.Client.send>` method provided by
the Client class.
Here some examples:
- Update first name, last name and bio:
.. code-block:: python
from pyrogram.api import functions
...
app.send(
functions.account.UpdateProfile(
first_name="Dan", last_name="Tès",
about="Bio written from Pyrogram"
)
)
- Share your Last Seen time only with your contacts:
.. code-block:: python
from pyrogram.api import functions, types
...
app.send(
functions.account.SetPrivacy(
key=types.InputPrivacyKeyStatusTimestamp(),
rules=[types.InputPrivacyValueAllowContacts()]
)
)
- Invite users to your channel/supergroup:
.. code-block:: python
from pyrogram.api import functions, types
...
app.send(
functions.channels.InviteToChannel(
channel=app.resolve_peer(123456789), # ID or Username
users=[ # The users you want to invite
app.resolve_peer(23456789), # By ID
app.resolve_peer("username"), # By username
app.resolve_peer("393281234567"), # By phone number
]
)
)
.. _`Bot API-like`: https://core.telegram.org/bots/api#available-methods

View File

@ -0,0 +1,50 @@
Installation
============
Being a Python library, Pyrogram requires Python to be installed in your system.
We recommend using the latest version of Python 3 and pip.
Get Python 3 from https://www.python.org/downloads/ or with your package manager and pip
by following the instructions at https://pip.pypa.io/en/latest/installing/.
Pyrogram supports Python 3 only, starting from version 3.4 and PyPy.
Install Pyrogram
----------------
- The easiest way to install and upgrade Pyrogram is by using **pip**:
.. code-block:: bash
$ pip3 install --upgrade pyrogram
- or, with TgCrypto_ (recommended):
.. code-block:: bash
$ pip3 install --upgrade pyrogram[tgcrypto]
Bleeding Edge
-------------
If you want the latest development version of Pyrogram, you can install it straight from the develop_
branch using this command:
.. code-block:: bash
$ pip3 install --upgrade git+https://github.com/pyrogram/pyrogram.git
Verifying
---------
To verify that Pyrogram is correctly installed, open a Python shell and import it.
If no error shows up you are good to go.
.. code-block:: bash
>>> import pyrogram
>>> pyrogram.__version__
'0.7.5'
.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto
.. _develop: http://github.com/pyrogram/pyrogram

View File

@ -1,37 +0,0 @@
Quick Installation
==================
- The easiest way to install and upgrade Pyrogram is by using **pip**:
.. code-block:: bash
$ pip3 install --upgrade pyrogram
- or, with TgCrypto_ (recommended):
.. code-block:: bash
$ pip3 install --upgrade pyrogram[tgcrypto]
Bleeding Edge
-------------
If you want the latest development version of the library, you can install it with:
.. code-block:: bash
$ pip3 install --upgrade git+https://github.com/pyrogram/pyrogram.git
Verifying
---------
To verify that Pyrogram is correctly installed, open a Python shell and import it.
If no error shows up you are good to go.
.. code-block:: bash
>>> import pyrogram
>>> pyrogram.__version__
'0.7.4'
.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto

View File

@ -1,8 +1,9 @@
Project Setup Setup
============= =====
This section provides all the information you need to setup your project with Pyrogram. Once you successfully `installed Pyrogram`_, you will still have to follow a few steps before you can actually use
There are a few steps you have to follow before you can actually use the library to make API calls. the library to make API calls. This section provides all the information you need in order to set up a project
with Pyrogram.
API Keys API Keys
-------- --------
@ -39,7 +40,7 @@ There are two ways to configure a Pyrogram application project, and you can choo
from pyrogram import Client from pyrogram import Client
app = Client( app = Client(
session_name="my_account", "my_account",
api_id=12345, api_id=12345,
api_hash="0123456789abcdef0123456789abcdef" api_hash="0123456789abcdef0123456789abcdef"
) )
@ -53,14 +54,14 @@ User Authorization
In order to use the API, Telegram requires that Users be authorized via their phone numbers. In order to use the API, Telegram requires that Users be authorized via their phone numbers.
Pyrogram automatically manages this access, all you need to do is create an instance of Pyrogram automatically manages this access, all you need to do is create an instance of
the :class:`Client <pyrogram.Client>` class by passing to it a ``session_name`` of your choice the :class:`Client <pyrogram.Client>` class by passing to it a ``session_name`` of your choice
(e.g.: "my_account") and call the :meth:`start() <pyrogram.Client.start>` method: (e.g.: "my_account") and call the :meth:`run() <pyrogram.Client.run>` method:
.. code-block:: python .. code-block:: python
from pyrogram import Client from pyrogram import Client
app = Client("my_account") app = Client("my_account")
app.start() app.run()
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:
@ -75,14 +76,14 @@ 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 file(s) must be kept secret. .. important:: Your ``*.session`` file(s) must be kept secret.
Bot Authorization Bot Authorization
----------------- -----------------
Being written entirely from the ground up, Pyrogram is also able to authorize Bots. Being written entirely from the ground up, Pyrogram is also able to authorize Bots.
Bots are a special kind of users which also make use of MTProto. This means that you can use Pyrogram to Bots are a special kind of users which also make use of MTProto, the underlying Telegram protocol.
execute API calls with a Bot identity. This means that you can use Pyrogram to execute API calls with a Bot identity.
Instead of phone numbers, Bots are authorized via their tokens which are created by BotFather_: Instead of phone numbers, Bots are authorized via their tokens which are created by BotFather_:
@ -91,10 +92,11 @@ Instead of phone numbers, Bots are authorized via their tokens which are created
from pyrogram import Client from pyrogram import Client
app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
app.start() app.run()
That's all, no further action is needed. The session file will be named after the Bot user_id, which is That's all, no further action is needed. The session file will be named after the Bot user_id, which is
``123456.session`` for the example above. ``123456.session`` for the example above.
.. _installed Pyrogram: Installation.html
.. _`Country Code`: https://en.wikipedia.org/wiki/List_of_country_calling_codes .. _`Country Code`: https://en.wikipedia.org/wiki/List_of_country_calling_codes
.. _BotFather: https://t.me/botfather .. _BotFather: https://t.me/botfather

118
docs/source/start/Usage.rst Normal file
View File

@ -0,0 +1,118 @@
Usage
=====
Having your `project set up`_ and your account authorized_, it's time to play with the API.
In this section, you'll be shown two ways of communicating with Telegram using Pyrogram. Let's start!
High-level API
--------------
The easiest and recommended way to interact with Telegram is via the high-level Pyrogram methods_ and types_, which are
named after the `Telegram Bot API`_.
Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_):
- Get information about the authorized user:
.. code-block:: python
print(app.get_me())
- Send a message to yourself (Saved Messages):
.. code-block:: python
app.send_message("me", "Hi there! I'm using Pyrogram")
- Upload a new photo (with caption):
.. code-block:: python
app.send_photo("me", "/home/dan/perla.jpg", "Cute!")
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,
you have to use the raw :mod:`functions <pyrogram.api.functions>` and :mod:`types <pyrogram.api.types>` exposed by the
``pyrogram.api`` package and call any Telegram API method you wish using the :meth:`send() <pyrogram.Client.send>`
method provided by the Client class.
.. hint:: Every high-level method mentioned in the section above is built on top of these raw functions.
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.
If you think a raw function should be wrapped and added as a high-level method, feel free to ask in our Community_!
Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_):
- Update first name, last name and bio:
.. code-block:: python
from pyrogram import Client
from pyrogram.api import functions
app = Client("my_account")
app.start()
app.send(
functions.account.UpdateProfile(
first_name="Dan", last_name="Tès",
about="Bio written from Pyrogram"
)
)
app.stop()
- Share your Last Seen time only with your contacts:
.. code-block:: python
from pyrogram import Client
from pyrogram.api import functions, types
app = Client("my_account")
app.start()
app.send(
functions.account.SetPrivacy(
key=types.InputPrivacyKeyStatusTimestamp(),
rules=[types.InputPrivacyValueAllowContacts()]
)
)
app.stop()
- Invite users to your channel/supergroup:
.. code-block:: python
from pyrogram import Client
from pyrogram.api import functions, types
app = Client("my_account")
app.start()
app.send(
functions.channels.InviteToChannel(
channel=app.resolve_peer(123456789), # ID or Username
users=[ # The users you want to invite
app.resolve_peer(23456789), # By ID
app.resolve_peer("username"), # By username
app.resolve_peer("393281234567"), # By phone number
]
)
)
app.stop()
.. _methods: ../pyrogram/Client.html#available-methods
.. _plenty of them: ../pyrogram/Client.html#available-methods
.. _types: ../pyrogram/types/index.html
.. _Raw Functions: Usage.html#using-raw-functions
.. _Community: https://t.me/PyrogramChat
.. _project set up: Setup.html
.. _authorized: Setup.html#user-authorization
.. _Telegram Bot API: https://core.telegram.org/bots/api

View File

@ -34,5 +34,4 @@ def answer(client, callback_query):
) )
app.start() app.run() # Automatically start() and idle()
app.idle()

View File

@ -36,5 +36,4 @@ def echo(client, message):
) )
app.start() app.run() # Automatically start() and idle()
app.idle()

View File

@ -24,12 +24,12 @@ from pyrogram.api.errors import FloodWait
"""This example shows how to retrieve the full message history of a chat""" """This example shows how to retrieve the full message history of a chat"""
app = Client("my_account") app = Client("my_account")
app.start()
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()
while True: while True:
try: try:
m = app.get_history(target, offset_id=offset_id) m = app.get_history(target, offset_id=offset_id)

View File

@ -28,13 +28,13 @@ Refer to get_participants2.py for more than 10.000 users.
""" """
app = Client("my_account") app = Client("my_account")
app.start()
target = "pyrogramchat" # Target channel/supergroup target = "pyrogramchat" # Target channel/supergroup
users = [] # List that will contain all the users of the target chat users = [] # List that will contain all the users of the target chat
limit = 200 # Amount of users to retrieve for each API call limit = 200 # Amount of users to retrieve for each API call
offset = 0 # Offset starts at 0 offset = 0 # Offset starts at 0
app.start()
while True: while True:
try: try:
participants = app.send( participants = app.send(

View File

@ -33,15 +33,14 @@ as some user names may not contain ascii letters at all.
""" """
app = Client("my_account") app = Client("my_account")
app.start()
target = "pyrogramchat" # Target channel/supergroup username or id target = "pyrogramchat" # Target channel/supergroup username or id
users = {} # To ensure uniqueness, users will be stored in a dictionary with user_id as key 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) limit = 200 # Amount of users to retrieve for each API call (200 is the maximum)
# "" + "0123456789" + "abcdefghijklmnopqrstuvwxyz" (as list) # "" + "0123456789" + "abcdefghijklmnopqrstuvwxyz" (as list)
queries = [""] + [str(i) for i in range(10)] + list(ascii_lowercase) queries = [""] + [str(i) for i in range(10)] + list(ascii_lowercase)
app.start()
for q in queries: for q in queries:
print("Searching for '{}'".format(q)) print("Searching for '{}'".format(q))
offset = 0 # For each query, offset restarts from 0 offset = 0 # For each query, offset restarts from 0

View File

@ -28,5 +28,4 @@ def raw(client, update, users, chats):
print(update) print(update)
app.start() app.run() # Automatically start() and idle()
app.idle()

View File

@ -49,5 +49,4 @@ def welcome(client, message):
) )
app.start() app.run() # Automatically start() and idle()
app.idle()

View File

@ -23,7 +23,7 @@ __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.7.4" __version__ = "0.7.5"
from .api.errors import Error from .api.errors import Error
from .client.types import ( from .client.types import (
@ -38,6 +38,6 @@ from .client.types.reply_markup import (
) )
from .client import ( from .client import (
Client, ChatAction, ParseMode, Emoji, Client, ChatAction, ParseMode, Emoji,
MessageHandler, CallbackQueryHandler, RawUpdateHandler, MessageHandler, DeletedMessagesHandler, CallbackQueryHandler,
Filters RawUpdateHandler, DisconnectHandler, Filters
) )

View File

@ -77,11 +77,14 @@ class Encoder(JSONEncoder):
if o is not None: if o is not None:
if o.startswith("pyrogram.client"): if o.startswith("pyrogram.client"):
r = remove_none(OrderedDict([("_", name)] + [i for i in content.items()])) r = remove_none(OrderedDict([("_", "pyrogram:" + name)] + [i for i in content.items()]))
r.pop("client", None) r.pop("_client", None)
return r return r
else: else:
return OrderedDict([("_", o)] + [i for i in content.items()]) return OrderedDict(
[("_", o.replace("pyrogram.api.types.", "telegram:"))]
+ [i for i in content.items()]
)
else: else:
return None return None

View File

@ -35,8 +35,6 @@ class Int(Object):
class Long(Int): class Long(Int):
SIZE = 8 SIZE = 8
# TODO: PyCharm can't infer types when overriding parent's __new__ and is showing unnecessary warnings.
# Add this to shut warnings down
def __new__(cls, *args): def __new__(cls, *args):
return super().__new__(cls, *args) return super().__new__(cls, *args)

View File

@ -19,4 +19,8 @@
from .client import Client from .client import Client
from .ext import BaseClient, ChatAction, Emoji, ParseMode from .ext import BaseClient, ChatAction, Emoji, ParseMode
from .filters import Filters from .filters import Filters
from .handlers import MessageHandler, CallbackQueryHandler, RawUpdateHandler from .handlers import (
MessageHandler, DeletedMessagesHandler,
CallbackQueryHandler, RawUpdateHandler,
DisconnectHandler
)

View File

@ -18,6 +18,7 @@
import base64 import base64
import binascii import binascii
import getpass
import json import json
import logging import logging
import math import math
@ -43,15 +44,13 @@ from pyrogram.api.errors import (
PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded, PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded,
PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned, PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned,
VolumeLocNotFound, UserMigrate, FileIdInvalid) VolumeLocNotFound, UserMigrate, FileIdInvalid)
from pyrogram.client.handlers import DisconnectHandler
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
from .ext import utils, Syncer, BaseClient from .ext import utils, Syncer, BaseClient
from .methods import Methods from .methods import Methods
# Custom format for nice looking log lines
LOG_FORMAT = "[%(asctime)s.%(msecs)03d] %(filename)s:%(lineno)s %(levelname)s: %(message)s"
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -75,6 +74,22 @@ class Client(Methods, BaseClient):
The *api_hash* part of your Telegram API Key, as string. E.g.: "0123456789abcdef0123456789abcdef" The *api_hash* part of your Telegram API Key, as string. E.g.: "0123456789abcdef0123456789abcdef"
This is an alternative way to pass it if you don't want to use the *config.ini* file. This is an alternative way to pass it if you don't want to use the *config.ini* file.
app_version (``str``, *optional*):
Application version. Defaults to "Pyrogram \U0001f525 vX.Y.Z"
This is an alternative way to set it if you don't want to use the *config.ini* file.
device_model (``str``, *optional*):
Device model. Defaults to *platform.python_implementation() + " " + platform.python_version()*
This is an alternative way to set it if you don't want to use the *config.ini* file.
system_version (``str``, *optional*):
Operating System version. Defaults to *platform.system() + " " + platform.release()*
This is an alternative way to set it if you don't want to use the *config.ini* file.
lang_code (``str``, *optional*):
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.
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")*.
@ -91,8 +106,8 @@ class Client(Methods, BaseClient):
entering it manually. Only applicable for new sessions. entering it manually. Only applicable for new sessions.
phone_code (``str`` | ``callable``, *optional*): phone_code (``str`` | ``callable``, *optional*):
Pass the phone code as string (for test numbers only), or pass a callback function Pass the phone code as string (for test numbers only), or pass a callback function which accepts
which must return the correct phone code as string (e.g., "12345"). a single positional argument *(phone_number)* and must return the correct phone code (e.g., "12345").
Only applicable for new sessions. Only applicable for new sessions.
password (``str``, *optional*): password (``str``, *optional*):
@ -127,6 +142,10 @@ class Client(Methods, BaseClient):
session_name: str, session_name: str,
api_id: int or str = None, api_id: int or str = None,
api_hash: str = None, api_hash: str = None,
app_version: str = None,
device_model: str = None,
system_version: str = None,
lang_code: str = None,
proxy: dict = None, proxy: dict = None,
test_mode: bool = False, test_mode: bool = False,
phone_number: str = None, phone_number: str = None,
@ -143,7 +162,12 @@ class Client(Methods, BaseClient):
self.session_name = session_name self.session_name = session_name
self.api_id = int(api_id) if api_id else None self.api_id = int(api_id) if api_id else None
self.api_hash = api_hash self.api_hash = api_hash
self.proxy = proxy self.app_version = app_version
self.device_model = device_model
self.system_version = system_version
self.lang_code = lang_code
# TODO: Make code consistent, use underscore for private/protected fields
self._proxy = proxy
self.test_mode = test_mode self.test_mode = test_mode
self.phone_number = phone_number self.phone_number = phone_number
self.phone_code = phone_code self.phone_code = phone_code
@ -157,15 +181,19 @@ class Client(Methods, BaseClient):
self.dispatcher = Dispatcher(self, workers) self.dispatcher = Dispatcher(self, workers)
def start(self, debug: bool = False): @property
def proxy(self):
return self._proxy
@proxy.setter
def proxy(self, value):
self._proxy["enabled"] = True
self._proxy.update(value)
def start(self):
"""Use this method to start the Client after creating it. """Use this method to start the Client after creating it.
Requires no parameters. Requires no parameters.
Args:
debug (``bool``, *optional*):
Enable or disable debug mode. When enabled, extra logging
lines will be printed out on your console.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.Error>`
""" """
@ -180,12 +208,9 @@ class Client(Methods, BaseClient):
self.load_session() self.load_session()
self.session = Session( self.session = Session(
self,
self.dc_id, self.dc_id,
self.test_mode, self.auth_key
self.proxy,
self.auth_key,
self.api_id,
client=self
) )
self.session.start() self.session.start()
@ -273,6 +298,39 @@ class Client(Methods, BaseClient):
self.is_started = False self.is_started = False
self.session.stop() self.session.stop()
def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)):
"""Blocks the program execution until one of the signals are received,
then gently stop the Client by closing the underlying connection.
Args:
stop_signals (``tuple``, *optional*):
Iterable containing signals the signal handler will listen to.
Defaults to (SIGINT, SIGTERM, SIGABRT).
"""
def signal_handler(*args):
self.is_idle = False
for s in stop_signals:
signal(s, signal_handler)
self.is_idle = True
while self.is_idle:
time.sleep(1)
self.stop()
def run(self):
"""Use this method to automatically start and idle a Client.
Requires no parameters.
Raises:
:class:`Error <pyrogram.Error>`
"""
self.start()
self.idle()
def add_handler(self, handler, group: int = 0): def add_handler(self, handler, group: int = 0):
"""Use this method to register an update handler. """Use this method to register an update handler.
@ -290,7 +348,10 @@ class Client(Methods, BaseClient):
Returns: Returns:
A tuple of (handler, group) A tuple of (handler, group)
""" """
self.dispatcher.add_handler(handler, group) if isinstance(handler, DisconnectHandler):
self.disconnect_handler = handler.callback
else:
self.dispatcher.add_handler(handler, group)
return handler, group return handler, group
@ -308,7 +369,10 @@ class Client(Methods, BaseClient):
group (``int``, *optional*): group (``int``, *optional*):
The group identifier, defaults to 0. The group identifier, defaults to 0.
""" """
self.dispatcher.remove_handler(handler, group) if isinstance(handler, DisconnectHandler):
self.disconnect_handler = None
else:
self.dispatcher.remove_handler(handler, group)
def authorize_bot(self): def authorize_bot(self):
try: try:
@ -324,15 +388,12 @@ 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._proxy).create()
self.session = Session( self.session = Session(
self,
self.dc_id, self.dc_id,
self.test_mode, self.auth_key
self.proxy,
self.auth_key,
self.api_id,
client=self
) )
self.session.start() self.session.start()
@ -372,15 +433,12 @@ 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._proxy).create()
self.session = Session( self.session = Session(
self,
self.dc_id, self.dc_id,
self.test_mode, self.auth_key
self.proxy,
self.auth_key,
self.api_id,
client=self
) )
self.session.start() self.session.start()
@ -399,8 +457,11 @@ class Client(Methods, BaseClient):
print(e.MESSAGE) print(e.MESSAGE)
self.phone_number = None self.phone_number = None
except FloodWait as e: except FloodWait as e:
print(e.MESSAGE.format(x=e.x)) if phone_number_invalid_raises:
time.sleep(e.x) raise
else:
print(e.MESSAGE.format(x=e.x))
time.sleep(e.x)
except Exception as e: except Exception as e:
log.error(e, exc_info=True) log.error(e, exc_info=True)
else: else:
@ -408,6 +469,10 @@ class Client(Methods, BaseClient):
phone_registered = r.phone_registered phone_registered = r.phone_registered
phone_code_hash = r.phone_code_hash phone_code_hash = r.phone_code_hash
terms_of_service = r.terms_of_service
if terms_of_service:
print("\n" + terms_of_service.text + "\n")
if self.force_sms: if self.force_sms:
self.send( self.send(
@ -421,7 +486,7 @@ class Client(Methods, BaseClient):
self.phone_code = ( self.phone_code = (
input("Enter phone code: ") if self.phone_code is None input("Enter phone code: ") if self.phone_code is None
else self.phone_code if type(self.phone_code) is str else self.phone_code if type(self.phone_code) is str
else str(self.phone_code()) else str(self.phone_code(self.phone_number))
) )
try: try:
@ -478,7 +543,7 @@ class Client(Methods, BaseClient):
if self.password is None: if self.password is None:
print("Hint: {}".format(r.hint)) print("Hint: {}".format(r.hint))
self.password = input("Enter password: ") # TODO: Use getpass self.password = getpass.getpass("Enter password: ")
if type(self.password) is str: if type(self.password) is str:
self.password = r.current_salt + self.password.encode() + r.current_salt self.password = r.current_salt + self.password.encode() + r.current_salt
@ -493,24 +558,35 @@ class Client(Methods, BaseClient):
print(e.MESSAGE) print(e.MESSAGE)
self.password = None self.password = None
except FloodWait as e: except FloodWait as e:
print(e.MESSAGE.format(x=e.x)) if password_hash_invalid_raises:
time.sleep(e.x) raise
else:
print(e.MESSAGE.format(x=e.x))
time.sleep(e.x)
except Exception as e: except Exception as e:
log.error(e, exc_info=True) log.error(e, exc_info=True)
else: else:
break break
break break
except FloodWait as e: except FloodWait as e:
print(e.MESSAGE.format(x=e.x)) if phone_code_invalid_raises or first_name_invalid_raises:
time.sleep(e.x) raise
else:
print(e.MESSAGE.format(x=e.x))
time.sleep(e.x)
except Exception as e: except Exception as e:
log.error(e, exc_info=True) log.error(e, exc_info=True)
else: else:
break break
if terms_of_service:
assert self.send(functions.help.AcceptTermsOfService(terms_of_service.id))
self.password = None self.password = None
self.user_id = r.user.id self.user_id = r.user.id
print("Login successful")
def fetch_peers(self, entities: list): def fetch_peers(self, entities: list):
for entity in entities: for entity in entities:
if isinstance(entity, types.User): if isinstance(entity, types.User):
@ -763,29 +839,7 @@ class Client(Methods, BaseClient):
log.debug("{} stopped".format(name)) log.debug("{} stopped".format(name))
def signal_handler(self, *args): def send(self, data: Object, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT):
self.is_idle = False
def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)):
"""Blocks the program execution until one of the signals are received,
then gently stop the Client by closing the underlying connection.
Args:
stop_signals (``tuple``, *optional*):
Iterable containing signals the signal handler will listen to.
Defaults to (SIGINT, SIGTERM, SIGABRT).
"""
for s in stop_signals:
signal(s, self.signal_handler)
self.is_idle = True
while self.is_idle:
time.sleep(1)
self.stop()
def send(self, data: Object):
"""Use this method to send Raw Function queries. """Use this method to send Raw Function queries.
This method makes possible to manually call every single Telegram API method in a low-level manner. This method makes possible to manually call every single Telegram API method in a low-level manner.
@ -796,13 +850,19 @@ class Client(Methods, BaseClient):
data (``Object``): data (``Object``):
The API Scheme function filled with proper arguments. The API Scheme function filled with proper arguments.
retries (``int``):
Number of retries.
timeout (``float``):
Timeout in seconds.
Raises: Raises:
:class:`Error <pyrogram.Error>` :class:`Error <pyrogram.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")
r = self.session.send(data) r = self.session.send(data, retries, timeout)
self.fetch_peers(getattr(r, "users", [])) self.fetch_peers(getattr(r, "users", []))
self.fetch_peers(getattr(r, "chats", [])) self.fetch_peers(getattr(r, "chats", []))
@ -825,19 +885,42 @@ class Client(Methods, BaseClient):
"More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration" "More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration"
) )
if self.proxy: for option in {"app_version", "device_model", "system_version", "lang_code"}:
self.proxy["enabled"] = True if getattr(self, option):
self.proxy["username"] = self.proxy.get("username", None) pass
self.proxy["password"] = self.proxy.get("password", None) else:
setattr(self, option, Client.APP_VERSION)
if parser.has_section("pyrogram"):
setattr(self, option, parser.get(
"pyrogram",
option,
fallback=getattr(Client, option.upper())
))
if self.lang_code:
pass
else: else:
self.proxy = {} self.lang_code = Client.LANG_CODE
if parser.has_section("pyrogram"):
self.lang_code = parser.get(
"pyrogram",
"lang_code",
fallback=Client.LANG_CODE
)
if self._proxy:
self._proxy["enabled"] = True
else:
self._proxy = {}
if parser.has_section("proxy"): if parser.has_section("proxy"):
self.proxy["enabled"] = parser.getboolean("proxy", "enabled") self._proxy["enabled"] = parser.getboolean("proxy", "enabled")
self.proxy["hostname"] = parser.get("proxy", "hostname") self._proxy["hostname"] = parser.get("proxy", "hostname")
self.proxy["port"] = parser.getint("proxy", "port") self._proxy["port"] = parser.getint("proxy", "port")
self.proxy["username"] = parser.get("proxy", "username", fallback=None) or None self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None
self.proxy["password"] = parser.get("proxy", "password", fallback=None) or None self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None
def load_session(self): def load_session(self):
try: try:
@ -846,7 +929,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._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"]
@ -983,7 +1066,6 @@ class Client(Methods, BaseClient):
except (KeyError, ValueError): except (KeyError, ValueError):
raise PeerIdInvalid raise PeerIdInvalid
# TODO: Improvements for the new API
def save_file(self, def save_file(self,
path: str, path: str,
file_id: int = None, file_id: int = None,
@ -998,7 +1080,7 @@ class Client(Methods, BaseClient):
file_id = file_id or self.rnd_id() file_id = file_id or self.rnd_id()
md5_sum = md5() if not is_big and not is_missing_part else None md5_sum = md5() if not is_big and not is_missing_part else None
session = Session(self.dc_id, self.test_mode, self.proxy, self.auth_key, self.api_id) session = Session(self, self.dc_id, self.auth_key, is_media=True)
session.start() session.start()
try: try:
@ -1027,7 +1109,7 @@ class Client(Methods, BaseClient):
bytes=chunk bytes=chunk
) )
assert self.send(rpc), "Couldn't upload file" assert session.send(rpc), "Couldn't upload file"
if is_missing_part: if is_missing_part:
return return
@ -1059,7 +1141,6 @@ class Client(Methods, BaseClient):
finally: finally:
session.stop() session.stop()
# TODO: Improvements for the new API
def get_file(self, def get_file(self,
dc_id: int, dc_id: int,
id: int = None, id: int = None,
@ -1083,11 +1164,10 @@ class Client(Methods, BaseClient):
) )
session = Session( session = Session(
self,
dc_id, dc_id,
self.test_mode, Auth(dc_id, self.test_mode, self._proxy).create(),
self.proxy, is_media=True
Auth(dc_id, self.test_mode, self.proxy).create(),
self.api_id
) )
session.start() session.start()
@ -1102,11 +1182,10 @@ class Client(Methods, BaseClient):
) )
else: else:
session = Session( session = Session(
self,
dc_id, dc_id,
self.test_mode,
self.proxy,
self.auth_key, self.auth_key,
self.api_id is_media=True
) )
session.start() session.start()
@ -1172,11 +1251,10 @@ class Client(Methods, BaseClient):
if cdn_session is None: if cdn_session is None:
cdn_session = Session( cdn_session = Session(
self,
r.dc_id, r.dc_id,
self.test_mode, Auth(r.dc_id, self.test_mode, self._proxy).create(),
self.proxy, is_media=True,
Auth(r.dc_id, self.test_mode, self.proxy).create(),
self.api_id,
is_cdn=True is_cdn=True
) )
@ -1213,11 +1291,13 @@ class Client(Methods, BaseClient):
chunk = r2.bytes chunk = r2.bytes
# https://core.telegram.org/cdn#decrypting-files # https://core.telegram.org/cdn#decrypting-files
decrypted_chunk = AES.ctr_decrypt( decrypted_chunk = AES.ctr256_decrypt(
chunk, chunk,
r.encryption_key, r.encryption_key,
r.encryption_iv, bytearray(
offset r.encryption_iv[:-4]
+ (offset // 16).to_bytes(4, "big")
)
) )
hashes = session.send( hashes = session.send(
@ -1245,9 +1325,6 @@ class Client(Methods, BaseClient):
break break
except Exception as e: except Exception as e:
raise e raise e
finally:
pass # Don't stop sessions, they are now cached and kept online
# cdn_session.stop() TODO: Remove this branch
except Exception as e: except Exception as e:
log.error(e, exc_info=True) log.error(e, exc_info=True)
@ -1259,6 +1336,3 @@ class Client(Methods, BaseClient):
return "" return ""
else: else:
return file_name return file_name
finally:
pass # Don't stop sessions, they are now cached and kept online
# session.stop() TODO: Remove this branch

View File

@ -25,7 +25,7 @@ from threading import Thread
import pyrogram 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 from ..handlers import RawUpdateHandler, CallbackQueryHandler, MessageHandler, DeletedMessagesHandler
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -41,6 +41,11 @@ class Dispatcher:
types.UpdateEditChannelMessage types.UpdateEditChannelMessage
) )
DELETE_MESSAGE_UPDATES = (
types.UpdateDeleteMessages,
types.UpdateDeleteChannelMessages
)
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):
@ -50,8 +55,6 @@ class Dispatcher:
self.updates = Queue() self.updates = Queue()
self.groups = OrderedDict() self.groups = OrderedDict()
self._handler_lock = threading.Lock()
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(
@ -73,52 +76,57 @@ class Dispatcher:
self.workers_list.clear() self.workers_list.clear()
def add_handler(self, handler, group: int): def add_handler(self, handler, group: int):
with self._handler_lock: if group not in self.groups:
if group not in self.groups: self.groups[group] = []
self.groups[group] = [] self.groups = OrderedDict(sorted(self.groups.items()))
self.groups = OrderedDict(sorted(self.groups.items()))
self.groups[group].append(handler) self.groups[group].append(handler)
def remove_handler(self, handler, group: int): def remove_handler(self, handler, group: int):
with self._handler_lock: 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): def dispatch(self, update, users: dict = None, chats: dict = None, is_raw: bool = False):
with self._handler_lock: for group in self.groups.values():
for group in self.groups.values(): for handler in group:
for handler in group: if is_raw:
if is_raw: if not isinstance(handler, RawUpdateHandler):
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 continue
args = (self.client, update, users, chats) 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: else:
message = (update.message continue
or update.channel_post
or update.edited_message
or update.edited_channel_post)
callback_query = update.callback_query handler.callback(*args)
break
if message and isinstance(handler, MessageHandler):
if not handler.check(message):
continue
args = (self.client, message)
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
@ -166,6 +174,22 @@ class Dispatcher:
else None) else None)
) )
) )
elif isinstance(update, Dispatcher.DELETE_MESSAGE_UPDATES):
is_channel = hasattr(update, 'channel_id')
messages = utils.parse_deleted_messages(
update.messages,
(update.channel_id if is_channel else None)
)
self.dispatch(
pyrogram.Update(
deleted_messages=(messages if not is_channel else None),
deleted_channel_posts=(messages if is_channel else None)
)
)
elif isinstance(update, types.UpdateBotCallbackQuery): elif isinstance(update, types.UpdateBotCallbackQuery):
self.dispatch( self.dispatch(
pyrogram.Update( pyrogram.Update(
@ -174,6 +198,14 @@ class Dispatcher:
) )
) )
) )
elif isinstance(update, types.UpdateInlineBotCallbackQuery):
self.dispatch(
pyrogram.Update(
callback_query=utils.parse_inline_callback_query(
update, users
)
)
)
else: else:
continue continue
except Exception as e: except Exception as e:

View File

@ -16,17 +16,35 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import platform
import re import re
from queue import Queue from queue import Queue
from threading import Lock from threading import Lock
from pyrogram import __version__
from ..style import Markdown, HTML from ..style import Markdown, HTML
from ...api.core import Object from ...api.core import Object
from ...session import Session
from ...session.internals import MsgId from ...session.internals import MsgId
class BaseClient: class BaseClient:
INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:t\.me/joinchat/)([\w-]+)$") APP_VERSION = "Pyrogram \U0001f525 {}".format(__version__)
DEVICE_MODEL = "{} {}".format(
platform.python_implementation(),
platform.python_version()
)
SYSTEM_VERSION = "{} {}".format(
platform.system(),
platform.release()
)
SYSTEM_LANG_CODE = "en"
LANG_CODE = "en"
INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/joinchat/)([\w-]+)$")
BOT_TOKEN_RE = re.compile(r"^\d+:[\w-]+$") BOT_TOKEN_RE = re.compile(r"^\d+:[\w-]+$")
DIALOGS_AT_ONCE = 100 DIALOGS_AT_ONCE = 100
UPDATES_WORKERS = 1 UPDATES_WORKERS = 1
@ -75,7 +93,9 @@ class BaseClient:
self.download_queue = Queue() self.download_queue = Queue()
self.download_workers_list = [] self.download_workers_list = []
def send(self, data: Object): self.disconnect_handler = None
def send(self, data: Object, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT):
pass pass
def resolve_peer(self, peer_id: int or str): def resolve_peer(self, peer_id: int or str):

View File

@ -33,26 +33,28 @@ log = logging.getLogger(__name__)
# TODO: Organize the code better? # TODO: Organize the code better?
class Str(str): class Str(str):
def __init__(self, value): __slots__ = "_client", "_entities"
str.__init__(value)
self._markdown = None def __init__(self, *args):
self._html = None super().__init__()
self._client = None
self._entities = None
def init(self, client, entities):
self._client = client
self._entities = entities
@property
def text(self):
return self
@property @property
def markdown(self): def markdown(self):
return self._markdown return self._client.markdown.unparse(self, self._entities)
@markdown.setter
def markdown(self, value):
self._markdown = value
@property @property
def html(self): def html(self):
return self._html return self._client.html.unparse(self, self._entities)
@html.setter
def html(self, value):
self._html = value
ENTITIES = { ENTITIES = {
@ -413,7 +415,7 @@ def parse_messages(
date=doc.date date=doc.date
) )
elif types.DocumentAttributeAnimated in attributes: elif types.DocumentAttributeAnimated in attributes:
video_attributes = attributes[types.DocumentAttributeVideo] video_attributes = attributes.get(types.DocumentAttributeVideo, None)
gif = pyrogram_types.GIF( gif = pyrogram_types.GIF(
file_id=encode( file_id=encode(
@ -425,9 +427,9 @@ def parse_messages(
doc.access_hash doc.access_hash
) )
), ),
width=video_attributes.w, width=getattr(video_attributes, "w", 0),
height=video_attributes.h, height=getattr(video_attributes, "h", 0),
duration=video_attributes.duration, duration=getattr(video_attributes, "duration", 0),
thumb=parse_thumb(doc.thumb), thumb=parse_thumb(doc.thumb),
mime_type=doc.mime_type, mime_type=doc.mime_type,
file_size=doc.size, file_size=doc.size,
@ -581,16 +583,10 @@ def parse_messages(
) )
if m.text: if m.text:
args = (m.text, m.entities or []) m.text.init(m._client, m.entities or [])
m.text.markdown = client.markdown.unparse(*args)
m.text.html = client.html.unparse(*args)
if m.caption: if m.caption:
args = (m.caption, m.caption_entities or []) m.caption.init(m._client, m.caption_entities or [])
m.caption.markdown = client.markdown.unparse(*args)
m.caption.html = client.html.unparse(*args)
if message.reply_to_msg_id and replies: if message.reply_to_msg_id and replies:
while True: while True:
@ -715,6 +711,25 @@ def parse_messages(
return parsed_messages if is_list else parsed_messages[0] return parsed_messages if is_list else parsed_messages[0]
def parse_deleted_messages(
messages: list,
channel_id: int
) -> pyrogram_types.Messages:
parsed_messages = []
for message in messages:
parsed_messages.append(
pyrogram_types.Message(
message_id=message,
chat=(pyrogram_types.Chat(id=int("-100" + str(channel_id)), type="channel")
if channel_id is not None
else None)
)
)
return pyrogram_types.Messages(len(parsed_messages), parsed_messages)
def get_peer_id(input_peer) -> int: def get_peer_id(input_peer) -> int:
return ( return (
input_peer.user_id if isinstance(input_peer, types.InputPeerUser) input_peer.user_id if isinstance(input_peer, types.InputPeerUser)
@ -805,13 +820,30 @@ def parse_callback_query(client, callback_query, users):
peer_id = int("-100" + str(peer.channel_id)) peer_id = int("-100" + str(peer.channel_id))
return pyrogram_types.CallbackQuery( return pyrogram_types.CallbackQuery(
id=callback_query.query_id, id=str(callback_query.query_id),
from_user=parse_user(users[callback_query.user_id]), from_user=parse_user(users[callback_query.user_id]),
message=client.get_messages(peer_id, callback_query.msg_id), message=client.get_messages(peer_id, callback_query.msg_id),
chat_instance=str(callback_query.chat_instance), chat_instance=str(callback_query.chat_instance),
data=callback_query.data.decode(), data=callback_query.data.decode(),
game_short_name=callback_query.game_short_name game_short_name=callback_query.game_short_name
# TODO: add inline_message_id )
def parse_inline_callback_query(callback_query, users):
return pyrogram_types.CallbackQuery(
id=str(callback_query.query_id),
from_user=parse_user(users[callback_query.user_id]),
chat_instance=str(callback_query.chat_instance),
inline_message_id=b64encode(
pack(
"<iqq",
callback_query.msg_id.dc_id,
callback_query.msg_id.id,
callback_query.msg_id.access_hash
),
b"-_"
).decode().rstrip("="),
game_short_name=callback_query.game_short_name
) )

View File

@ -19,6 +19,7 @@
import re import re
from .filter import Filter from .filter import Filter
from ..types.reply_markup import InlineKeyboardMarkup, ReplyKeyboardMarkup
def build(name: str, func: callable, **kwargs) -> type: def build(name: str, func: callable, **kwargs) -> type:
@ -89,13 +90,13 @@ class Filters:
venue = build("Venue", lambda _, m: bool(m.venue)) venue = build("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."""
private = build("Private", lambda _, m: bool(m.chat.type == "private")) private = build("Private", lambda _, m: bool(m.chat and m.chat.type == "private"))
"""Filter messages sent in private chats.""" """Filter messages sent in private chats."""
group = build("Group", lambda _, m: bool(m.chat.type in {"group", "supergroup"})) group = build("Group", lambda _, m: bool(m.chat and m.chat.type in {"group", "supergroup"}))
"""Filter messages sent in group or supergroup chats.""" """Filter messages sent in group or supergroup chats."""
channel = build("Channel", lambda _, m: bool(m.chat.type == "channel")) channel = build("Channel", lambda _, m: bool(m.chat and m.chat.type == "channel"))
"""Filter messages sent in channels.""" """Filter messages sent in channels."""
new_chat_members = build("NewChatMembers", lambda _, m: bool(m.new_chat_members)) new_chat_members = build("NewChatMembers", lambda _, m: bool(m.new_chat_members))
@ -131,10 +132,17 @@ class Filters:
pinned_message = build("PinnedMessage", lambda _, m: bool(m.pinned_message)) pinned_message = build("PinnedMessage", lambda _, m: bool(m.pinned_message))
"""Filter service messages for pinned messages.""" """Filter service messages for pinned messages."""
# TODO: Add filters for reply markups reply_keyboard = build("ReplyKeyboard", lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup))
"""Filter messages containing reply keyboard markups"""
inline_keyboard = build("InlineKeyboard", lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup))
"""Filter messages containing inline keyboard markups"""
@staticmethod @staticmethod
def command(command: str or list, prefix: str = "/", separator: str = " "): def command(command: str or list,
prefix: str = "/",
separator: str = " ",
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.
Args: Args:
@ -144,25 +152,40 @@ 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``): prefix (``str``, *optional*):
The command prefix. Defaults to "/". The command prefix. Defaults to "/" (slash).
Examples: /start, .help, !settings. Examples: /start, .help, !settings.
separator (``str``): separator (``str``, *optional*):
The command arguments separator. Defaults to " " (white space). The command arguments separator. Defaults to " " (white space).
Examples: /start first second, /start-first-second, /start.first.second. Examples: /start first second, /start-first-second, /start.first.second.
case_sensitive (``bool``, *optional*):
Pass True if you want your command(s) to be case sensitive. Defaults to False.
Examples: when True, command="Start" would trigger /Start but not /start.
""" """
def f(_, m): def f(_, m):
if m.text and m.text.startswith(_.p): if m.text and m.text.startswith(_.p):
c = m.text[1:].split(_.s)[0] t = m.text.split(_.s)
m.command = ([c] + m.text.split(_.s)[1:]) if c in _.c else None c, a = t[0][len(_.p):], t[1:]
c = c if _.cs else c.lower()
m.command = ([c] + a) if c in _.c else None
return bool(m.command) return bool(m.command)
return build( return build(
"Command", f, c={command} if not isinstance(command, list) else {c for c in command}, "Command",
p=prefix, s=separator f,
c={command if case_sensitive
else command.lower()}
if not isinstance(command, list)
else {c if case_sensitive
else c.lower()
for c in command},
p=prefix,
s=separator,
cs=case_sensitive
) )
@staticmethod @staticmethod
@ -253,6 +276,7 @@ class Filters:
or Filters.photo(m) or Filters.photo(m)
or Filters.sticker(m) or Filters.sticker(m)
or Filters.video(m) or Filters.video(m)
or Filters.gif(m)
or Filters.voice(m) or Filters.voice(m)
or Filters.video_note(m) or Filters.video_note(m)
or Filters.contact(m) or Filters.contact(m)

View File

@ -16,4 +16,8 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .handlers import MessageHandler, CallbackQueryHandler, RawUpdateHandler from .callback_query_handler import CallbackQueryHandler
from .disconnect_handler import DisconnectHandler
from .message_handler import MessageHandler
from .deleted_messages_handler import DeletedMessagesHandler
from .raw_update_handler import RawUpdateHandler

View File

@ -0,0 +1,54 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .handler import Handler
class CallbackQueryHandler(Handler):
"""The CallbackQuery handler class. Used to handle callback queries coming from inline buttons.
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_callback_query() <pyrogram.Client.on_callback_query>` decorator.
Args:
callback (``callable``):
Pass a function that will be called when a new CallbackQuery arrives. It takes *(client, callback_query)*
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 callback queries to be passed
in your callback function.
Other parameters:
client (:obj:`Client <pyrogram.Client>`):
The Client itself, useful when you want to call other API methods inside the message handler.
callback_query (:obj:`CallbackQuery <pyrogram.CallbackQuery>`):
The received callback query.
"""
def __init__(self, callback: callable, filters=None):
super().__init__(callback, filters)
def check(self, callback_query):
return (
self.filters(callback_query)
if self.filters
else True
)

View File

@ -0,0 +1,55 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .handler import Handler
class DeletedMessagesHandler(Handler):
"""The deleted Messages handler class. Used to handle deleted messages coming from any chat
(private, group, channel). 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_deleted_messages() <pyrogram.Client.on_deleted_messages>` decorator.
Args:
callback (``callable``):
Pass a function that will be called when one or more Messages have been deleted.
It takes *(client, messages)* as positional arguments (look at the section below for a detailed description).
filters (:obj:`Filters <pyrogram.Filters>`):
Pass one or more filters to allow only a subset of messages to be passed
in your callback function.
Other parameters:
client (:obj:`Client <pyrogram.Client>`):
The Client itself, useful when you want to call other API methods inside the message handler.
messages (:obj:`Messages <pyrogram.Messages>`):
The deleted messages.
"""
def __init__(self, callback: callable, filters=None):
super().__init__(callback, filters)
def check(self, messages):
return (
self.filters(messages.messages[0])
if self.filters
else True
)

View File

@ -0,0 +1,41 @@
# 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 DisconnectHandler(Handler):
"""The Disconnect handler class. Used to handle disconnections. 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_disconnect() <pyrogram.Client.on_disconnect>` decorator.
Args:
callback (``callable``):
Pass a function that will be called when a disconnection occurs. It takes *(client)*
as positional argument (look at the section below for a detailed description).
Other parameters:
client (:obj:`Client <pyrogram.Client>`):
The Client itself. Useful, for example, when you want to change the proxy before a new connection
is established.
"""
def __init__(self, callback: callable):
super().__init__(callback)

View File

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

View File

@ -19,75 +19,13 @@
from .handler import Handler from .handler import Handler
class MessageHandler(Handler):
"""The Message handler class. Used to handle text, media and service messages coming from
any chat (private, group, channel). It is intended to be used with
:meth:`add_handler() <pyrogram.Client.add_handler>`
Args:
callback (``callable``):
Pass a function that will be called when a new Message arrives. It takes *(client, message)*
as positional arguments (look at the section below for a detailed description).
filters (:obj:`Filters <pyrogram.Filters>`):
Pass one or more filters to allow only a subset of messages to be passed
in your callback function.
Other parameters:
client (:obj:`Client <pyrogram.Client>`):
The Client itself, useful when you want to call other API methods inside the message handler.
message (:obj:`Message <pyrogram.Message>`):
The received message.
"""
def __init__(self, callback: callable, filters=None):
super().__init__(callback, filters)
def check(self, message):
return (
self.filters(message)
if self.filters
else True
)
class CallbackQueryHandler(Handler):
"""The CallbackQuery handler class. Used to handle callback queries coming from inline buttons.
It is intended to be used with :meth:`add_handler() <pyrogram.Client.add_handler>`
Args:
callback (``callable``):
Pass a function that will be called when a new CallbackQuery arrives. It takes *(client, callback_query)*
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 callback queries to be passed
in your callback function.
Other parameters:
client (:obj:`Client <pyrogram.Client>`):
The Client itself, useful when you want to call other API methods inside the message handler.
callback_query (:obj:`CallbackQuery <pyrogram.CallbackQuery>`):
The received callback query.
"""
def __init__(self, callback: callable, filters=None):
super().__init__(callback, filters)
def check(self, callback_query):
return (
self.filters(callback_query)
if self.filters
else True
)
class RawUpdateHandler(Handler): class RawUpdateHandler(Handler):
"""The Raw Update handler class. Used to handle raw updates. It is intended to be used with """The Raw Update handler class. Used to handle raw updates. It is intended to be used with
:meth:`add_handler() <pyrogram.Client.add_handler>` :meth:`add_handler() <pyrogram.Client.add_handler>`
For a nicer way to register this handler, have a look at the
:meth:`on_raw_update() <pyrogram.Client.on_raw_update>` decorator.
Args: Args:
callback (``callable``): callback (``callable``):
A function that will be called when a new update is received from the server. It takes A function that will be called when a new update is received from the server. It takes

View File

@ -16,12 +16,16 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# 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 import CallbackQuery from .answer_callback_query import AnswerCallbackQuery
from .inline import Inline from .get_inline_bot_results import GetInlineBotResults
from .request_callback_answer import RequestCallbackAnswer
from .send_inline_bot_result import SendInlineBotResult
class Bots( class Bots(
CallbackQuery, AnswerCallbackQuery,
Inline GetInlineBotResults,
RequestCallbackAnswer,
SendInlineBotResult
): ):
pass pass

View File

@ -17,7 +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 from pyrogram.api import functions
from ....ext import BaseClient from pyrogram.client.ext import BaseClient
class AnswerCallbackQuery(BaseClient): class AnswerCallbackQuery(BaseClient):

View File

@ -18,7 +18,7 @@
from pyrogram.api import functions, types from pyrogram.api import functions, types
from pyrogram.api.errors import UnknownError from pyrogram.api.errors import UnknownError
from ....ext import BaseClient from pyrogram.client.ext import BaseClient
class GetInlineBotResults(BaseClient): class GetInlineBotResults(BaseClient):

View File

@ -1,27 +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/>.
from .get_inline_bot_results import GetInlineBotResults
from .send_inline_bot_result import SendInlineBotResult
class Inline(
SendInlineBotResult,
GetInlineBotResults
):
pass

View File

@ -0,0 +1,60 @@
# 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
from pyrogram.client.ext import BaseClient
class RequestCallbackAnswer(BaseClient):
def request_callback_answer(self,
chat_id: int or str,
message_id: int,
callback_data: str):
"""Use this method to request a callback answer from bots. This is the equivalent of clicking an
inline button containing callback data.
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).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
message_id (``int``):
The message id the inline keyboard is attached on.
callback_data (``str``):
Callback data associated with the inline button you want to get the answer from.
Returns:
The answer containing info useful for clients to display a notification at the top of the chat screen
or as an alert.
Raises:
:class:`Error <pyrogram.Error>`
``TimeoutError``: If the bot fails to answer within 10 seconds
"""
return self.send(
functions.messages.GetBotCallbackAnswer(
peer=self.resolve_peer(chat_id),
msg_id=message_id,
data=callback_data.encode()
),
retries=0,
timeout=10
)

View File

@ -17,7 +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 from pyrogram.api import functions
from ....ext import BaseClient from pyrogram.client.ext import BaseClient
class SendInlineBotResult(BaseClient): class SendInlineBotResult(BaseClient):

View File

@ -19,13 +19,21 @@
from .export_chat_invite_link import ExportChatInviteLink from .export_chat_invite_link import ExportChatInviteLink
from .get_chat import GetChat from .get_chat import GetChat
from .join_chat import JoinChat from .join_chat import JoinChat
from .kick_chat_member import KickChatMember
from .leave_chat import LeaveChat from .leave_chat import LeaveChat
from .promote_chat_member import PromoteChatMember
from .restrict_chat_member import RestrictChatMember
from .unban_chat_member import UnbanChatMember
class Chats( class Chats(
GetChat, GetChat,
ExportChatInviteLink, ExportChatInviteLink,
LeaveChat, LeaveChat,
JoinChat JoinChat,
KickChatMember,
UnbanChatMember,
RestrictChatMember,
PromoteChatMember
): ):
pass pass

View File

@ -0,0 +1,87 @@
# 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 KickChatMember(BaseClient):
def kick_chat_member(self,
chat_id: int or str,
user_id: int or str,
until_date: int = 0):
"""Use this method to kick a user from a group, a supergroup or a channel.
In the case of supergroups and channels, the user will not be able to return to the group on their own using
invite links, etc., unless unbanned first. You must be an administrator in the chat for this to work and must
have the appropriate admin rights.
Note:
In regular groups (non-supergroups), this method will only work if the "All Members Are Admins" setting is
off in the target group. Otherwise members may only be removed by the group's creator or by the member
that added them.
Args:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For a private channel/supergroup you can use its *t.me/joinchat/* link.
user_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target user.
For a contact that exists in your Telegram address book you can use his phone number (str).
until_date (``int``, *optional*):
Date when the user will be unbanned, unix time.
If user is banned for more than 366 days or less than 30 seconds from the current time they are
considered to be banned forever. Defaults to 0 (ban forever).
Returns:
True on success.
Raises:
:class:`Error <pyrogram.Error>`
"""
chat_peer = self.resolve_peer(chat_id)
user_peer = self.resolve_peer(user_id)
if isinstance(chat_peer, types.InputPeerChannel):
self.send(
functions.channels.EditBanned(
channel=chat_peer,
user_id=user_peer,
banned_rights=types.ChannelBannedRights(
until_date=until_date,
view_messages=True,
send_messages=True,
send_media=True,
send_stickers=True,
send_gifs=True,
send_games=True,
send_inline=True,
embed_links=True
)
)
)
else:
self.send(
functions.messages.DeleteChatUser(
chat_id=abs(chat_id),
user_id=user_peer
)
)
return True

View File

@ -0,0 +1,99 @@
# 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 PromoteChatMember(BaseClient):
def promote_chat_member(self,
chat_id: int or str,
user_id: int or str,
can_change_info: bool = True,
can_post_messages: bool = True,
can_edit_messages: bool = True,
can_delete_messages: bool = True,
can_invite_users: bool = True,
can_restrict_members: bool = True,
can_pin_messages: bool = True,
can_promote_members: bool = False):
"""Use this method to promote or demote a user in a supergroup or a channel.
You must be an administrator in the chat for this to work and must have the appropriate admin rights.
Pass False for all boolean parameters to demote a user.
Args:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For a private channel/supergroup you can use its *t.me/joinchat/* link.
user_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target user.
For a contact that exists in your Telegram address book you can use his phone number (str).
can_change_info (``bool``, *optional*):
Pass True, if the administrator can change chat title, photo and other settings.
can_post_messages (``bool``, *optional*):
Pass True, if the administrator can create channel posts, channels only.
can_edit_messages (``bool``, *optional*):
Pass True, if the administrator can edit messages of other users and can pin messages, channels only.
can_delete_messages (``bool``, *optional*):
Pass True, if the administrator can delete messages of other users.
can_invite_users (``bool``, *optional*):
Pass True, if the administrator can invite new users to the chat.
can_restrict_members (``bool``, *optional*):
Pass True, if the administrator can restrict, ban or unban chat members.
can_pin_messages (``bool``, *optional*):
Pass True, if the administrator can pin messages, supergroups only.
can_promote_members (``bool``, *optional*):
Pass True, if the administrator can add new administrators with a subset of his own privileges or
demote administrators that he has promoted, directly or indirectly (promoted by administrators that
were appointed by him).
Returns:
True on success.
Raises:
:class:`Error <pyrogram.Error>`
"""
self.send(
functions.channels.EditAdmin(
channel=self.resolve_peer(chat_id),
user_id=self.resolve_peer(user_id),
admin_rights=types.ChannelAdminRights(
change_info=can_change_info or None,
post_messages=can_post_messages or None,
edit_messages=can_edit_messages or None,
delete_messages=can_delete_messages or None,
ban_users=can_restrict_members or None,
invite_users=can_invite_users or None,
invite_link=can_invite_users or None,
pin_messages=can_pin_messages or None,
add_admins=can_promote_members or None,
manage_call=None
)
)
)
return True

View File

@ -0,0 +1,113 @@
# 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 RestrictChatMember(BaseClient):
def restrict_chat_member(self,
chat_id: int or str,
user_id: int or str,
until_date: int = 0,
can_send_messages: bool = False,
can_send_media_messages: bool = False,
can_send_other_messages: bool = False,
can_add_web_page_previews: bool = False):
"""Use this method to restrict a user in a supergroup. The bot must be an administrator in the supergroup for
this to work and must have the appropriate admin rights. Pass True for all boolean parameters to lift
restrictions from a user.
Args:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For a private channel/supergroup you can use its *t.me/joinchat/* link.
user_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target user.
For a contact that exists in your Telegram address book you can use his phone number (str).
until_date (``int``, *optional*):
Date when the user will be unbanned, unix time.
If user is banned for more than 366 days or less than 30 seconds from the current time they are
considered to be banned forever. Defaults to 0 (ban forever).
can_send_messages (``bool``, *optional*):
Pass True, if the user can send text messages, contacts, locations and venues.
can_send_media_messages (``bool``, *optional*):
Pass True, if the user can send audios, documents, photos, videos, video notes and voice notes,
implies can_send_messages.
can_send_other_messages (``bool``, *optional*):
Pass True, if the user can send animations, games, stickers and use inline bots,
implies can_send_media_messages.
can_add_web_page_previews (``bool``, *optional*):
Pass True, if the user may add web page previews to their messages, implies can_send_media_messages
Returns:
True on success.
Raises:
:class:`Error <pyrogram.Error>`
"""
send_messages = True
send_media = True
send_stickers = True
send_gifs = True
send_games = True
send_inline = True
embed_links = True
if can_send_messages:
send_messages = None
if can_send_media_messages:
send_messages = None
send_media = None
if can_send_other_messages:
send_media = None
send_stickers = None
send_gifs = None
send_games = None
send_inline = None
if can_add_web_page_previews:
send_media = None
embed_links = None
self.send(
functions.channels.EditBanned(
channel=self.resolve_peer(chat_id),
user_id=self.resolve_peer(user_id),
banned_rights=types.ChannelBannedRights(
until_date=until_date,
send_messages=send_messages,
send_media=send_media,
send_stickers=send_stickers,
send_gifs=send_gifs,
send_games=send_games,
send_inline=send_inline,
embed_links=embed_links
)
)
)
return True

View File

@ -0,0 +1,56 @@
# 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 UnbanChatMember(BaseClient):
def unban_chat_member(self,
chat_id: int or str,
user_id: int or str):
"""Use this method to unban a previously kicked user in a supergroup or channel.
The user will **not** return to the group or channel automatically, but will be able to join via link, etc.
You must be an administrator for this to work.
Args:
chat_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target chat.
For a private channel/supergroup you can use its *t.me/joinchat/* link.
user_id (``int`` | ``str``):
Unique identifier (int) or username (str) of the target user.
For a contact that exists in your Telegram address book you can use his phone number (str).
Returns:
True on success.
Raises:
:class:`Error <pyrogram.Error>`
"""
self.send(
functions.channels.EditBanned(
channel=self.resolve_peer(chat_id),
user_id=self.resolve_peer(user_id),
banned_rights=types.ChannelBannedRights(
until_date=0
)
)
)
return True

View File

@ -17,9 +17,11 @@
# 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_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
class Decorators(OnMessage, OnCallbackQuery, OnRawUpdate): class Decorators(OnMessage, OnDeletedMessages, OnCallbackQuery, OnRawUpdate, OnDisconnect):
pass pass

View File

@ -24,7 +24,7 @@ class OnCallbackQuery(BaseClient):
def on_callback_query(self, filters=None, group: int = 0): def on_callback_query(self, 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
CallbackQueryHandler. :class:`CallbackQueryHandler`.
Args: Args:
filters (:obj:`Filters <pyrogram.Filters>`): filters (:obj:`Filters <pyrogram.Filters>`):

View File

@ -0,0 +1,42 @@
# 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 ...ext import BaseClient
class OnDeletedMessages(BaseClient):
def on_deleted_messages(self, filters=None, group: int = 0):
"""Use this decorator to automatically register a function for handling
deleted messages. This does the same thing as :meth:`add_handler` using the
:class:`DeletedMessagesHandler`.
Args:
filters (:obj:`Filters <pyrogram.Filters>`):
Pass one or more filters to allow only a subset of messages to be passed
in your function.
group (``int``, *optional*):
The group identifier, defaults to 0.
"""
def decorator(func):
self.add_handler(pyrogram.DeletedMessagesHandler(func, filters), group)
return func
return decorator

View File

@ -16,10 +16,19 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .answer_callback_query import AnswerCallbackQuery import pyrogram
from ...ext import BaseClient
class CallbackQuery( class OnDisconnect(BaseClient):
AnswerCallbackQuery def on_disconnect(self):
): """Use this decorator to automatically register a function for handling
pass disconnections. This does the same thing as :meth:`add_handler` using the
:class:`DisconnectHandler`.
"""
def decorator(func):
self.add_handler(pyrogram.DisconnectHandler(func))
return func
return decorator

View File

@ -24,7 +24,7 @@ class OnMessage(BaseClient):
def on_message(self, filters=None, group: int = 0): def on_message(self, 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
MessageHandler. :class:`MessageHandler`.
Args: Args:
filters (:obj:`Filters <pyrogram.Filters>`): filters (:obj:`Filters <pyrogram.Filters>`):

View File

@ -24,7 +24,7 @@ class OnRawUpdate(BaseClient):
def on_raw_update(self, group: int = 0): def on_raw_update(self, 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
RawUpdateHandler. :class:`RawUpdateHandler`.
Args: Args:
group (``int``, *optional*): group (``int``, *optional*):

View File

@ -16,22 +16,50 @@
# You should have received a copy of the GNU Lesser General Public License # You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .action import Action from .delete_messages import DeleteMessages
from .edit_message_caption import EditMessageCaption
from .edit_message_reply_markup import EditMessageReplyMarkup
from .edit_message_text import EditMessageText
from .forward_messages import ForwardMessages from .forward_messages import ForwardMessages
from .get_history import GetHistory from .get_history import GetHistory
from .get_messages import GetMessages from .get_messages import GetMessages
from .media import Media from .send_audio import SendAudio
from .send_chat_action import SendChatAction
from .send_contact import SendContact
from .send_document import SendDocument
from .send_gif import SendGIF
from .send_location import SendLocation
from .send_media_group import SendMediaGroup
from .send_message import SendMessage from .send_message import SendMessage
from .update import Update from .send_photo import SendPhoto
from .send_sticker import SendSticker
from .send_venue import SendVenue
from .send_video import SendVideo
from .send_video_note import SendVideoNote
from .send_voice import SendVoice
class Messages( class Messages(
DeleteMessages,
EditMessageCaption,
EditMessageReplyMarkup,
EditMessageText,
ForwardMessages,
GetHistory, GetHistory,
GetMessages, GetMessages,
Action, SendAudio,
Media, SendChatAction,
Update, SendContact,
ForwardMessages, SendDocument,
SendMessage SendGIF,
SendLocation,
SendMediaGroup,
SendMessage,
SendPhoto,
SendSticker,
SendVenue,
SendVideo,
SendVideoNote,
SendVoice
): ):
pass pass

View File

@ -1,25 +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/>.
from .send_chat_action import SendChatAction
class Action(
SendChatAction
):
pass

View File

@ -17,7 +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 ....ext import BaseClient from pyrogram.client.ext import BaseClient
class DeleteMessages(BaseClient): class DeleteMessages(BaseClient):

View File

@ -17,7 +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 ....ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
class EditMessageCaption(BaseClient): class EditMessageCaption(BaseClient):

View File

@ -17,7 +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 ....ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
class EditMessageReplyMarkup(BaseClient): class EditMessageReplyMarkup(BaseClient):

View File

@ -17,7 +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 ....ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
class EditMessageText(BaseClient): class EditMessageText(BaseClient):

View File

@ -1,47 +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/>.
from .send_audio import SendAudio
from .send_contact import SendContact
from .send_document import SendDocument
from .send_gif import SendGIF
from .send_location import SendLocation
from .send_media_group import SendMediaGroup
from .send_photo import SendPhoto
from .send_sticker import SendSticker
from .send_venue import SendVenue
from .send_video import SendVideo
from .send_video_note import SendVideoNote
from .send_voice import SendVoice
class Media(
SendContact,
SendVenue,
SendLocation,
SendMediaGroup,
SendVideoNote,
SendVoice,
SendVideo,
SendGIF,
SendSticker,
SendDocument,
SendAudio,
SendPhoto
):
pass

View File

@ -23,7 +23,7 @@ import struct
from pyrogram.api import functions, types from pyrogram.api import functions, types
from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.api.errors import FileIdInvalid, FilePartMissing
from ....ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
class SendAudio(BaseClient): class SendAudio(BaseClient):

View File

@ -17,7 +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 from pyrogram.api import functions
from ....ext import BaseClient, ChatAction from pyrogram.client.ext import BaseClient, ChatAction
class SendChatAction(BaseClient): class SendChatAction(BaseClient):

View File

@ -17,7 +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 ....ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
class SendContact(BaseClient): class SendContact(BaseClient):

View File

@ -23,7 +23,7 @@ import struct
from pyrogram.api import functions, types from pyrogram.api import functions, types
from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.api.errors import FileIdInvalid, FilePartMissing
from ....ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
class SendDocument(BaseClient): class SendDocument(BaseClient):

View File

@ -23,7 +23,7 @@ import struct
from pyrogram.api import functions, types from pyrogram.api import functions, types
from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.api.errors import FileIdInvalid, FilePartMissing
from ....ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
class SendGIF(BaseClient): class SendGIF(BaseClient):

View File

@ -17,7 +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 ....ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
class SendLocation(BaseClient): class SendLocation(BaseClient):

View File

@ -24,7 +24,7 @@ import struct
from pyrogram.api import functions, types from pyrogram.api import functions, types
from pyrogram.api.errors import FileIdInvalid from pyrogram.api.errors import FileIdInvalid
from pyrogram.client import types as pyrogram_types from pyrogram.client import types as pyrogram_types
from ....ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
class SendMediaGroup(BaseClient): class SendMediaGroup(BaseClient):

View File

@ -22,7 +22,7 @@ import struct
from pyrogram.api import functions, types from pyrogram.api import functions, types
from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.api.errors import FileIdInvalid, FilePartMissing
from ....ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
class SendPhoto(BaseClient): class SendPhoto(BaseClient):

View File

@ -22,7 +22,7 @@ import struct
from pyrogram.api import functions, types from pyrogram.api import functions, types
from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.api.errors import FileIdInvalid, FilePartMissing
from ....ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
class SendSticker(BaseClient): class SendSticker(BaseClient):

View File

@ -17,7 +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 ....ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
class SendVenue(BaseClient): class SendVenue(BaseClient):

View File

@ -23,7 +23,7 @@ import struct
from pyrogram.api import functions, types from pyrogram.api import functions, types
from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.api.errors import FileIdInvalid, FilePartMissing
from ....ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
class SendVideo(BaseClient): class SendVideo(BaseClient):

View File

@ -23,7 +23,7 @@ import struct
from pyrogram.api import functions, types from pyrogram.api import functions, types
from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.api.errors import FileIdInvalid, FilePartMissing
from ....ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
class SendVideoNote(BaseClient): class SendVideoNote(BaseClient):

View File

@ -23,7 +23,7 @@ import struct
from pyrogram.api import functions, types from pyrogram.api import functions, types
from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.api.errors import FileIdInvalid, FilePartMissing
from ....ext import BaseClient, utils from pyrogram.client.ext import BaseClient, utils
class SendVoice(BaseClient): class SendVoice(BaseClient):

View File

@ -1,31 +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/>.
from .delete_messages import DeleteMessages
from .edit_message_caption import EditMessageCaption
from .edit_message_reply_markup import EditMessageReplyMarkup
from .edit_message_text import EditMessageText
class Update(
DeleteMessages,
EditMessageReplyMarkup,
EditMessageCaption,
EditMessageText
):
pass

View File

@ -30,6 +30,7 @@ from .input_phone_contact import InputPhoneContact
from .location import Location from .location import Location
from .message import Message from .message import Message
from .message_entity import MessageEntity from .message_entity import MessageEntity
from .messages import Messages
from .photo_size import PhotoSize from .photo_size import PhotoSize
from .reply_markup import ( from .reply_markup import (
ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup,
@ -43,4 +44,3 @@ from .venue import Venue
from .video import Video from .video import Video
from .video_note import VideoNote from .video_note import VideoNote
from .voice import Voice from .voice import Voice
from .messages import Messages

View File

@ -17,6 +17,7 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>. # along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from pyrogram.api.core import Object from pyrogram.api.core import Object
from .reply_markup import InlineKeyboardMarkup, ReplyKeyboardMarkup
class Message(Object): class Message(Object):
@ -260,7 +261,7 @@ class Message(Object):
reply_markup=None, reply_markup=None,
): ):
self.message_id = message_id # int self.message_id = message_id # int
self.client = client self._client = client
self.date = date # int self.date = date # int
self.chat = chat # Chat self.chat = chat # Chat
self.from_user = from_user # flags.0?User self.from_user = from_user # flags.0?User
@ -309,3 +310,267 @@ class Message(Object):
self.matches = matches self.matches = matches
self.command = command self.command = command
self.reply_markup = reply_markup self.reply_markup = reply_markup
def reply(self,
text: str,
quote: bool = None,
parse_mode: str = "",
disable_web_page_preview: bool = None,
disable_notification: bool = None,
reply_to_message_id: int = None,
reply_markup=None):
"""Use this method as a shortcut for:
.. code-block:: python
client.send_message(
chat_id=message.chat.id,
text="hello",
reply_to_message_id=message.message_id
)
Example:
.. code-block:: python
message.reply("hello", quote=True)
Args:
text (``str``):
Text of the message to be sent.
quote (``bool``, *optional*):
If ``True``, the message will be sent as a reply to this message.
If *reply_to_message_id* is passed, this parameter will be ignored.
Defaults to ``True`` in group chats and ``False`` in private chats.
parse_mode (``str``, *optional*):
Use :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or :obj:`HTML <pyrogram.ParseMode.HTML>`
if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your message.
Defaults to Markdown.
disable_web_page_preview (``bool``, *optional*):
Disables link previews for links in this message.
disable_notification (``bool``, *optional*):
Sends the message silently.
Users will receive a notification with no sound.
reply_to_message_id (``int``, *optional*):
If the message is a reply, ID of the original message.
reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*):
Additional interface options. An object for an inline keyboard, custom reply keyboard,
instructions to remove reply keyboard or to force a reply from the user.
Returns:
On success, the sent Message is returned.
Raises:
:class:`Error <pyrogram.Error>`
"""
if quote is None:
quote = self.chat.type != "private"
if reply_to_message_id is None and quote:
reply_to_message_id = self.message_id
return self._client.send_message(
chat_id=self.chat.id,
text=text,
parse_mode=parse_mode,
disable_web_page_preview=disable_web_page_preview,
disable_notification=disable_notification,
reply_to_message_id=reply_to_message_id,
reply_markup=reply_markup
)
def forward(self,
chat_id: int or str,
disable_notification: bool = None):
"""Use this method as a shortcut for:
.. code-block:: python
client.forward_messages(
chat_id=chat_id,
from_chat_id=message.chat.id,
message_ids=message.message_id,
)
Example:
.. code-block:: python
message.forward(chat_id)
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).
For a private channel/supergroup you can use its *t.me/joinchat/* link.
disable_notification (``bool``, *optional*):
Sends the message silently.
Users will receive a notification with no sound.
Returns:
On success, the forwarded Message is returned.
Raises:
:class:`Error <pyrogram.Error>`
"""
return self._client.forward_messages(
chat_id=chat_id,
from_chat_id=self.chat.id,
message_ids=self.message_id,
disable_notification=disable_notification
)
def delete(self, revoke: bool = True):
"""Use this method as a shortcut for:
.. code-block:: python
client.delete_messages(
chat_id=chat_id,
message_ids=message.message_id
)
Example:
.. code-block:: python
message.delete()
Args:
revoke (``bool``, *optional*):
Deletes messages on both parts.
This is only for private cloud chats and normal groups, messages on
channels and supergroups are always revoked (i.e.: deleted for everyone).
Defaults to True.
Returns:
True on success.
Raises:
:class:`Error <pyrogram.Error>`
"""
self._client.delete_messages(
chat_id=self.chat.id,
message_ids=self.message_id,
revoke=revoke
)
return True
def click(self, x: int or str, y: int = None, quote: bool = None):
"""Use this method to click a button attached to the message.
It's a shortcut for:
- Clicking inline buttons:
.. code-block:: python
client.request_callback_answer(
chat_id=message.chat.id,
message_id=message.message_id,
callback_data=message.reply_markup[i][j].callback_data
)
- Clicking normal buttons:
.. code-block:: python
client.send_message(
chat_id=message.chat.id,
text=message.reply_markup[i][j].text
)
This method can be used in three different ways:
1. Pass one integer argument only (e.g.: ``.click(2)``, to click a button at index 2).
Buttons are counted left to right, starting from the top.
2. Pass two integer arguments (e.g.: ``.click(1, 0)``, to click a button at position (1, 0)).
The origin (0, 0) is top-left.
3. Pass one string argument only (e.g.: ``.click("Settings")``, to click a button by using its label).
Only the first matching button will be pressed.
Args:
x (``int`` | ``str``):
Used as integer index, integer abscissa (in pair with y) or as string label.
y (``int``, *optional*):
Used as ordinate only (in pair with x).
quote (``bool``, *optional*):
Useful for normal buttons only, where pressing it will result in a new message sent.
If ``True``, the message will be sent as a reply to this message.
Defaults to ``True`` in group chats and ``False`` in private chats.
Returns:
- The result of *request_callback_answer()* in case of inline callback button clicks.
- The result of *reply()* in case of normal button clicks.
- A string in case the inline button is an URL, switch_inline_query or switch_inline_query_current_chat
button.
Raises:
:class:`Error <pyrogram.Error>`
``ValueError``: If the provided index or position is out of range or the button label was not found
``TimeoutError``: If, after clicking an inline button, the bot fails to answer within 10 seconds
"""
if isinstance(self.reply_markup, ReplyKeyboardMarkup):
if quote is None:
quote = self.chat.type != "private"
return self.reply(x, quote=quote)
elif isinstance(self.reply_markup, InlineKeyboardMarkup):
if isinstance(x, int) and y is None:
try:
button = [
button
for row in self.reply_markup.inline_keyboard
for button in row
][x]
except IndexError:
raise ValueError("The button at index {} doesn't exist".format(x)) from None
elif isinstance(x, int) and isinstance(y, int):
try:
button = self.reply_markup.inline_keyboard[y][x]
except IndexError:
raise ValueError("The button at position ({}, {}) doesn't exist".format(x, y)) from None
elif isinstance(x, str):
x = x.encode("utf-16", "surrogatepass").decode("utf-16")
try:
button = [
button
for row in self.reply_markup.inline_keyboard
for button in row
if x == button.text
][0]
except IndexError:
raise ValueError(
"The button with label '{}' doesn't exists".format(
x.encode("unicode_escape").decode()
)
) from None
else:
raise ValueError("Invalid arguments")
if button.callback_data:
return self._client.request_callback_answer(
chat_id=self.chat.id,
message_id=self.message_id,
callback_data=button.callback_data
)
elif button.url:
return button.url
elif button.switch_inline_query:
return button.switch_inline_query
elif button.switch_inline_query_current_chat:
return button.switch_inline_query_current_chat
else:
raise ValueError("This button is not supported yet")
else:
raise ValueError("The message doesn't contain any keyboard")

View File

@ -30,12 +30,18 @@ class Update(Object):
edited_message (:obj:`Message <pyrogram.Message>`, *optional*): edited_message (:obj:`Message <pyrogram.Message>`, *optional*):
New version of a message that is known to the bot and was edited. New version of a message that is known to the bot and was edited.
deleted_messages (:obj:`Messages <pyrogram.Messages>`, *optional*):
Deleted messages.
channel_post (:obj:`Message <pyrogram.Message>`, *optional*): channel_post (:obj:`Message <pyrogram.Message>`, *optional*):
New incoming channel post of any kind text, photo, sticker, etc. New incoming channel post of any kind text, photo, sticker, etc.
edited_channel_post (:obj:`Message <pyrogram.Message>`, *optional*): edited_channel_post (:obj:`Message <pyrogram.Message>`, *optional*):
New version of a channel post that is known to the bot and was edited. New version of a channel post that is known to the bot and was edited.
deleted_channel_posts (:obj:`Messages <pyrogram.Messages>`, *optional*):
Deleted channel posts.
inline_query (:obj:`InlineQuery <pyrogram.InlineQuery>`, *optional*): inline_query (:obj:`InlineQuery <pyrogram.InlineQuery>`, *optional*):
New incoming inline query. New incoming inline query.
@ -60,8 +66,10 @@ class Update(Object):
self, self,
message=None, message=None,
edited_message=None, edited_message=None,
deleted_messages=None,
channel_post=None, channel_post=None,
edited_channel_post=None, edited_channel_post=None,
deleted_channel_posts=None,
inline_query=None, inline_query=None,
chosen_inline_result=None, chosen_inline_result=None,
callback_query=None, callback_query=None,
@ -70,8 +78,10 @@ class Update(Object):
): ):
self.message = message self.message = message
self.edited_message = edited_message self.edited_message = edited_message
self.deleted_messages = deleted_messages
self.channel_post = channel_post self.channel_post = channel_post
self.edited_channel_post = edited_channel_post self.edited_channel_post = edited_channel_post
self.deleted_channel_posts = deleted_channel_posts
self.inline_query = inline_query self.inline_query = inline_query
self.chosen_inline_result = chosen_inline_result self.chosen_inline_result = chosen_inline_result
self.callback_query = callback_query self.callback_query = callback_query

View File

@ -35,9 +35,6 @@ class VideoNote(Object):
thumb (:obj:`PhotoSize <pyrogram.PhotoSize>`, *optional*): thumb (:obj:`PhotoSize <pyrogram.PhotoSize>`, *optional*):
Video thumbnail. Video thumbnail.
file_size (``int``, *optional*):
File size.
file_name (``str``, *optional*): file_name (``str``, *optional*):
Video note file name. Video note file name.

View File

@ -26,10 +26,14 @@ log = logging.getLogger(__name__)
class Connection: class Connection:
MAX_RETRIES = 3
MODES = { MODES = {
0: TCPFull, 0: TCPFull,
1: TCPAbridged, 1: TCPAbridged,
2: TCPIntermediate 2: TCPIntermediate,
3: TCPAbridgedO,
4: TCPIntermediateO
} }
def __init__(self, address: tuple, proxy: dict, mode: int = 1): def __init__(self, address: tuple, proxy: dict, mode: int = 1):
@ -40,7 +44,7 @@ class Connection:
self.connection = None self.connection = None
def connect(self): def connect(self):
while True: for i in range(Connection.MAX_RETRIES):
self.connection = self.mode(self.proxy) self.connection = self.mode(self.proxy)
try: try:
@ -51,6 +55,8 @@ class Connection:
time.sleep(1) time.sleep(1)
else: else:
break break
else:
raise TimeoutError
def close(self): def close(self):
self.connection.close() self.connection.close()

View File

@ -17,5 +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 .tcp_abridged import TCPAbridged from .tcp_abridged import TCPAbridged
from .tcp_abridged_o import TCPAbridgedO
from .tcp_full import TCPFull from .tcp_full import TCPFull
from .tcp_intermediate import TCPIntermediate from .tcp_intermediate import TCPIntermediate
from .tcp_intermediate_o import TCPIntermediateO

View File

@ -41,15 +41,15 @@ class TCP(socks.socksocket):
if proxy and self.proxy_enabled: if proxy and self.proxy_enabled:
self.set_proxy( self.set_proxy(
proxy_type=socks.SOCKS5, proxy_type=socks.SOCKS5,
addr=proxy["hostname"], addr=proxy.get("hostname", None),
port=proxy["port"], port=proxy.get("port", None),
username=proxy["username"], username=proxy.get("username", None),
password=proxy["password"] password=proxy.get("password", None)
) )
log.info("Using proxy {}:{}".format( log.info("Using proxy {}:{}".format(
proxy["hostname"], proxy.get("hostname", None),
proxy["port"] proxy.get("port", None)
)) ))
def close(self): def close(self):

View File

@ -26,28 +26,23 @@ log = logging.getLogger(__name__)
class TCPAbridged(TCP): class TCPAbridged(TCP):
def __init__(self, proxy: dict): def __init__(self, proxy: dict):
super().__init__(proxy) super().__init__(proxy)
self.is_first_packet = None
def connect(self, address: tuple): def connect(self, address: tuple):
super().connect(address) super().connect(address)
self.is_first_packet = True super().sendall(b"\xef")
log.info("Connected{}!".format(" with proxy" if self.proxy_enabled else "")) log.info("Connected{}!".format(" with proxy" if self.proxy_enabled else ""))
def sendall(self, data: bytes, *args): def sendall(self, data: bytes, *args):
length = len(data) // 4 length = len(data) // 4
data = ( super().sendall(
bytes([length]) + data (bytes([length])
if length <= 126 if length <= 126
else b"\x7f" + int.to_bytes(length, 3, "little") + data else b"\x7f" + length.to_bytes(3, "little"))
+ data
) )
if self.is_first_packet:
data = b"\xef" + data
self.is_first_packet = False
super().sendall(data)
def recvall(self, length: int = 0) -> bytes or None: def recvall(self, length: int = 0) -> bytes or None:
length = super().recvall(1) length = super().recvall(1)

View File

@ -0,0 +1,93 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import logging
import os
from .tcp import TCP
from ....crypto.aes import AES
log = logging.getLogger(__name__)
class TCPAbridgedO(TCP):
RESERVED = (b"HEAD", b"POST", b"GET ", b"OPTI", b"\xee" * 4)
def __init__(self, proxy: dict):
super().__init__(proxy)
self.encrypt = None
self.decrypt = None
def connect(self, address: tuple):
super().connect(address)
while True:
nonce = bytearray(os.urandom(64))
if (nonce[0] != b"\xef"
and nonce[:4] not in self.RESERVED
and nonce[4:4] != b"\x00" * 4):
nonce[56] = nonce[57] = nonce[58] = nonce[59] = 0xef
break
temp = bytearray(nonce[55:7:-1])
self.encrypt = (nonce[8:40], nonce[40:56], bytearray(1))
self.decrypt = (temp[0:32], temp[32:48], bytearray(1))
nonce[56:64] = AES.ctr256_encrypt(nonce, *self.encrypt)[56:64]
super().sendall(nonce)
log.info("Connected{}!".format(" with proxy" if self.proxy_enabled else ""))
def sendall(self, data: bytes, *args):
length = len(data) // 4
super().sendall(
AES.ctr256_encrypt(
(bytes([length])
if length <= 126
else b"\x7f" + length.to_bytes(3, "little"))
+ data,
*self.encrypt
)
)
def recvall(self, length: int = 0) -> bytes or None:
length = super().recvall(1)
if length is None:
return None
length = AES.ctr256_decrypt(length, *self.decrypt)
if length == b"\x7f":
length = super().recvall(3)
if length is None:
return None
length = AES.ctr256_decrypt(length, *self.decrypt)
data = super().recvall(int.from_bytes(length, "little") * 4)
if data is None:
return None
return AES.ctr256_decrypt(data, *self.decrypt)

View File

@ -19,7 +19,7 @@
import logging import logging
from struct import pack, unpack from struct import pack, unpack
from .tcp_abridged import TCP from .tcp import TCP
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -27,22 +27,15 @@ log = logging.getLogger(__name__)
class TCPIntermediate(TCP): class TCPIntermediate(TCP):
def __init__(self, proxy: dict): def __init__(self, proxy: dict):
super().__init__(proxy) super().__init__(proxy)
self.is_first_packet = None
def connect(self, address: tuple): def connect(self, address: tuple):
super().connect(address) super().connect(address)
self.is_first_packet = True super().sendall(b"\xee" * 4)
log.info("Connected{}!".format(" with proxy" if self.proxy_enabled else "")) log.info("Connected{}!".format(" with proxy" if self.proxy_enabled else ""))
def sendall(self, data: bytes, *args): def sendall(self, data: bytes, *args):
length = len(data) super().sendall(pack("<i", len(data)) + data)
data = pack("<i", length) + data
if self.is_first_packet:
data = b"\xee" * 4 + data
self.is_first_packet = False
super().sendall(data)
def recvall(self, length: int = 0) -> bytes or None: def recvall(self, length: int = 0) -> bytes or None:
length = super().recvall(4) length = super().recvall(4)
@ -50,4 +43,4 @@ class TCPIntermediate(TCP):
if length is None: if length is None:
return None return None
return super().recvall(unpack("<I", length)[0]) return super().recvall(unpack("<i", length)[0])

View File

@ -0,0 +1,81 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# This file is part of Pyrogram.
#
# Pyrogram is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Pyrogram is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import logging
import os
from struct import pack, unpack
from .tcp import TCP
from ....crypto.aes import AES
log = logging.getLogger(__name__)
class TCPIntermediateO(TCP):
RESERVED = (b"HEAD", b"POST", b"GET ", b"OPTI", b"\xee" * 4)
def __init__(self, proxy: dict):
super().__init__(proxy)
self.encrypt = None
self.decrypt = None
def connect(self, address: tuple):
super().connect(address)
while True:
nonce = bytearray(os.urandom(64))
if (nonce[0] != b"\xef"
and nonce[:4] not in self.RESERVED
and nonce[4:4] != b"\x00" * 4):
nonce[56] = nonce[57] = nonce[58] = nonce[59] = 0xee
break
temp = bytearray(nonce[55:7:-1])
self.encrypt = (nonce[8:40], nonce[40:56], bytearray(1))
self.decrypt = (temp[0:32], temp[32:48], bytearray(1))
nonce[56:64] = AES.ctr256_encrypt(nonce, *self.encrypt)[56:64]
super().sendall(nonce)
log.info("Connected{}!".format(" with proxy" if self.proxy_enabled else ""))
def sendall(self, data: bytes, *args):
super().sendall(
AES.ctr256_encrypt(
pack("<i", len(data)) + data,
*self.encrypt
)
)
def recvall(self, length: int = 0) -> bytes or None:
length = super().recvall(4)
if length is None:
return None
length = AES.ctr256_decrypt(length, *self.decrypt)
data = super().recvall(unpack("<i", length)[0])
if data is None:
return None
return AES.ctr256_decrypt(data, *self.decrypt)

View File

@ -22,71 +22,114 @@ log = logging.getLogger(__name__)
try: try:
import tgcrypto import tgcrypto
log.info("Using TgCrypto")
class AES:
# TODO: Use new tgcrypto function names
@classmethod
def ige256_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
return tgcrypto.ige_encrypt(data, key, iv)
@classmethod
def ige256_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
return tgcrypto.ige_decrypt(data, key, iv)
@staticmethod
def ctr256_encrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
return tgcrypto.ctr_encrypt(data, key, iv, state or bytearray(1))
@staticmethod
def ctr256_decrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
return tgcrypto.ctr_decrypt(data, key, iv, state or bytearray(1))
@staticmethod
def xor(a: bytes, b: bytes) -> bytes:
return int.to_bytes(
int.from_bytes(a, "big") ^ int.from_bytes(b, "big"),
len(a),
"big",
)
except ImportError: except ImportError:
import pyaes
log.warning( log.warning(
"TgCrypto is missing! " "TgCrypto is missing! "
"Pyrogram will work the same, but at a much slower speed. " "Pyrogram will work the same, but at a much slower speed. "
"More info: https://docs.pyrogram.ml/resources/TgCrypto" "More info: https://docs.pyrogram.ml/resources/TgCrypto"
) )
is_fast = False
import pyaes
else:
log.info("Using TgCrypto")
is_fast = True
# TODO: Ugly IFs class AES:
class AES: @classmethod
@classmethod def ige256_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
def ige_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
if is_fast:
return tgcrypto.ige_encrypt(data, key, iv)
else:
return cls.ige(data, key, iv, True) return cls.ige(data, key, iv, True)
@classmethod @classmethod
def ige_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes: def ige256_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
if is_fast:
return tgcrypto.ige_decrypt(data, key, iv)
else:
return cls.ige(data, key, iv, False) return cls.ige(data, key, iv, False)
@staticmethod @classmethod
def ctr_decrypt(data: bytes, key: bytes, iv: bytes, offset: int) -> bytes: def ctr256_encrypt(cls, data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
replace = int.to_bytes(offset // 16, 4, "big") return cls.ctr(data, key, iv, state or bytearray(1))
iv = iv[:-4] + replace
if is_fast: @classmethod
return tgcrypto.ctr_decrypt(data, key, iv) def ctr256_decrypt(cls, data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
else: return cls.ctr(data, key, iv, state or bytearray(1))
ctr = pyaes.AESModeOfOperationCTR(key)
ctr._counter._counter = list(iv)
return ctr.decrypt(data)
@staticmethod @staticmethod
def xor(a: bytes, b: bytes) -> bytes: def xor(a: bytes, b: bytes) -> bytes:
return int.to_bytes( return int.to_bytes(
int.from_bytes(a, "big") ^ int.from_bytes(b, "big"), int.from_bytes(a, "big") ^ int.from_bytes(b, "big"),
len(a), len(a),
"big", "big",
) )
@classmethod @classmethod
def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes: def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
cipher = pyaes.AES(key) cipher = pyaes.AES(key)
iv_1 = iv[:16] iv_1 = iv[:16]
iv_2 = iv[16:] iv_2 = iv[16:]
data = [data[i: i + 16] for i in range(0, len(data), 16)] data = [data[i: i + 16] for i in range(0, len(data), 16)]
if encrypt: if encrypt:
for i, chunk in enumerate(data): for i, chunk in enumerate(data):
iv_1 = data[i] = cls.xor(cipher.encrypt(cls.xor(chunk, iv_1)), iv_2) iv_1 = data[i] = cls.xor(cipher.encrypt(cls.xor(chunk, iv_1)), iv_2)
iv_2 = chunk iv_2 = chunk
else: else:
for i, chunk in enumerate(data): for i, chunk in enumerate(data):
iv_2 = data[i] = cls.xor(cipher.decrypt(cls.xor(chunk, iv_2)), iv_1) iv_2 = data[i] = cls.xor(cipher.decrypt(cls.xor(chunk, iv_2)), iv_1)
iv_1 = chunk iv_1 = chunk
return b"".join(data) return b"".join(data)
@classmethod
def ctr(cls, data: bytes, key: bytes, iv: bytearray, state: bytearray) -> bytes:
cipher = pyaes.AES(key)
out = bytearray(data)
chunk = cipher.encrypt(iv)
for i in range(0, len(data), 16):
for j in range(0, min(len(data) - i, 16)):
out[i + j] ^= chunk[state[0]]
state[0] += 1
if state[0] >= 16:
state[0] = 0
if state[0] == 0:
for k in range(15, -1, -1):
try:
iv[k] += 1
break
except ValueError:
iv[k] = 0
chunk = cipher.encrypt(iv)
return out

View File

@ -49,8 +49,9 @@ class Auth:
def __init__(self, dc_id: int, test_mode: bool, proxy: dict): def __init__(self, dc_id: int, test_mode: bool, proxy: dict):
self.dc_id = dc_id self.dc_id = dc_id
self.test_mode = test_mode self.test_mode = test_mode
self.proxy = proxy
self.connection = Connection(DataCenter(dc_id, test_mode), proxy) self.connection = None
@staticmethod @staticmethod
def pack(data: Object) -> bytes: def pack(data: Object) -> bytes:
@ -83,6 +84,8 @@ class Auth:
# The server may close the connection at any time, causing the auth key creation to fail. # The server may close the connection at any time, causing the auth key creation to fail.
# If that happens, just try again up to MAX_RETRIES times. # If that happens, just try again up to MAX_RETRIES times.
while True: while True:
self.connection = Connection(DataCenter(self.dc_id, self.test_mode), self.proxy)
try: try:
log.info("Start creating a new auth key on DC{}".format(self.dc_id)) log.info("Start creating a new auth key on DC{}".format(self.dc_id))
@ -163,7 +166,7 @@ class Auth:
server_nonce = int.from_bytes(server_nonce, "little", signed=True) server_nonce = int.from_bytes(server_nonce, "little", signed=True)
answer_with_hash = AES.ige_decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv) answer_with_hash = AES.ige256_decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv)
answer = answer_with_hash[20:] answer = answer_with_hash[20:]
server_dh_inner_data = Object.read(BytesIO(answer)) server_dh_inner_data = Object.read(BytesIO(answer))
@ -192,7 +195,7 @@ class Auth:
sha = sha1(data).digest() sha = sha1(data).digest()
padding = urandom(- (len(data) + len(sha)) % 16) padding = urandom(- (len(data) + len(sha)) % 16)
data_with_hash = sha + data + padding data_with_hash = sha + data + padding
encrypted_data = AES.ige_encrypt(data_with_hash, tmp_aes_key, tmp_aes_iv) encrypted_data = AES.ige256_encrypt(data_with_hash, tmp_aes_key, tmp_aes_iv)
log.debug("Send set_client_DH_params") log.debug("Send set_client_DH_params")
set_client_dh_params_answer = self.send( set_client_dh_params_answer = self.send(

View File

@ -17,8 +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 pyrogram.api.core import Message, MsgContainer, Object from pyrogram.api.core import Message, MsgContainer, Object
from pyrogram.api.functions import Ping, HttpWait from pyrogram.api.functions import Ping
from pyrogram.api.types import MsgsAck from pyrogram.api.types import MsgsAck, HttpWait
from .msg_id import MsgId from .msg_id import MsgId
from .seq_no import SeqNo from .seq_no import SeqNo

View File

@ -17,7 +17,6 @@
# 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 platform
import threading import threading
import time import time
from datetime import timedelta, datetime from datetime import timedelta, datetime
@ -47,19 +46,6 @@ class Result:
class Session: class Session:
VERSION = __version__
APP_VERSION = "Pyrogram \U0001f525 {}".format(VERSION)
DEVICE_MODEL = "{} {}".format(
platform.python_implementation(),
platform.python_version()
)
SYSTEM_VERSION = "{} {}".format(
platform.system(),
platform.release()
)
INITIAL_SALT = 0x616e67656c696361 INITIAL_SALT = 0x616e67656c696361
NET_WORKERS = 1 NET_WORKERS = 1
WAIT_TIMEOUT = 15 WAIT_TIMEOUT = 15
@ -84,24 +70,24 @@ class Session:
} }
def __init__(self, def __init__(self,
client: pyrogram,
dc_id: int, dc_id: int,
test_mode: bool,
proxy: dict,
auth_key: bytes, auth_key: bytes,
api_id: int, is_media: bool = False,
is_cdn: bool = False, is_cdn: bool = False):
client: pyrogram = None):
if not Session.notice_displayed: if not Session.notice_displayed:
print("Pyrogram v{}, {}".format(__version__, __copyright__)) print("Pyrogram v{}, {}".format(__version__, __copyright__))
print("Licensed under the terms of the " + __license__, end="\n\n") print("Licensed under the terms of the " + __license__, end="\n\n")
Session.notice_displayed = True Session.notice_displayed = True
self.connection = Connection(DataCenter(dc_id, test_mode), proxy)
self.api_id = api_id
self.is_cdn = is_cdn
self.client = client self.client = client
self.dc_id = dc_id
self.auth_key = auth_key self.auth_key = auth_key
self.is_media = is_media
self.is_cdn = is_cdn
self.connection = None
self.auth_key_id = sha1(auth_key).digest()[-8:] self.auth_key_id = sha1(auth_key).digest()[-8:]
self.session_id = Long(MsgId()) self.session_id = Long(MsgId())
@ -126,6 +112,8 @@ class Session:
def start(self): def start(self):
while True: while True:
self.connection = Connection(DataCenter(self.dc_id, self.client.test_mode), self.client.proxy)
try: try:
self.connection.connect() self.connection.connect()
@ -153,12 +141,14 @@ class Session:
functions.InvokeWithLayer( functions.InvokeWithLayer(
layer, layer,
functions.InitConnection( functions.InitConnection(
self.api_id, api_id=self.client.api_id,
self.DEVICE_MODEL, app_version=self.client.app_version,
self.SYSTEM_VERSION, device_model=self.client.device_model,
self.APP_VERSION, system_version=self.client.system_version,
"en", "", "en", system_lang_code=self.client.lang_code,
functions.help.GetConfig(), lang_code=self.client.lang_code,
lang_pack="",
query=functions.help.GetConfig(),
) )
) )
) )
@ -207,6 +197,12 @@ class Session:
for i in self.results.values(): for i in self.results.values():
i.event.set() i.event.set()
if not self.is_media and callable(self.client.disconnect_handler):
try:
self.client.disconnect_handler(self.client)
except Exception as e:
log.error(e, exc_info=True)
log.debug("Session stopped") log.debug("Session stopped")
def restart(self): def restart(self):
@ -222,14 +218,14 @@ class Session:
msg_key = msg_key_large[8:24] msg_key = msg_key_large[8:24]
aes_key, aes_iv = KDF(self.auth_key, msg_key, True) aes_key, aes_iv = KDF(self.auth_key, msg_key, True)
return self.auth_key_id + msg_key + AES.ige_encrypt(data + padding, aes_key, aes_iv) return self.auth_key_id + msg_key + AES.ige256_encrypt(data + padding, aes_key, aes_iv)
def unpack(self, b: BytesIO) -> Message: def unpack(self, b: BytesIO) -> Message:
assert b.read(8) == self.auth_key_id, b.getvalue() assert b.read(8) == self.auth_key_id, b.getvalue()
msg_key = b.read(16) msg_key = b.read(16)
aes_key, aes_iv = KDF(self.auth_key, msg_key, False) aes_key, aes_iv = KDF(self.auth_key, msg_key, False)
data = BytesIO(AES.ige_decrypt(b.read(), aes_key, aes_iv)) data = BytesIO(AES.ige256_decrypt(b.read(), aes_key, aes_iv))
data.read(8) data.read(8)
# https://core.telegram.org/mtproto/security_guidelines#checking-session-id # https://core.telegram.org/mtproto/security_guidelines#checking-session-id
@ -379,7 +375,7 @@ class Session:
log.debug("RecvThread stopped") log.debug("RecvThread stopped")
def _send(self, data: Object, wait_response: bool = True): def _send(self, data: Object, wait_response: bool = True, timeout: float = WAIT_TIMEOUT):
message = self.msg_factory(data) message = self.msg_factory(data)
msg_id = message.msg_id msg_id = message.msg_id
@ -395,7 +391,7 @@ class Session:
raise e raise e
if wait_response: if wait_response:
self.results[msg_id].event.wait(self.WAIT_TIMEOUT) self.results[msg_id].event.wait(timeout)
result = self.results.pop(msg_id).value result = self.results.pop(msg_id).value
if result is None: if result is None:
@ -410,11 +406,11 @@ class Session:
else: else:
return result return result
def send(self, data: Object, retries: int = MAX_RETRIES): def send(self, data: Object, retries: int = MAX_RETRIES, timeout: float = WAIT_TIMEOUT):
self.is_connected.wait(self.WAIT_TIMEOUT) self.is_connected.wait(self.WAIT_TIMEOUT)
try: try:
return self._send(data) return self._send(data, timeout=timeout)
except (OSError, TimeoutError, InternalServerError) as e: except (OSError, TimeoutError, InternalServerError) as e:
if retries == 0: if retries == 0:
raise e from None raise e from None
@ -425,4 +421,4 @@ class Session:
datetime.now(), type(data))) datetime.now(), type(data)))
time.sleep(0.5) time.sleep(0.5)
return self.send(data, retries - 1) return self.send(data, retries - 1, timeout)

View File

@ -1 +0,0 @@
tgcrypto>=1.0.4

View File

@ -85,5 +85,5 @@ setup(
packages=find_packages(exclude=["compiler*"]), packages=find_packages(exclude=["compiler*"]),
zip_safe=False, zip_safe=False,
install_requires=read("requirements.txt"), install_requires=read("requirements.txt"),
extras_require={"tgcrypto": read("requirements_extras.txt")} extras_require={"tgcrypto": ["tgcrypto>=1.0.4"]}
) )