mirror of
https://github.com/TeamPGM/pyrogram.git
synced 2024-11-18 05:30:15 +00:00
commit
24013d6528
@ -1,5 +1,5 @@
|
||||
## 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
|
||||
|
||||
## Exclude
|
||||
|
31
README.rst
31
README.rst
@ -1,7 +1,7 @@
|
||||
|header|
|
||||
|
||||
Pyrogram |twitter|
|
||||
==================
|
||||
Pyrogram
|
||||
========
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@ -12,12 +12,10 @@ Pyrogram |twitter|
|
||||
|
||||
@app.on_message(Filters.private)
|
||||
def hello(client, message):
|
||||
client.send_message(
|
||||
message.chat.id, "Hello {}".format(message.from_user.first_name))
|
||||
message.reply("Hello {}".format(message.from_user.first_name))
|
||||
|
||||
|
||||
app.start()
|
||||
app.idle()
|
||||
app.run()
|
||||
|
||||
**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.
|
||||
@ -28,8 +26,8 @@ Features
|
||||
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
|
||||
- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
|
||||
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
|
||||
- **Updated** to the latest Telegram API version, currently Layer 76 running on MTProto 2.0.
|
||||
- **Documented**: Pyrogram API methods are documented and resemble the Telegram Bot API.
|
||||
- **Updated** to the latest Telegram API version, currently Layer 81 on top of MTProto 2.0.
|
||||
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
|
||||
- **Full API**, allowing to execute any advanced action an official client is able to do, and more.
|
||||
|
||||
Requirements
|
||||
@ -56,7 +54,7 @@ Getting Started
|
||||
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
|
||||
and documentation. Any help is appreciated!
|
||||
|
||||
@ -102,28 +100,25 @@ Copyright & License
|
||||
</a>
|
||||
<br><br>
|
||||
<a href="compiler/api/source/main_api.tl">
|
||||
<img src="https://media.pyrogram.ml/images/scheme.svg"
|
||||
alt="Scheme Layer 76">
|
||||
<img src="https://img.shields.io/badge/SCHEME-LAYER%2081-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
|
||||
alt="Scheme Layer">
|
||||
</a>
|
||||
<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">
|
||||
</a>
|
||||
</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
|
||||
:target: https://pyrogram.ml
|
||||
:alt: Pyrogram
|
||||
|
||||
.. |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
|
||||
: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
|
||||
:alt: TgCrypto
|
||||
|
@ -7,7 +7,9 @@
|
||||
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_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_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;
|
||||
|
||||
|
@ -60,6 +60,7 @@ inputPhoto#fb95c6c4 id:long access_hash:long = InputPhoto;
|
||||
inputFileLocation#14637196 volume_id:long local_id:int secret:long = InputFileLocation;
|
||||
inputEncryptedFileLocation#f5235d55 id:long access_hash:long = 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;
|
||||
|
||||
@ -97,7 +98,7 @@ userStatusLastMonth#77ebc742 = UserStatus;
|
||||
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;
|
||||
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;
|
||||
|
||||
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;
|
||||
messageActionCustomAction#fae69f56 message: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;
|
||||
|
||||
@ -164,7 +167,7 @@ geoPoint#2049d70c long:double lat:double = GeoPoint;
|
||||
|
||||
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;
|
||||
|
||||
@ -173,18 +176,10 @@ auth.exportedAuthorization#df969c2d id:int bytes:bytes = auth.ExportedAuthorizat
|
||||
inputNotifyPeer#b8bc5b0c peer:InputPeer = InputNotifyPeer;
|
||||
inputNotifyUsers#193b4417 = InputNotifyPeer;
|
||||
inputNotifyChats#4a95e84e = InputNotifyPeer;
|
||||
inputNotifyAll#a429b886 = InputNotifyPeer;
|
||||
|
||||
inputPeerNotifyEventsEmpty#f03064d8 = InputPeerNotifyEvents;
|
||||
inputPeerNotifyEventsAll#e86a2c74 = InputPeerNotifyEvents;
|
||||
inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = InputPeerNotifySettings;
|
||||
|
||||
inputPeerNotifySettings#38935eb2 flags:# show_previews:flags.0?true silent:flags.1?true mute_until:int sound:string = InputPeerNotifySettings;
|
||||
|
||||
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;
|
||||
peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings;
|
||||
|
||||
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.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;
|
||||
|
||||
@ -385,7 +380,6 @@ help.support#17c6b5f6 phone_number:string user:User = help.Support;
|
||||
notifyPeer#9fd40bd8 peer:Peer = NotifyPeer;
|
||||
notifyUsers#b4c83b4c = NotifyPeer;
|
||||
notifyChats#c007cec3 = NotifyPeer;
|
||||
notifyAll#74d07c60 = NotifyPeer;
|
||||
|
||||
sendMessageTypingAction#16bf744e = SendMessageAction;
|
||||
sendMessageCancelAction#fd5ec8f5 = SendMessageAction;
|
||||
@ -438,7 +432,7 @@ documentAttributeFilename#15590068 file_name:string = DocumentAttribute;
|
||||
documentAttributeHasStickers#9801d2f7 = DocumentAttribute;
|
||||
|
||||
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;
|
||||
|
||||
@ -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.noPassword#96dabc18 new_salt: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.noPassword#5ea182f6 new_salt:bytes new_secure_salt:bytes secure_random:bytes 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;
|
||||
|
||||
@ -554,7 +548,7 @@ channels.channelParticipantsNotModified#f0173fe9 = channels.ChannelParticipants;
|
||||
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
@ -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;
|
||||
|
||||
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;
|
||||
|
||||
@ -832,15 +828,69 @@ messages.foundStickerSets#5108d648 hash:int sets:Vector<StickerSetCovered> = mes
|
||||
|
||||
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---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id: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;
|
||||
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.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;
|
||||
@ -888,9 +938,20 @@ account.getTmpPassword#4a82327e password_hash:bytes period:int = account.TmpPass
|
||||
account.getWebAuthorizations#182e6d6f = account.WebAuthorizations;
|
||||
account.resetWebAuthorization#2d01b9ef hash:long = 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.getFullUser#ca30a5b1 id:InputUser = UserFull;
|
||||
users.setSecureValueErrors#90c894b5 id:InputUser errors:Vector<SecureValueError> = Bool;
|
||||
|
||||
contacts.getStatuses#c4a353ee = Vector<ContactStatus>;
|
||||
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.reportEncryptedSpam#4b0c8c0f peer:InputEncryptedChat = Bool;
|
||||
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.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector<MessageEntity> = MessageMedia;
|
||||
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.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.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.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.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#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.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;
|
||||
@ -1030,10 +1091,13 @@ help.saveAppLog#6f02f748 events:Vector<InputAppEvent> = Bool;
|
||||
help.getInviteText#4d392343 = help.InviteText;
|
||||
help.getSupport#9cdf08cd = help.Support;
|
||||
help.getAppChangelog#9010ef6f prev_app_version:string = Updates;
|
||||
help.getTermsOfService#350170f3 = help.TermsOfService;
|
||||
help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool;
|
||||
help.getCdnConfig#52029342 = CdnConfig;
|
||||
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.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.getLanguages#800fd57d = Vector<LangPackLanguage>;
|
||||
|
||||
// LAYER 76
|
||||
// LAYER 81
|
||||
|
@ -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
|
||||
// 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"))
|
||||
// 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;
|
||||
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---
|
||||
|
||||
@ -58,5 +65,3 @@ ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong;
|
||||
destroy_session#e7512126 session_id:long = DestroySessionRes;
|
||||
|
||||
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;
|
||||
|
@ -77,7 +77,8 @@ def generate(source_path, base):
|
||||
|
||||
build(source_path)
|
||||
|
||||
for k, v in all_entities.items():
|
||||
for k, v in sorted(all_entities.items()):
|
||||
v = sorted(v)
|
||||
entities = []
|
||||
|
||||
for i in v:
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
SPHINXBUILD = ~/PycharmProjects/pyrogram/venv3.6/bin/sphinx-build
|
||||
SPHINXPROJ = Pyrogram
|
||||
SOURCEDIR = source
|
||||
BUILDDIR = build
|
||||
|
20
docs/Makefile_
Normal file
20
docs/Makefile_
Normal 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)
|
@ -26,11 +26,11 @@ Welcome to Pyrogram
|
||||
</a>
|
||||
<br><br>
|
||||
<a href="https://github.com/pyrogram/pyrogram/blob/master/compiler/api/source/main_api.tl">
|
||||
<img src="https://media.pyrogram.ml/images/scheme.svg"
|
||||
alt="Scheme Layer 75">
|
||||
<img src="https://img.shields.io/badge/SCHEME-LAYER%2081-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
|
||||
alt="Scheme Layer">
|
||||
</a>
|
||||
<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">
|
||||
</a>
|
||||
</p>
|
||||
@ -44,12 +44,10 @@ Welcome to Pyrogram
|
||||
|
||||
@app.on_message(Filters.private)
|
||||
def hello(client, message):
|
||||
client.send_message(
|
||||
message.chat.id, "Hello {}".format(message.from_user.first_name))
|
||||
message.reply("Hello {}".format(message.from_user.first_name))
|
||||
|
||||
|
||||
app.start()
|
||||
app.idle()
|
||||
app.run()
|
||||
|
||||
Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the library.
|
||||
Contents are organized by topic and can be accessed from the sidebar, or by following them one by one using the Next
|
||||
@ -58,7 +56,7 @@ button at the end of each page. But first, here's a brief overview of what is th
|
||||
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.
|
||||
|
||||
Features
|
||||
@ -67,29 +65,30 @@ Features
|
||||
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
|
||||
- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
|
||||
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
|
||||
- **Updated** to the latest Telegram API version, currently Layer 76 running on MTProto 2.0.
|
||||
- **Documented**: Pyrogram API methods are documented and resemble the Telegram Bot API.
|
||||
- **Updated** to the latest Telegram API version, currently Layer 81 on top of MTProto 2.0.
|
||||
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
|
||||
- **Full API**, allowing to execute any advanced action an official client is able to do, and more.
|
||||
|
||||
To get started, press the Next button.
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Getting Started
|
||||
:caption: Quick Start
|
||||
|
||||
start/QuickInstallation
|
||||
start/ProjectSetup
|
||||
start/BasicUsage
|
||||
start/Installation
|
||||
start/Setup
|
||||
start/Usage
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
:caption: Resources
|
||||
|
||||
resources/UpdateHandling
|
||||
resources/SOCKS5Proxy
|
||||
resources/TgCrypto
|
||||
resources/AutoAuthorization
|
||||
resources/CustomizeSessions
|
||||
resources/TgCrypto
|
||||
resources/TextFormatting
|
||||
resources/SOCKS5Proxy
|
||||
resources/BotsInteraction
|
||||
resources/ErrorHandling
|
||||
|
||||
@ -107,5 +106,4 @@ To get started, press the Next button.
|
||||
types/index
|
||||
|
||||
.. _`Telegram`: https://telegram.org/
|
||||
|
||||
.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto/
|
||||
.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto/
|
@ -3,4 +3,3 @@ ChatAction
|
||||
|
||||
.. autoclass:: pyrogram.ChatAction
|
||||
:members:
|
||||
:undoc-members:
|
||||
|
@ -7,6 +7,8 @@ Client
|
||||
:inherited-members:
|
||||
:members:
|
||||
|
||||
.. _available-methods:
|
||||
|
||||
**Available methods**
|
||||
|
||||
.. autosummary::
|
||||
@ -15,6 +17,7 @@ Client
|
||||
start
|
||||
stop
|
||||
idle
|
||||
run
|
||||
on_message
|
||||
on_callback_query
|
||||
on_raw_update
|
||||
@ -49,6 +52,10 @@ Client
|
||||
enable_cloud_password
|
||||
change_cloud_password
|
||||
remove_cloud_password
|
||||
kick_chat_member
|
||||
unban_chat_member
|
||||
restrict_chat_member
|
||||
promote_chat_member
|
||||
add_contacts
|
||||
get_contacts
|
||||
delete_contacts
|
||||
|
6
docs/source/pyrogram/handlers/DeletedMessagesHandler.rst
Normal file
6
docs/source/pyrogram/handlers/DeletedMessagesHandler.rst
Normal file
@ -0,0 +1,6 @@
|
||||
DeletedMessagesHandler
|
||||
======================
|
||||
|
||||
.. autoclass:: pyrogram.DeletedMessagesHandler
|
||||
:members:
|
||||
:undoc-members:
|
6
docs/source/pyrogram/handlers/DisconnectHandler.rst
Normal file
6
docs/source/pyrogram/handlers/DisconnectHandler.rst
Normal file
@ -0,0 +1,6 @@
|
||||
DisconnectHandler
|
||||
=================
|
||||
|
||||
.. autoclass:: pyrogram.DisconnectHandler
|
||||
:members:
|
||||
:undoc-members:
|
@ -5,5 +5,7 @@ Handlers
|
||||
|
||||
.. toctree::
|
||||
MessageHandler
|
||||
DeletedMessagesHandler
|
||||
CallbackQueryHandler
|
||||
RawUpdateHandler
|
||||
DisconnectHandler
|
||||
RawUpdateHandler
|
||||
|
@ -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.
|
||||
If you want to retrieve the phone code programmatically, pass a callback function in the ``phone_code`` field — this
|
||||
function must return the correct phone code as string (e.g., "12345") — otherwise, ignore this parameter, Pyrogram will
|
||||
ask you to input the phone code manually.
|
||||
function accepts a single positional argument (phone_number) and must return the correct phone code (e.g., "12345")
|
||||
— otherwise, ignore this parameter, Pyrogram will ask you to input the phone code manually.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pyrogram import Client
|
||||
|
||||
def phone_code_callback():
|
||||
def phone_code_callback(phone_number):
|
||||
code = ... # Get your code programmatically
|
||||
return code # Must be string, e.g., "12345"
|
||||
return code # e.g., "12345"
|
||||
|
||||
|
||||
app = Client(
|
||||
session_name="example",
|
||||
phone_number="39**********",
|
||||
phone_code=phone_code_callback,
|
||||
phone_code=phone_code_callback, # Note the missing parentheses
|
||||
password="password" # (if you have one)
|
||||
)
|
||||
|
||||
app.start()
|
||||
|
||||
print(app.get_me())
|
||||
|
||||
app.stop()
|
||||
|
||||
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
|
||||
Telegram account in case the phone number you passed is not registered yet.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from pyrogram import Client
|
||||
|
||||
def phone_code_callback():
|
||||
def phone_code_callback(phone_number):
|
||||
code = ... # Get your code programmatically
|
||||
return code # Must be string, e.g., "12345"
|
||||
return code # e.g., "12345"
|
||||
|
||||
|
||||
app = Client(
|
||||
session_name="example",
|
||||
phone_number="39**********",
|
||||
phone_code=phone_code_callback,
|
||||
phone_code=phone_code_callback, # Note the missing parentheses
|
||||
first_name="Pyrogram",
|
||||
last_name="" # Can be an empty string
|
||||
)
|
||||
|
||||
app.start()
|
||||
print(app.get_me())
|
||||
|
||||
print(app.get_me())
|
||||
|
||||
app.stop()
|
@ -28,8 +28,12 @@ Inline Bots
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
# Send the first result (bot_results.results[0]) to your own chat (Saved Messages)
|
||||
app.send_inline_bot_result("me", bot_results.query_id, bot_results.results[0].id)
|
||||
# Send the first result to your own chat
|
||||
app.send_inline_bot_result(
|
||||
"me",
|
||||
bot_results.query_id,
|
||||
bot_results.results[0].id
|
||||
)
|
||||
|
||||
.. figure:: https://i.imgur.com/wwxr7B7.png
|
||||
:width: 90%
|
||||
|
66
docs/source/resources/CustomizeSessions.rst
Normal file
66
docs/source/resources/CustomizeSessions.rst
Normal 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",
|
||||
)
|
@ -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:
|
||||
|
||||
- `MessageHandler <../pyrogram/handlers/MessageHandler.html>`_
|
||||
- `DeletedMessagesHandler <../pyrogram/handlers/DeletedMessagesHandler.html>`_
|
||||
- `CallbackQueryHandler <../pyrogram/handlers/CallbackQueryHandler.html>`_
|
||||
- `RawUpdateHandler <../pyrogram/handlers/RawUpdateHandler.html>`_
|
||||
- `DisconnectHandler <../pyrogram/handlers/DisconnectHandler.html>`_
|
||||
|
||||
Registering an Handler
|
||||
----------------------
|
||||
@ -31,8 +33,7 @@ We shall examine the :obj:`MessageHandler <pyrogram.MessageHandler>`, which will
|
||||
print(message)
|
||||
|
||||
|
||||
app.start()
|
||||
app.idle()
|
||||
app.run()
|
||||
|
||||
- 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.
|
||||
@ -50,8 +51,7 @@ We shall examine the :obj:`MessageHandler <pyrogram.MessageHandler>`, which will
|
||||
|
||||
app.add_handler(MessageHandler(my_handler))
|
||||
|
||||
app.start()
|
||||
app.idle()
|
||||
app.run()
|
||||
|
||||
Using Filters
|
||||
-------------
|
||||
|
@ -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
|
50
docs/source/start/Installation.rst
Normal file
50
docs/source/start/Installation.rst
Normal 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
|
@ -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
|
@ -1,8 +1,9 @@
|
||||
Project Setup
|
||||
=============
|
||||
Setup
|
||||
=====
|
||||
|
||||
This section provides all the information you need to setup your project with Pyrogram.
|
||||
There are a few steps you have to follow before you can actually use the library to make API calls.
|
||||
Once you successfully `installed Pyrogram`_, you will still have to follow a few steps before you can actually use
|
||||
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
|
||||
--------
|
||||
@ -39,7 +40,7 @@ There are two ways to configure a Pyrogram application project, and you can choo
|
||||
from pyrogram import Client
|
||||
|
||||
app = Client(
|
||||
session_name="my_account",
|
||||
"my_account",
|
||||
api_id=12345,
|
||||
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.
|
||||
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
|
||||
(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
|
||||
|
||||
from pyrogram import Client
|
||||
|
||||
app = Client("my_account")
|
||||
app.start()
|
||||
app.run()
|
||||
|
||||
This starts an interactive shell asking you to input your **phone number** (including your `Country Code`_)
|
||||
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,
|
||||
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
|
||||
-----------------
|
||||
|
||||
Being written entirely from the ground up, Pyrogram is also able to authorize Bots.
|
||||
Bots are a special kind of users which also make use of MTProto. This means that you can use Pyrogram to
|
||||
execute API calls with a Bot identity.
|
||||
Bots are a special kind of users which also make use of MTProto, the underlying Telegram protocol.
|
||||
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_:
|
||||
|
||||
@ -91,10 +92,11 @@ Instead of phone numbers, Bots are authorized via their tokens which are created
|
||||
from pyrogram import Client
|
||||
|
||||
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
|
||||
``123456.session`` for the example above.
|
||||
|
||||
.. _installed Pyrogram: Installation.html
|
||||
.. _`Country Code`: https://en.wikipedia.org/wiki/List_of_country_calling_codes
|
||||
.. _BotFather: https://t.me/botfather
|
118
docs/source/start/Usage.rst
Normal file
118
docs/source/start/Usage.rst
Normal 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
|
@ -34,5 +34,4 @@ def answer(client, callback_query):
|
||||
)
|
||||
|
||||
|
||||
app.start()
|
||||
app.idle()
|
||||
app.run() # Automatically start() and idle()
|
||||
|
@ -36,5 +36,4 @@ def echo(client, message):
|
||||
)
|
||||
|
||||
|
||||
app.start()
|
||||
app.idle()
|
||||
app.run() # Automatically start() and idle()
|
||||
|
@ -24,12 +24,12 @@ from pyrogram.api.errors import FloodWait
|
||||
"""This example shows how to retrieve the full message history of a chat"""
|
||||
|
||||
app = Client("my_account")
|
||||
app.start()
|
||||
|
||||
target = "me" # "me" refers to your own chat (Saved Messages)
|
||||
messages = [] # List that will contain all the messages of the target chat
|
||||
offset_id = 0 # ID of the last message of the chunk
|
||||
|
||||
app.start()
|
||||
|
||||
while True:
|
||||
try:
|
||||
m = app.get_history(target, offset_id=offset_id)
|
||||
|
@ -28,13 +28,13 @@ Refer to get_participants2.py for more than 10.000 users.
|
||||
"""
|
||||
|
||||
app = Client("my_account")
|
||||
app.start()
|
||||
|
||||
target = "pyrogramchat" # Target channel/supergroup
|
||||
users = [] # List that will contain all the users of the target chat
|
||||
limit = 200 # Amount of users to retrieve for each API call
|
||||
offset = 0 # Offset starts at 0
|
||||
|
||||
app.start()
|
||||
|
||||
while True:
|
||||
try:
|
||||
participants = app.send(
|
||||
|
@ -33,15 +33,14 @@ as some user names may not contain ascii letters at all.
|
||||
"""
|
||||
|
||||
app = Client("my_account")
|
||||
app.start()
|
||||
|
||||
target = "pyrogramchat" # Target channel/supergroup username or id
|
||||
users = {} # To ensure uniqueness, users will be stored in a dictionary with user_id as key
|
||||
limit = 200 # Amount of users to retrieve for each API call (200 is the maximum)
|
||||
|
||||
# "" + "0123456789" + "abcdefghijklmnopqrstuvwxyz" (as list)
|
||||
queries = [""] + [str(i) for i in range(10)] + list(ascii_lowercase)
|
||||
|
||||
app.start()
|
||||
|
||||
for q in queries:
|
||||
print("Searching for '{}'".format(q))
|
||||
offset = 0 # For each query, offset restarts from 0
|
||||
|
@ -28,5 +28,4 @@ def raw(client, update, users, chats):
|
||||
print(update)
|
||||
|
||||
|
||||
app.start()
|
||||
app.idle()
|
||||
app.run() # Automatically start() and idle()
|
||||
|
@ -49,5 +49,4 @@ def welcome(client, message):
|
||||
)
|
||||
|
||||
|
||||
app.start()
|
||||
app.idle()
|
||||
app.run() # Automatically start() and idle()
|
||||
|
@ -23,7 +23,7 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance
|
||||
"e" if sys.getfilesystemencoding() != "utf-8" else "\xe8"
|
||||
)
|
||||
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
|
||||
__version__ = "0.7.4"
|
||||
__version__ = "0.7.5"
|
||||
|
||||
from .api.errors import Error
|
||||
from .client.types import (
|
||||
@ -38,6 +38,6 @@ from .client.types.reply_markup import (
|
||||
)
|
||||
from .client import (
|
||||
Client, ChatAction, ParseMode, Emoji,
|
||||
MessageHandler, CallbackQueryHandler, RawUpdateHandler,
|
||||
Filters
|
||||
MessageHandler, DeletedMessagesHandler, CallbackQueryHandler,
|
||||
RawUpdateHandler, DisconnectHandler, Filters
|
||||
)
|
||||
|
@ -77,11 +77,14 @@ class Encoder(JSONEncoder):
|
||||
|
||||
if o is not None:
|
||||
if o.startswith("pyrogram.client"):
|
||||
r = remove_none(OrderedDict([("_", name)] + [i for i in content.items()]))
|
||||
r.pop("client", None)
|
||||
r = remove_none(OrderedDict([("_", "pyrogram:" + name)] + [i for i in content.items()]))
|
||||
r.pop("_client", None)
|
||||
|
||||
return r
|
||||
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:
|
||||
return None
|
||||
|
@ -35,8 +35,6 @@ class Int(Object):
|
||||
class Long(Int):
|
||||
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):
|
||||
return super().__new__(cls, *args)
|
||||
|
||||
|
@ -19,4 +19,8 @@
|
||||
from .client import Client
|
||||
from .ext import BaseClient, ChatAction, Emoji, ParseMode
|
||||
from .filters import Filters
|
||||
from .handlers import MessageHandler, CallbackQueryHandler, RawUpdateHandler
|
||||
from .handlers import (
|
||||
MessageHandler, DeletedMessagesHandler,
|
||||
CallbackQueryHandler, RawUpdateHandler,
|
||||
DisconnectHandler
|
||||
)
|
||||
|
@ -18,6 +18,7 @@
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import getpass
|
||||
import json
|
||||
import logging
|
||||
import math
|
||||
@ -43,15 +44,13 @@ from pyrogram.api.errors import (
|
||||
PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded,
|
||||
PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned,
|
||||
VolumeLocNotFound, UserMigrate, FileIdInvalid)
|
||||
from pyrogram.client.handlers import DisconnectHandler
|
||||
from pyrogram.crypto import AES
|
||||
from pyrogram.session import Auth, Session
|
||||
from .dispatcher import Dispatcher
|
||||
from .ext import utils, Syncer, BaseClient
|
||||
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__)
|
||||
|
||||
|
||||
@ -75,6 +74,22 @@ class Client(Methods, BaseClient):
|
||||
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.
|
||||
|
||||
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*):
|
||||
Your SOCKS5 Proxy settings as dict,
|
||||
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.
|
||||
|
||||
phone_code (``str`` | ``callable``, *optional*):
|
||||
Pass the phone code as string (for test numbers only), or pass a callback function
|
||||
which must return the correct phone code as string (e.g., "12345").
|
||||
Pass the phone code as string (for test numbers only), or pass a callback function which accepts
|
||||
a single positional argument *(phone_number)* and must return the correct phone code (e.g., "12345").
|
||||
Only applicable for new sessions.
|
||||
|
||||
password (``str``, *optional*):
|
||||
@ -127,6 +142,10 @@ class Client(Methods, BaseClient):
|
||||
session_name: str,
|
||||
api_id: int or 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,
|
||||
test_mode: bool = False,
|
||||
phone_number: str = None,
|
||||
@ -143,7 +162,12 @@ class Client(Methods, BaseClient):
|
||||
self.session_name = session_name
|
||||
self.api_id = int(api_id) if api_id else None
|
||||
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.phone_number = phone_number
|
||||
self.phone_code = phone_code
|
||||
@ -157,15 +181,19 @@ class Client(Methods, BaseClient):
|
||||
|
||||
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.
|
||||
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:
|
||||
:class:`Error <pyrogram.Error>`
|
||||
"""
|
||||
@ -180,12 +208,9 @@ class Client(Methods, BaseClient):
|
||||
self.load_session()
|
||||
|
||||
self.session = Session(
|
||||
self,
|
||||
self.dc_id,
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
self.auth_key,
|
||||
self.api_id,
|
||||
client=self
|
||||
self.auth_key
|
||||
)
|
||||
|
||||
self.session.start()
|
||||
@ -273,6 +298,39 @@ class Client(Methods, BaseClient):
|
||||
self.is_started = False
|
||||
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):
|
||||
"""Use this method to register an update handler.
|
||||
|
||||
@ -290,7 +348,10 @@ class Client(Methods, BaseClient):
|
||||
Returns:
|
||||
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
|
||||
|
||||
@ -308,7 +369,10 @@ class Client(Methods, BaseClient):
|
||||
group (``int``, *optional*):
|
||||
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):
|
||||
try:
|
||||
@ -324,15 +388,12 @@ class Client(Methods, BaseClient):
|
||||
self.session.stop()
|
||||
|
||||
self.dc_id = e.x
|
||||
self.auth_key = Auth(self.dc_id, self.test_mode, self.proxy).create()
|
||||
self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create()
|
||||
|
||||
self.session = Session(
|
||||
self,
|
||||
self.dc_id,
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
self.auth_key,
|
||||
self.api_id,
|
||||
client=self
|
||||
self.auth_key
|
||||
)
|
||||
|
||||
self.session.start()
|
||||
@ -372,15 +433,12 @@ class Client(Methods, BaseClient):
|
||||
self.session.stop()
|
||||
|
||||
self.dc_id = e.x
|
||||
self.auth_key = Auth(self.dc_id, self.test_mode, self.proxy).create()
|
||||
self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create()
|
||||
|
||||
self.session = Session(
|
||||
self,
|
||||
self.dc_id,
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
self.auth_key,
|
||||
self.api_id,
|
||||
client=self
|
||||
self.auth_key
|
||||
)
|
||||
self.session.start()
|
||||
|
||||
@ -399,8 +457,11 @@ class Client(Methods, BaseClient):
|
||||
print(e.MESSAGE)
|
||||
self.phone_number = None
|
||||
except FloodWait as e:
|
||||
print(e.MESSAGE.format(x=e.x))
|
||||
time.sleep(e.x)
|
||||
if phone_number_invalid_raises:
|
||||
raise
|
||||
else:
|
||||
print(e.MESSAGE.format(x=e.x))
|
||||
time.sleep(e.x)
|
||||
except Exception as e:
|
||||
log.error(e, exc_info=True)
|
||||
else:
|
||||
@ -408,6 +469,10 @@ class Client(Methods, BaseClient):
|
||||
|
||||
phone_registered = r.phone_registered
|
||||
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:
|
||||
self.send(
|
||||
@ -421,7 +486,7 @@ class Client(Methods, BaseClient):
|
||||
self.phone_code = (
|
||||
input("Enter phone code: ") if self.phone_code is None
|
||||
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:
|
||||
@ -478,7 +543,7 @@ class Client(Methods, BaseClient):
|
||||
|
||||
if self.password is None:
|
||||
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:
|
||||
self.password = r.current_salt + self.password.encode() + r.current_salt
|
||||
@ -493,24 +558,35 @@ class Client(Methods, BaseClient):
|
||||
print(e.MESSAGE)
|
||||
self.password = None
|
||||
except FloodWait as e:
|
||||
print(e.MESSAGE.format(x=e.x))
|
||||
time.sleep(e.x)
|
||||
if password_hash_invalid_raises:
|
||||
raise
|
||||
else:
|
||||
print(e.MESSAGE.format(x=e.x))
|
||||
time.sleep(e.x)
|
||||
except Exception as e:
|
||||
log.error(e, exc_info=True)
|
||||
else:
|
||||
break
|
||||
break
|
||||
except FloodWait as e:
|
||||
print(e.MESSAGE.format(x=e.x))
|
||||
time.sleep(e.x)
|
||||
if phone_code_invalid_raises or first_name_invalid_raises:
|
||||
raise
|
||||
else:
|
||||
print(e.MESSAGE.format(x=e.x))
|
||||
time.sleep(e.x)
|
||||
except Exception as e:
|
||||
log.error(e, exc_info=True)
|
||||
else:
|
||||
break
|
||||
|
||||
if terms_of_service:
|
||||
assert self.send(functions.help.AcceptTermsOfService(terms_of_service.id))
|
||||
|
||||
self.password = None
|
||||
self.user_id = r.user.id
|
||||
|
||||
print("Login successful")
|
||||
|
||||
def fetch_peers(self, entities: list):
|
||||
for entity in entities:
|
||||
if isinstance(entity, types.User):
|
||||
@ -763,29 +839,7 @@ class Client(Methods, BaseClient):
|
||||
|
||||
log.debug("{} stopped".format(name))
|
||||
|
||||
def signal_handler(self, *args):
|
||||
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):
|
||||
def send(self, data: Object, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT):
|
||||
"""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.
|
||||
@ -796,13 +850,19 @@ class Client(Methods, BaseClient):
|
||||
data (``Object``):
|
||||
The API Scheme function filled with proper arguments.
|
||||
|
||||
retries (``int``):
|
||||
Number of retries.
|
||||
|
||||
timeout (``float``):
|
||||
Timeout in seconds.
|
||||
|
||||
Raises:
|
||||
:class:`Error <pyrogram.Error>`
|
||||
"""
|
||||
if not self.is_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, "chats", []))
|
||||
@ -825,19 +885,42 @@ class Client(Methods, BaseClient):
|
||||
"More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration"
|
||||
)
|
||||
|
||||
if self.proxy:
|
||||
self.proxy["enabled"] = True
|
||||
self.proxy["username"] = self.proxy.get("username", None)
|
||||
self.proxy["password"] = self.proxy.get("password", None)
|
||||
for option in {"app_version", "device_model", "system_version", "lang_code"}:
|
||||
if getattr(self, option):
|
||||
pass
|
||||
else:
|
||||
setattr(self, option, Client.APP_VERSION)
|
||||
|
||||
if parser.has_section("pyrogram"):
|
||||
setattr(self, option, parser.get(
|
||||
"pyrogram",
|
||||
option,
|
||||
fallback=getattr(Client, option.upper())
|
||||
))
|
||||
|
||||
if self.lang_code:
|
||||
pass
|
||||
else:
|
||||
self.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"):
|
||||
self.proxy["enabled"] = parser.getboolean("proxy", "enabled")
|
||||
self.proxy["hostname"] = parser.get("proxy", "hostname")
|
||||
self.proxy["port"] = parser.getint("proxy", "port")
|
||||
self.proxy["username"] = parser.get("proxy", "username", fallback=None) or None
|
||||
self.proxy["password"] = parser.get("proxy", "password", fallback=None) or None
|
||||
self._proxy["enabled"] = parser.getboolean("proxy", "enabled")
|
||||
self._proxy["hostname"] = parser.get("proxy", "hostname")
|
||||
self._proxy["port"] = parser.getint("proxy", "port")
|
||||
self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None
|
||||
self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None
|
||||
|
||||
def load_session(self):
|
||||
try:
|
||||
@ -846,7 +929,7 @@ class Client(Methods, BaseClient):
|
||||
except FileNotFoundError:
|
||||
self.dc_id = 1
|
||||
self.date = 0
|
||||
self.auth_key = Auth(self.dc_id, self.test_mode, self.proxy).create()
|
||||
self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create()
|
||||
else:
|
||||
self.dc_id = s["dc_id"]
|
||||
self.test_mode = s["test_mode"]
|
||||
@ -983,7 +1066,6 @@ class Client(Methods, BaseClient):
|
||||
except (KeyError, ValueError):
|
||||
raise PeerIdInvalid
|
||||
|
||||
# TODO: Improvements for the new API
|
||||
def save_file(self,
|
||||
path: str,
|
||||
file_id: int = None,
|
||||
@ -998,7 +1080,7 @@ class Client(Methods, BaseClient):
|
||||
file_id = file_id or self.rnd_id()
|
||||
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()
|
||||
|
||||
try:
|
||||
@ -1027,7 +1109,7 @@ class Client(Methods, BaseClient):
|
||||
bytes=chunk
|
||||
)
|
||||
|
||||
assert self.send(rpc), "Couldn't upload file"
|
||||
assert session.send(rpc), "Couldn't upload file"
|
||||
|
||||
if is_missing_part:
|
||||
return
|
||||
@ -1059,7 +1141,6 @@ class Client(Methods, BaseClient):
|
||||
finally:
|
||||
session.stop()
|
||||
|
||||
# TODO: Improvements for the new API
|
||||
def get_file(self,
|
||||
dc_id: int,
|
||||
id: int = None,
|
||||
@ -1083,11 +1164,10 @@ class Client(Methods, BaseClient):
|
||||
)
|
||||
|
||||
session = Session(
|
||||
self,
|
||||
dc_id,
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
Auth(dc_id, self.test_mode, self.proxy).create(),
|
||||
self.api_id
|
||||
Auth(dc_id, self.test_mode, self._proxy).create(),
|
||||
is_media=True
|
||||
)
|
||||
|
||||
session.start()
|
||||
@ -1102,11 +1182,10 @@ class Client(Methods, BaseClient):
|
||||
)
|
||||
else:
|
||||
session = Session(
|
||||
self,
|
||||
dc_id,
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
self.auth_key,
|
||||
self.api_id
|
||||
is_media=True
|
||||
)
|
||||
|
||||
session.start()
|
||||
@ -1172,11 +1251,10 @@ class Client(Methods, BaseClient):
|
||||
|
||||
if cdn_session is None:
|
||||
cdn_session = Session(
|
||||
self,
|
||||
r.dc_id,
|
||||
self.test_mode,
|
||||
self.proxy,
|
||||
Auth(r.dc_id, self.test_mode, self.proxy).create(),
|
||||
self.api_id,
|
||||
Auth(r.dc_id, self.test_mode, self._proxy).create(),
|
||||
is_media=True,
|
||||
is_cdn=True
|
||||
)
|
||||
|
||||
@ -1213,11 +1291,13 @@ class Client(Methods, BaseClient):
|
||||
chunk = r2.bytes
|
||||
|
||||
# https://core.telegram.org/cdn#decrypting-files
|
||||
decrypted_chunk = AES.ctr_decrypt(
|
||||
decrypted_chunk = AES.ctr256_decrypt(
|
||||
chunk,
|
||||
r.encryption_key,
|
||||
r.encryption_iv,
|
||||
offset
|
||||
bytearray(
|
||||
r.encryption_iv[:-4]
|
||||
+ (offset // 16).to_bytes(4, "big")
|
||||
)
|
||||
)
|
||||
|
||||
hashes = session.send(
|
||||
@ -1245,9 +1325,6 @@ class Client(Methods, BaseClient):
|
||||
break
|
||||
except Exception as 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:
|
||||
log.error(e, exc_info=True)
|
||||
|
||||
@ -1259,6 +1336,3 @@ class Client(Methods, BaseClient):
|
||||
return ""
|
||||
else:
|
||||
return file_name
|
||||
finally:
|
||||
pass # Don't stop sessions, they are now cached and kept online
|
||||
# session.stop() TODO: Remove this branch
|
||||
|
@ -25,7 +25,7 @@ from threading import Thread
|
||||
import pyrogram
|
||||
from pyrogram.api import types
|
||||
from ..ext import utils
|
||||
from ..handlers import RawUpdateHandler, CallbackQueryHandler, MessageHandler
|
||||
from ..handlers import RawUpdateHandler, CallbackQueryHandler, MessageHandler, DeletedMessagesHandler
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -41,6 +41,11 @@ class Dispatcher:
|
||||
types.UpdateEditChannelMessage
|
||||
)
|
||||
|
||||
DELETE_MESSAGE_UPDATES = (
|
||||
types.UpdateDeleteMessages,
|
||||
types.UpdateDeleteChannelMessages
|
||||
)
|
||||
|
||||
MESSAGE_UPDATES = NEW_MESSAGE_UPDATES + EDIT_MESSAGE_UPDATES
|
||||
|
||||
def __init__(self, client, workers):
|
||||
@ -50,8 +55,6 @@ class Dispatcher:
|
||||
self.updates = Queue()
|
||||
self.groups = OrderedDict()
|
||||
|
||||
self._handler_lock = threading.Lock()
|
||||
|
||||
def start(self):
|
||||
for i in range(self.workers):
|
||||
self.workers_list.append(
|
||||
@ -73,52 +76,57 @@ class Dispatcher:
|
||||
self.workers_list.clear()
|
||||
|
||||
def add_handler(self, handler, group: int):
|
||||
with self._handler_lock:
|
||||
if group not in self.groups:
|
||||
self.groups[group] = []
|
||||
self.groups = OrderedDict(sorted(self.groups.items()))
|
||||
if group not in self.groups:
|
||||
self.groups[group] = []
|
||||
self.groups = OrderedDict(sorted(self.groups.items()))
|
||||
|
||||
self.groups[group].append(handler)
|
||||
self.groups[group].append(handler)
|
||||
|
||||
def remove_handler(self, handler, group: int):
|
||||
with self._handler_lock:
|
||||
if group not in self.groups:
|
||||
raise ValueError("Group {} does not exist. "
|
||||
"Handler was not removed.".format(group))
|
||||
self.groups[group].remove(handler)
|
||||
if group not in self.groups:
|
||||
raise ValueError("Group {} does not exist. "
|
||||
"Handler was not removed.".format(group))
|
||||
self.groups[group].remove(handler)
|
||||
|
||||
def dispatch(self, update, users: dict = None, chats: dict = None, is_raw: bool = False):
|
||||
with self._handler_lock:
|
||||
for group in self.groups.values():
|
||||
for handler in group:
|
||||
if is_raw:
|
||||
if not isinstance(handler, RawUpdateHandler):
|
||||
for group in self.groups.values():
|
||||
for handler in group:
|
||||
if is_raw:
|
||||
if not isinstance(handler, RawUpdateHandler):
|
||||
continue
|
||||
|
||||
args = (self.client, update, users, chats)
|
||||
else:
|
||||
message = (update.message
|
||||
or update.channel_post
|
||||
or update.edited_message
|
||||
or update.edited_channel_post)
|
||||
|
||||
deleted_messages = (update.deleted_channel_posts
|
||||
or update.deleted_messages)
|
||||
|
||||
callback_query = update.callback_query
|
||||
|
||||
if message and isinstance(handler, MessageHandler):
|
||||
if not handler.check(message):
|
||||
continue
|
||||
|
||||
args = (self.client, 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:
|
||||
message = (update.message
|
||||
or update.channel_post
|
||||
or update.edited_message
|
||||
or update.edited_channel_post)
|
||||
continue
|
||||
|
||||
callback_query = update.callback_query
|
||||
|
||||
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
|
||||
handler.callback(*args)
|
||||
break
|
||||
|
||||
def update_worker(self):
|
||||
name = threading.current_thread().name
|
||||
@ -166,6 +174,22 @@ class Dispatcher:
|
||||
else None)
|
||||
)
|
||||
)
|
||||
|
||||
elif isinstance(update, Dispatcher.DELETE_MESSAGE_UPDATES):
|
||||
is_channel = hasattr(update, 'channel_id')
|
||||
|
||||
messages = utils.parse_deleted_messages(
|
||||
update.messages,
|
||||
(update.channel_id if is_channel else None)
|
||||
)
|
||||
|
||||
self.dispatch(
|
||||
pyrogram.Update(
|
||||
deleted_messages=(messages if not is_channel else None),
|
||||
deleted_channel_posts=(messages if is_channel else None)
|
||||
)
|
||||
)
|
||||
|
||||
elif isinstance(update, types.UpdateBotCallbackQuery):
|
||||
self.dispatch(
|
||||
pyrogram.Update(
|
||||
@ -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:
|
||||
continue
|
||||
except Exception as e:
|
||||
|
@ -16,17 +16,35 @@
|
||||
# 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 platform
|
||||
import re
|
||||
from queue import Queue
|
||||
from threading import Lock
|
||||
|
||||
from pyrogram import __version__
|
||||
from ..style import Markdown, HTML
|
||||
from ...api.core import Object
|
||||
from ...session import Session
|
||||
from ...session.internals import MsgId
|
||||
|
||||
|
||||
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-]+$")
|
||||
DIALOGS_AT_ONCE = 100
|
||||
UPDATES_WORKERS = 1
|
||||
@ -75,7 +93,9 @@ class BaseClient:
|
||||
self.download_queue = Queue()
|
||||
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
|
||||
|
||||
def resolve_peer(self, peer_id: int or str):
|
||||
|
@ -33,26 +33,28 @@ log = logging.getLogger(__name__)
|
||||
# TODO: Organize the code better?
|
||||
|
||||
class Str(str):
|
||||
def __init__(self, value):
|
||||
str.__init__(value)
|
||||
self._markdown = None
|
||||
self._html = None
|
||||
__slots__ = "_client", "_entities"
|
||||
|
||||
def __init__(self, *args):
|
||||
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
|
||||
def markdown(self):
|
||||
return self._markdown
|
||||
|
||||
@markdown.setter
|
||||
def markdown(self, value):
|
||||
self._markdown = value
|
||||
return self._client.markdown.unparse(self, self._entities)
|
||||
|
||||
@property
|
||||
def html(self):
|
||||
return self._html
|
||||
|
||||
@html.setter
|
||||
def html(self, value):
|
||||
self._html = value
|
||||
return self._client.html.unparse(self, self._entities)
|
||||
|
||||
|
||||
ENTITIES = {
|
||||
@ -413,7 +415,7 @@ def parse_messages(
|
||||
date=doc.date
|
||||
)
|
||||
elif types.DocumentAttributeAnimated in attributes:
|
||||
video_attributes = attributes[types.DocumentAttributeVideo]
|
||||
video_attributes = attributes.get(types.DocumentAttributeVideo, None)
|
||||
|
||||
gif = pyrogram_types.GIF(
|
||||
file_id=encode(
|
||||
@ -425,9 +427,9 @@ def parse_messages(
|
||||
doc.access_hash
|
||||
)
|
||||
),
|
||||
width=video_attributes.w,
|
||||
height=video_attributes.h,
|
||||
duration=video_attributes.duration,
|
||||
width=getattr(video_attributes, "w", 0),
|
||||
height=getattr(video_attributes, "h", 0),
|
||||
duration=getattr(video_attributes, "duration", 0),
|
||||
thumb=parse_thumb(doc.thumb),
|
||||
mime_type=doc.mime_type,
|
||||
file_size=doc.size,
|
||||
@ -581,16 +583,10 @@ def parse_messages(
|
||||
)
|
||||
|
||||
if m.text:
|
||||
args = (m.text, m.entities or [])
|
||||
|
||||
m.text.markdown = client.markdown.unparse(*args)
|
||||
m.text.html = client.html.unparse(*args)
|
||||
m.text.init(m._client, m.entities or [])
|
||||
|
||||
if m.caption:
|
||||
args = (m.caption, m.caption_entities or [])
|
||||
|
||||
m.caption.markdown = client.markdown.unparse(*args)
|
||||
m.caption.html = client.html.unparse(*args)
|
||||
m.caption.init(m._client, m.caption_entities or [])
|
||||
|
||||
if message.reply_to_msg_id and replies:
|
||||
while True:
|
||||
@ -715,6 +711,25 @@ def parse_messages(
|
||||
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:
|
||||
return (
|
||||
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))
|
||||
|
||||
return pyrogram_types.CallbackQuery(
|
||||
id=callback_query.query_id,
|
||||
id=str(callback_query.query_id),
|
||||
from_user=parse_user(users[callback_query.user_id]),
|
||||
message=client.get_messages(peer_id, callback_query.msg_id),
|
||||
chat_instance=str(callback_query.chat_instance),
|
||||
data=callback_query.data.decode(),
|
||||
game_short_name=callback_query.game_short_name
|
||||
# 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
|
||||
)
|
||||
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
import re
|
||||
|
||||
from .filter import Filter
|
||||
from ..types.reply_markup import InlineKeyboardMarkup, ReplyKeyboardMarkup
|
||||
|
||||
|
||||
def build(name: str, func: callable, **kwargs) -> type:
|
||||
@ -89,13 +90,13 @@ class Filters:
|
||||
venue = build("Venue", lambda _, m: bool(m.venue))
|
||||
"""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."""
|
||||
|
||||
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."""
|
||||
|
||||
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."""
|
||||
|
||||
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))
|
||||
"""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
|
||||
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.
|
||||
|
||||
Args:
|
||||
@ -144,25 +152,40 @@ class Filters:
|
||||
a command arrives, the command itself and its arguments will be stored in the *command*
|
||||
field of the :class:`Message <pyrogram.Message>`.
|
||||
|
||||
prefix (``str``):
|
||||
The command prefix. Defaults to "/".
|
||||
prefix (``str``, *optional*):
|
||||
The command prefix. Defaults to "/" (slash).
|
||||
Examples: /start, .help, !settings.
|
||||
|
||||
separator (``str``):
|
||||
separator (``str``, *optional*):
|
||||
The command arguments separator. Defaults to " " (white space).
|
||||
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):
|
||||
if m.text and m.text.startswith(_.p):
|
||||
c = m.text[1:].split(_.s)[0]
|
||||
m.command = ([c] + m.text.split(_.s)[1:]) if c in _.c else None
|
||||
t = m.text.split(_.s)
|
||||
c, a = t[0][len(_.p):], t[1:]
|
||||
c = c if _.cs else c.lower()
|
||||
m.command = ([c] + a) if c in _.c else None
|
||||
|
||||
return bool(m.command)
|
||||
|
||||
return build(
|
||||
"Command", f, c={command} if not isinstance(command, list) else {c for c in command},
|
||||
p=prefix, s=separator
|
||||
"Command",
|
||||
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
|
||||
@ -253,6 +276,7 @@ class Filters:
|
||||
or Filters.photo(m)
|
||||
or Filters.sticker(m)
|
||||
or Filters.video(m)
|
||||
or Filters.gif(m)
|
||||
or Filters.voice(m)
|
||||
or Filters.video_note(m)
|
||||
or Filters.contact(m)
|
||||
|
@ -16,4 +16,8 @@
|
||||
# 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 .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
|
||||
|
54
pyrogram/client/handlers/callback_query_handler.py
Normal file
54
pyrogram/client/handlers/callback_query_handler.py
Normal file
@ -0,0 +1,54 @@
|
||||
# Pyrogram - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
|
||||
#
|
||||
# This file is part of Pyrogram.
|
||||
#
|
||||
# Pyrogram is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Pyrogram is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .handler import Handler
|
||||
|
||||
|
||||
class 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
|
||||
)
|
55
pyrogram/client/handlers/deleted_messages_handler.py
Normal file
55
pyrogram/client/handlers/deleted_messages_handler.py
Normal 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
|
||||
)
|
41
pyrogram/client/handlers/disconnect_handler.py
Normal file
41
pyrogram/client/handlers/disconnect_handler.py
Normal 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)
|
55
pyrogram/client/handlers/message_handler.py
Normal file
55
pyrogram/client/handlers/message_handler.py
Normal 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
|
||||
)
|
@ -19,75 +19,13 @@
|
||||
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):
|
||||
"""The Raw Update handler class. Used to handle raw updates. 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_raw_update() <pyrogram.Client.on_raw_update>` decorator.
|
||||
|
||||
Args:
|
||||
callback (``callable``):
|
||||
A function that will be called when a new update is received from the server. It takes
|
@ -16,12 +16,16 @@
|
||||
# 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 .callback_query import CallbackQuery
|
||||
from .inline import Inline
|
||||
from .answer_callback_query import AnswerCallbackQuery
|
||||
from .get_inline_bot_results import GetInlineBotResults
|
||||
from .request_callback_answer import RequestCallbackAnswer
|
||||
from .send_inline_bot_result import SendInlineBotResult
|
||||
|
||||
|
||||
class Bots(
|
||||
CallbackQuery,
|
||||
Inline
|
||||
AnswerCallbackQuery,
|
||||
GetInlineBotResults,
|
||||
RequestCallbackAnswer,
|
||||
SendInlineBotResult
|
||||
):
|
||||
pass
|
||||
|
@ -17,7 +17,7 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram.api import functions
|
||||
from ....ext import BaseClient
|
||||
from pyrogram.client.ext import BaseClient
|
||||
|
||||
|
||||
class AnswerCallbackQuery(BaseClient):
|
@ -18,7 +18,7 @@
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from pyrogram.api.errors import UnknownError
|
||||
from ....ext import BaseClient
|
||||
from pyrogram.client.ext import BaseClient
|
||||
|
||||
|
||||
class GetInlineBotResults(BaseClient):
|
@ -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
|
60
pyrogram/client/methods/bots/request_callback_answer.py
Normal file
60
pyrogram/client/methods/bots/request_callback_answer.py
Normal 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
|
||||
)
|
@ -17,7 +17,7 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram.api import functions
|
||||
from ....ext import BaseClient
|
||||
from pyrogram.client.ext import BaseClient
|
||||
|
||||
|
||||
class SendInlineBotResult(BaseClient):
|
@ -19,13 +19,21 @@
|
||||
from .export_chat_invite_link import ExportChatInviteLink
|
||||
from .get_chat import GetChat
|
||||
from .join_chat import JoinChat
|
||||
from .kick_chat_member import KickChatMember
|
||||
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(
|
||||
GetChat,
|
||||
ExportChatInviteLink,
|
||||
LeaveChat,
|
||||
JoinChat
|
||||
JoinChat,
|
||||
KickChatMember,
|
||||
UnbanChatMember,
|
||||
RestrictChatMember,
|
||||
PromoteChatMember
|
||||
):
|
||||
pass
|
||||
|
87
pyrogram/client/methods/chats/kick_chat_member.py
Normal file
87
pyrogram/client/methods/chats/kick_chat_member.py
Normal 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
|
99
pyrogram/client/methods/chats/promote_chat_member.py
Normal file
99
pyrogram/client/methods/chats/promote_chat_member.py
Normal 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
|
113
pyrogram/client/methods/chats/restrict_chat_member.py
Normal file
113
pyrogram/client/methods/chats/restrict_chat_member.py
Normal 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
|
56
pyrogram/client/methods/chats/unban_chat_member.py
Normal file
56
pyrogram/client/methods/chats/unban_chat_member.py
Normal 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
|
@ -17,9 +17,11 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .on_callback_query import OnCallbackQuery
|
||||
from .on_disconnect import OnDisconnect
|
||||
from .on_message import OnMessage
|
||||
from .on_deleted_messages import OnDeletedMessages
|
||||
from .on_raw_update import OnRawUpdate
|
||||
|
||||
|
||||
class Decorators(OnMessage, OnCallbackQuery, OnRawUpdate):
|
||||
class Decorators(OnMessage, OnDeletedMessages, OnCallbackQuery, OnRawUpdate, OnDisconnect):
|
||||
pass
|
||||
|
@ -24,7 +24,7 @@ class OnCallbackQuery(BaseClient):
|
||||
def on_callback_query(self, filters=None, group: int = 0):
|
||||
"""Use this decorator to automatically register a function for handling
|
||||
callback queries. This does the same thing as :meth:`add_handler` using the
|
||||
CallbackQueryHandler.
|
||||
:class:`CallbackQueryHandler`.
|
||||
|
||||
Args:
|
||||
filters (:obj:`Filters <pyrogram.Filters>`):
|
||||
|
42
pyrogram/client/methods/decorators/on_deleted_messages.py
Normal file
42
pyrogram/client/methods/decorators/on_deleted_messages.py
Normal 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
|
@ -16,10 +16,19 @@
|
||||
# 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 .answer_callback_query import AnswerCallbackQuery
|
||||
import pyrogram
|
||||
from ...ext import BaseClient
|
||||
|
||||
|
||||
class CallbackQuery(
|
||||
AnswerCallbackQuery
|
||||
):
|
||||
pass
|
||||
class OnDisconnect(BaseClient):
|
||||
def on_disconnect(self):
|
||||
"""Use this decorator to automatically register a function for handling
|
||||
disconnections. This does the same thing as :meth:`add_handler` using the
|
||||
:class:`DisconnectHandler`.
|
||||
"""
|
||||
|
||||
def decorator(func):
|
||||
self.add_handler(pyrogram.DisconnectHandler(func))
|
||||
return func
|
||||
|
||||
return decorator
|
@ -24,7 +24,7 @@ class OnMessage(BaseClient):
|
||||
def on_message(self, filters=None, group: int = 0):
|
||||
"""Use this decorator to automatically register a function for handling
|
||||
messages. This does the same thing as :meth:`add_handler` using the
|
||||
MessageHandler.
|
||||
:class:`MessageHandler`.
|
||||
|
||||
Args:
|
||||
filters (:obj:`Filters <pyrogram.Filters>`):
|
||||
|
@ -24,7 +24,7 @@ class OnRawUpdate(BaseClient):
|
||||
def on_raw_update(self, group: int = 0):
|
||||
"""Use this decorator to automatically register a function for handling
|
||||
raw updates. This does the same thing as :meth:`add_handler` using the
|
||||
RawUpdateHandler.
|
||||
:class:`RawUpdateHandler`.
|
||||
|
||||
Args:
|
||||
group (``int``, *optional*):
|
||||
|
@ -16,22 +16,50 @@
|
||||
# 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 .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 .get_history import GetHistory
|
||||
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 .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(
|
||||
DeleteMessages,
|
||||
EditMessageCaption,
|
||||
EditMessageReplyMarkup,
|
||||
EditMessageText,
|
||||
ForwardMessages,
|
||||
GetHistory,
|
||||
GetMessages,
|
||||
Action,
|
||||
Media,
|
||||
Update,
|
||||
ForwardMessages,
|
||||
SendMessage
|
||||
SendAudio,
|
||||
SendChatAction,
|
||||
SendContact,
|
||||
SendDocument,
|
||||
SendGIF,
|
||||
SendLocation,
|
||||
SendMediaGroup,
|
||||
SendMessage,
|
||||
SendPhoto,
|
||||
SendSticker,
|
||||
SendVenue,
|
||||
SendVideo,
|
||||
SendVideoNote,
|
||||
SendVoice
|
||||
):
|
||||
pass
|
||||
|
@ -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
|
@ -17,7 +17,7 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from ....ext import BaseClient
|
||||
from pyrogram.client.ext import BaseClient
|
||||
|
||||
|
||||
class DeleteMessages(BaseClient):
|
@ -17,7 +17,7 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from ....ext import BaseClient, utils
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
|
||||
|
||||
class EditMessageCaption(BaseClient):
|
@ -17,7 +17,7 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from ....ext import BaseClient, utils
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
|
||||
|
||||
class EditMessageReplyMarkup(BaseClient):
|
@ -17,7 +17,7 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from ....ext import BaseClient, utils
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
|
||||
|
||||
class EditMessageText(BaseClient):
|
@ -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
|
@ -23,7 +23,7 @@ import struct
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from pyrogram.api.errors import FileIdInvalid, FilePartMissing
|
||||
from ....ext import BaseClient, utils
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
|
||||
|
||||
class SendAudio(BaseClient):
|
@ -17,7 +17,7 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram.api import functions
|
||||
from ....ext import BaseClient, ChatAction
|
||||
from pyrogram.client.ext import BaseClient, ChatAction
|
||||
|
||||
|
||||
class SendChatAction(BaseClient):
|
@ -17,7 +17,7 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from ....ext import BaseClient, utils
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
|
||||
|
||||
class SendContact(BaseClient):
|
@ -23,7 +23,7 @@ import struct
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from pyrogram.api.errors import FileIdInvalid, FilePartMissing
|
||||
from ....ext import BaseClient, utils
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
|
||||
|
||||
class SendDocument(BaseClient):
|
@ -23,7 +23,7 @@ import struct
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from pyrogram.api.errors import FileIdInvalid, FilePartMissing
|
||||
from ....ext import BaseClient, utils
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
|
||||
|
||||
class SendGIF(BaseClient):
|
@ -17,7 +17,7 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from ....ext import BaseClient, utils
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
|
||||
|
||||
class SendLocation(BaseClient):
|
@ -24,7 +24,7 @@ import struct
|
||||
from pyrogram.api import functions, types
|
||||
from pyrogram.api.errors import FileIdInvalid
|
||||
from pyrogram.client import types as pyrogram_types
|
||||
from ....ext import BaseClient, utils
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
|
||||
|
||||
class SendMediaGroup(BaseClient):
|
@ -22,7 +22,7 @@ import struct
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from pyrogram.api.errors import FileIdInvalid, FilePartMissing
|
||||
from ....ext import BaseClient, utils
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
|
||||
|
||||
class SendPhoto(BaseClient):
|
@ -22,7 +22,7 @@ import struct
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from pyrogram.api.errors import FileIdInvalid, FilePartMissing
|
||||
from ....ext import BaseClient, utils
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
|
||||
|
||||
class SendSticker(BaseClient):
|
@ -17,7 +17,7 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from ....ext import BaseClient, utils
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
|
||||
|
||||
class SendVenue(BaseClient):
|
@ -23,7 +23,7 @@ import struct
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from pyrogram.api.errors import FileIdInvalid, FilePartMissing
|
||||
from ....ext import BaseClient, utils
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
|
||||
|
||||
class SendVideo(BaseClient):
|
@ -23,7 +23,7 @@ import struct
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from pyrogram.api.errors import FileIdInvalid, FilePartMissing
|
||||
from ....ext import BaseClient, utils
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
|
||||
|
||||
class SendVideoNote(BaseClient):
|
@ -23,7 +23,7 @@ import struct
|
||||
|
||||
from pyrogram.api import functions, types
|
||||
from pyrogram.api.errors import FileIdInvalid, FilePartMissing
|
||||
from ....ext import BaseClient, utils
|
||||
from pyrogram.client.ext import BaseClient, utils
|
||||
|
||||
|
||||
class SendVoice(BaseClient):
|
@ -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
|
@ -30,6 +30,7 @@ from .input_phone_contact import InputPhoneContact
|
||||
from .location import Location
|
||||
from .message import Message
|
||||
from .message_entity import MessageEntity
|
||||
from .messages import Messages
|
||||
from .photo_size import PhotoSize
|
||||
from .reply_markup import (
|
||||
ForceReply, InlineKeyboardButton, InlineKeyboardMarkup,
|
||||
@ -43,4 +44,3 @@ from .venue import Venue
|
||||
from .video import Video
|
||||
from .video_note import VideoNote
|
||||
from .voice import Voice
|
||||
from .messages import Messages
|
||||
|
@ -17,6 +17,7 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram.api.core import Object
|
||||
from .reply_markup import InlineKeyboardMarkup, ReplyKeyboardMarkup
|
||||
|
||||
|
||||
class Message(Object):
|
||||
@ -260,7 +261,7 @@ class Message(Object):
|
||||
reply_markup=None,
|
||||
):
|
||||
self.message_id = message_id # int
|
||||
self.client = client
|
||||
self._client = client
|
||||
self.date = date # int
|
||||
self.chat = chat # Chat
|
||||
self.from_user = from_user # flags.0?User
|
||||
@ -309,3 +310,267 @@ class Message(Object):
|
||||
self.matches = matches
|
||||
self.command = command
|
||||
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")
|
||||
|
@ -30,12 +30,18 @@ class Update(Object):
|
||||
edited_message (:obj:`Message <pyrogram.Message>`, *optional*):
|
||||
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*):
|
||||
New incoming channel post of any kind — text, photo, sticker, etc.
|
||||
|
||||
edited_channel_post (:obj:`Message <pyrogram.Message>`, *optional*):
|
||||
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*):
|
||||
New incoming inline query.
|
||||
|
||||
@ -60,8 +66,10 @@ class Update(Object):
|
||||
self,
|
||||
message=None,
|
||||
edited_message=None,
|
||||
deleted_messages=None,
|
||||
channel_post=None,
|
||||
edited_channel_post=None,
|
||||
deleted_channel_posts=None,
|
||||
inline_query=None,
|
||||
chosen_inline_result=None,
|
||||
callback_query=None,
|
||||
@ -70,8 +78,10 @@ class Update(Object):
|
||||
):
|
||||
self.message = message
|
||||
self.edited_message = edited_message
|
||||
self.deleted_messages = deleted_messages
|
||||
self.channel_post = channel_post
|
||||
self.edited_channel_post = edited_channel_post
|
||||
self.deleted_channel_posts = deleted_channel_posts
|
||||
self.inline_query = inline_query
|
||||
self.chosen_inline_result = chosen_inline_result
|
||||
self.callback_query = callback_query
|
||||
|
@ -35,9 +35,6 @@ class VideoNote(Object):
|
||||
thumb (:obj:`PhotoSize <pyrogram.PhotoSize>`, *optional*):
|
||||
Video thumbnail.
|
||||
|
||||
file_size (``int``, *optional*):
|
||||
File size.
|
||||
|
||||
file_name (``str``, *optional*):
|
||||
Video note file name.
|
||||
|
||||
|
@ -26,10 +26,14 @@ log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Connection:
|
||||
MAX_RETRIES = 3
|
||||
|
||||
MODES = {
|
||||
0: TCPFull,
|
||||
1: TCPAbridged,
|
||||
2: TCPIntermediate
|
||||
2: TCPIntermediate,
|
||||
3: TCPAbridgedO,
|
||||
4: TCPIntermediateO
|
||||
}
|
||||
|
||||
def __init__(self, address: tuple, proxy: dict, mode: int = 1):
|
||||
@ -40,7 +44,7 @@ class Connection:
|
||||
self.connection = None
|
||||
|
||||
def connect(self):
|
||||
while True:
|
||||
for i in range(Connection.MAX_RETRIES):
|
||||
self.connection = self.mode(self.proxy)
|
||||
|
||||
try:
|
||||
@ -51,6 +55,8 @@ class Connection:
|
||||
time.sleep(1)
|
||||
else:
|
||||
break
|
||||
else:
|
||||
raise TimeoutError
|
||||
|
||||
def close(self):
|
||||
self.connection.close()
|
||||
|
@ -17,5 +17,7 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from .tcp_abridged import TCPAbridged
|
||||
from .tcp_abridged_o import TCPAbridgedO
|
||||
from .tcp_full import TCPFull
|
||||
from .tcp_intermediate import TCPIntermediate
|
||||
from .tcp_intermediate_o import TCPIntermediateO
|
||||
|
@ -41,15 +41,15 @@ class TCP(socks.socksocket):
|
||||
if proxy and self.proxy_enabled:
|
||||
self.set_proxy(
|
||||
proxy_type=socks.SOCKS5,
|
||||
addr=proxy["hostname"],
|
||||
port=proxy["port"],
|
||||
username=proxy["username"],
|
||||
password=proxy["password"]
|
||||
addr=proxy.get("hostname", None),
|
||||
port=proxy.get("port", None),
|
||||
username=proxy.get("username", None),
|
||||
password=proxy.get("password", None)
|
||||
)
|
||||
|
||||
log.info("Using proxy {}:{}".format(
|
||||
proxy["hostname"],
|
||||
proxy["port"]
|
||||
proxy.get("hostname", None),
|
||||
proxy.get("port", None)
|
||||
))
|
||||
|
||||
def close(self):
|
||||
|
@ -26,28 +26,23 @@ log = logging.getLogger(__name__)
|
||||
class TCPAbridged(TCP):
|
||||
def __init__(self, proxy: dict):
|
||||
super().__init__(proxy)
|
||||
self.is_first_packet = None
|
||||
|
||||
def connect(self, address: tuple):
|
||||
super().connect(address)
|
||||
self.is_first_packet = True
|
||||
super().sendall(b"\xef")
|
||||
|
||||
log.info("Connected{}!".format(" with proxy" if self.proxy_enabled else ""))
|
||||
|
||||
def sendall(self, data: bytes, *args):
|
||||
length = len(data) // 4
|
||||
|
||||
data = (
|
||||
bytes([length]) + data
|
||||
if length <= 126
|
||||
else b"\x7f" + int.to_bytes(length, 3, "little") + data
|
||||
super().sendall(
|
||||
(bytes([length])
|
||||
if length <= 126
|
||||
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:
|
||||
length = super().recvall(1)
|
||||
|
||||
|
93
pyrogram/connection/transport/tcp/tcp_abridged_o.py
Normal file
93
pyrogram/connection/transport/tcp/tcp_abridged_o.py
Normal 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)
|
@ -19,7 +19,7 @@
|
||||
import logging
|
||||
from struct import pack, unpack
|
||||
|
||||
from .tcp_abridged import TCP
|
||||
from .tcp import TCP
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -27,22 +27,15 @@ log = logging.getLogger(__name__)
|
||||
class TCPIntermediate(TCP):
|
||||
def __init__(self, proxy: dict):
|
||||
super().__init__(proxy)
|
||||
self.is_first_packet = None
|
||||
|
||||
def connect(self, address: tuple):
|
||||
super().connect(address)
|
||||
self.is_first_packet = True
|
||||
super().sendall(b"\xee" * 4)
|
||||
|
||||
log.info("Connected{}!".format(" with proxy" if self.proxy_enabled else ""))
|
||||
|
||||
def sendall(self, data: bytes, *args):
|
||||
length = len(data)
|
||||
data = pack("<i", length) + data
|
||||
|
||||
if self.is_first_packet:
|
||||
data = b"\xee" * 4 + data
|
||||
self.is_first_packet = False
|
||||
|
||||
super().sendall(data)
|
||||
super().sendall(pack("<i", len(data)) + data)
|
||||
|
||||
def recvall(self, length: int = 0) -> bytes or None:
|
||||
length = super().recvall(4)
|
||||
@ -50,4 +43,4 @@ class TCPIntermediate(TCP):
|
||||
if length is None:
|
||||
return None
|
||||
|
||||
return super().recvall(unpack("<I", length)[0])
|
||||
return super().recvall(unpack("<i", length)[0])
|
||||
|
81
pyrogram/connection/transport/tcp/tcp_intermediate_o.py
Normal file
81
pyrogram/connection/transport/tcp/tcp_intermediate_o.py
Normal 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)
|
@ -22,71 +22,114 @@ log = logging.getLogger(__name__)
|
||||
|
||||
try:
|
||||
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:
|
||||
import pyaes
|
||||
|
||||
log.warning(
|
||||
"TgCrypto is missing! "
|
||||
"Pyrogram will work the same, but at a much slower speed. "
|
||||
"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:
|
||||
@classmethod
|
||||
def ige_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
|
||||
if is_fast:
|
||||
return tgcrypto.ige_encrypt(data, key, iv)
|
||||
else:
|
||||
class AES:
|
||||
@classmethod
|
||||
def ige256_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
|
||||
return cls.ige(data, key, iv, True)
|
||||
|
||||
@classmethod
|
||||
def ige_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
|
||||
if is_fast:
|
||||
return tgcrypto.ige_decrypt(data, key, iv)
|
||||
else:
|
||||
@classmethod
|
||||
def ige256_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes:
|
||||
return cls.ige(data, key, iv, False)
|
||||
|
||||
@staticmethod
|
||||
def ctr_decrypt(data: bytes, key: bytes, iv: bytes, offset: int) -> bytes:
|
||||
replace = int.to_bytes(offset // 16, 4, "big")
|
||||
iv = iv[:-4] + replace
|
||||
@classmethod
|
||||
def ctr256_encrypt(cls, data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
|
||||
return cls.ctr(data, key, iv, state or bytearray(1))
|
||||
|
||||
if is_fast:
|
||||
return tgcrypto.ctr_decrypt(data, key, iv)
|
||||
else:
|
||||
ctr = pyaes.AESModeOfOperationCTR(key)
|
||||
ctr._counter._counter = list(iv)
|
||||
return ctr.decrypt(data)
|
||||
@classmethod
|
||||
def ctr256_decrypt(cls, data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes:
|
||||
return cls.ctr(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",
|
||||
)
|
||||
@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",
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
|
||||
cipher = pyaes.AES(key)
|
||||
@classmethod
|
||||
def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes:
|
||||
cipher = pyaes.AES(key)
|
||||
|
||||
iv_1 = iv[:16]
|
||||
iv_2 = iv[16:]
|
||||
iv_1 = 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:
|
||||
for i, chunk in enumerate(data):
|
||||
iv_1 = data[i] = cls.xor(cipher.encrypt(cls.xor(chunk, iv_1)), iv_2)
|
||||
iv_2 = chunk
|
||||
else:
|
||||
for i, chunk in enumerate(data):
|
||||
iv_2 = data[i] = cls.xor(cipher.decrypt(cls.xor(chunk, iv_2)), iv_1)
|
||||
iv_1 = chunk
|
||||
if encrypt:
|
||||
for i, chunk in enumerate(data):
|
||||
iv_1 = data[i] = cls.xor(cipher.encrypt(cls.xor(chunk, iv_1)), iv_2)
|
||||
iv_2 = chunk
|
||||
else:
|
||||
for i, chunk in enumerate(data):
|
||||
iv_2 = data[i] = cls.xor(cipher.decrypt(cls.xor(chunk, iv_2)), iv_1)
|
||||
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
|
||||
|
@ -49,8 +49,9 @@ class Auth:
|
||||
def __init__(self, dc_id: int, test_mode: bool, proxy: dict):
|
||||
self.dc_id = dc_id
|
||||
self.test_mode = test_mode
|
||||
self.proxy = proxy
|
||||
|
||||
self.connection = Connection(DataCenter(dc_id, test_mode), proxy)
|
||||
self.connection = None
|
||||
|
||||
@staticmethod
|
||||
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.
|
||||
# If that happens, just try again up to MAX_RETRIES times.
|
||||
while True:
|
||||
self.connection = Connection(DataCenter(self.dc_id, self.test_mode), self.proxy)
|
||||
|
||||
try:
|
||||
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)
|
||||
|
||||
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:]
|
||||
|
||||
server_dh_inner_data = Object.read(BytesIO(answer))
|
||||
@ -192,7 +195,7 @@ class Auth:
|
||||
sha = sha1(data).digest()
|
||||
padding = urandom(- (len(data) + len(sha)) % 16)
|
||||
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")
|
||||
set_client_dh_params_answer = self.send(
|
||||
|
@ -17,8 +17,8 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram.api.core import Message, MsgContainer, Object
|
||||
from pyrogram.api.functions import Ping, HttpWait
|
||||
from pyrogram.api.types import MsgsAck
|
||||
from pyrogram.api.functions import Ping
|
||||
from pyrogram.api.types import MsgsAck, HttpWait
|
||||
from .msg_id import MsgId
|
||||
from .seq_no import SeqNo
|
||||
|
||||
|
@ -17,7 +17,6 @@
|
||||
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
import platform
|
||||
import threading
|
||||
import time
|
||||
from datetime import timedelta, datetime
|
||||
@ -47,19 +46,6 @@ class Result:
|
||||
|
||||
|
||||
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
|
||||
NET_WORKERS = 1
|
||||
WAIT_TIMEOUT = 15
|
||||
@ -84,24 +70,24 @@ class Session:
|
||||
}
|
||||
|
||||
def __init__(self,
|
||||
client: pyrogram,
|
||||
dc_id: int,
|
||||
test_mode: bool,
|
||||
proxy: dict,
|
||||
auth_key: bytes,
|
||||
api_id: int,
|
||||
is_cdn: bool = False,
|
||||
client: pyrogram = None):
|
||||
is_media: bool = False,
|
||||
is_cdn: bool = False):
|
||||
if not Session.notice_displayed:
|
||||
print("Pyrogram v{}, {}".format(__version__, __copyright__))
|
||||
print("Licensed under the terms of the " + __license__, end="\n\n")
|
||||
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.dc_id = dc_id
|
||||
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.session_id = Long(MsgId())
|
||||
@ -126,6 +112,8 @@ class Session:
|
||||
|
||||
def start(self):
|
||||
while True:
|
||||
self.connection = Connection(DataCenter(self.dc_id, self.client.test_mode), self.client.proxy)
|
||||
|
||||
try:
|
||||
self.connection.connect()
|
||||
|
||||
@ -153,12 +141,14 @@ class Session:
|
||||
functions.InvokeWithLayer(
|
||||
layer,
|
||||
functions.InitConnection(
|
||||
self.api_id,
|
||||
self.DEVICE_MODEL,
|
||||
self.SYSTEM_VERSION,
|
||||
self.APP_VERSION,
|
||||
"en", "", "en",
|
||||
functions.help.GetConfig(),
|
||||
api_id=self.client.api_id,
|
||||
app_version=self.client.app_version,
|
||||
device_model=self.client.device_model,
|
||||
system_version=self.client.system_version,
|
||||
system_lang_code=self.client.lang_code,
|
||||
lang_code=self.client.lang_code,
|
||||
lang_pack="",
|
||||
query=functions.help.GetConfig(),
|
||||
)
|
||||
)
|
||||
)
|
||||
@ -207,6 +197,12 @@ class Session:
|
||||
for i in self.results.values():
|
||||
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")
|
||||
|
||||
def restart(self):
|
||||
@ -222,14 +218,14 @@ class Session:
|
||||
msg_key = msg_key_large[8:24]
|
||||
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:
|
||||
assert b.read(8) == self.auth_key_id, b.getvalue()
|
||||
|
||||
msg_key = b.read(16)
|
||||
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)
|
||||
|
||||
# https://core.telegram.org/mtproto/security_guidelines#checking-session-id
|
||||
@ -379,7 +375,7 @@ class Session:
|
||||
|
||||
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)
|
||||
msg_id = message.msg_id
|
||||
|
||||
@ -395,7 +391,7 @@ class Session:
|
||||
raise e
|
||||
|
||||
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
|
||||
|
||||
if result is None:
|
||||
@ -410,11 +406,11 @@ class Session:
|
||||
else:
|
||||
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)
|
||||
|
||||
try:
|
||||
return self._send(data)
|
||||
return self._send(data, timeout=timeout)
|
||||
except (OSError, TimeoutError, InternalServerError) as e:
|
||||
if retries == 0:
|
||||
raise e from None
|
||||
@ -425,4 +421,4 @@ class Session:
|
||||
datetime.now(), type(data)))
|
||||
|
||||
time.sleep(0.5)
|
||||
return self.send(data, retries - 1)
|
||||
return self.send(data, retries - 1, timeout)
|
||||
|
@ -1 +0,0 @@
|
||||
tgcrypto>=1.0.4
|
Loading…
Reference in New Issue
Block a user