diff --git a/README.rst b/README.rst index 0b9efb76..db2957c3 100644 --- a/README.rst +++ b/README.rst @@ -26,7 +26,7 @@ Features - **Easy to use**: You can easily install Pyrogram using pip and start building your app right away. - **High-level**: The low-level details of MTProto are abstracted and automatically handled. - **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C. -- **Updated** to the latest Telegram API version, currently Layer 81 on top of MTProto 2.0. +- **Updated** to the latest Telegram API version, currently Layer 82 on top of MTProto 2.0. - **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API. - **Full API**, allowing to execute any advanced action an official client is able to do, and more. @@ -78,14 +78,13 @@ Copyright & License

-
Pyrogram Icon
-
Pyrogram Label
+
Pyrogram Logo

Telegram MTProto API Client Library for Python - +
Download @@ -100,25 +99,25 @@ Copyright & License

- Scheme Layer - TgCrypto

-.. |logo| image:: https://pyrogram.ml/images/logo.png +.. |logo| image:: https://raw.githubusercontent.com/pyrogram/logos/master/logos/pyrogram_logo2.png :target: https://pyrogram.ml :alt: Pyrogram .. |description| replace:: **Telegram MTProto API Client Library for Python** -.. |scheme| image:: "https://img.shields.io/badge/SCHEME-LAYER%2081-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30" +.. |scheme| image:: "https://img.shields.io/badge/SCHEME-LAYER%2082-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30" :target: compiler/api/source/main_api.tl :alt: Scheme Layer -.. |tgcrypto| image:: "https://img.shields.io/badge/TGCRYPTO-V1.0.4-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30" +.. |tgcrypto| image:: "https://img.shields.io/badge/TGCRYPTO-V1.1.1-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30" :target: https://github.com/pyrogram/tgcrypto :alt: TgCrypto diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index d1bf8cfa..bcb96ea4 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -172,9 +172,8 @@ def start(): with open("{}/source/auth_key.tl".format(HOME), encoding="utf-8") as auth, \ open("{}/source/sys_msgs.tl".format(HOME), encoding="utf-8") as system, \ - open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api, \ - open("{}/source/pyrogram.tl".format(HOME), encoding="utf-8") as pyrogram: - schema = (auth.read() + system.read() + api.read() + pyrogram.read()).splitlines() + open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api: + schema = (auth.read() + system.read() + api.read()).splitlines() with open("{}/template/mtproto.txt".format(HOME), encoding="utf-8") as f: mtproto_template = f.read() @@ -494,17 +493,19 @@ def start(): f.write("\n 0xb0700015: \"pyrogram.client.types.ChatPhoto\",") f.write("\n 0xb0700016: \"pyrogram.client.types.ChatMember\",") f.write("\n 0xb0700017: \"pyrogram.client.types.Sticker\",") - f.write("\n 0xb0700025: \"pyrogram.client.types.GIF\",") + f.write("\n 0xb0700018: \"pyrogram.client.types.bots.ForceReply\",") + f.write("\n 0xb0700019: \"pyrogram.client.types.bots.InlineKeyboardButton\",") + f.write("\n 0xb0700020: \"pyrogram.client.types.bots.InlineKeyboardMarkup\",") + f.write("\n 0xb0700021: \"pyrogram.client.types.bots.KeyboardButton\",") + f.write("\n 0xb0700022: \"pyrogram.client.types.bots.ReplyKeyboardMarkup\",") + f.write("\n 0xb0700023: \"pyrogram.client.types.bots.ReplyKeyboardRemove\",") + f.write("\n 0xb0700024: \"pyrogram.client.types.CallbackQuery\",") + f.write("\n 0xb0700025: \"pyrogram.client.types.Animation\",") f.write("\n 0xb0700026: \"pyrogram.client.types.Messages\",") - - f.write("\n 0xb0700018: \"pyrogram.client.types.reply_markup.ForceReply\",") - f.write("\n 0xb0700019: \"pyrogram.client.types.reply_markup.InlineKeyboardButton\",") - f.write("\n 0xb0700020: \"pyrogram.client.types.reply_markup.InlineKeyboardMarkup\",") - f.write("\n 0xb0700021: \"pyrogram.client.types.reply_markup.KeyboardButton\",") - f.write("\n 0xb0700022: \"pyrogram.client.types.reply_markup.ReplyKeyboardMarkup\",") - f.write("\n 0xb0700023: \"pyrogram.client.types.reply_markup.ReplyKeyboardRemove\",") - - f.write("\n 0xb0700024: \"pyrogram.client.types.CallbackQuery\"") + f.write("\n 0xb0700027: \"pyrogram.client.types.Photo\",") + f.write("\n 0xb0700028: \"pyrogram.client.types.Dialog\",") + f.write("\n 0xb0700029: \"pyrogram.client.types.Dialogs\",") + f.write("\n 0xb0700030: \"pyrogram.client.types.ChatMembers\",") f.write("\n}\n") diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index d1f2fa33..9ee01cb3 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -36,7 +36,7 @@ inputMediaEmpty#9664f57f = InputMedia; inputMediaUploadedPhoto#1e287d04 flags:# file:InputFile stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMedia; inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia; -inputMediaContact#a6e45987 phone_number:string first_name:string last_name:string = InputMedia; +inputMediaContact#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia; inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; inputMediaDocument#23ab23d2 flags:# id:InputDocument ttl_seconds:flags.0?int = InputMedia; inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia; @@ -61,6 +61,7 @@ inputFileLocation#14637196 volume_id:long local_id:int secret:long = InputFileLo 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; +inputTakeoutFileLocation#29be5899 = InputFileLocation; inputAppEvent#770656a8 time:double type:string peer:long data:string = InputAppEvent; @@ -121,7 +122,7 @@ messageService#9e19a1f6 flags:# out:flags.1?true mentioned:flags.4?true media_un messageMediaEmpty#3ded6320 = MessageMedia; messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; -messageMediaContact#5e7d2f39 phone_number:string first_name:string last_name:string user_id:int = MessageMedia; +messageMediaContact#cbf24940 phone_number:string first_name:string last_name:string vcard:string user_id:int = MessageMedia; messageMediaUnsupported#9f84f49e = MessageMedia; messageMediaDocument#9cb070d7 flags:# document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia; messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia; @@ -153,7 +154,7 @@ messageActionBotAllowed#abe9affe domain:string = MessageAction; messageActionSecureValuesSentMe#1b287353 values:Vector credentials:SecureCredentialsEncrypted = MessageAction; messageActionSecureValuesSent#d95c6154 types:Vector = MessageAction; -dialog#e4def5db flags:# pinned:flags.2?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage = Dialog; +dialog#e4def5db flags:# pinned:flags.2?true unread_mark:flags.3?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; photoEmpty#2331b22d id:long = Photo; photo#9288dd29 flags:# has_stickers:flags.0?true id:long access_hash:long date:int sizes:Vector = Photo; @@ -163,7 +164,7 @@ photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = Phot photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize; geoPointEmpty#1117dd5f = GeoPoint; -geoPoint#2049d70c long:double lat:double = GeoPoint; +geoPoint#296f104 long:double lat:double access_hash:long = GeoPoint; auth.checkedPhone#811ea28e phone_registered:Bool = auth.CheckedPhone; @@ -213,6 +214,7 @@ contacts.blockedSlice#900802a1 count:int blocked:Vector users:Ve messages.dialogs#15ba6c40 dialogs:Vector messages:Vector chats:Vector users:Vector = messages.Dialogs; messages.dialogsSlice#71e094f3 count:int dialogs:Vector messages:Vector chats:Vector users:Vector = messages.Dialogs; +messages.dialogsNotModified#f0e3e596 count:int = messages.Dialogs; messages.messages#8c718e87 messages:Vector chats:Vector users:Vector = messages.Messages; messages.messagesSlice#b446ae3 count:int messages:Vector chats:Vector users:Vector = messages.Messages; @@ -309,6 +311,7 @@ updateFavedStickers#e511996d = Update; updateChannelReadMessagesContents#89893b45 channel_id:int messages:Vector = Update; updateContactsReset#7084a7be = Update; updateChannelAvailableMessages#70db6837 channel_id:int available_min_id:int = Update; +updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -335,7 +338,7 @@ upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes 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#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 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; +config#3213dbba 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 dc_txt_domain_name:string 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 gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int = Config; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; @@ -562,7 +565,7 @@ inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1? inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaGeo#c1b15d65 flags:# geo_point:InputGeoPoint period:int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; -inputBotInlineMessageMediaContact#2daf01a7 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; +inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineResult#88bf9319 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?InputWebDocument content:flags.5?InputWebDocument send_message:InputBotInlineMessage = InputBotInlineResult; @@ -574,7 +577,7 @@ botInlineMessageMediaAuto#764cf810 flags:# message:string entities:flags.1?Vecto botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaGeo#b722de65 flags:# geo:GeoPoint period:int reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; -botInlineMessageMediaContact#35edb4d4 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; +botInlineMessageMediaContact#18d1cdc2 flags:# phone_number:string first_name:string last_name:string vcard: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; botInlineMediaResult#17db940b flags:# id:string type:string photo:flags.0?Photo document:flags.1?Document title:flags.2?string description:flags.3?string send_message:BotInlineMessage = BotInlineResult; @@ -617,8 +620,9 @@ topPeerCategoryPeers#fb834291 category:TopPeerCategory count:int peers:Vector chats:Vector users:Vector = contacts.TopPeers; +contacts.topPeersDisabled#b52c939d = contacts.TopPeers; -draftMessageEmpty#ba4baec5 = DraftMessage; +draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector date:int = DraftMessage; messages.featuredStickersNotModified#4ede3cf = messages.FeaturedStickers; @@ -706,14 +710,13 @@ paymentRequestedInfo#909c3f94 flags:# name:flags.0?string phone:flags.1?string e paymentSavedCredentialsCard#cdc27a1f id:string title:string = PaymentSavedCredentials; -webDocument#c61acbd8 url:string access_hash:long size:int mime_type:string attributes:Vector dc_id:int = WebDocument; +webDocument#1c570ed1 url:string access_hash:long size:int mime_type:string attributes:Vector = WebDocument; webDocumentNoProxy#f9c8bcc6 url:string size:int mime_type:string attributes:Vector = WebDocument; inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector = 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; +inputWebFileGeoPointLocation#9f2221c9 geo_point:InputGeoPoint access_hash:long 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; @@ -883,6 +886,10 @@ account.sentEmailCode#811f854f email_pattern:string length:int = account.SentEma help.deepLinkInfoEmpty#66afa166 = help.DeepLinkInfo; help.deepLinkInfo#6a4ee832 flags:# update_app:flags.0?true message:string entities:flags.1?Vector = help.DeepLinkInfo; +savedPhoneContact#1142bd56 phone:string first_name:string last_name:string date:int = SavedContact; + +account.takeout#4dba4501 id:long = account.Takeout; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -890,13 +897,14 @@ invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector 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; +invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X; +invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X; 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; auth.logOut#5717da40 = Bool; auth.resetAuthorizations#9fab0d1a = Bool; -auth.sendInvites#771c1d97 phone_numbers:Vector message:string = Bool; auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization; auth.importAuthorization#e3ef9613 id:int bytes:bytes = auth.Authorization; auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool; @@ -948,6 +956,8 @@ account.sendVerifyPhoneCode#823380b4 flags:# allow_flashcall:flags.0?true phone_ 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; +account.initTakeoutSession#f05b4804 flags:# contacts:flags.0?true message_users:flags.1?true message_chats:flags.2?true message_megagroups:flags.3?true message_channels:flags.4?true files:flags.5?true file_max_size:flags.5?int = account.Takeout; +account.finishTakeoutSession#1d2652ee flags:# success:flags.0?true = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#ca30a5b1 id:InputUser = UserFull; @@ -968,9 +978,11 @@ contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer; contacts.getTopPeers#d4982db5 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:int = contacts.TopPeers; contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = Bool; contacts.resetSaved#879537f1 = Bool; +contacts.getSaved#82f1e39f = Vector; +contacts.toggleTopPeers#8514bdda enabled:Bool = Bool; messages.getMessages#63c66506 id:Vector = messages.Messages; -messages.getDialogs#191ba9c5 flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int = messages.Dialogs; +messages.getDialogs#b098aee6 flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:int = messages.Dialogs; messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; messages.search#8614ef68 flags:# peer:InputPeer q:string from_id:flags.0?InputUser filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages; @@ -1065,6 +1077,9 @@ messages.getRecentLocations#bbc45b09 peer:InputPeer limit:int hash:int = message messages.sendMultiMedia#2095512f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector = Updates; messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile; messages.searchStickerSets#c2b7d08b flags:# exclude_featured:flags.0?true q:string hash:int = messages.FoundStickerSets; +messages.getSplitRanges#1cff7e08 = Vector; +messages.markDialogUnread#c286d98f flags:# unread:flags.0?true peer:InputDialogPeer = Bool; +messages.getDialogUnreadMarks#22e24e22 = Vector; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1131,6 +1146,7 @@ channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector = Bool; channels.deleteHistory#af369d42 channel:InputChannel max_id:int = Bool; channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates; +channels.getLeftChannels#8341ecc0 offset:int = messages.Chats; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -1161,4 +1177,4 @@ langpack.getStrings#2e1ee318 lang_code:string keys:Vector = Vector; -// LAYER 81 +// LAYER 82 diff --git a/compiler/api/source/pyrogram.tl b/compiler/api/source/pyrogram.tl deleted file mode 100644 index a8232bd6..00000000 --- a/compiler/api/source/pyrogram.tl +++ /dev/null @@ -1,22 +0,0 @@ -// Pyrogram - ----types--- - -//pyrogram.update#b0700000 flags:# update_id:int message:flags.0?Message edited_message:flags.1?Message channel_post:flags.2?Message edited_channel_post:flags.3?Message inline_query:flags.4?InlineQuery chosen_inline_result:flags.5?ChosenInlineResult callback_query:flags.6?CallbackQuery shipping_query:flags.7?ShippingQuery pre_checkout_query:flags.8?PreCheckoutQuery = pyrogram.Update; -//pyrogram.user#b0700001 flags:# id:int is_bot:Bool first_name:string last_name:flags.0?string username:flags.1?string language_code:flags.2?string phone_number:flags.3?string photo:flags.4?ChatPhoto = pyrogram.User; -//pyrogram.chat#b0700002 flags:# id:int type:string title:flags.0?string username:flags.1?string first_name:flags.2?string last_name:flags.3?string all_members_are_administrators:flags.4?Bool photo:flags.5?ChatPhoto description:flags.6?string invite_link:flags.7?string pinned_message:flags.8?Message sticker_set_name:flags.9?string can_set_sticker_set:flags.10?Bool = pyrogram.Chat; -//pyrogram.message#b0700003 flags:# message_id:int from_user:flags.0?User date:int chat:Chat forward_from:flags.1?User forward_from_chat:flags.2?Chat forward_from_message_id:flags.3?int forward_signature:flags.4?string forward_date:flags.5?int reply_to_message:flags.6?Message edit_date:flags.7?int media_group_id:flags.8?string author_signature:flags.9?string text:flags.10?string entities:flags.11?Vector caption_entities:flags.12?Vector audio:flags.13?Audio document:flags.14?Document game:flags.15?Game photo:flags.16?Vector sticker:flags.17?Sticker video:flags.18?Video voice:flags.19?Voice video_note:flags.20?VideoNote caption:flags.21?string contact:flags.22?Contact location:flags.23?Location venue:flags.24?Venue new_chat_members:flags.25?Vector left_chat_member:flags.26?User new_chat_title:flags.27?string new_chat_photo:flags.28?Vector delete_chat_photo:flags.29?true group_chat_created:flags.30?true supergroup_chat_created:flags.31?true channel_chat_created:flags.32?true migrate_to_chat_id:flags.33?int migrate_from_chat_id:flags.34?int pinned_message:flags.35?Message invoice:flags.36?Invoice successful_payment:flags.37?SuccessfulPayment connected_website:flags.38?string views:flags.39?int via_bot:flags.40?User = pyrogram.Message; -//pyrogram.messageEntity#b0700004 flags:# type:string offset:int length:int url:flags.0?string user:flags.1?User = pyrogram.MessageEntity; -//pyrogram.photoSize#b0700005 flags:# file_id:string file_size:flags.0?int date:flags.1?int width:int height:int = pyrogram.PhotoSize; -//pyrogram.audio#b0700006 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int duration:int performer:flags.5?string title:flags.6?string = pyrogram.Audio; -//pyrogram.document#b0700007 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int = pyrogram.Document; -//pyrogram.video#b0700008 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int width:int height:int duration:int = pyrogram.Video; -//pyrogram.voice#b0700009 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int duration:int = pyrogram.Voice; -//pyrogram.videoNote#b0700010 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int length:int duration:int = pyrogram.VideoNote; -//pyrogram.contact#b0700011 flags:# phone_number:string first_name:string last_name:flags.0?string user_id:flags.1?int = pyrogram.Contact; -//pyrogram.location#b0700012 longitude:double latitude:double = pyrogram.Location; -//pyrogram.venue#b0700013 flags:# location:Location title:string address:string foursquare_id:flags.0?string = pyrogram.Venue; -//pyrogram.userProfilePhotos#b0700014 total_count:int photos:Vector> = pyrogram.UserProfilePhotos; -//pyrogram.chatPhoto#b0700015 small_file_id:string big_file_id:string = pyrogram.ChatPhoto; -//pyrogram.chatMember#b0700016 flags:# user:User status:string until_date:flags.0?int can_be_edited:flags.1?Bool can_change_info:flags.2?Bool can_post_messages:flags.3?Bool can_edit_messages:flags.4?Bool can_delete_messages:flags.5?Bool can_invite_users:flags.6?Bool can_restrict_members:flags.7?Bool can_pin_messages:flags.8?Bool can_promote_members:flags.9?Bool can_send_messages:flags.10?Bool can_send_media_messages:flags.11?Bool can_send_other_messages:flags.12?Bool can_add_web_page_previews:flags.13?Bool = pyrogram.ChatMember; -//pyrogram.sticker#b0700017 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int width:int height:int emoji:flags.5?string set_name:flags.6?string mask_position:flags.7?MaskPosition = pyrogram.Sticker; diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index cd196077..ed332838 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -62,4 +62,8 @@ USER_IS_BOT A bot cannot send messages to other bots or to itself WEBPAGE_CURL_FAILED Telegram could not fetch the provided URL STICKERSET_INVALID The requested sticker set is invalid PEER_FLOOD The method can't be used because your account is limited -MEDIA_CAPTION_TOO_LONG The media caption is longer than 200 characters \ No newline at end of file +MEDIA_CAPTION_TOO_LONG The media caption is longer than 200 characters +USER_NOT_MUTUAL_CONTACT The user is not a mutual contact +USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups +API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side +USER_NOT_PARTICIPANT The user is not a member of this chat \ No newline at end of file diff --git a/compiler/error/source/403_FORBIDDEN.tsv b/compiler/error/source/403_FORBIDDEN.tsv new file mode 100644 index 00000000..7bacbe7d --- /dev/null +++ b/compiler/error/source/403_FORBIDDEN.tsv @@ -0,0 +1,2 @@ +id message +CHAT_WRITE_FORBIDDEN You don't have rights to send messages in this chat \ No newline at end of file diff --git a/compiler/error/source/406_NOT_ACCEPTABLE.tsv b/compiler/error/source/406_NOT_ACCEPTABLE.tsv new file mode 100644 index 00000000..3a88a7b6 --- /dev/null +++ b/compiler/error/source/406_NOT_ACCEPTABLE.tsv @@ -0,0 +1,2 @@ +id message +AUTH_KEY_DUPLICATED Authorization error. You must log out and log in again with your phone number. We apologize for the inconvenience. \ No newline at end of file diff --git a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv index abb58495..60d1b51a 100644 --- a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv +++ b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv @@ -3,4 +3,5 @@ AUTH_RESTART User authorization has restarted RPC_CALL_FAIL Telegram is having internal problems. Please try again later RPC_MCGET_FAIL Telegram is having internal problems. Please try again later PERSISTENT_TIMESTAMP_OUTDATED Telegram is having internal problems. Please try again later -HISTORY_GET_FAILED Telegram is having internal problems. Please try again later \ No newline at end of file +HISTORY_GET_FAILED Telegram is having internal problems. Please try again later +REG_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile index c01e3d3d..c647eb13 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -3,7 +3,7 @@ # You can set these variables from the command line. SPHINXOPTS = -SPHINXBUILD = ~/PycharmProjects/pyrogram/venv3.6/bin/sphinx-build +SPHINXBUILD = sphinx-build SPHINXPROJ = Pyrogram SOURCEDIR = source BUILDDIR = build diff --git a/docs/Makefile_ b/docs/Makefile_ deleted file mode 100644 index c647eb13..00000000 --- a/docs/Makefile_ +++ /dev/null @@ -1,20 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -SPHINXPROJ = Pyrogram -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index 051b5af8..6e740dd0 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -5,8 +5,7 @@ Welcome to Pyrogram @@ -26,11 +25,11 @@ Welcome to Pyrogram

- Scheme Layer - TgCrypto

@@ -84,6 +83,7 @@ To get started, press the Next button. :caption: Resources resources/UpdateHandling + resources/UsingFilters resources/AutoAuthorization resources/CustomizeSessions resources/TgCrypto diff --git a/docs/source/pyrogram/Client.rst b/docs/source/pyrogram/Client.rst index 0448c3cb..1cd1a072 100644 --- a/docs/source/pyrogram/Client.rst +++ b/docs/source/pyrogram/Client.rst @@ -1,68 +1,135 @@ Client ====== -.. currentmodule:::: pyrogram.Client +.. currentmodule:: pyrogram.Client + +.. autoclass:: pyrogram.Client + +Utilities +--------- + +.. autosummary:: + :nosignatures: + + start + stop + idle + run + add_handler + remove_handler + send + resolve_peer + download_media + +Decorators +---------- + +.. autosummary:: + :nosignatures: + + on_message + on_callback_query + on_deleted_messages + on_disconnect + on_raw_update + +Messages +-------- + +.. autosummary:: + :nosignatures: + + send_message + forward_messages + send_photo + send_audio + send_document + send_sticker + send_video + send_animation + send_voice + send_video_note + send_media_group + send_location + send_venue + send_contact + send_chat_action + edit_message_text + edit_message_caption + edit_message_reply_markup + edit_message_media + delete_messages + get_messages + get_history + +Chats +----- + +.. autosummary:: + :nosignatures: + + join_chat + leave_chat + kick_chat_member + unban_chat_member + restrict_chat_member + promote_chat_member + export_chat_invite_link + set_chat_photo + delete_chat_photo + set_chat_title + set_chat_description + pin_chat_message + unpin_chat_message + get_chat + get_chat_member + get_chat_members + get_chat_members_count + get_dialogs + +Users +----- + +.. autosummary:: + :nosignatures: + + get_me + get_users + get_user_profile_photos + delete_profile_photos + +Contacts +-------- + +.. autosummary:: + :nosignatures: + + add_contacts + get_contacts + delete_contacts + +Password +-------- + +.. autosummary:: + :nosignatures: + + enable_cloud_password + change_cloud_password + remove_cloud_password + +Bots +---- + +.. autosummary:: + :nosignatures: + + get_inline_bot_results + send_inline_bot_result + answer_callback_query + request_callback_answer + .. autoclass:: pyrogram.Client :inherited-members: :members: - - .. _available-methods: - - **Available methods** - - .. autosummary:: - :nosignatures: - - start - stop - idle - run - on_message - on_callback_query - on_raw_update - add_handler - remove_handler - send - resolve_peer - get_me - send_message - forward_messages - send_photo - send_audio - send_document - send_sticker - send_video - send_voice - send_video_note - send_media_group - send_location - send_venue - send_contact - send_chat_action - download_media - get_user_profile_photos - edit_message_text - edit_message_caption - edit_message_reply_markup - delete_messages - join_chat - leave_chat - export_chat_invite_link - 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 - get_inline_bot_results - send_inline_bot_result - answer_callback_query - get_users - get_chat - get_messages - get_history \ No newline at end of file diff --git a/docs/source/pyrogram/Error.rst b/docs/source/pyrogram/Error.rst index 96a140fa..b5474e73 100644 --- a/docs/source/pyrogram/Error.rst +++ b/docs/source/pyrogram/Error.rst @@ -1,5 +1,3 @@ -:tocdepth: 1 - Error ===== diff --git a/docs/source/pyrogram/Handlers.rst b/docs/source/pyrogram/Handlers.rst new file mode 100644 index 00000000..9f69c2c9 --- /dev/null +++ b/docs/source/pyrogram/Handlers.rst @@ -0,0 +1,29 @@ +Handlers +======== + +.. currentmodule:: pyrogram + +.. autosummary:: + :nosignatures: + + MessageHandler + DeletedMessagesHandler + CallbackQueryHandler + DisconnectHandler + RawUpdateHandler + +.. autoclass:: MessageHandler + :members: + +.. autoclass:: DeletedMessagesHandler + :members: + +.. autoclass:: CallbackQueryHandler + :members: + +.. autoclass:: DisconnectHandler + :members: + +.. autoclass:: RawUpdateHandler + :members: + diff --git a/docs/source/pyrogram/Types.rst b/docs/source/pyrogram/Types.rst new file mode 100644 index 00000000..e8dc709c --- /dev/null +++ b/docs/source/pyrogram/Types.rst @@ -0,0 +1,188 @@ +Types +===== + +.. currentmodule:: pyrogram + +Users & Chats +------------- + +.. autosummary:: + :nosignatures: + + User + Chat + ChatPhoto + ChatMember + ChatMembers + Dialog + Dialogs + +Messages & Media +---------------- + +.. autosummary:: + :nosignatures: + + Message + Messages + MessageEntity + Photo + PhotoSize + UserProfilePhotos + Audio + Document + Animation + Video + Voice + VideoNote + Contact + Location + Venue + Sticker + +Bots +---- + +.. autosummary:: + :nosignatures: + + ReplyKeyboardMarkup + KeyboardButton + ReplyKeyboardRemove + InlineKeyboardMarkup + InlineKeyboardButton + ForceReply + CallbackQuery + +Input Media +----------- + +.. autosummary:: + :nosignatures: + + InputMediaPhoto + InputMediaVideo + InputMediaAudio + InputMediaAnimation + InputMediaDocument + InputPhoneContact + +.. User & Chats + ------------ + +.. autoclass:: User + :members: + +.. autoclass:: Chat + :members: + +.. autoclass:: ChatPhoto + :members: + +.. autoclass:: ChatMember + :members: + +.. autoclass:: ChatMembers + :members: + +.. autoclass:: Dialog + :members: + +.. autoclass:: Dialogs + :members: + +.. Messages & Media + ---------------- + +.. autoclass:: Message + :members: + +.. autoclass:: Messages + :members: + +.. autoclass:: MessageEntity + :members: + +.. autoclass:: Photo + :members: + +.. autoclass:: PhotoSize + :members: + +.. autoclass:: UserProfilePhotos + :members: + +.. autoclass:: Audio + :members: + +.. autoclass:: Document + :members: + +.. autoclass:: Animation + :members: + +.. autoclass:: Video + :members: + +.. autoclass:: Voice + :members: + +.. autoclass:: VideoNote + :members: + +.. autoclass:: Contact + :members: + +.. autoclass:: Location + :members: + +.. autoclass:: Venue + :members: + +.. autoclass:: Sticker + :members: + +.. Bots + ---- + +.. autoclass:: ReplyKeyboardMarkup + :members: + +.. autoclass:: KeyboardButton + :members: + +.. autoclass:: ReplyKeyboardRemove + :members: + +.. autoclass:: InlineKeyboardMarkup + :members: + +.. autoclass:: InlineKeyboardButton + :members: + +.. autoclass:: ForceReply + :members: + +.. autoclass:: CallbackQuery + :members: + +.. Input Media + ----------- + +.. autoclass:: InputMediaPhoto + :members: + +.. autoclass:: InputMediaVideo + :members: + +.. autoclass:: InputMediaAudio + :members: + +.. autoclass:: InputMediaAnimation + :members: + +.. autoclass:: InputMediaDocument + :members: + +.. autoclass:: InputPhoneContact + :members: diff --git a/docs/source/pyrogram/handlers/CallbackQueryHandler.rst b/docs/source/pyrogram/handlers/CallbackQueryHandler.rst deleted file mode 100644 index 5c9f4c17..00000000 --- a/docs/source/pyrogram/handlers/CallbackQueryHandler.rst +++ /dev/null @@ -1,6 +0,0 @@ -CallbackQueryHandler -==================== - -.. autoclass:: pyrogram.CallbackQueryHandler - :members: - :undoc-members: diff --git a/docs/source/pyrogram/handlers/DeletedMessagesHandler.rst b/docs/source/pyrogram/handlers/DeletedMessagesHandler.rst deleted file mode 100644 index 128bc656..00000000 --- a/docs/source/pyrogram/handlers/DeletedMessagesHandler.rst +++ /dev/null @@ -1,6 +0,0 @@ -DeletedMessagesHandler -====================== - -.. autoclass:: pyrogram.DeletedMessagesHandler - :members: - :undoc-members: diff --git a/docs/source/pyrogram/handlers/DisconnectHandler.rst b/docs/source/pyrogram/handlers/DisconnectHandler.rst deleted file mode 100644 index 594081f1..00000000 --- a/docs/source/pyrogram/handlers/DisconnectHandler.rst +++ /dev/null @@ -1,6 +0,0 @@ -DisconnectHandler -================= - -.. autoclass:: pyrogram.DisconnectHandler - :members: - :undoc-members: diff --git a/docs/source/pyrogram/handlers/MessageHandler.rst b/docs/source/pyrogram/handlers/MessageHandler.rst deleted file mode 100644 index de908bd3..00000000 --- a/docs/source/pyrogram/handlers/MessageHandler.rst +++ /dev/null @@ -1,6 +0,0 @@ -MessageHandler -============== - -.. autoclass:: pyrogram.MessageHandler - :members: - :undoc-members: diff --git a/docs/source/pyrogram/handlers/RawUpdateHandler.rst b/docs/source/pyrogram/handlers/RawUpdateHandler.rst deleted file mode 100644 index a6d21ef3..00000000 --- a/docs/source/pyrogram/handlers/RawUpdateHandler.rst +++ /dev/null @@ -1,6 +0,0 @@ -RawUpdateHandler -================ - -.. autoclass:: pyrogram.RawUpdateHandler - :members: - :undoc-members: diff --git a/docs/source/pyrogram/handlers/index.rst b/docs/source/pyrogram/handlers/index.rst deleted file mode 100644 index 272e529f..00000000 --- a/docs/source/pyrogram/handlers/index.rst +++ /dev/null @@ -1,11 +0,0 @@ -:tocdepth: 1 - -Handlers -======== - -.. toctree:: - MessageHandler - DeletedMessagesHandler - CallbackQueryHandler - DisconnectHandler - RawUpdateHandler diff --git a/docs/source/pyrogram/index.rst b/docs/source/pyrogram/index.rst index c272bc42..20e7c918 100644 --- a/docs/source/pyrogram/index.rst +++ b/docs/source/pyrogram/index.rst @@ -7,9 +7,11 @@ In this section you can find a detailed description of the Pyrogram package and after the well established `Telegram Bot API`_ methods, thus offering a familiar look to Bot developers. .. toctree:: + :maxdepth: 1 + Client - types/index - handlers/index + Types + Handlers Filters ChatAction ParseMode diff --git a/docs/source/pyrogram/types/Audio.rst b/docs/source/pyrogram/types/Audio.rst deleted file mode 100644 index 66ccb32a..00000000 --- a/docs/source/pyrogram/types/Audio.rst +++ /dev/null @@ -1,5 +0,0 @@ -Audio -===== - -.. autoclass:: pyrogram.Audio - :members: diff --git a/docs/source/pyrogram/types/CallbackQuery.rst b/docs/source/pyrogram/types/CallbackQuery.rst deleted file mode 100644 index 9ce0a578..00000000 --- a/docs/source/pyrogram/types/CallbackQuery.rst +++ /dev/null @@ -1,5 +0,0 @@ -CallbackQuery -============= - -.. autoclass:: pyrogram.CallbackQuery - :members: diff --git a/docs/source/pyrogram/types/Chat.rst b/docs/source/pyrogram/types/Chat.rst deleted file mode 100644 index 50a26838..00000000 --- a/docs/source/pyrogram/types/Chat.rst +++ /dev/null @@ -1,5 +0,0 @@ -Chat -==== - -.. autoclass:: pyrogram.Chat - :members: diff --git a/docs/source/pyrogram/types/ChatMember.rst b/docs/source/pyrogram/types/ChatMember.rst deleted file mode 100644 index 0c4f45a3..00000000 --- a/docs/source/pyrogram/types/ChatMember.rst +++ /dev/null @@ -1,5 +0,0 @@ -ChatMember -========== - -.. autoclass:: pyrogram.ChatMember - :members: diff --git a/docs/source/pyrogram/types/ChatPhoto.rst b/docs/source/pyrogram/types/ChatPhoto.rst deleted file mode 100644 index 2b6b49e5..00000000 --- a/docs/source/pyrogram/types/ChatPhoto.rst +++ /dev/null @@ -1,5 +0,0 @@ -ChatPhoto -========= - -.. autoclass:: pyrogram.ChatPhoto - :members: diff --git a/docs/source/pyrogram/types/Contact.rst b/docs/source/pyrogram/types/Contact.rst deleted file mode 100644 index 7d8b0fb2..00000000 --- a/docs/source/pyrogram/types/Contact.rst +++ /dev/null @@ -1,5 +0,0 @@ -Contact -======= - -.. autoclass:: pyrogram.Contact - :members: diff --git a/docs/source/pyrogram/types/Document.rst b/docs/source/pyrogram/types/Document.rst deleted file mode 100644 index 03a57753..00000000 --- a/docs/source/pyrogram/types/Document.rst +++ /dev/null @@ -1,5 +0,0 @@ -Document -======== - -.. autoclass:: pyrogram.Document - :members: diff --git a/docs/source/pyrogram/types/GIF.rst b/docs/source/pyrogram/types/GIF.rst deleted file mode 100644 index 501f187f..00000000 --- a/docs/source/pyrogram/types/GIF.rst +++ /dev/null @@ -1,5 +0,0 @@ -GIF -=== - -.. autoclass:: pyrogram.GIF - :members: diff --git a/docs/source/pyrogram/types/InputMediaPhoto.rst b/docs/source/pyrogram/types/InputMediaPhoto.rst deleted file mode 100644 index aaf8548f..00000000 --- a/docs/source/pyrogram/types/InputMediaPhoto.rst +++ /dev/null @@ -1,5 +0,0 @@ -InputMediaPhoto -=============== - -.. autoclass:: pyrogram.InputMediaPhoto - :members: diff --git a/docs/source/pyrogram/types/InputMediaVideo.rst b/docs/source/pyrogram/types/InputMediaVideo.rst deleted file mode 100644 index 4df4d128..00000000 --- a/docs/source/pyrogram/types/InputMediaVideo.rst +++ /dev/null @@ -1,5 +0,0 @@ -InputMediaVideo -=============== - -.. autoclass:: pyrogram.InputMediaVideo - :members: diff --git a/docs/source/pyrogram/types/InputPhoneContact.rst b/docs/source/pyrogram/types/InputPhoneContact.rst deleted file mode 100644 index bc4e38bc..00000000 --- a/docs/source/pyrogram/types/InputPhoneContact.rst +++ /dev/null @@ -1,5 +0,0 @@ -InputPhoneContact -================= - -.. autoclass:: pyrogram.InputPhoneContact - :members: diff --git a/docs/source/pyrogram/types/Location.rst b/docs/source/pyrogram/types/Location.rst deleted file mode 100644 index 99aeaa70..00000000 --- a/docs/source/pyrogram/types/Location.rst +++ /dev/null @@ -1,5 +0,0 @@ -Location -======== - -.. autoclass:: pyrogram.Location - :members: diff --git a/docs/source/pyrogram/types/Message.rst b/docs/source/pyrogram/types/Message.rst deleted file mode 100644 index 432693fd..00000000 --- a/docs/source/pyrogram/types/Message.rst +++ /dev/null @@ -1,5 +0,0 @@ -Message -======= - -.. autoclass:: pyrogram.Message - :members: diff --git a/docs/source/pyrogram/types/MessageEntity.rst b/docs/source/pyrogram/types/MessageEntity.rst deleted file mode 100644 index 4f2f3be0..00000000 --- a/docs/source/pyrogram/types/MessageEntity.rst +++ /dev/null @@ -1,5 +0,0 @@ -MessageEntity -============= - -.. autoclass:: pyrogram.MessageEntity - :members: diff --git a/docs/source/pyrogram/types/Messages.rst b/docs/source/pyrogram/types/Messages.rst deleted file mode 100644 index f740d092..00000000 --- a/docs/source/pyrogram/types/Messages.rst +++ /dev/null @@ -1,5 +0,0 @@ -Messages -======== - -.. autoclass:: pyrogram.Messages - :members: diff --git a/docs/source/pyrogram/types/PhotoSize.rst b/docs/source/pyrogram/types/PhotoSize.rst deleted file mode 100644 index 7ced6190..00000000 --- a/docs/source/pyrogram/types/PhotoSize.rst +++ /dev/null @@ -1,5 +0,0 @@ -PhotoSize -========= - -.. autoclass:: pyrogram.PhotoSize - :members: diff --git a/docs/source/pyrogram/types/Sticker.rst b/docs/source/pyrogram/types/Sticker.rst deleted file mode 100644 index d2646c99..00000000 --- a/docs/source/pyrogram/types/Sticker.rst +++ /dev/null @@ -1,5 +0,0 @@ -Sticker -======= - -.. autoclass:: pyrogram.Sticker - :members: diff --git a/docs/source/pyrogram/types/Update.rst b/docs/source/pyrogram/types/Update.rst deleted file mode 100644 index 1fb22b00..00000000 --- a/docs/source/pyrogram/types/Update.rst +++ /dev/null @@ -1,5 +0,0 @@ -Update -====== - -.. autoclass:: pyrogram.Update - :members: diff --git a/docs/source/pyrogram/types/User.rst b/docs/source/pyrogram/types/User.rst deleted file mode 100644 index f0074a93..00000000 --- a/docs/source/pyrogram/types/User.rst +++ /dev/null @@ -1,5 +0,0 @@ -User -==== - -.. autoclass:: pyrogram.User - :members: diff --git a/docs/source/pyrogram/types/UserProfilePhotos.rst b/docs/source/pyrogram/types/UserProfilePhotos.rst deleted file mode 100644 index 8c9c4d75..00000000 --- a/docs/source/pyrogram/types/UserProfilePhotos.rst +++ /dev/null @@ -1,5 +0,0 @@ -UserProfilePhotos -================= - -.. autoclass:: pyrogram.UserProfilePhotos - :members: diff --git a/docs/source/pyrogram/types/Venue.rst b/docs/source/pyrogram/types/Venue.rst deleted file mode 100644 index 471ab026..00000000 --- a/docs/source/pyrogram/types/Venue.rst +++ /dev/null @@ -1,5 +0,0 @@ -Venue -===== - -.. autoclass:: pyrogram.Venue - :members: diff --git a/docs/source/pyrogram/types/Video.rst b/docs/source/pyrogram/types/Video.rst deleted file mode 100644 index de28ae1c..00000000 --- a/docs/source/pyrogram/types/Video.rst +++ /dev/null @@ -1,5 +0,0 @@ -Video -===== - -.. autoclass:: pyrogram.Video - :members: diff --git a/docs/source/pyrogram/types/VideoNote.rst b/docs/source/pyrogram/types/VideoNote.rst deleted file mode 100644 index 69667454..00000000 --- a/docs/source/pyrogram/types/VideoNote.rst +++ /dev/null @@ -1,5 +0,0 @@ -VideoNote -========= - -.. autoclass:: pyrogram.VideoNote - :members: diff --git a/docs/source/pyrogram/types/Voice.rst b/docs/source/pyrogram/types/Voice.rst deleted file mode 100644 index c80ce124..00000000 --- a/docs/source/pyrogram/types/Voice.rst +++ /dev/null @@ -1,5 +0,0 @@ -Voice -===== - -.. autoclass:: pyrogram.Voice - :members: diff --git a/docs/source/pyrogram/types/index.rst b/docs/source/pyrogram/types/index.rst deleted file mode 100644 index 2fc74e49..00000000 --- a/docs/source/pyrogram/types/index.rst +++ /dev/null @@ -1,35 +0,0 @@ -:tocdepth: 1 - -Types -===== - -.. toctree:: - User - Chat - Message - MessageEntity - Messages - PhotoSize - Audio - Document - GIF - Video - Voice - VideoNote - Contact - Location - Venue - UserProfilePhotos - ChatPhoto - ChatMember - InputMediaPhoto - InputMediaVideo - InputPhoneContact - Sticker - reply_markup/ForceReply - reply_markup/InlineKeyboardButton - reply_markup/InlineKeyboardMarkup - reply_markup/KeyboardButton - reply_markup/ReplyKeyboardMarkup - reply_markup/ReplyKeyboardRemove - CallbackQuery \ No newline at end of file diff --git a/docs/source/pyrogram/types/reply_markup/ForceReply.rst b/docs/source/pyrogram/types/reply_markup/ForceReply.rst deleted file mode 100644 index db70a834..00000000 --- a/docs/source/pyrogram/types/reply_markup/ForceReply.rst +++ /dev/null @@ -1,5 +0,0 @@ -ForceReply -========== - -.. autoclass:: pyrogram.ForceReply - :members: diff --git a/docs/source/pyrogram/types/reply_markup/InlineKeyboardButton.rst b/docs/source/pyrogram/types/reply_markup/InlineKeyboardButton.rst deleted file mode 100644 index 2e536596..00000000 --- a/docs/source/pyrogram/types/reply_markup/InlineKeyboardButton.rst +++ /dev/null @@ -1,5 +0,0 @@ -InlineKeyboardButton -==================== - -.. autoclass:: pyrogram.InlineKeyboardButton - :members: diff --git a/docs/source/pyrogram/types/reply_markup/InlineKeyboardMarkup.rst b/docs/source/pyrogram/types/reply_markup/InlineKeyboardMarkup.rst deleted file mode 100644 index 7ffa2582..00000000 --- a/docs/source/pyrogram/types/reply_markup/InlineKeyboardMarkup.rst +++ /dev/null @@ -1,5 +0,0 @@ -InlineKeyboardMarkup -==================== - -.. autoclass:: pyrogram.InlineKeyboardMarkup - :members: diff --git a/docs/source/pyrogram/types/reply_markup/KeyboardButton.rst b/docs/source/pyrogram/types/reply_markup/KeyboardButton.rst deleted file mode 100644 index 69488656..00000000 --- a/docs/source/pyrogram/types/reply_markup/KeyboardButton.rst +++ /dev/null @@ -1,5 +0,0 @@ -KeyboardButton -============== - -.. autoclass:: pyrogram.KeyboardButton - :members: diff --git a/docs/source/pyrogram/types/reply_markup/ReplyKeyboardMarkup.rst b/docs/source/pyrogram/types/reply_markup/ReplyKeyboardMarkup.rst deleted file mode 100644 index 2b1e6d16..00000000 --- a/docs/source/pyrogram/types/reply_markup/ReplyKeyboardMarkup.rst +++ /dev/null @@ -1,5 +0,0 @@ -ReplyKeyboardMarkup -=================== - -.. autoclass:: pyrogram.ReplyKeyboardMarkup - :members: diff --git a/docs/source/pyrogram/types/reply_markup/ReplyKeyboardRemove.rst b/docs/source/pyrogram/types/reply_markup/ReplyKeyboardRemove.rst deleted file mode 100644 index 4146d564..00000000 --- a/docs/source/pyrogram/types/reply_markup/ReplyKeyboardRemove.rst +++ /dev/null @@ -1,5 +0,0 @@ -ReplyKeyboardRemove -=================== - -.. autoclass:: pyrogram.ReplyKeyboardRemove - :members: diff --git a/docs/source/resources/TextFormatting.rst b/docs/source/resources/TextFormatting.rst index aaa78f0a..0ab08694 100644 --- a/docs/source/resources/TextFormatting.rst +++ b/docs/source/resources/TextFormatting.rst @@ -1,8 +1,11 @@ Text Formatting =============== -Pyrogram, just like `Telegram Bot API`_, supports basic Markdown and HTML formatting styles for text messages and -media captions; Markdown uses the same syntax as Telegram Desktop's and is enabled by default. +Pyrogram, just like the `Telegram Bot API`_, natively supports basic Markdown and HTML formatting styles for text +messages and media captions. + +Markdown style uses the same syntax as Telegram Desktop's and is enabled by default. + Beside bold, italic, and pre-formatted code, **Pyrogram does also support inline URLs and inline mentions of users**. Markdown Style @@ -11,7 +14,7 @@ Markdown Style To use this mode, pass :obj:`MARKDOWN ` or "markdown" in the *parse_mode* field when using :obj:`send_message() `. Use the following syntax in your message: -.. code-block:: txt +.. code-block:: text **bold text** @@ -34,7 +37,7 @@ HTML Style To use this mode, pass :obj:`HTML ` or "html" in the *parse_mode* field when using :obj:`send_message() `. The following tags are currently supported: -.. code-block:: txt +.. code-block:: text bold, bold @@ -46,9 +49,7 @@ To use this mode, pass :obj:`HTML ` or "html" in the *p inline fixed-width code -
pre-formatted fixed-width
-    code block
-    
+
pre-formatted fixed-width code block
.. note:: Mentions are only guaranteed to work if you have already met the user (in groups or private chats). diff --git a/docs/source/resources/UpdateHandling.rst b/docs/source/resources/UpdateHandling.rst index 0aa6457f..781a48af 100644 --- a/docs/source/resources/UpdateHandling.rst +++ b/docs/source/resources/UpdateHandling.rst @@ -2,188 +2,58 @@ Update Handling =============== Updates are events that happen in your Telegram account (incoming messages, new channel posts, new members join, ...) -and are handled by registering one or more callback functions with an Handler. There are multiple Handlers to choose -from, one for each kind of update: +and can be handled by registering one or more callback functions in your app by using an `Handler <../pyrogram/Handlers.html>`_. -- `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>`_ +To put it simply, whenever an update is received from Telegram it will be dispatched and your previously defined callback +function(s) will be called back with the update itself as argument. Registering an Handler ---------------------- -We shall examine the :obj:`MessageHandler `, which will be in charge for handling -:obj:`Message ` objects. - -- The easiest and nicest way to register a MessageHandler is by decorating your function with the - :meth:`on_message() ` decorator. Here's a full example that prints out the content - of a message as soon as it arrives. - - .. code-block:: python - - from pyrogram import Client - - app = Client("my_account") +To explain how `Handlers <../pyrogram/Handlers.html>`_ work let's have a look at the most used one, the +:obj:`MessageHandler `, which will be in charge for handling :obj:`Message ` +updates coming from all around your chats. Every other handler shares the same setup logic; you should not have troubles +settings them up once you learn from this section. - @app.on_message() - def my_handler(client, message): - print(message) - - - 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. - - .. code-block:: python - - from pyrogram import Client, MessageHandler - - - def my_handler(client, message): - print(message) - - - app = Client("my_account") - - app.add_handler(MessageHandler(my_handler)) - - app.run() - -Using Filters -------------- - -For a finer grained control over what kind of messages will be allowed or not in your callback functions, you can use -:class:`Filters `. - -- This example will show you how to **only** handle messages containing an - :obj:`Audio ` object and filter out any other message: - - .. code-block:: python - - from pyrogram import Filters - - - @app.on_message(Filters.audio) - def my_handler(client, message): - print(message) - -- or, without decorators: - - .. code-block:: python - - from pyrogram import Filters, MessageHandler - - - def my_handler(client, message): - print(message) - - - app.add_handler(MessageHandler(my_handler, Filters.audio)) - -Combining Filters ------------------ - -Filters can also be used in a more advanced way by combining more filters together using bitwise operators: - -- Use ``~`` to invert a filter (behaves like the ``not`` operator). -- Use ``&`` and ``|`` to merge two filters (behave like ``and``, ``or`` operators respectively). - -Here are some examples: - -- Message is a **text** message **and** is **not edited**. - - .. code-block:: python - - @app.on_message(Filters.text & ~Filters.edited) - def my_handler(client, message): - print(message) - -- Message is a **sticker** **and** is coming from a **channel or** a **private** chat. - - .. code-block:: python - - @app.on_message(Filters.sticker & (Filters.channel | Filters.private)) - def my_handler(client, message): - print(message) - -Advanced Filters +Using Decorators ---------------- -Some filters, like :obj:`command() ` or :obj:`regex() ` -can also accept arguments: - -- Message is either a */start* or */help* **command**. - - .. code-block:: python - - @app.on_message(Filters.command(["start", "help"])) - def my_handler(client, message): - print(message) - -- Message is a **text** message matching the given **regex** pattern. - - .. code-block:: python - - @app.on_message(Filters.regex("pyrogram")) - def my_handler(client, message): - print(message) - -More handlers using different filters can also live together. +The easiest and nicest way to register a MessageHandler is by decorating your function with the +:meth:`on_message() ` decorator. Here's a full example that prints out the content +of a message as soon as it arrives. .. code-block:: python - @app.on_message(Filters.command("start")) - def start_command(client, message): - print("This is the /start command") + from pyrogram import Client + + app = Client("my_account") - @app.on_message(Filters.command("help")) - def help_command(client, message): - print("This is the /help command") + @app.on_message() + def my_handler(client, message): + print(message) - @app.on_message(Filters.chat("PyrogramChat")) - def from_pyrogramchat(client, message): - print("New message in @PyrogramChat") + app.run() -Handler Groups --------------- +Using add_handler() +------------------- -If you register handlers with overlapping filters, only the first one is executed and any other handler will be ignored. - -In order to process the same message more than once, you can register your handler in a different group. -Groups are identified by a number (number 0 being the default) and are sorted. This means that a lower group number has -a higher priority. - -For example, in: +If you prefer not to use decorators for any reason, there is an alternative way for registering Handlers. +This is useful, for example, when you want to keep your callback functions in separate files. .. code-block:: python - @app.on_message(Filters.text | Filters.sticker) - def text_or_sticker(client, message): - print("Text or Sticker") + from pyrogram import Client, MessageHandler - @app.on_message(Filters.text) - def just_text(client, message): - print("Just Text") + def my_handler(client, message): + print(message) -``just_text`` is never executed. To enable it, simply register the function using a different group: -.. code-block:: python + app = Client("my_account") - @app.on_message(Filters.text, group=1) - def just_text(client, message): - print("Just Text") + app.add_handler(MessageHandler(my_handler)) -or, if you want ``just_text`` to be fired *before* ``text_or_sticker``: - -.. code-block:: python - - @app.on_message(Filters.text, group=-1) - def just_text(client, message): - print("Just Text") \ No newline at end of file + app.run() diff --git a/docs/source/resources/UsingFilters.rst b/docs/source/resources/UsingFilters.rst new file mode 100644 index 00000000..79ecd24f --- /dev/null +++ b/docs/source/resources/UsingFilters.rst @@ -0,0 +1,228 @@ +Using Filters +============= + +For a finer grained control over what kind of messages will be allowed or not in your callback functions, you can use +:class:`Filters `. + +.. note:: + This section makes use of Handlers to handle updates. Learn more at `Update Handling `_. + +- This example will show you how to **only** handle messages containing an :obj:`Audio ` object and + ignore any other message: + + .. code-block:: python + + from pyrogram import Filters + + + @app.on_message(Filters.audio) + def my_handler(client, message): + print(message) + +- or, without decorators: + + .. code-block:: python + + from pyrogram import Filters, MessageHandler + + + def my_handler(client, message): + print(message) + + + app.add_handler(MessageHandler(my_handler, Filters.audio)) + +Combining Filters +----------------- + +Filters can also be used in a more advanced way by inverting and combining more filters together using bitwise +operators: + +- Use ``~`` to invert a filter (behaves like the ``not`` operator). +- Use ``&`` and ``|`` to merge two filters (behave like ``and``, ``or`` operators respectively). + +Here are some examples: + +- Message is a **text** message **and** is **not edited**. + + .. code-block:: python + + @app.on_message(Filters.text & ~Filters.edited) + def my_handler(client, message): + print(message) + +- Message is a **sticker** **and** is coming from a **channel or** a **private** chat. + + .. code-block:: python + + @app.on_message(Filters.sticker & (Filters.channel | Filters.private)) + def my_handler(client, message): + print(message) + +Advanced Filters +---------------- + +Some filters, like :meth:`command() ` or :meth:`regex() ` +can also accept arguments: + +- Message is either a */start* or */help* **command**. + + .. code-block:: python + + @app.on_message(Filters.command(["start", "help"])) + def my_handler(client, message): + print(message) + +- Message is a **text** message matching the given **regex** pattern. + + .. code-block:: python + + @app.on_message(Filters.regex("pyrogram")) + def my_handler(client, message): + print(message) + +More handlers using different filters can also live together. + +.. code-block:: python + + @app.on_message(Filters.command("start")) + def start_command(client, message): + print("This is the /start command") + + + @app.on_message(Filters.command("help")) + def help_command(client, message): + print("This is the /help command") + + + @app.on_message(Filters.chat("PyrogramChat")) + def from_pyrogramchat(client, message): + print("New message in @PyrogramChat") + +Handler Groups +-------------- + +If you register handlers with overlapping filters, only the first one is executed and any other handler will be ignored. + +In order to process the same message more than once, you can register your handler in a different group. +Groups are identified by a number (number 0 being the default) and are sorted. This means that a lower group number has +a higher priority. + +For example, in: + +.. code-block:: python + + @app.on_message(Filters.text | Filters.sticker) + def text_or_sticker(client, message): + print("Text or Sticker") + + + @app.on_message(Filters.text) + def just_text(client, message): + print("Just Text") + +``just_text`` is never executed because ``text_or_sticker`` already handles texts. To enable it, simply register the +function using a different group: + +.. code-block:: python + + @app.on_message(Filters.text, group=1) + def just_text(client, message): + print("Just Text") + +or, if you want ``just_text`` to be fired *before* ``text_or_sticker`` (note ``-1``, which is less than ``0``): + +.. code-block:: python + + @app.on_message(Filters.text, group=-1) + def just_text(client, message): + print("Just Text") + +Custom Filters +-------------- + +Pyrogram already provides lots of built-in :class:`Filters ` to work with, but in case you can't find +a specific one for your needs or want to build a custom filter by yourself (to be used in a different handler, for +example) you can use :meth:`Filters.create() `. + +.. note:: + At the moment, the built-in filters are intended to be used with the :obj:`MessageHandler ` + only. + +An example to demonstrate how custom filters work is to show how to create and use one for the +:obj:`CallbackQueryHandler `. Note that callback queries updates are only received by Bots; +create and `authorize your bot <../start/Setup.html#bot-authorization>`_, then send a message with an inline keyboard to +yourself. This allows you to test your filter by pressing the inline button: + +.. code-block:: python + + from pyrogram import InlineKeyboardMarkup, InlineKeyboardButton + + app.send_message( + "username", # Change this to your username or id + "Pyrogram's custom filter test", + reply_markup=InlineKeyboardMarkup( + [[InlineKeyboardButton("Press me", "pyrogram")]] + ) + ) + +Basic Filters +^^^^^^^^^^^^^ + +For this basic filter we will be using only the first two parameters of :meth:`Filters.create() `. + +The code below creates a simple filter for hardcoded callback data. This filter will only allow callback queries +containing "pyrogram" as data: + +.. code-block:: python + + hardcoded_data = Filters.create( + name="HardcodedData", + func=lambda filter, callback_query: callback_query.data == "pyrogram" + ) + +The ``lambda`` operator in python is used to create small anonymous functions and is perfect for this example, the same +could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter itself: + +.. code-block:: python + + def func(filter, callback_query): + return callback_query.data == "pyrogram" + + hardcoded_data = Filters.create( + name="HardcodedData", + func=func + ) + +The filter usage remains the same: + +.. code-block:: python + + @app.on_callback_query(hardcoded_data) + def pyrogram_data(client, callback_query): + client.answer_callback_query(callback_query.id, "it works!") + +Filters with Arguments +^^^^^^^^^^^^^^^^^^^^^^ + +A much cooler filter would be one that accepts "pyrogram" or any other data as argument at usage time. +A dynamic filter like this will make use of the third parameter of :meth:`Filters.create() `. + +This is how a dynamic custom filter looks like: + +.. code-block:: python + + def dynamic_data(data): + return Filters.create( + name="DynamicData", + func=lambda filter, callback_query: filter.data == callback_query.data, + data=data # "data" kwarg is accessed with "filter.data" + ) + +And its usage: + +.. code-block:: python + + @app.on_callback_query(dynamic_data("pyrogram")) + def pyrogram_data(client, callback_query): + client.answer_callback_query(callback_query.id, "it works!") \ No newline at end of file diff --git a/docs/source/start/Installation.rst b/docs/source/start/Installation.rst index 8aea16de..d3ddfe7d 100644 --- a/docs/source/start/Installation.rst +++ b/docs/source/start/Installation.rst @@ -7,7 +7,8 @@ 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. +.. note:: + Pyrogram supports Python 3 only, starting from version 3.4 and PyPy. Install Pyrogram ---------------- @@ -44,7 +45,7 @@ If no error shows up you are good to go. >>> import pyrogram >>> pyrogram.__version__ - '0.7.5' + '0.8.0' .. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto .. _develop: http://github.com/pyrogram/pyrogram \ No newline at end of file diff --git a/docs/source/start/Setup.rst b/docs/source/start/Setup.rst index 417d62d8..e0cccc2c 100644 --- a/docs/source/start/Setup.rst +++ b/docs/source/start/Setup.rst @@ -8,22 +8,29 @@ with Pyrogram. API Keys -------- -The very first step requires you to obtain a valid Telegram API key. +The very first step requires you to obtain a valid Telegram API key (API id/hash pair). If you already have one you can skip this step, otherwise: #. Visit https://my.telegram.org/apps and log in with your Telegram Account. #. Fill out the form to register a new Telegram application. -#. Done. The Telegram API key consists of two parts: the **App api_id** and the **App api_hash**. +#. Done. The API key consists of two parts: **App api_id** and **App api_hash**. -.. important:: This key should be kept secret. + +.. important:: + + This API key is personal and should be kept secret. Configuration ------------- -There are two ways to configure a Pyrogram application project, and you can choose the one that fits better for you: +The API key obtained in the `previous step <#api-keys>`_ defines a token for your application allowing you to access +the Telegram database using the MTProto API — **it is therefore required for all authorizations of both Users and Bots**. + +Having it handy, it's time to configure your Pyrogram project. There are two ways to do so, and you can choose what +fits better for you: - Create a new ``config.ini`` file at the root of your working directory, copy-paste the following and replace the - **api_id** and **api_hash** values with `your own <#api-keys>`_. This is the preferred method because allows you + **api_id** and **api_hash** values with your own. This is the preferred method because allows you to keep your credentials out of your code without having to deal with how to load them: .. code-block:: ini @@ -45,7 +52,8 @@ There are two ways to configure a Pyrogram application project, and you can choo api_hash="0123456789abcdef0123456789abcdef" ) -.. note:: The examples below assume you have created a ``config.ini`` file, thus they won't show the *api_id* +.. note:: + The examples below assume you have created a ``config.ini`` file, thus they won't show the *api_id* and *api_hash* parameters usage. User Authorization @@ -76,16 +84,17 @@ 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`` files are personal and 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, the underlying Telegram protocol. -This means that you can use Pyrogram to execute API calls with a Bot identity. +Bots are a special kind of users and are authorized via their tokens (instead of phone numbers), which are created by +BotFather_. Bot tokens replace the Users' phone numbers only — you still need to +`configure a Telegram API key <#configuration>`_ with Pyrogram, even when using Bots. -Instead of phone numbers, Bots are authorized via their tokens which are created by BotFather_: +The authorization process is automatically managed. All you need to do is pass the bot token as ``session_name``. +The session file will be named after the Bot user_id, which is ``123456.session`` for the example below. .. code-block:: python @@ -94,9 +103,6 @@ Instead of phone numbers, Bots are authorized via their tokens which are created app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") 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 \ No newline at end of file diff --git a/docs/source/start/Usage.rst b/docs/source/start/Usage.rst index 6c20decd..6c560f90 100644 --- a/docs/source/start/Usage.rst +++ b/docs/source/start/Usage.rst @@ -108,9 +108,9 @@ Examples (more on `GitHub . +import logging from collections import OrderedDict from datetime import datetime from io import BytesIO @@ -23,13 +24,23 @@ from json import JSONEncoder, dumps from ..all import objects +log = logging.getLogger(__name__) + class Object: all = {} @staticmethod def read(b: BytesIO, *args): - return Object.all[int.from_bytes(b.read(4), "little")].read(b, *args) + constructor_id = int.from_bytes(b.read(4), "little") + + try: + return Object.all[constructor_id].read(b, *args) + except KeyError: + log.error("Unknown constructor found: {}. Full data: {}".format( + hex(constructor_id), + b.getvalue().hex()) + ) def write(self, *args) -> bytes: pass diff --git a/pyrogram/api/core/primitives/bool.py b/pyrogram/api/core/primitives/bool.py index cc8655d9..9641e865 100644 --- a/pyrogram/api/core/primitives/bool.py +++ b/pyrogram/api/core/primitives/bool.py @@ -30,7 +30,7 @@ class BoolFalse(Object): return cls.value def __new__(cls) -> bytes: - return int.to_bytes(cls.ID, 4, "little") + return cls.ID.to_bytes(4, "little") class BoolTrue(BoolFalse): diff --git a/pyrogram/api/core/primitives/bytes.py b/pyrogram/api/core/primitives/bytes.py index da438ef1..d161cc9c 100644 --- a/pyrogram/api/core/primitives/bytes.py +++ b/pyrogram/api/core/primitives/bytes.py @@ -48,7 +48,7 @@ class Bytes(Object): else: return ( bytes([254]) - + int.to_bytes(length, 3, "little") + + length.to_bytes(3, "little") + value + bytes(-length % 4) ) diff --git a/pyrogram/api/core/primitives/int.py b/pyrogram/api/core/primitives/int.py index 0985367f..4b9aded8 100644 --- a/pyrogram/api/core/primitives/int.py +++ b/pyrogram/api/core/primitives/int.py @@ -29,15 +29,12 @@ class Int(Object): return int.from_bytes(b.read(cls.SIZE), "little", signed=signed) def __new__(cls, value: int, signed: bool = True) -> bytes: - return int.to_bytes(value, cls.SIZE, "little", signed=signed) + return value.to_bytes(cls.SIZE, "little", signed=signed) class Long(Int): SIZE = 8 - def __new__(cls, *args): - return super().__new__(cls, *args) - class Int128(Int): SIZE = 16 diff --git a/pyrogram/api/core/primitives/null.py b/pyrogram/api/core/primitives/null.py index 3d73c06e..7a26b112 100644 --- a/pyrogram/api/core/primitives/null.py +++ b/pyrogram/api/core/primitives/null.py @@ -29,4 +29,4 @@ class Null(Object): return None def __new__(cls) -> bytes: - return int.to_bytes(cls.ID, 4, "little") + return cls.ID.to_bytes(4, "little") diff --git a/pyrogram/api/core/primitives/string.py b/pyrogram/api/core/primitives/string.py index 5c05e5b0..3584d1b9 100644 --- a/pyrogram/api/core/primitives/string.py +++ b/pyrogram/api/core/primitives/string.py @@ -24,7 +24,7 @@ from . import Bytes class String(Bytes): @staticmethod def read(b: BytesIO, *args) -> str: - return super(String, String).read(b).decode() + return super(String, String).read(b).decode(errors="replace") def __new__(cls, value: str) -> bytes: return super().__new__(cls, value.encode()) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 20a3e50c..3d8911a7 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -146,6 +146,7 @@ class Client(Methods, BaseClient): device_model: str = None, system_version: str = None, lang_code: str = None, + ipv6: bool = False, proxy: dict = None, test_mode: bool = False, phone_number: str = None, @@ -166,6 +167,7 @@ class Client(Methods, BaseClient): self.device_model = device_model self.system_version = system_version self.lang_code = lang_code + self.ipv6 = ipv6 # TODO: Make code consistent, use underscore for private/protected fields self._proxy = proxy self.test_mode = test_mode @@ -181,6 +183,13 @@ class Client(Methods, BaseClient): self.dispatcher = Dispatcher(self, workers) + def __enter__(self): + self.start() + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.stop() + @property def proxy(self): return self._proxy @@ -201,7 +210,7 @@ class Client(Methods, BaseClient): raise ConnectionError("Client has already been started") if self.BOT_TOKEN_RE.match(self.session_name): - self.token = self.session_name + self.bot_token = self.session_name self.session_name = self.session_name.split(":")[0] self.load_config() @@ -216,28 +225,33 @@ class Client(Methods, BaseClient): self.session.start() self.is_started = True - if self.user_id is None: - if self.token is None: - self.authorize_user() + try: + if self.user_id is None: + if self.bot_token is None: + self.authorize_user() + else: + self.authorize_bot() + + self.save_session() + + if self.bot_token is None: + now = time.time() + + if abs(now - self.date) > Client.OFFLINE_SLEEP: + self.peers_by_username = {} + self.peers_by_phone = {} + + self.get_initial_dialogs() + self.get_contacts() + else: + self.send(functions.messages.GetPinnedDialogs()) + self.get_initial_dialogs_chunk() else: - self.authorize_bot() - - self.save_session() - - if self.token is None: - now = time.time() - - if abs(now - self.date) > Client.OFFLINE_SLEEP: - self.peers_by_username = {} - self.peers_by_phone = {} - - self.get_dialogs() - self.get_contacts() - else: - self.send(functions.messages.GetPinnedDialogs()) - self.get_dialogs_chunk(0) - else: - self.send(functions.updates.GetState()) + self.send(functions.updates.GetState()) + except Exception as e: + self.is_started = False + self.session.stop() + raise e for i in range(self.UPDATES_WORKERS): self.updates_workers_list.append( @@ -381,14 +395,14 @@ class Client(Methods, BaseClient): flags=0, api_id=self.api_id, api_hash=self.api_hash, - bot_auth_token=self.token + bot_auth_token=self.bot_token ) ) except UserMigrate as e: self.session.stop() self.dc_id = e.x - self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create() + self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() self.session = Session( self, @@ -433,7 +447,7 @@ class Client(Methods, BaseClient): self.session.stop() self.dc_id = e.x - self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create() + self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() self.session = Session( self, @@ -613,7 +627,7 @@ class Client(Methods, BaseClient): if phone is not None: self.peers_by_phone[phone] = input_peer - if isinstance(entity, types.Chat): + if isinstance(entity, (types.Chat, types.ChatForbidden)): chat_id = entity.id peer_id = -chat_id @@ -623,7 +637,7 @@ class Client(Methods, BaseClient): self.peers_by_id[peer_id] = input_peer - if isinstance(entity, types.Channel): + if isinstance(entity, (types.Channel, types.ChannelForbidden)): channel_id = entity.id peer_id = int("-100" + str(channel_id)) @@ -632,7 +646,7 @@ class Client(Methods, BaseClient): if access_hash is None: continue - username = entity.username + username = getattr(entity, "username", None) input_peer = types.InputPeerChannel( channel_id=channel_id, @@ -711,7 +725,9 @@ class Client(Methods, BaseClient): file_name = "{}_{}_{}{}".format( media_type_str, - datetime.fromtimestamp(media.date or time.time()).strftime("%Y-%m-%d_%H-%M-%S"), + datetime.fromtimestamp( + getattr(media, "date", None) or time.time() + ).strftime("%Y-%m-%d_%H-%M-%S"), self.rnd_id(), extension ) @@ -776,6 +792,9 @@ class Client(Methods, BaseClient): pts = getattr(update, "pts", None) pts_count = getattr(update, "pts_count", None) + if isinstance(update, types.UpdateChannelTooLong): + log.warning(update) + if isinstance(update, types.UpdateNewChannelMessage): message = update.message @@ -834,6 +853,8 @@ class Client(Methods, BaseClient): self.dispatcher.updates.put((diff.other_updates[0], [], [])) elif isinstance(updates, types.UpdateShort): self.dispatcher.updates.put((updates.update, [], [])) + elif isinstance(updates, types.UpdatesTooLong): + log.warning(updates) except Exception as e: log.error(e, exc_info=True) @@ -885,30 +906,18 @@ class Client(Methods, BaseClient): "More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration" ) - for option in {"app_version", "device_model", "system_version", "lang_code"}: + for option in ["app_version", "device_model", "system_version", "lang_code"]: if getattr(self, option): pass else: - setattr(self, option, Client.APP_VERSION) - if parser.has_section("pyrogram"): setattr(self, option, parser.get( "pyrogram", option, fallback=getattr(Client, option.upper()) )) - - if self.lang_code: - pass - else: - self.lang_code = Client.LANG_CODE - - if parser.has_section("pyrogram"): - self.lang_code = parser.get( - "pyrogram", - "lang_code", - fallback=Client.LANG_CODE - ) + else: + setattr(self, option, getattr(Client, option.upper())) if self._proxy: self._proxy["enabled"] = True @@ -929,7 +938,7 @@ class Client(Methods, BaseClient): except FileNotFoundError: self.dc_id = 1 self.date = 0 - self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create() + self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() else: self.dc_id = s["dc_id"] self.test_mode = s["test_mode"] @@ -971,13 +980,17 @@ class Client(Methods, BaseClient): indent=4 ) - def get_dialogs_chunk(self, offset_date): + def get_initial_dialogs_chunk(self, offset_date: int = 0): while True: try: r = self.send( functions.messages.GetDialogs( - offset_date, 0, types.InputPeerEmpty(), - self.DIALOGS_AT_ONCE, True + offset_date=offset_date, + offset_id=0, + offset_peer=types.InputPeerEmpty(), + limit=self.DIALOGS_AT_ONCE, + hash=0, + exclude_pinned=True ) ) except FloodWait as e: @@ -987,34 +1000,32 @@ class Client(Methods, BaseClient): log.info("Total peers: {}".format(len(self.peers_by_id))) return r - def get_dialogs(self): + def get_initial_dialogs(self): self.send(functions.messages.GetPinnedDialogs()) - dialogs = self.get_dialogs_chunk(0) + dialogs = self.get_initial_dialogs_chunk() offset_date = utils.get_offset_date(dialogs) while len(dialogs.dialogs) == self.DIALOGS_AT_ONCE: - dialogs = self.get_dialogs_chunk(offset_date) + dialogs = self.get_initial_dialogs_chunk(offset_date) offset_date = utils.get_offset_date(dialogs) - self.get_dialogs_chunk(0) + self.get_initial_dialogs_chunk() def resolve_peer(self, peer_id: int or str): - """Use this method to get the *InputPeer* of a known *peer_id*. + """Use this method to get the InputPeer of a known peer_id. - It is intended to be used when working with Raw Functions (i.e: a Telegram API method you wish to use which is - not available yet in the Client class as an easy-to-use method). + This is a utility method intended to be used only when working with Raw Functions (i.e: a Telegram API method + you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an InputPeer + type is required. Args: - peer_id (``int`` | ``str`` | ``Peer``): - The Peer ID you want to extract the InputPeer from. Can be one of these types: ``int`` (direct ID), - ``str`` (@username), :obj:`PeerUser `, - :obj:`PeerChat `, :obj:`PeerChannel ` + peer_id (``int`` | ``str``): + The peer id you want to extract the InputPeer from. + Can be a direct id (int), a username (str) or a phone number (str). Returns: - :obj:`InputPeerUser ` or - :obj:`InputPeerChat ` or - :obj:`InputPeerChannel ` depending on the *peer_id*. + On success, the resolved peer id is returned in form of an InputPeer object. Raises: :class:`Error ` @@ -1023,38 +1034,21 @@ class Client(Methods, BaseClient): if peer_id in ("self", "me"): return types.InputPeerSelf() - match = self.INVITE_LINK_RE.match(peer_id) - - try: - decoded = base64.b64decode(match.group(1) + "=" * (-len(match.group(1)) % 4), "-_") - return self.resolve_peer(struct.unpack(">2iq", decoded)[1]) - except (AttributeError, binascii.Error, struct.error): - pass - peer_id = re.sub(r"[@+\s]", "", peer_id.lower()) try: int(peer_id) except ValueError: - try: - return self.peers_by_username[peer_id] - except KeyError: + if peer_id not in self.peers_by_username: self.send(functions.contacts.ResolveUsername(peer_id)) - return self.peers_by_username[peer_id] + + return self.peers_by_username[peer_id] else: try: return self.peers_by_phone[peer_id] except KeyError: raise PeerIdInvalid - if type(peer_id) is not int: - if isinstance(peer_id, types.PeerUser): - peer_id = peer_id.user_id - elif isinstance(peer_id, types.PeerChat): - peer_id = -peer_id.chat_id - elif isinstance(peer_id, types.PeerChannel): - peer_id = int("-100" + str(peer_id.channel_id)) - try: # User return self.peers_by_id[peer_id] except KeyError: @@ -1166,7 +1160,7 @@ class Client(Methods, BaseClient): session = Session( self, dc_id, - Auth(dc_id, self.test_mode, self._proxy).create(), + Auth(dc_id, self.test_mode, self.ipv6, self._proxy).create(), is_media=True ) @@ -1229,8 +1223,6 @@ class Client(Methods, BaseClient): break f.write(chunk) - f.flush() - os.fsync(f.fileno()) offset += limit @@ -1253,7 +1245,7 @@ class Client(Methods, BaseClient): cdn_session = Session( self, r.dc_id, - Auth(r.dc_id, self.test_mode, self._proxy).create(), + Auth(r.dc_id, self.test_mode, self.ipv6, self._proxy).create(), is_media=True, is_cdn=True ) @@ -1313,8 +1305,6 @@ class Client(Methods, BaseClient): assert h.hash == sha256(cdn_chunk).digest(), "Invalid CDN hash part {}".format(i) f.write(decrypted_chunk) - f.flush() - os.fsync(f.fileno()) offset += limit diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index cd54570d..fa96e1db 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -41,7 +41,6 @@ class BaseClient: platform.release() ) - SYSTEM_LANG_CODE = "en" LANG_CODE = "en" INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/joinchat/)([\w-]+)$") @@ -60,12 +59,12 @@ class BaseClient: 5: "document", 8: "sticker", 9: "audio", - 10: "gif", + 10: "animation", 13: "video_note" } def __init__(self): - self.token = None + self.bot_token = None self.dc_id = None self.auth_key = None self.user_id = None diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index 2913c38b..b98a05a8 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -60,6 +60,7 @@ class Str(str): ENTITIES = { types.MessageEntityMention.ID: "mention", types.MessageEntityHashtag.ID: "hashtag", + types.MessageEntityCashtag.ID: "cashtag", types.MessageEntityBotCommand.ID: "bot_command", types.MessageEntityUrl.ID: "url", types.MessageEntityEmail.ID: "email", @@ -68,7 +69,8 @@ ENTITIES = { types.MessageEntityCode.ID: "code", types.MessageEntityPre.ID: "pre", types.MessageEntityTextUrl.ID: "text_link", - types.MessageEntityMentionName.ID: "text_mention" + types.MessageEntityMentionName.ID: "text_mention", + types.MessageEntityPhone.ID: "phone_number" } @@ -79,18 +81,20 @@ def parse_entities(entities: list, users: dict) -> list: entity_type = ENTITIES.get(entity.ID, None) if entity_type: - output_entities.append(pyrogram_types.MessageEntity( - type=entity_type, - offset=entity.offset, - length=entity.length, - url=getattr(entity, "url", None), - user=parse_user( - users.get( - getattr(entity, "user_id", None), - None + output_entities.append( + pyrogram_types.MessageEntity( + type=entity_type, + offset=entity.offset, + length=entity.length, + url=getattr(entity, "url", None), + user=parse_user( + users.get( + getattr(entity, "user_id", None), + None + ) ) ) - )) + ) return output_entities @@ -128,6 +132,10 @@ def parse_chat_photo(photo): def parse_user(user: types.User) -> pyrogram_types.User or None: return pyrogram_types.User( id=user.id, + is_self=user.is_self, + is_contact=user.contact, + is_mutual_contact=user.mutual_contact, + is_deleted=user.deleted, is_bot=user.bot, first_name=user.first_name, last_name=user.last_name, @@ -288,7 +296,7 @@ def parse_messages( venue = None audio = None voice = None - gif = None + animation = None video = None video_note = None sticker = None @@ -329,13 +337,23 @@ def parse_messages( ), width=size.w, height=size.h, - file_size=file_size, - date=photo.date + file_size=file_size ) photo_sizes.append(photo_size) - photo = photo_sizes + photo = pyrogram_types.Photo( + id=b64encode( + pack( + " type: +def create(name: str, func: callable, **kwargs) -> type: + """Use this method to create a Filter. + + Custom filters give you extra control over which updates are allowed or not to be processed by your handlers. + + Args: + name (``str``): + Your filter's name. Can be anything you like. + + func (``callable``): + A function that accepts two arguments *(filter, update)* and returns a Boolean: True if the update should be + handled, False otherwise. + The "update" argument type will vary depending on which `Handler `_ is coming from. + For example, in a :obj:`MessageHandler ` the update type will be + a :obj:`Message `; in a :obj:`CallbackQueryHandler ` the + update type will be a :obj:`CallbackQuery `. Your function body can then access the + incoming update and decide whether to allow it or not. + + **kwargs (``any``, *optional*): + Any keyword argument you would like to pass. Useful for custom filters that accept parameters (e.g.: + :meth:`Filters.command`, :meth:`Filters.regex`). + """ + # TODO: unpack kwargs using **kwargs into the dict itself. For Python 3.5+ only d = {"__call__": func} d.update(kwargs) @@ -30,112 +52,118 @@ def build(name: str, func: callable, **kwargs) -> type: class Filters: - """This class provides access to all Filters available in Pyrogram. - Filters are intended to be used with the :obj:`MessageHandler `.""" + """This class provides access to all library-defined Filters available in Pyrogram. - bot = build("Bot", lambda _, m: bool(m.from_user and m.from_user.is_bot)) + The Filters listed here are intended to be used with the :obj:`MessageHandler ` only. + At the moment, if you want to filter updates coming from different `Handlers `_ you have to create + your own filters with :meth:`Filters.create` and use them in the same way. + """ + + create = create + + bot = create("Bot", lambda _, m: bool(m.from_user and m.from_user.is_bot)) """Filter messages coming from bots""" - incoming = build("Incoming", lambda _, m: not m.outgoing) + incoming = create("Incoming", lambda _, m: not m.outgoing) """Filter incoming messages.""" - outgoing = build("Outgoing", lambda _, m: m.outgoing) + outgoing = create("Outgoing", lambda _, m: m.outgoing) """Filter outgoing messages.""" - text = build("Text", lambda _, m: bool(m.text)) + text = create("Text", lambda _, m: bool(m.text)) """Filter text messages.""" - reply = build("Reply", lambda _, m: bool(m.reply_to_message)) + reply = create("Reply", lambda _, m: bool(m.reply_to_message)) """Filter messages that are replies to other messages.""" - forwarded = build("Forwarded", lambda _, m: bool(m.forward_date)) + forwarded = create("Forwarded", lambda _, m: bool(m.forward_date)) """Filter messages that are forwarded.""" - caption = build("Caption", lambda _, m: bool(m.caption)) + caption = create("Caption", lambda _, m: bool(m.caption)) """Filter media messages that contain captions.""" - edited = build("Edited", lambda _, m: bool(m.edit_date)) + edited = create("Edited", lambda _, m: bool(m.edit_date)) """Filter edited messages.""" - audio = build("Audio", lambda _, m: bool(m.audio)) + audio = create("Audio", lambda _, m: bool(m.audio)) """Filter messages that contain :obj:`Audio ` objects.""" - document = build("Document", lambda _, m: bool(m.document)) + document = create("Document", lambda _, m: bool(m.document)) """Filter messages that contain :obj:`Document ` objects.""" - photo = build("Photo", lambda _, m: bool(m.photo)) + photo = create("Photo", lambda _, m: bool(m.photo)) """Filter messages that contain :obj:`Photo ` objects.""" - sticker = build("Sticker", lambda _, m: bool(m.sticker)) + sticker = create("Sticker", lambda _, m: bool(m.sticker)) """Filter messages that contain :obj:`Sticker ` objects.""" - gif = build("GIF", lambda _, m: bool(m.gif)) - """Filter messages that contain :obj:`GIF ` objects.""" + animation = create("GIF", lambda _, m: bool(m.animation)) + """Filter messages that contain :obj:`Animation ` objects.""" - video = build("Video", lambda _, m: bool(m.video)) + video = create("Video", lambda _, m: bool(m.video)) """Filter messages that contain :obj:`Video ` objects.""" - voice = build("Voice", lambda _, m: bool(m.voice)) + voice = create("Voice", lambda _, m: bool(m.voice)) """Filter messages that contain :obj:`Voice ` note objects.""" - video_note = build("Voice", lambda _, m: bool(m.video_note)) + video_note = create("Voice", lambda _, m: bool(m.video_note)) """Filter messages that contain :obj:`VideoNote ` objects.""" - contact = build("Contact", lambda _, m: bool(m.contact)) + contact = create("Contact", lambda _, m: bool(m.contact)) """Filter messages that contain :obj:`Contact ` objects.""" - location = build("Location", lambda _, m: bool(m.location)) + location = create("Location", lambda _, m: bool(m.location)) """Filter messages that contain :obj:`Location ` objects.""" - venue = build("Venue", lambda _, m: bool(m.venue)) + venue = create("Venue", lambda _, m: bool(m.venue)) """Filter messages that contain :obj:`Venue ` objects.""" - private = build("Private", lambda _, m: bool(m.chat and m.chat.type == "private")) + private = create("Private", lambda _, m: bool(m.chat and m.chat.type == "private")) """Filter messages sent in private chats.""" - group = build("Group", lambda _, m: bool(m.chat and m.chat.type in {"group", "supergroup"})) + group = create("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 and m.chat.type == "channel")) + channel = create("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)) + new_chat_members = create("NewChatMembers", lambda _, m: bool(m.new_chat_members)) """Filter service messages for new chat members.""" - left_chat_member = build("LeftChatMember", lambda _, m: bool(m.left_chat_member)) + left_chat_member = create("LeftChatMember", lambda _, m: bool(m.left_chat_member)) """Filter service messages for members that left the chat.""" - new_chat_title = build("NewChatTitle", lambda _, m: bool(m.new_chat_title)) + new_chat_title = create("NewChatTitle", lambda _, m: bool(m.new_chat_title)) """Filter service messages for new chat titles.""" - new_chat_photo = build("NewChatPhoto", lambda _, m: bool(m.new_chat_photo)) + new_chat_photo = create("NewChatPhoto", lambda _, m: bool(m.new_chat_photo)) """Filter service messages for new chat photos.""" - delete_chat_photo = build("DeleteChatPhoto", lambda _, m: bool(m.delete_chat_photo)) + delete_chat_photo = create("DeleteChatPhoto", lambda _, m: bool(m.delete_chat_photo)) """Filter service messages for deleted photos.""" - group_chat_created = build("GroupChatCreated", lambda _, m: bool(m.group_chat_created)) + group_chat_created = create("GroupChatCreated", lambda _, m: bool(m.group_chat_created)) """Filter service messages for group chat creations.""" - supergroup_chat_created = build("SupergroupChatCreated", lambda _, m: bool(m.supergroup_chat_created)) + supergroup_chat_created = create("SupergroupChatCreated", lambda _, m: bool(m.supergroup_chat_created)) """Filter service messages for supergroup chat creations.""" - channel_chat_created = build("ChannelChatCreated", lambda _, m: bool(m.channel_chat_created)) + channel_chat_created = create("ChannelChatCreated", lambda _, m: bool(m.channel_chat_created)) """Filter service messages for channel chat creations.""" - migrate_to_chat_id = build("MigrateToChatId", lambda _, m: bool(m.migrate_to_chat_id)) + migrate_to_chat_id = create("MigrateToChatId", lambda _, m: bool(m.migrate_to_chat_id)) """Filter service messages that contain migrate_to_chat_id.""" - migrate_from_chat_id = build("MigrateFromChatId", lambda _, m: bool(m.migrate_from_chat_id)) + migrate_from_chat_id = create("MigrateFromChatId", lambda _, m: bool(m.migrate_from_chat_id)) """Filter service messages that contain migrate_from_chat_id.""" - pinned_message = build("PinnedMessage", lambda _, m: bool(m.pinned_message)) + pinned_message = create("PinnedMessage", lambda _, m: bool(m.pinned_message)) """Filter service messages for pinned messages.""" - reply_keyboard = build("ReplyKeyboard", lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup)) + reply_keyboard = create("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)) + inline_keyboard = create("InlineKeyboard", lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup)) """Filter messages containing inline keyboard markups""" @staticmethod @@ -174,7 +202,7 @@ class Filters: return bool(m.command) - return build( + return create( "Command", f, c={command if case_sensitive @@ -206,51 +234,67 @@ class Filters: m.matches = [i for i in _.p.finditer(m.text or "")] return bool(m.matches) - return build("Regex", f, p=re.compile(pattern, flags)) + return create("Regex", f, p=re.compile(pattern, flags)) - @staticmethod - def user(user: int or str or list): - """Filter messages coming from specific users. + # noinspection PyPep8Naming + class user(Filter, set): + """Filter messages coming from one or more users. + + You can use `set bound methods `_ to manipulate the + users container. Args: - user (``int`` | ``str`` | ``list``): - The user or list of user IDs (int) or usernames (str) the filter should look for. + users (``int`` | ``str`` | ``list``): + Pass one or more user ids/usernames to filter users. + Defaults to None (no users). """ - return build( - "User", - lambda _, m: bool(m.from_user - and (m.from_user.id in _.u - or (m.from_user.username - and m.from_user.username.lower() in _.u))), - u=( - {user.lower().strip("@") if type(user) is str else user} - if not isinstance(user, list) - else {i.lower().strip("@") if type(i) is str else i for i in user} - ) - ) - @staticmethod - def chat(chat: int or str or list): - """Filter messages coming from specific chats. + def __init__(self, users: int or str or list = None): + users = [] if users is None else users if type(users) is list else [users] + super().__init__( + {i.lower().strip("@") if type(i) is str else i for i in users} + if type(users) is list else + {users.lower().strip("@") if type(users) is str else users} + ) + + def __call__(self, message): + return bool( + message.from_user + and (message.from_user.id in self + or (message.from_user.username + and message.from_user.username.lower() in self)) + ) + + # noinspection PyPep8Naming + class chat(Filter, set): + """Filter messages coming from one or more chats. + + You can use `set bound methods `_ to manipulate the + chats container. Args: - chat (``int`` | ``str`` | ``list``): - The chat or list of chat IDs (int) or usernames (str) the filter should look for. + chats (``int`` | ``str`` | ``list``): + Pass one or more chat ids/usernames to filter chats. + Defaults to None (no chats). """ - return build( - "Chat", - lambda _, m: bool(m.chat - and (m.chat.id in _.c - or (m.chat.username - and m.chat.username.lower() in _.c))), - c=( - {chat.lower().strip("@") if type(chat) is str else chat} - if not isinstance(chat, list) - else {i.lower().strip("@") if type(i) is str else i for i in chat} - ) - ) - service = build( + def __init__(self, chats: int or str or list = None): + chats = [] if chats is None else chats if type(chats) is list else [chats] + super().__init__( + {i.lower().strip("@") if type(i) is str else i for i in chats} + if type(chats) is list else + {chats.lower().strip("@") if type(chats) is str else chats} + ) + + def __call__(self, message): + return bool( + message.chat + and (message.chat.id in self + or (message.chat.username + and message.chat.username.lower() in self)) + ) + + service = create( "Service", lambda _, m: bool( Filters.new_chat_members(m) @@ -268,7 +312,7 @@ class Filters: ) """Filter all service messages.""" - media = build( + media = create( "Media", lambda _, m: bool( Filters.audio(m) @@ -276,7 +320,7 @@ class Filters: or Filters.photo(m) or Filters.sticker(m) or Filters.video(m) - or Filters.gif(m) + or Filters.animation(m) or Filters.voice(m) or Filters.video_note(m) or Filters.contact(m) diff --git a/pyrogram/client/handlers/callback_query_handler.py b/pyrogram/client/handlers/callback_query_handler.py index c5346519..5d09f7d9 100644 --- a/pyrogram/client/handlers/callback_query_handler.py +++ b/pyrogram/client/handlers/callback_query_handler.py @@ -49,6 +49,6 @@ class CallbackQueryHandler(Handler): def check(self, callback_query): return ( self.filters(callback_query) - if self.filters + if callable(self.filters) else True ) diff --git a/pyrogram/client/handlers/deleted_messages_handler.py b/pyrogram/client/handlers/deleted_messages_handler.py index 55d5715f..8f5ef448 100644 --- a/pyrogram/client/handlers/deleted_messages_handler.py +++ b/pyrogram/client/handlers/deleted_messages_handler.py @@ -50,6 +50,6 @@ class DeletedMessagesHandler(Handler): def check(self, messages): return ( self.filters(messages.messages[0]) - if self.filters + if callable(self.filters) else True ) diff --git a/pyrogram/client/handlers/message_handler.py b/pyrogram/client/handlers/message_handler.py index 1b4770b3..e4c3d13f 100644 --- a/pyrogram/client/handlers/message_handler.py +++ b/pyrogram/client/handlers/message_handler.py @@ -50,6 +50,6 @@ class MessageHandler(Handler): def check(self, message): return ( self.filters(message) - if self.filters + if callable(self.filters) else True ) diff --git a/pyrogram/client/methods/__init__.py b/pyrogram/client/methods/__init__.py index 396fd3c2..eba768bb 100644 --- a/pyrogram/client/methods/__init__.py +++ b/pyrogram/client/methods/__init__.py @@ -20,10 +20,10 @@ from .bots import Bots from .chats import Chats from .contacts import Contacts from .decorators import Decorators -from .download_media import DownloadMedia from .messages import Messages from .password import Password from .users import Users +from .utilities import Utilities class Methods( @@ -32,7 +32,7 @@ class Methods( Password, Chats, Users, - DownloadMedia, + Utilities, Messages, Decorators ): diff --git a/pyrogram/client/methods/bots/request_callback_answer.py b/pyrogram/client/methods/bots/request_callback_answer.py index 52dab58c..b242f4af 100644 --- a/pyrogram/client/methods/bots/request_callback_answer.py +++ b/pyrogram/client/methods/bots/request_callback_answer.py @@ -33,7 +33,6 @@ class RequestCallbackAnswer(BaseClient): 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. diff --git a/pyrogram/client/methods/bots/send_inline_bot_result.py b/pyrogram/client/methods/bots/send_inline_bot_result.py index c194298a..bdfcc65c 100644 --- a/pyrogram/client/methods/bots/send_inline_bot_result.py +++ b/pyrogram/client/methods/bots/send_inline_bot_result.py @@ -35,7 +35,6 @@ class SendInlineBotResult(BaseClient): 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. query_id (``int``): Unique identifier for the answered query. diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index 9c887ef5..f9eb25f3 100644 --- a/pyrogram/client/methods/chats/__init__.py +++ b/pyrogram/client/methods/chats/__init__.py @@ -16,14 +16,24 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from .delete_chat_photo import DeleteChatPhoto from .export_chat_invite_link import ExportChatInviteLink from .get_chat import GetChat +from .get_chat_member import GetChatMember +from .get_chat_members import GetChatMembers +from .get_chat_members_count import GetChatMembersCount +from .get_dialogs import GetDialogs from .join_chat import JoinChat from .kick_chat_member import KickChatMember from .leave_chat import LeaveChat +from .pin_chat_message import PinChatMessage from .promote_chat_member import PromoteChatMember from .restrict_chat_member import RestrictChatMember +from .set_chat_description import SetChatDescription +from .set_chat_photo import SetChatPhoto +from .set_chat_title import SetChatTitle from .unban_chat_member import UnbanChatMember +from .unpin_chat_message import UnpinChatMessage class Chats( @@ -34,6 +44,16 @@ class Chats( KickChatMember, UnbanChatMember, RestrictChatMember, - PromoteChatMember + PromoteChatMember, + GetChatMembers, + GetChatMember, + SetChatPhoto, + DeleteChatPhoto, + SetChatTitle, + SetChatDescription, + PinChatMessage, + UnpinChatMessage, + GetDialogs, + GetChatMembersCount ): pass diff --git a/pyrogram/client/methods/chats/delete_chat_photo.py b/pyrogram/client/methods/chats/delete_chat_photo.py new file mode 100644 index 00000000..31e02923 --- /dev/null +++ b/pyrogram/client/methods/chats/delete_chat_photo.py @@ -0,0 +1,63 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class DeleteChatPhoto(BaseClient): + def delete_chat_photo(self, chat_id: int or str): + """Use this method to delete a chat photo. + Photos can't be changed for private chats. + 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. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + Returns: + True on success. + + Raises: + :class:`Error ` + ``ValueError``: If a chat_id belongs to user. + """ + peer = self.resolve_peer(chat_id) + + if isinstance(peer, types.InputPeerChat): + self.send( + functions.messages.EditChatPhoto( + chat_id=peer.chat_id, + photo=types.InputChatPhotoEmpty() + ) + ) + elif isinstance(peer, types.InputPeerChannel): + self.send( + functions.channels.EditPhoto( + channel=peer, + photo=types.InputChatPhotoEmpty() + ) + ) + else: + raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) + + return True diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index 194e6171..5cb22db1 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -25,6 +25,10 @@ class GetChat(BaseClient): """Use this method to get up to date information about the chat (current name of the user for one-on-one conversations, current username of a user, group or channel, etc.) + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + Returns: On success, a :obj:`Chat ` object is returned. diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/client/methods/chats/get_chat_member.py new file mode 100644 index 00000000..51e07f91 --- /dev/null +++ b/pyrogram/client/methods/chats/get_chat_member.py @@ -0,0 +1,75 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from pyrogram.api import functions, types, errors +from ...ext import BaseClient, utils + + +class GetChatMember(BaseClient): + def get_chat_member(self, + chat_id: int or str, + user_id: int or str): + """Use this method to get information about one member of a chat. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + user_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). + + Returns: + On success, a :obj:`ChatMember ` object is returned. + + Raises: + :class:`Error ` + """ + chat_id = self.resolve_peer(chat_id) + user_id = self.resolve_peer(user_id) + + if isinstance(chat_id, types.InputPeerChat): + full_chat = self.send( + functions.messages.GetFullChat( + chat_id=chat_id.chat_id + ) + ) + + for member in utils.parse_chat_members(full_chat).chat_members: + if member.user.id == user_id.user_id: + return member + else: + raise errors.UserNotParticipant + elif isinstance(chat_id, types.InputPeerChannel): + r = self.send( + functions.channels.GetParticipant( + channel=chat_id, + user_id=user_id + ) + ) + + return utils.parse_chat_members( + types.channels.ChannelParticipants( + count=1, + participants=[r.participant], + users=r.users + ) + ).chat_members[0] + else: + raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py new file mode 100644 index 00000000..818c667e --- /dev/null +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -0,0 +1,115 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from pyrogram.api import functions, types +from ...ext import BaseClient, utils + + +class Filters: + ALL = "all" + KICKED = "kicked" + RESTRICTED = "restricted" + BOTS = "bots" + RECENT = "recent" + ADMINISTRATORS = "administrators" + + +class GetChatMembers(BaseClient): + def get_chat_members(self, + chat_id: int or str, + offset: int = 0, + limit: int = 200, + query: str = "", + filter: str = Filters.ALL): + """Use this method to get the members list of a chat. + + A chat can be either a basic group, a supergroup or a channel. + You must be admin to retrieve the members (also known as "subscribers") list of a channel. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + offset (``int``, *optional*): + Sequential number of the first member to be returned. + Defaults to 0 [1]_. + + limit (``int``, *optional*): + Limits the number of members to be retrieved. + Defaults to 200, which is also the maximum limit allowed per method call. + + query (``str``, *optional*): + Query string to filter members based on their display names and usernames. + Defaults to "" (empty string) [2]_. + + filter (``str``, *optional*): + Filter used to select the kind of members you want to retrieve. Only applicable for supergroups + and channels. It can be any of the followings: + *"all"* - all kind of members, + *"kicked"* - kicked (banned) members only, + *"restricted"* - restricted members only, + *"bots"* - bots only, + *"recent"* - recent members only, + *"administrators"* - chat administrators only. + Defaults to *"all"*. + + .. [1] On supergroups and channels you can get up to 10,000 members for a single query string. + + .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only. + """ + peer = self.resolve_peer(chat_id) + + if isinstance(peer, types.InputPeerChat): + return utils.parse_chat_members( + self.send( + functions.messages.GetFullChat( + peer.chat_id + ) + ) + ) + elif isinstance(peer, types.InputPeerChannel): + filter = filter.lower() + + if filter == Filters.ALL: + filter = types.ChannelParticipantsSearch(q=query) + elif filter == Filters.KICKED: + filter = types.ChannelParticipantsKicked(q=query) + elif filter == Filters.RESTRICTED: + filter = types.ChannelParticipantsBanned(q=query) + elif filter == Filters.BOTS: + filter = types.ChannelParticipantsBots() + elif filter == Filters.RECENT: + filter = types.ChannelParticipantsRecent() + elif filter == Filters.ADMINISTRATORS: + filter = types.ChannelParticipantsAdmins() + else: + raise ValueError("Invalid filter \"{}\"".format(filter)) + + return utils.parse_chat_members( + self.send( + functions.channels.GetParticipants( + channel=peer, + filter=filter, + offset=offset, + limit=limit, + hash=0 + ) + ) + ) + else: + raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) diff --git a/pyrogram/client/methods/chats/get_chat_members_count.py b/pyrogram/client/methods/chats/get_chat_members_count.py new file mode 100644 index 00000000..41650bdd --- /dev/null +++ b/pyrogram/client/methods/chats/get_chat_members_count.py @@ -0,0 +1,53 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class GetChatMembersCount(BaseClient): + def get_chat_members_count(self, chat_id: int or str): + """Use this method to get the number of members in a chat. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + Returns: + On success, an integer is returned. + + Raises: + :class:`Error ` + ``ValueError``: If a chat_id belongs to user. + """ + peer = self.resolve_peer(chat_id) + + if isinstance(peer, types.InputPeerChat): + return self.send( + functions.messages.GetChats( + id=[peer.chat_id] + ) + ).chats[0].participants_count + elif isinstance(peer, types.InputPeerChannel): + return self.send( + functions.channels.GetFullChannel( + channel=peer + ) + ).full_chat.participants_count + else: + raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py new file mode 100644 index 00000000..e22f7e2f --- /dev/null +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -0,0 +1,125 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +import pyrogram +from pyrogram.api import functions, types +from ...ext import BaseClient, utils + + +class GetDialogs(BaseClient): + def get_dialogs(self, + offset_dialogs=None, + limit: int = 100, + pinned_only: bool = False): + """Use this method to get the user's dialogs + + You can get up to 100 dialogs at once. + + Args: + limit (``str``, *optional*): + Limits the number of dialogs to be retrieved. + Defaults to 100 + + pinned_only (``bool``, *optional*): + Pass True if you want to get only pinned dialogs. + Defaults to False. + + offset_dialogs (:obj:`Dialogs`): + Pass the previous dialogs object to retrieve the next dialogs chunk starting from the last dialog. + Defaults to None (start from the beginning). + + Returns: + On success, a :obj:`Dialogs` object is returned. + + Raises: + :class:`Error` + """ + + if pinned_only: + r = self.send(functions.messages.GetPinnedDialogs()) + else: + offset_date = 0 + + if offset_dialogs: + for dialog in reversed(offset_dialogs.dialogs): + top_message = dialog.top_message + + if top_message: + message_date = top_message.date + + if message_date: + offset_date = message_date + break + + r = self.send( + functions.messages.GetDialogs( + offset_date=offset_date, + offset_id=0, + offset_peer=types.InputPeerEmpty(), + limit=limit, + hash=0, + exclude_pinned=True + ) + ) + + users = {i.id: i for i in r.users} + chats = {i.id: i for i in r.chats} + messages = {} + + for message in r.messages: + to_id = message.to_id + + if isinstance(to_id, types.PeerUser): + if message.out: + chat_id = to_id.user_id + else: + chat_id = message.from_id + elif isinstance(to_id, types.PeerChat): + chat_id = -to_id.chat_id + else: + chat_id = int("-100" + str(to_id.channel_id)) + + messages[chat_id] = utils.parse_messages(self, message, users, chats) + + dialogs = [] + + for dialog in r.dialogs: + chat_id = dialog.peer + + if isinstance(chat_id, types.PeerUser): + chat_id = chat_id.user_id + elif isinstance(chat_id, types.PeerChat): + chat_id = -chat_id.chat_id + else: + chat_id = int("-100" + str(chat_id.channel_id)) + + dialogs.append( + pyrogram.Dialog( + chat=utils.parse_dialog_chat(dialog.peer, users, chats), + top_message=messages.get(chat_id), + unread_messages_count=dialog.unread_count, + unread_mentions_count=dialog.unread_mentions_count, + unread_mark=dialog.unread_mark, + is_pinned=dialog.pinned + ) + ) + + return pyrogram.Dialogs( + total_count=getattr(r, "count", len(r.dialogs)), + dialogs=dialogs + ) diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/client/methods/chats/kick_chat_member.py index 6275718c..1c849285 100644 --- a/pyrogram/client/methods/chats/kick_chat_member.py +++ b/pyrogram/client/methods/chats/kick_chat_member.py @@ -38,7 +38,6 @@ class KickChatMember(BaseClient): 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. diff --git a/pyrogram/client/methods/chats/pin_chat_message.py b/pyrogram/client/methods/chats/pin_chat_message.py new file mode 100644 index 00000000..eb653f3c --- /dev/null +++ b/pyrogram/client/methods/chats/pin_chat_message.py @@ -0,0 +1,62 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class PinChatMessage(BaseClient): + def pin_chat_message(self, chat_id: int or str, message_id: int, disable_notification: bool = None): + """Use this method to pin a message in a supergroup or a channel. + You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin right in + the supergroup or "can_edit_messages" admin right in the channel. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + message_id (``int``): + Identifier of a message to pin. + + disable_notification (``bool``): + Pass True, if it is not necessary to send a notification to all chat members about the new pinned + message. Notifications are always disabled in channels. + + Returns: + True on success. + + Raises: + :class:`Error ` + ``ValueError``: If a chat_id doesn't belong to a supergroup or a channel. + """ + peer = self.resolve_peer(chat_id) + + if isinstance(peer, types.InputPeerChannel): + self.send( + functions.channels.UpdatePinnedMessage( + channel=peer, + id=message_id, + silent=disable_notification or None + ) + ) + elif isinstance(peer, types.InputPeerChat): + raise ValueError("The chat_id \"{}\" belongs to a basic group".format(chat_id)) + else: + raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) + + return True diff --git a/pyrogram/client/methods/chats/promote_chat_member.py b/pyrogram/client/methods/chats/promote_chat_member.py index eb70578a..134d1ba3 100644 --- a/pyrogram/client/methods/chats/promote_chat_member.py +++ b/pyrogram/client/methods/chats/promote_chat_member.py @@ -39,7 +39,6 @@ class PromoteChatMember(BaseClient): 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. diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index ae1e4d9c..fb11a989 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -36,7 +36,6 @@ class RestrictChatMember(BaseClient): 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. diff --git a/pyrogram/client/methods/chats/set_chat_description.py b/pyrogram/client/methods/chats/set_chat_description.py new file mode 100644 index 00000000..f9b5c7a2 --- /dev/null +++ b/pyrogram/client/methods/chats/set_chat_description.py @@ -0,0 +1,56 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class SetChatDescription(BaseClient): + def set_chat_description(self, chat_id: int or str, description: str): + """Use this method to change the description of a supergroup or a channel. + You must be an administrator in the chat for this to work and must have the appropriate admin rights. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + description (``str``): + New chat description, 0-255 characters. + + Returns: + True on success. + + Raises: + :class:`Error ` + ``ValueError``: If a chat_id doesn't belong to a supergroup or a channel. + """ + peer = self.resolve_peer(chat_id) + + if isinstance(peer, types.InputPeerChannel): + self.send( + functions.channels.EditAbout( + channel=peer, + about=description + ) + ) + elif isinstance(peer, types.InputPeerChat): + raise ValueError("The chat_id \"{}\" belongs to a basic group".format(chat_id)) + else: + raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) + + return True diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py new file mode 100644 index 00000000..224a8d99 --- /dev/null +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -0,0 +1,82 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +import os +from base64 import b64decode +from struct import unpack + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class SetChatPhoto(BaseClient): + def set_chat_photo(self, chat_id: int or str, photo: str): + """Use this method to set a new profile photo for the chat. + Photos can't be changed for private chats. + 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. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + photo (``str``): + New chat photo. You can pass a :class:`Photo` id or a file path to upload a new photo. + + Returns: + True on success. + + Raises: + :class:`Error ` + ``ValueError``: If a chat_id belongs to user. + """ + peer = self.resolve_peer(chat_id) + + if os.path.exists(photo): + photo = types.InputChatUploadedPhoto(file=self.save_file(photo)) + else: + s = unpack(" +# +# 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 . + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class SetChatTitle(BaseClient): + def set_chat_title(self, chat_id: int or str, title: str): + """Use this method to change the title of a chat. + Titles can't be changed for private chats. + 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. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + title (``str``): + New chat title, 1-255 characters. + + Returns: + True on success. + + Raises: + :class:`Error ` + ``ValueError``: If a chat_id belongs to user. + """ + peer = self.resolve_peer(chat_id) + + if isinstance(peer, types.InputPeerChat): + self.send( + functions.messages.EditChatTitle( + chat_id=peer.chat_id, + title=title + ) + ) + elif isinstance(peer, types.InputPeerChannel): + self.send( + functions.channels.EditTitle( + channel=peer, + title=title + ) + ) + else: + raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) + + return True diff --git a/pyrogram/client/methods/chats/unban_chat_member.py b/pyrogram/client/methods/chats/unban_chat_member.py index b0916eb4..e191a0b7 100644 --- a/pyrogram/client/methods/chats/unban_chat_member.py +++ b/pyrogram/client/methods/chats/unban_chat_member.py @@ -31,7 +31,6 @@ class UnbanChatMember(BaseClient): 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. diff --git a/pyrogram/client/methods/chats/unpin_chat_message.py b/pyrogram/client/methods/chats/unpin_chat_message.py new file mode 100644 index 00000000..4b0ee46b --- /dev/null +++ b/pyrogram/client/methods/chats/unpin_chat_message.py @@ -0,0 +1,54 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class UnpinChatMessage(BaseClient): + def unpin_chat_message(self, chat_id: int or str): + """Use this method to unpin a message in a supergroup or a channel. + You must be an administrator in the chat for this to work and must have the "can_pin_messages" admin + right in the supergroup or "can_edit_messages" admin right in the channel. + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + Returns: + True on success. + + Raises: + :class:`Error ` + ``ValueError``: If a chat_id doesn't belong to a supergroup or a channel. + """ + peer = self.resolve_peer(chat_id) + + if isinstance(peer, types.InputPeerChannel): + self.send( + functions.channels.UpdatePinnedMessage( + channel=peer, + id=0 + ) + ) + elif isinstance(peer, types.InputPeerChat): + raise ValueError("The chat_id \"{}\" belongs to a basic group".format(chat_id)) + else: + raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) + + return True diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index e2c2462e..35dae756 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -18,16 +18,17 @@ from .delete_messages import DeleteMessages from .edit_message_caption import EditMessageCaption +from .edit_message_media import EditMessageMedia 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 .send_animation import SendAnimation 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 @@ -43,6 +44,7 @@ class Messages( DeleteMessages, EditMessageCaption, EditMessageReplyMarkup, + EditMessageMedia, EditMessageText, ForwardMessages, GetHistory, @@ -51,7 +53,7 @@ class Messages( SendChatAction, SendContact, SendDocument, - SendGIF, + SendAnimation, SendLocation, SendMediaGroup, SendMessage, diff --git a/pyrogram/client/methods/messages/delete_messages.py b/pyrogram/client/methods/messages/delete_messages.py index a4c97c76..8932a699 100644 --- a/pyrogram/client/methods/messages/delete_messages.py +++ b/pyrogram/client/methods/messages/delete_messages.py @@ -38,7 +38,6 @@ class DeleteMessages(BaseClient): 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_ids (``iterable``): A list of Message identifiers to delete or a single message id. diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py index 25276dc2..a83c670e 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/client/methods/messages/edit_message_caption.py @@ -34,7 +34,6 @@ class EditMessageCaption(BaseClient): 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``): Message identifier in the chat specified in chat_id. diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py new file mode 100644 index 00000000..7bf66d5e --- /dev/null +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -0,0 +1,312 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +import binascii +import mimetypes +import os +import struct + +from pyrogram.api import functions, types +from pyrogram.api.errors import FileIdInvalid +from pyrogram.client.ext import BaseClient, utils +from pyrogram.client.types import ( + InputMediaPhoto, InputMediaVideo, InputMediaAudio, + InputMediaAnimation, InputMediaDocument +) + + +class EditMessageMedia(BaseClient): + def edit_message_media(self, + chat_id: int or str, + message_id: int, + media, + reply_markup=None): + style = self.html if media.parse_mode.lower() == "html" else self.markdown + caption = media.caption + + if isinstance(media, InputMediaPhoto): + if os.path.exists(media.media): + media = self.send( + functions.messages.UploadMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaUploadedPhoto( + file=self.save_file(media.media) + ) + ) + ) + + media = types.InputMediaPhoto( + id=types.InputPhoto( + id=media.photo.id, + access_hash=media.photo.access_hash + ) + ) + elif media.media.startswith("http"): + media = types.InputMediaPhotoExternal( + url=media.media + ) + else: + try: + decoded = utils.decode(media.media) + fmt = " 24 else " 24 else " 24 else " 24 else " 24 else "` even if a list contains just one element, otherwise if + :obj:`Messages ` even if a list contains just one element, otherwise if *message_ids* was an integer, the single requested :obj:`Message ` is returned. diff --git a/pyrogram/client/methods/messages/send_gif.py b/pyrogram/client/methods/messages/send_animation.py similarity index 77% rename from pyrogram/client/methods/messages/send_gif.py rename to pyrogram/client/methods/messages/send_animation.py index a4a14d84..30f28b30 100644 --- a/pyrogram/client/methods/messages/send_gif.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -26,38 +26,37 @@ from pyrogram.api.errors import FileIdInvalid, FilePartMissing from pyrogram.client.ext import BaseClient, utils -class SendGIF(BaseClient): - def send_gif(self, - chat_id: int or str, - gif: str, - caption: str = "", - parse_mode: str = "", - duration: int = 0, - width: int = 0, - height: int = 0, - thumb: str = None, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup=None, - progress: callable = None, - progress_args: tuple = ()): - """Use this method to send GIF files. +class SendAnimation(BaseClient): + def send_animation(self, + chat_id: int or str, + animation: str, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + width: int = 0, + height: int = 0, + thumb: str = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup=None, + progress: callable = None, + progress_args: tuple = ()): + """Use this method to send animation files (animation or H.264/MPEG-4 AVC video without sound). 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. - gif (``str``): - GIF to send. - Pass a file_id as string to send a GIF that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get a GIF from the Internet, or - pass a file path as string to upload a new GIF that exists on your local machine. + animation (``str``): + Animation to send. + Pass a file_id as string to send an animation that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get an animation from the Internet, or + pass a file path as string to upload a new animation that exists on your local machine. caption (``str``, *optional*): - GIF caption, 0-200 characters. + Animation caption, 0-200 characters. parse_mode (``str``, *optional*): Use :obj:`MARKDOWN ` or :obj:`HTML ` @@ -65,18 +64,19 @@ class SendGIF(BaseClient): Defaults to Markdown. duration (``int``, *optional*): - Duration of sent GIF in seconds. + Duration of sent animation in seconds. width (``int``, *optional*): - GIF width. + Animation width. height (``int``, *optional*): - GIF height. + Animation height. thumb (``str``, *optional*): - GIF thumbnail. - Pass a file path as string to send an image that exists on your local machine. - Thumbnail should have 90 or less pixels of width and 90 or less pixels of height. + Thumbnail of the animation file sent. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. disable_notification (``bool``, *optional*): Sends the message silently. @@ -121,9 +121,9 @@ class SendGIF(BaseClient): file = None style = self.html if parse_mode.lower() == "html" else self.markdown - if os.path.exists(gif): + if os.path.exists(animation): thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(gif, progress=progress, progress_args=progress_args) + file = self.save_file(animation, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=mimetypes.types_map[".mp4"], file=file, @@ -135,17 +135,17 @@ class SendGIF(BaseClient): w=width, h=height ), - types.DocumentAttributeFilename(os.path.basename(gif)), + types.DocumentAttributeFilename(os.path.basename(animation)), types.DocumentAttributeAnimated() ] ) - elif gif.startswith("http"): + elif animation.startswith("http"): media = types.InputMediaDocumentExternal( - url=gif + url=animation ) else: try: - decoded = utils.decode(gif) + decoded = utils.decode(animation) fmt = " 24 else "` | ``str``): Type of action to broadcast. diff --git a/pyrogram/client/methods/messages/send_contact.py b/pyrogram/client/methods/messages/send_contact.py index fc0abdd5..f661a31f 100644 --- a/pyrogram/client/methods/messages/send_contact.py +++ b/pyrogram/client/methods/messages/send_contact.py @@ -26,6 +26,7 @@ class SendContact(BaseClient): phone_number: str, first_name: str, last_name: str = "", + vcard: str = "", disable_notification: bool = None, reply_to_message_id: int = None, reply_markup=None): @@ -36,7 +37,6 @@ class SendContact(BaseClient): 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. phone_number (``str``): Contact's phone number. @@ -47,6 +47,9 @@ class SendContact(BaseClient): last_name (``str``, *optional*): Contact's last name. + vcard (``str``, *optional*): + Additional data about the contact in the form of a vCard, 0-2048 bytes + disable_notification (``bool``, *optional*): Sends the message silently. Users will receive a notification with no sound. @@ -68,9 +71,10 @@ class SendContact(BaseClient): functions.messages.SendMedia( peer=self.resolve_peer(chat_id), media=types.InputMediaContact( - phone_number, - first_name, - last_name + phone_number=phone_number, + first_name=first_name, + last_name=last_name, + vcard=vcard ), message="", silent=disable_notification or None, diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index b2b5c532..28d93f07 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -30,6 +30,7 @@ class SendDocument(BaseClient): def send_document(self, chat_id: int or str, document: str, + thumb: str = None, caption: str = "", parse_mode: str = "", disable_notification: bool = None, @@ -44,7 +45,6 @@ class SendDocument(BaseClient): 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. document (``str``): File to send. @@ -52,6 +52,12 @@ class SendDocument(BaseClient): pass an HTTP URL as a string for Telegram to get a file from the Internet, or pass a file path as string to upload a new file that exists on your local machine. + thumb (``str``): + Thumbnail of the file sent. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + caption (``str``, *optional*): Document caption, 0-200 characters. @@ -104,10 +110,12 @@ class SendDocument(BaseClient): style = self.html if parse_mode.lower() == "html" else self.markdown if os.path.exists(document): + thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(document, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=mimetypes.types_map.get("." + document.split(".")[-1], "text/plain"), file=file, + thumb=thumb, attributes=[ types.DocumentAttributeFilename(os.path.basename(document)) ] diff --git a/pyrogram/client/methods/messages/send_location.py b/pyrogram/client/methods/messages/send_location.py index a3db2561..49a41256 100644 --- a/pyrogram/client/methods/messages/send_location.py +++ b/pyrogram/client/methods/messages/send_location.py @@ -35,7 +35,6 @@ class SendLocation(BaseClient): 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. latitude (``float``): Latitude of the location. diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 297d8f83..5465c9f2 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -44,7 +44,6 @@ class SendMediaGroup(BaseClient): 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. media (``list``): A list containing either :obj:`InputMediaPhoto ` or diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index 44acaa2e..00087307 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -37,7 +37,6 @@ class SendMessage(BaseClient): 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. text (``str``): Text of the message to be sent. @@ -62,7 +61,7 @@ class SendMessage(BaseClient): instructions to remove reply keyboard or to force a reply from the user. Returns: - On success, the sent Message is returned. + On success, the sent :obj:`Message` is returned. Raises: :class:`Error ` diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index a6264bf3..2d0036f2 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -44,7 +44,6 @@ class SendPhoto(BaseClient): 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. photo (``str``): Photo to send. diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index fbf7a205..9f1f64da 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -41,7 +41,6 @@ class SendSticker(BaseClient): 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. sticker (``str``): Sticker to send. diff --git a/pyrogram/client/methods/messages/send_venue.py b/pyrogram/client/methods/messages/send_venue.py index 50946e86..3decd5fb 100644 --- a/pyrogram/client/methods/messages/send_venue.py +++ b/pyrogram/client/methods/messages/send_venue.py @@ -28,6 +28,7 @@ class SendVenue(BaseClient): title: str, address: str, foursquare_id: str = "", + foursquare_type: str = "", disable_notification: bool = None, reply_to_message_id: int = None, reply_markup=None): @@ -38,7 +39,6 @@ class SendVenue(BaseClient): 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. latitude (``float``): Latitude of the venue. @@ -55,6 +55,10 @@ class SendVenue(BaseClient): foursquare_id (``str``, *optional*): Foursquare identifier of the venue. + foursquare_type (``str``, *optional*): + Foursquare type of the venue, if known. + (For example, "arts_entertainment/default", "arts_entertainment/aquarium" or "food/icecream".) + disable_notification (``bool``, *optional*): Sends the message silently. Users will receive a notification with no sound. @@ -84,7 +88,7 @@ class SendVenue(BaseClient): address=address, provider="", venue_id=foursquare_id, - venue_type="" + venue_type=foursquare_type ), message="", silent=disable_notification or None, diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index b86b4702..4317d88b 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -49,7 +49,6 @@ class SendVideo(BaseClient): 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. video (``str``): Video to send. @@ -75,9 +74,10 @@ class SendVideo(BaseClient): Video height. thumb (``str``, *optional*): - Video thumbnail. - Pass a file path as string to send an image that exists on your local machine. - Thumbnail should have 90 or less pixels of width and 90 or less pixels of height. + Thumbnail of the video sent. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. supports_streaming (``bool``, *optional*): Pass True, if the uploaded video is suitable for streaming. diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index a266e5dd..6da1962a 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -32,6 +32,7 @@ class SendVideoNote(BaseClient): video_note: str, duration: int = 0, length: int = 1, + thumb: str = None, disable_notification: bool = None, reply_to_message_id: int = None, reply_markup=None, @@ -44,7 +45,6 @@ class SendVideoNote(BaseClient): 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. video_note (``str``): Video note to send. @@ -58,6 +58,12 @@ class SendVideoNote(BaseClient): length (``int``, *optional*): Video width and height. + thumb (``str``, *optional*): + Thumbnail of the video sent. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + disable_notification (``bool``, *optional*): Sends the message silently. Users will receive a notification with no sound. @@ -101,10 +107,12 @@ class SendVideoNote(BaseClient): file = None if os.path.exists(video_note): + thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video_note, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=mimetypes.types_map[".mp4"], file=file, + thumb=thumb, attributes=[ types.DocumentAttributeVideo( round_message=True, diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index f4fe4229..5b577672 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -45,7 +45,6 @@ class SendVoice(BaseClient): 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. voice (``str``): Audio file to send. diff --git a/pyrogram/client/methods/users/__init__.py b/pyrogram/client/methods/users/__init__.py index 7a82667d..f7c32b3b 100644 --- a/pyrogram/client/methods/users/__init__.py +++ b/pyrogram/client/methods/users/__init__.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from .delete_profile_photos import DeleteProfilePhotos from .get_me import GetMe from .get_user_profile_photos import GetUserProfilePhotos from .get_users import GetUsers @@ -23,6 +24,7 @@ from .get_users import GetUsers class Users( GetUserProfilePhotos, + DeleteProfilePhotos, GetUsers, GetMe ): diff --git a/pyrogram/client/methods/users/delete_profile_photos.py b/pyrogram/client/methods/users/delete_profile_photos.py new file mode 100644 index 00000000..47a6682a --- /dev/null +++ b/pyrogram/client/methods/users/delete_profile_photos.py @@ -0,0 +1,58 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from base64 import b64decode +from struct import unpack + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class DeleteProfilePhotos(BaseClient): + def delete_profile_photos(self, id: str or list): + """Use this method to delete your own profile photos + + Args: + id (``str`` | ``list``): + A single :obj:`Photo ` id as string or multiple ids as list of strings for deleting + more than one photos at once. + + Returns: + True on success. + + Raises: + :class:`Error ` + """ + id = id if isinstance(id, list) else [id] + input_photos = [] + + for i in id: + s = unpack("` """ - return utils.parse_photos( + return utils.parse_profile_photos( self.send( functions.photos.GetUserPhotos( user_id=self.resolve_peer(user_id), diff --git a/pyrogram/client/methods/utilities/__init__.py b/pyrogram/client/methods/utilities/__init__.py new file mode 100644 index 00000000..f8db23e5 --- /dev/null +++ b/pyrogram/client/methods/utilities/__init__.py @@ -0,0 +1,25 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from .download_media import DownloadMedia + + +class Utilities( + DownloadMedia +): + pass diff --git a/pyrogram/client/methods/download_media.py b/pyrogram/client/methods/utilities/download_media.py similarity index 81% rename from pyrogram/client/methods/download_media.py rename to pyrogram/client/methods/utilities/download_media.py index 5eb04fbc..26918f27 100644 --- a/pyrogram/client/methods/download_media.py +++ b/pyrogram/client/methods/utilities/download_media.py @@ -19,7 +19,7 @@ from threading import Event from pyrogram.client import types as pyrogram_types -from ..ext import BaseClient +from ...ext import BaseClient class DownloadMedia(BaseClient): @@ -74,10 +74,18 @@ class DownloadMedia(BaseClient): Raises: :class:`Error ` + ``ValueError``: If the message doesn't contain any downloadable media """ + error_message = "This message doesn't contain any downloadable media" + if isinstance(message, pyrogram_types.Message): if message.photo: - media = message.photo[-1] + media = pyrogram_types.Document( + file_id=message.photo.sizes[-1].file_id, + file_size=message.photo.sizes[-1].file_size, + mime_type="", + date=message.photo.date + ) elif message.audio: media = message.audio elif message.document: @@ -90,11 +98,12 @@ class DownloadMedia(BaseClient): media = message.video_note elif message.sticker: media = message.sticker - elif message.gif: - media = message.gif + elif message.animation: + media = message.animation else: - return + raise ValueError(error_message) elif isinstance(message, ( + pyrogram_types.Photo, pyrogram_types.PhotoSize, pyrogram_types.Audio, pyrogram_types.Document, @@ -102,9 +111,17 @@ class DownloadMedia(BaseClient): pyrogram_types.Voice, pyrogram_types.VideoNote, pyrogram_types.Sticker, - pyrogram_types.GIF + pyrogram_types.Animation )): - media = message + if isinstance(message, pyrogram_types.Photo): + media = pyrogram_types.Document( + file_id=message.sizes[-1].file_id, + file_size=message.sizes[-1].file_size, + mime_type="", + date=message.date + ) + else: + media = message elif isinstance(message, str): media = pyrogram_types.Document( file_id=message, @@ -112,7 +129,7 @@ class DownloadMedia(BaseClient): mime_type="" ) else: - return + raise ValueError(error_message) done = Event() path = [None] diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index acd001dd..230d5e5d 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -16,31 +16,25 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .audio import Audio -from .callback_query import CallbackQuery -from .chat import Chat -from .chat_member import ChatMember -from .chat_photo import ChatPhoto -from .contact import Contact -from .document import Document -from .gif import GIF -from .input_media_photo import InputMediaPhoto -from .input_media_video import InputMediaVideo -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 ( +from .bots import ( + CallbackQuery, ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, + KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove +) +from .bots import ( ForceReply, InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove ) -from .sticker import Sticker +from .input_media import ( + InputMediaAudio, InputPhoneContact, InputMediaVideo, InputMediaPhoto, + InputMediaDocument, InputMediaAnimation +) +from .messages_and_media import ( + Audio, Contact, Document, Animation, Location, Photo, PhotoSize, + Sticker, Venue, Video, VideoNote, Voice, UserProfilePhotos, + Message, Messages, MessageEntity +) from .update import Update -from .user import User -from .user_profile_photos import UserProfilePhotos -from .venue import Venue -from .video import Video -from .video_note import VideoNote -from .voice import Voice +from .user_and_chats import ( + Chat, ChatMember, ChatMembers, ChatPhoto, + Dialog, Dialogs, User +) diff --git a/pyrogram/client/types/reply_markup/__init__.py b/pyrogram/client/types/bots/__init__.py similarity index 96% rename from pyrogram/client/types/reply_markup/__init__.py rename to pyrogram/client/types/bots/__init__.py index 62ce7152..9f7cc7e6 100644 --- a/pyrogram/client/types/reply_markup/__init__.py +++ b/pyrogram/client/types/bots/__init__.py @@ -16,6 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from .callback_query import CallbackQuery from .force_reply import ForceReply from .inline_keyboard_button import InlineKeyboardButton from .inline_keyboard_markup import InlineKeyboardMarkup diff --git a/pyrogram/client/types/callback_query.py b/pyrogram/client/types/bots/callback_query.py similarity index 100% rename from pyrogram/client/types/callback_query.py rename to pyrogram/client/types/bots/callback_query.py diff --git a/pyrogram/client/types/reply_markup/force_reply.py b/pyrogram/client/types/bots/force_reply.py similarity index 100% rename from pyrogram/client/types/reply_markup/force_reply.py rename to pyrogram/client/types/bots/force_reply.py diff --git a/pyrogram/client/types/reply_markup/inline_keyboard_button.py b/pyrogram/client/types/bots/inline_keyboard_button.py similarity index 100% rename from pyrogram/client/types/reply_markup/inline_keyboard_button.py rename to pyrogram/client/types/bots/inline_keyboard_button.py diff --git a/pyrogram/client/types/reply_markup/inline_keyboard_markup.py b/pyrogram/client/types/bots/inline_keyboard_markup.py similarity index 100% rename from pyrogram/client/types/reply_markup/inline_keyboard_markup.py rename to pyrogram/client/types/bots/inline_keyboard_markup.py diff --git a/pyrogram/client/types/reply_markup/keyboard_button.py b/pyrogram/client/types/bots/keyboard_button.py similarity index 100% rename from pyrogram/client/types/reply_markup/keyboard_button.py rename to pyrogram/client/types/bots/keyboard_button.py diff --git a/pyrogram/client/types/reply_markup/reply_keyboard_markup.py b/pyrogram/client/types/bots/reply_keyboard_markup.py similarity index 100% rename from pyrogram/client/types/reply_markup/reply_keyboard_markup.py rename to pyrogram/client/types/bots/reply_keyboard_markup.py diff --git a/pyrogram/client/types/reply_markup/reply_keyboard_remove.py b/pyrogram/client/types/bots/reply_keyboard_remove.py similarity index 99% rename from pyrogram/client/types/reply_markup/reply_keyboard_remove.py rename to pyrogram/client/types/bots/reply_keyboard_remove.py index de32f740..3e2aebf5 100644 --- a/pyrogram/client/types/reply_markup/reply_keyboard_remove.py +++ b/pyrogram/client/types/bots/reply_keyboard_remove.py @@ -35,7 +35,7 @@ class ReplyKeyboardRemove(Object): keyboard for that user, while still showing the keyboard with poll options to users who haven't voted yet. """ - ID = 0xb0700002 + ID = 0xb0700023 def __init__(self, selective: bool = None): self.selective = selective diff --git a/pyrogram/client/types/input_media/__init__.py b/pyrogram/client/types/input_media/__init__.py new file mode 100644 index 00000000..5f5be30d --- /dev/null +++ b/pyrogram/client/types/input_media/__init__.py @@ -0,0 +1,25 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from .input_media import InputMedia +from .input_media_animation import InputMediaAnimation +from .input_media_audio import InputMediaAudio +from .input_media_document import InputMediaDocument +from .input_media_photo import InputMediaPhoto +from .input_media_video import InputMediaVideo +from .input_phone_contact import InputPhoneContact diff --git a/pyrogram/client/types/input_media/input_media.py b/pyrogram/client/types/input_media/input_media.py new file mode 100644 index 00000000..611d5865 --- /dev/null +++ b/pyrogram/client/types/input_media/input_media.py @@ -0,0 +1,24 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + + +class InputMedia: + def __init__(self, media: str, caption: str, parse_mode: str): + self.media = media + self.caption = caption + self.parse_mode = parse_mode diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/client/types/input_media/input_media_animation.py new file mode 100644 index 00000000..14f8c2de --- /dev/null +++ b/pyrogram/client/types/input_media/input_media_animation.py @@ -0,0 +1,68 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from . import InputMedia + + +class InputMediaAnimation(InputMedia): + """This object represents an animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent. + + Args: + media (``str``): + Animation to send. + Pass a file_id as string to send a file that exists on the Telegram servers or + pass a file path as string to upload a new file that exists on your local machine. + + thumb (``str``, *optional*): + Thumbnail of the animation file sent. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + + caption (``str``, *optional*): + Caption of the animation to be sent, 0-200 characters + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + width (``int``, *optional*): + Animation width. + + height (``int``, *optional*): + Animation height. + + duration (``int``, *optional*): + Animation duration. + """ + + def __init__(self, + media: str, + thumb: str = None, + caption: str = "", + parse_mode: str = "", + width: int = 0, + height: int = 0, + duration: int = 0): + super().__init__(media, caption, parse_mode) + + self.thumb = thumb + self.width = width + self.height = height + self.duration = duration diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/client/types/input_media/input_media_audio.py new file mode 100644 index 00000000..83979b4a --- /dev/null +++ b/pyrogram/client/types/input_media/input_media_audio.py @@ -0,0 +1,69 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from . import InputMedia + + +class InputMediaAudio(InputMedia): + """This object represents a video to be sent inside an album. + It is intended to be used with :obj:`send_media_group() `. + + Args: + media (``str``): + Audio to send. + Pass a file_id as string to send an audio that exists on the Telegram servers or + pass a file path as string to upload a new audio that exists on your local machine. + + thumb (``str``, *optional*): + Thumbnail of the music file album cover. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + + caption (``str``, *optional*): + Caption of the audio to be sent, 0-200 characters + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + + duration (``int``, *optional*): + Duration of the audio in seconds + + performer (``int``, *optional*): + Performer of the audio + + title (``int``, *optional*): + Title of the audio + """ + + def __init__(self, + media: str, + thumb: str = None, + caption: str = "", + parse_mode: str = "", + duration: int = 0, + performer: int = "", + title: str = ""): + super().__init__(media, caption, parse_mode) + + self.thumb = thumb + self.duration = duration + self.performer = performer + self.title = title diff --git a/pyrogram/client/types/input_media/input_media_document.py b/pyrogram/client/types/input_media/input_media_document.py new file mode 100644 index 00000000..07965534 --- /dev/null +++ b/pyrogram/client/types/input_media/input_media_document.py @@ -0,0 +1,53 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from . import InputMedia + + +class InputMediaDocument(InputMedia): + """This object represents a general file to be sent. + + Args: + media (``str``): + File to send. + Pass a file_id as string to send a file that exists on the Telegram servers or + pass a file path as string to upload a new file that exists on your local machine. + + thumb (``str``): + Thumbnail of the file sent. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + + caption (``str``, *optional*): + Caption of the document to be sent, 0-200 characters + + parse_mode (``str``, *optional*): + Use :obj:`MARKDOWN ` or :obj:`HTML ` + if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in your caption. + Defaults to Markdown. + """ + + def __init__(self, + media: str, + thumb: str = None, + caption: str = "", + parse_mode: str = ""): + super().__init__(media, caption, parse_mode) + + self.thumb = thumb diff --git a/pyrogram/client/types/input_media_photo.py b/pyrogram/client/types/input_media/input_media_photo.py similarity index 93% rename from pyrogram/client/types/input_media_photo.py rename to pyrogram/client/types/input_media/input_media_photo.py index 336ee849..d58f75a6 100644 --- a/pyrogram/client/types/input_media_photo.py +++ b/pyrogram/client/types/input_media/input_media_photo.py @@ -16,8 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from . import InputMedia -class InputMediaPhoto: + +class InputMediaPhoto(InputMedia): """This object represents a photo to be sent inside an album. It is intended to be used with :obj:`send_media_group() `. @@ -41,6 +43,4 @@ class InputMediaPhoto: media: str, caption: str = "", parse_mode: str = ""): - self.media = media - self.caption = caption - self.parse_mode = parse_mode + super().__init__(media, caption, parse_mode) diff --git a/pyrogram/client/types/input_media_video.py b/pyrogram/client/types/input_media/input_media_video.py similarity index 83% rename from pyrogram/client/types/input_media_video.py rename to pyrogram/client/types/input_media/input_media_video.py index eb6c003e..54ce5e5e 100644 --- a/pyrogram/client/types/input_media_video.py +++ b/pyrogram/client/types/input_media/input_media_video.py @@ -16,8 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from . import InputMedia -class InputMediaVideo: + +class InputMediaVideo(InputMedia): """This object represents a video to be sent inside an album. It is intended to be used with :obj:`send_media_group() `. @@ -28,6 +30,12 @@ class InputMediaVideo: pass a file path as string to upload a new video that exists on your local machine. Sending video by a URL is currently unsupported. + thumb (``str``): + Thumbnail of the video sent. + The thumbnail should be in JPEG format and less than 200 KB in size. + A thumbnail's width and height should not exceed 90 pixels. + Thumbnails can't be reused and can be only uploaded as a new file. + caption (``str``, *optional*): Caption of the video to be sent, 0-200 characters @@ -51,15 +59,16 @@ class InputMediaVideo: def __init__(self, media: str, + thumb: str = None, caption: str = "", parse_mode: str = "", width: int = 0, height: int = 0, duration: int = 0, supports_streaming: bool = True): - self.media = media - self.caption = caption - self.parse_mode = parse_mode + super().__init__(media, caption, parse_mode) + + self.thumb = thumb self.width = width self.height = height self.duration = duration diff --git a/pyrogram/client/types/input_phone_contact.py b/pyrogram/client/types/input_media/input_phone_contact.py similarity index 100% rename from pyrogram/client/types/input_phone_contact.py rename to pyrogram/client/types/input_media/input_phone_contact.py diff --git a/pyrogram/client/types/messages_and_media/__init__.py b/pyrogram/client/types/messages_and_media/__init__.py new file mode 100644 index 00000000..3ab359ae --- /dev/null +++ b/pyrogram/client/types/messages_and_media/__init__.py @@ -0,0 +1,34 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from .animation import Animation +from .audio import Audio +from .contact import Contact +from .document import Document +from .location import Location +from .message import Message +from .message_entity import MessageEntity +from .messages import Messages +from .photo import Photo +from .photo_size import PhotoSize +from .sticker import Sticker +from .user_profile_photos import UserProfilePhotos +from .venue import Venue +from .video import Video +from .video_note import VideoNote +from .voice import Voice diff --git a/pyrogram/client/types/gif.py b/pyrogram/client/types/messages_and_media/animation.py similarity index 69% rename from pyrogram/client/types/gif.py rename to pyrogram/client/types/messages_and_media/animation.py index 71e975f5..85d5a365 100644 --- a/pyrogram/client/types/gif.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -19,27 +19,27 @@ from pyrogram.api.core import Object -class GIF(Object): - """This object represents a GIF file. +class Animation(Object): + """This object represents an animation file (GIF or H.264/MPEG-4 AVC video without sound). Args: file_id (``str``): Unique identifier for this file. width (``int``): - GIF width as defined by sender. + Animation width as defined by sender. height (``int``): - GIF height as defined by sender. + Animation height as defined by sender. duration (``int``): - Duration of the GIF in seconds as defined by sender. + Duration of the animation in seconds as defined by sender. thumb (:obj:`PhotoSize `, *optional*): - GIF thumbnail. + Animation thumbnail. file_name (``str``, *optional*): - GIF file name. + Animation file name. mime_type (``str``, *optional*): Mime type of a file as defined by sender. @@ -48,7 +48,7 @@ class GIF(Object): File size. date (``int``, *optional*): - Date the GIF was sent in Unix time. + Date the Animation was sent in Unix time. """ ID = 0xb0700025 @@ -65,12 +65,12 @@ class GIF(Object): file_size: int = None, date: int = None ): - self.file_id = file_id # string - self.thumb = thumb # flags.0?PhotoSize - self.file_name = file_name # flags.1?string - self.mime_type = mime_type # flags.2?string - self.file_size = file_size # flags.3?int - self.date = date # flags.4?int - self.width = width # int - self.height = height # int - self.duration = duration # int + self.file_id = file_id + self.thumb = thumb + self.file_name = file_name + self.mime_type = mime_type + self.file_size = file_size + self.date = date + self.width = width + self.height = height + self.duration = duration diff --git a/pyrogram/client/types/audio.py b/pyrogram/client/types/messages_and_media/audio.py similarity index 82% rename from pyrogram/client/types/audio.py rename to pyrogram/client/types/messages_and_media/audio.py index 51cec257..37f91992 100644 --- a/pyrogram/client/types/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -30,7 +30,7 @@ class Audio(Object): Duration of the audio in seconds as defined by sender. thumb (:obj:`PhotoSize `, *optional*): - Audio thumbnail. + Thumbnail of the music file album cover. file_name (``str``, *optional*): Audio file name. @@ -65,12 +65,12 @@ class Audio(Object): performer: str = None, title: str = None ): - self.file_id = file_id # string - self.thumb = thumb # flags.0?PhotoSize - self.file_name = file_name # flags.1?string - self.mime_type = mime_type # flags.2?string - self.file_size = file_size # flags.3?int - self.date = date # flags.4?int - self.duration = duration # int - self.performer = performer # flags.5?string - self.title = title # flags.6?string + self.file_id = file_id + self.thumb = thumb + self.file_name = file_name + self.mime_type = mime_type + self.file_size = file_size + self.date = date + self.duration = duration + self.performer = performer + self.title = title diff --git a/pyrogram/client/types/contact.py b/pyrogram/client/types/messages_and_media/contact.py similarity index 72% rename from pyrogram/client/types/contact.py rename to pyrogram/client/types/messages_and_media/contact.py index f625120c..a7be558e 100644 --- a/pyrogram/client/types/contact.py +++ b/pyrogram/client/types/messages_and_media/contact.py @@ -34,12 +34,23 @@ class Contact(Object): user_id (``int``, *optional*): Contact's user identifier in Telegram. + + vcard (``str``, *optional*): + Additional data about the contact in the form of a vCard """ ID = 0xb0700011 - def __init__(self, phone_number: str, first_name: str, last_name: str = None, user_id=None): - self.phone_number = phone_number # string - self.first_name = first_name # string - self.last_name = last_name # flags.0?string - self.user_id = user_id # flags.1?int + def __init__( + self, + phone_number: str, + first_name: str, + last_name: str = None, + user_id: int = None, + vcard: str = None + ): + self.phone_number = phone_number + self.first_name = first_name + self.last_name = last_name + self.user_id = user_id + self.vcard = vcard diff --git a/pyrogram/client/types/document.py b/pyrogram/client/types/messages_and_media/document.py similarity index 84% rename from pyrogram/client/types/document.py rename to pyrogram/client/types/messages_and_media/document.py index 076c1934..d87fa666 100644 --- a/pyrogram/client/types/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -20,7 +20,7 @@ from pyrogram.api.core import Object class Document(Object): - """This object represents a general file (as opposed to photos, voice messages and audio files). + """This object represents a general file (as opposed to photos, voice messages, audio files, ...). Args: file_id (``str``): @@ -53,9 +53,9 @@ class Document(Object): file_size: int = None, date: int = None ): - self.file_id = file_id # string - self.thumb = thumb # flags.0?PhotoSize - self.file_name = file_name # flags.1?string - self.mime_type = mime_type # flags.2?string - self.file_size = file_size # flags.3?int - self.date = date # flags.3?int + self.file_id = file_id + self.thumb = thumb + self.file_name = file_name + self.mime_type = mime_type + self.file_size = file_size + self.date = date diff --git a/pyrogram/client/types/location.py b/pyrogram/client/types/messages_and_media/location.py similarity index 93% rename from pyrogram/client/types/location.py rename to pyrogram/client/types/messages_and_media/location.py index 1f5feb00..be8c839f 100644 --- a/pyrogram/client/types/location.py +++ b/pyrogram/client/types/messages_and_media/location.py @@ -33,5 +33,5 @@ class Location(Object): ID = 0xb0700012 def __init__(self, longitude: float, latitude: float): - self.longitude = longitude # double - self.latitude = latitude # double + self.longitude = longitude + self.latitude = latitude diff --git a/pyrogram/client/types/message.py b/pyrogram/client/types/messages_and_media/message.py similarity index 93% rename from pyrogram/client/types/message.py rename to pyrogram/client/types/messages_and_media/message.py index b969865c..3065cf9f 100644 --- a/pyrogram/client/types/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . from pyrogram.api.core import Object -from .reply_markup import InlineKeyboardMarkup, ReplyKeyboardMarkup +from ..bots import InlineKeyboardMarkup, ReplyKeyboardMarkup class Message(Object): @@ -26,9 +26,6 @@ class Message(Object): Args: message_id (``int``): Unique message identifier inside this chat. - - client (:obj:`Client `, *optional*): - The client instance this message is bound to. date (``int``, *optional*): Date the message was sent in Unix time. @@ -89,14 +86,14 @@ class Message(Object): game (:obj:`Game `, *optional*): Message is a game, information about the game. More about games. - photo (List of :obj:`PhotoSize `, *optional*): - Message is a photo, available sizes of the photo. + photo (:obj:`Photo `, *optional*): + Message is a photo, information about the photo. sticker (:obj:`Sticker `, *optional*): Message is a sticker, information about the sticker. - gif (:obj:`Video `, *optional*): - Message is a GIF, information about the GIF. + animation (:obj:`Animation `, *optional*): + Message is an animation, information about the animation. video (:obj:`Video `, *optional*): Message is a video, information about the video. @@ -132,7 +129,7 @@ class Message(Object): new_chat_title (``str``, *optional*): A chat title was changed to this value. - new_chat_photo (List of :obj:`PhotoSize `, *optional*): + new_chat_photo (:obj:`Photo `, *optional*): A chat photo was change to this value. delete_chat_photo (``bool``, *optional*): @@ -231,7 +228,7 @@ class Message(Object): game=None, photo=None, sticker=None, - gif=None, + animation=None, video=None, voice=None, video_note=None, @@ -282,7 +279,7 @@ class Message(Object): self.game = game # flags.15?Game self.photo = photo # flags.16?Vector self.sticker = sticker # flags.17?Sticker - self.gif = gif + self.animation = animation self.video = video # flags.18?Video self.voice = voice # flags.19?Voice self.video_note = video_note # flags.20?VideoNote @@ -407,7 +404,6 @@ class Message(Object): 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. @@ -574,3 +570,39 @@ class Message(Object): raise ValueError("This button is not supported yet") else: raise ValueError("The message doesn't contain any keyboard") + + def download(self, file_name: str = "", block: bool = True): + """Use this method as a shortcut for: + + .. code-block:: python + + client.download_media(message) + + Example: + .. code-block:: python + + message.download() + + Args: + file_name (``str``, *optional*): + A custom *file_name* to be used instead of the one provided by Telegram. + By default, all files are downloaded in the *downloads* folder in your working directory. + You can also specify a path for downloading files in a custom location: paths that end with "/" + are considered directories. All non-existent folders will be created automatically. + + block (``bool``, *optional*): + Blocks the code execution until the file has been downloaded. + Defaults to True. + + Returns: + On success, the absolute path of the downloaded file as string is returned, None otherwise. + + Raises: + :class:`Error ` + ``ValueError``: If the message doesn't contain any downloadable media + """ + return self._client.download_media( + message=self, + file_name=file_name, + block=block + ) diff --git a/pyrogram/client/types/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py similarity index 71% rename from pyrogram/client/types/message_entity.py rename to pyrogram/client/types/messages_and_media/message_entity.py index 460da6e7..f8f41734 100644 --- a/pyrogram/client/types/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -26,9 +26,9 @@ class MessageEntity(Object): Args: type (``str``): Type of the entity. - Can be mention (@username), hashtag, bot_command, url, email, bold (bold text), italic (italic text), - code (monowidth string), pre (monowidth block), text_link (for clickable text URLs), - text_mention (for users without usernames). + Can be "mention" (@username), "hashtag", "cashtag", "bot_command", "url", "email", "phone_number", "bold" + (bold text), italic (italic text), "code" (monowidth string), "pre" (monowidth block), "text_link" + (for clickable text URLs), "text_mention" (for users without usernames). offset (``int``): Offset in UTF-16 code units to the start of the entity. @@ -45,9 +45,16 @@ class MessageEntity(Object): ID = 0xb0700004 - def __init__(self, type: str, offset: int, length: int, url: str = None, user=None): - self.type = type # string - self.offset = offset # int - self.length = length # int - self.url = url # flags.0?string - self.user = user # flags.1?User + def __init__( + self, + type: str, + offset: int, + length: int, + url: str = None, + user=None + ): + self.type = type + self.offset = offset + self.length = length + self.url = url + self.user = user diff --git a/pyrogram/client/types/messages.py b/pyrogram/client/types/messages_and_media/messages.py similarity index 91% rename from pyrogram/client/types/messages.py rename to pyrogram/client/types/messages_and_media/messages.py index 0d5d0a36..7a2546a9 100644 --- a/pyrogram/client/types/messages.py +++ b/pyrogram/client/types/messages_and_media/messages.py @@ -33,5 +33,5 @@ class Messages(Object): ID = 0xb0700026 def __init__(self, total_count: int, messages: list): - self.total_count = total_count # int - self.messages = messages # Vector> + self.total_count = total_count + self.messages = messages diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py new file mode 100644 index 00000000..d9deed76 --- /dev/null +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -0,0 +1,41 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from pyrogram.api.core import Object + + +class Photo(Object): + """This object represents a Photo + + Args: + id (``str``): + Unique identifier for this photo. + + date (``int``): + Date the photo was sent in Unix time. + + sizes (List of :obj:`PhotoSize `): + Available sizes of this photo. + """ + + ID = 0xb0700027 + + def __init__(self, id: str, date: int, sizes: list): + self.id = id + self.date = date + self.sizes = sizes diff --git a/pyrogram/client/types/photo_size.py b/pyrogram/client/types/messages_and_media/photo_size.py similarity index 70% rename from pyrogram/client/types/photo_size.py rename to pyrogram/client/types/messages_and_media/photo_size.py index 7e9b3cba..764b046b 100644 --- a/pyrogram/client/types/photo_size.py +++ b/pyrogram/client/types/messages_and_media/photo_size.py @@ -20,7 +20,7 @@ from pyrogram.api.core import Object class PhotoSize(Object): - """This object represents one size of a photo or a file / sticker thumbnail. + """This object represents one size of a photo or a file/sticker thumbnail. Args: file_id (``str``): @@ -32,18 +32,14 @@ class PhotoSize(Object): height (``int``): Photo height. - file_size (``int``, *optional*): + file_size (``int``): File size. - - date (``int``, *optional*): - Date the photo was sent in Unix time """ ID = 0xb0700005 - def __init__(self, file_id, width, height, file_size=None, date=None): - self.file_id = file_id # string - self.width = width # int - self.height = height # int - self.file_size = file_size # flags.0?int - self.date = date + def __init__(self, file_id: str, width: int, height: int, file_size: int): + self.file_id = file_id + self.width = width + self.height = height + self.file_size = file_size diff --git a/pyrogram/client/types/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py similarity index 81% rename from pyrogram/client/types/sticker.py rename to pyrogram/client/types/messages_and_media/sticker.py index a5ed9085..2e7ac901 100644 --- a/pyrogram/client/types/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -73,14 +73,14 @@ class Sticker(Object): set_name: str = None, mask_position=None ): - self.file_id = file_id # string - self.thumb = thumb # flags.0?PhotoSize - self.file_name = file_name # flags.1?string - self.mime_type = mime_type # flags.2?string - self.file_size = file_size # flags.3?int - self.date = date # flags.4?int - self.width = width # int - self.height = height # int - self.emoji = emoji # flags.5?string - self.set_name = set_name # flags.6?string - self.mask_position = mask_position # flags.7?MaskPosition + self.file_id = file_id + self.thumb = thumb + self.file_name = file_name + self.mime_type = mime_type + self.file_size = file_size + self.date = date + self.width = width + self.height = height + self.emoji = emoji + self.set_name = set_name + self.mask_position = mask_position diff --git a/pyrogram/client/types/user_profile_photos.py b/pyrogram/client/types/messages_and_media/user_profile_photos.py similarity index 82% rename from pyrogram/client/types/user_profile_photos.py rename to pyrogram/client/types/messages_and_media/user_profile_photos.py index 1b7ecbb4..c8ca9e39 100644 --- a/pyrogram/client/types/user_profile_photos.py +++ b/pyrogram/client/types/messages_and_media/user_profile_photos.py @@ -26,12 +26,12 @@ class UserProfilePhotos(Object): total_count (``int``): Total number of profile pictures the target user has. - photos (List of List of :obj:`PhotoSize `): - Requested profile pictures (in up to 4 sizes each). + photos (List of :obj:`Photo `): + Requested profile pictures. """ ID = 0xb0700014 def __init__(self, total_count: int, photos: list): - self.total_count = total_count # int - self.photos = photos # Vector> + self.total_count = total_count + self.photos = photos diff --git a/pyrogram/client/types/venue.py b/pyrogram/client/types/messages_and_media/venue.py similarity index 68% rename from pyrogram/client/types/venue.py rename to pyrogram/client/types/messages_and_media/venue.py index 653f0dd3..3c5b2b05 100644 --- a/pyrogram/client/types/venue.py +++ b/pyrogram/client/types/messages_and_media/venue.py @@ -35,12 +35,24 @@ class Venue(Object): foursquare_id (``str``, *optional*): Foursquare identifier of the venue. + foursquare_type (``str``, *optional*): + Foursquare type of the venue. + (For example, "arts_entertainment/default", "arts_entertainment/aquarium" or "food/icecream".) + """ ID = 0xb0700013 - def __init__(self, location, title: str, address: str, foursquare_id: str = None): - self.location = location # Location - self.title = title # string - self.address = address # string - self.foursquare_id = foursquare_id # flags.0?string + def __init__( + self, + location, + title: str, + address: str, + foursquare_id: str = None, + foursquare_type: str = None + ): + self.location = location + self.title = title + self.address = address + self.foursquare_id = foursquare_id + self.foursquare_type = foursquare_type diff --git a/pyrogram/client/types/video.py b/pyrogram/client/types/messages_and_media/video.py similarity index 83% rename from pyrogram/client/types/video.py rename to pyrogram/client/types/messages_and_media/video.py index b4ffff2b..8b272a2d 100644 --- a/pyrogram/client/types/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -65,12 +65,12 @@ class Video(Object): file_size: int = None, date: int = None ): - self.file_id = file_id # string - self.thumb = thumb # flags.0?PhotoSize - self.file_name = file_name # flags.1?string - self.mime_type = mime_type # flags.2?string - self.file_size = file_size # flags.3?int - self.date = date # flags.4?int - self.width = width # int - self.height = height # int - self.duration = duration # int + self.file_id = file_id + self.thumb = thumb + self.file_name = file_name + self.mime_type = mime_type + self.file_size = file_size + self.date = date + self.width = width + self.height = height + self.duration = duration diff --git a/pyrogram/client/types/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py similarity index 79% rename from pyrogram/client/types/video_note.py rename to pyrogram/client/types/messages_and_media/video_note.py index 7f0b6736..642eb5aa 100644 --- a/pyrogram/client/types/video_note.py +++ b/pyrogram/client/types/messages_and_media/video_note.py @@ -35,9 +35,6 @@ class VideoNote(Object): thumb (:obj:`PhotoSize `, *optional*): Video thumbnail. - file_name (``str``, *optional*): - Video note file name. - mime_type (``str``, *optional*): MIME type of the file as defined by sender. @@ -56,16 +53,14 @@ class VideoNote(Object): length: int, duration: int, thumb=None, - file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None ): - self.file_id = file_id # string - self.thumb = thumb # flags.0?PhotoSize - self.file_name = file_name # flags.1?string - self.mime_type = mime_type # flags.2?string - self.file_size = file_size # flags.3?int - self.date = date # flags.4?int - self.length = length # int - self.duration = duration # int + self.file_id = file_id + self.thumb = thumb + self.mime_type = mime_type + self.file_size = file_size + self.date = date + self.length = length + self.duration = duration diff --git a/pyrogram/client/types/voice.py b/pyrogram/client/types/messages_and_media/voice.py similarity index 73% rename from pyrogram/client/types/voice.py rename to pyrogram/client/types/messages_and_media/voice.py index 414d2267..d838cf59 100644 --- a/pyrogram/client/types/voice.py +++ b/pyrogram/client/types/messages_and_media/voice.py @@ -29,11 +29,8 @@ class Voice(Object): duration (``int``): Duration of the audio in seconds as defined by sender. - thumb (:obj:`PhotoSize `, *optional*): - Voice thumbnail. - - file_name (``str``, *optional*): - Voice file name. + waveform (``bytes``, *optional*): + Voice waveform. mime_type (``str``, *optional*): MIME type of the file as defined by sender. @@ -51,15 +48,13 @@ class Voice(Object): self, file_id: str, duration: int, - thumb=None, - file_name: str = None, + waveform: bytes = None, mime_type: str = None, file_size: int = None, date: int = None): - self.file_id = file_id # string - self.thumb = thumb # flags.0?PhotoSize - self.file_name = file_name # flags.1?string - self.mime_type = mime_type # flags.2?string - self.file_size = file_size # flags.3?int - self.date = date # flags.4?int - self.duration = duration # int + self.file_id = file_id + self.duration = duration + self.waveform = waveform + self.mime_type = mime_type + self.file_size = file_size + self.date = date diff --git a/pyrogram/client/types/user_and_chats/__init__.py b/pyrogram/client/types/user_and_chats/__init__.py new file mode 100644 index 00000000..45915edc --- /dev/null +++ b/pyrogram/client/types/user_and_chats/__init__.py @@ -0,0 +1,25 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from .chat import Chat +from .chat_member import ChatMember +from .chat_members import ChatMembers +from .chat_photo import ChatPhoto +from .dialog import Dialog +from .dialogs import Dialogs +from .user import User diff --git a/pyrogram/client/types/chat.py b/pyrogram/client/types/user_and_chats/chat.py similarity index 72% rename from pyrogram/client/types/chat.py rename to pyrogram/client/types/user_and_chats/chat.py index 1455bd0f..afed1ca4 100644 --- a/pyrogram/client/types/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -24,15 +24,13 @@ class Chat(Object): Args: id (``int``): - Unique identifier for this chat. This number may be greater than 32 bits and some programming - languages may have difficulty/silent defects in interpreting it. But it is smaller than 52 bits, - so a signed 64 bit integer or double-precision float type are safe for storing this identifier. + Unique identifier for this chat. type (``str``): Type of chat, can be either "private", "group", "supergroup" or "channel". title (``str``, *optional*): - Title, for supergroups, channels and group chats. + Title, for supergroups, channels and basic group chats. username (``str``, *optional*): Username, for private chats, supergroups and channels if available. @@ -44,25 +42,33 @@ class Chat(Object): Last name of the other party in a private chat. all_members_are_administrators (``bool``, *optional*): - True if a group has 'All Members Are Admins' enabled. + True if a basic group has "All Members Are Admins" enabled. photo (:obj:`ChatPhoto `, *optional*): - Chat photo. Returned only in getChat. + Chat photo. Suitable for downloads only. description (``str``, *optional*): - Description, for supergroups and channel chats. Returned only in getChat. + Description, for supergroups and channel chats. + Returned only in :meth:`get_chat() `. invite_link (``str``, *optional*): - Chat invite link, for supergroups and channel chats. Returned only in getChat. + Chat invite link, for supergroups and channel chats. + Returned only in :meth:`get_chat() `. pinned_message (:obj:`Message `, *optional*): - Pinned message, for supergroups and channel chats. Returned only in getChat. + Pinned message, for supergroups and channel chats. + Returned only in :meth:`get_chat() `. sticker_set_name (``str``, *optional*): - For supergroups, name of group sticker set. Returned only in getChat. + For supergroups, name of group sticker set. + Returned only in :meth:`get_chat() `. can_set_sticker_set (``bool``, *optional*): - True, if the bot can change the group sticker set. Returned only in getChat. + True, if the group sticker set can be changed by you. + Returned only in :meth:`get_chat() `. + + members_count (``int``, *optional*): + Chat members count, for groups and channels only. """ ID = 0xb0700002 @@ -81,7 +87,8 @@ class Chat(Object): invite_link: str = None, pinned_message=None, sticker_set_name: str = None, - can_set_sticker_set: bool = None + can_set_sticker_set: bool = None, + members_count: int = None ): self.id = id self.type = type @@ -96,3 +103,4 @@ class Chat(Object): self.pinned_message = pinned_message self.sticker_set_name = sticker_set_name self.can_set_sticker_set = can_set_sticker_set + self.members_count = members_count diff --git a/pyrogram/client/types/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py similarity index 100% rename from pyrogram/client/types/chat_member.py rename to pyrogram/client/types/user_and_chats/chat_member.py diff --git a/pyrogram/client/types/user_and_chats/chat_members.py b/pyrogram/client/types/user_and_chats/chat_members.py new file mode 100644 index 00000000..622f8abc --- /dev/null +++ b/pyrogram/client/types/user_and_chats/chat_members.py @@ -0,0 +1,37 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from pyrogram.api.core import Object + + +class ChatMembers(Object): + """This object contains information about the members list of a chat. + + Args: + total_count (``int``): + Total number of members the chat has. + + chat_members (List of :obj:`ChatMember `): + Requested chat members. + """ + + ID = 0xb0700030 + + def __init__(self, total_count: int, chat_members: list): + self.total_count = total_count + self.chat_members = chat_members diff --git a/pyrogram/client/types/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py similarity index 100% rename from pyrogram/client/types/chat_photo.py rename to pyrogram/client/types/user_and_chats/chat_photo.py diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py new file mode 100644 index 00000000..fa5fb2b8 --- /dev/null +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -0,0 +1,58 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from pyrogram.api.core import Object + + +class Dialog(Object): + """This object represents a dialog + + Args: + chat (:obj:`Chat `): + Conversation the dialog belongs to. + + top_message (:obj:`Message `): + The last message sent in the dialog at this time. + + unread_messages_count (``int``): + Amount of unread messages in this dialogs. + + unread_mentions_count (``int``): + Amount of unread messages containing a mention in this dialog. + + unread_mark (``bool``): + True, if the dialog has the unread mark set. + + is_pinned (``bool``): + True, if the dialog is pinned. + """ + ID = 0xb0700028 + + def __init__(self, + chat, + top_message, + unread_messages_count: int, + unread_mentions_count: int, + unread_mark: bool, + is_pinned: bool): + self.chat = chat + self.top_message = top_message + self.unread_messages_count = unread_messages_count + self.unread_mentions_count = unread_mentions_count + self.unread_mark = unread_mark + self.is_pinned = is_pinned diff --git a/pyrogram/client/types/user_and_chats/dialogs.py b/pyrogram/client/types/user_and_chats/dialogs.py new file mode 100644 index 00000000..cdf1d951 --- /dev/null +++ b/pyrogram/client/types/user_and_chats/dialogs.py @@ -0,0 +1,36 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# 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 . + +from pyrogram.api.core import Object + + +class Dialogs(Object): + """This object represents a user's dialogs chunk + + Args: + total_count (``int``): + Total number of dialogs the user has. + + dialogs (List of :obj:`Dialog `): + Requested dialogs. + """ + ID = 0xb0700029 + + def __init__(self, total_count: int, dialogs: list): + self.total_count = total_count + self.dialogs = dialogs diff --git a/pyrogram/client/types/user.py b/pyrogram/client/types/user_and_chats/user.py similarity index 74% rename from pyrogram/client/types/user.py rename to pyrogram/client/types/user_and_chats/user.py index 62e79e19..9ae5dab2 100644 --- a/pyrogram/client/types/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -26,6 +26,18 @@ class User(Object): id (``int``): Unique identifier for this user or bot. + is_self(``bool``): + True, if this user is you yourself. + + is_contact(``bool``): + True, if this user is in your contacts. + + is_mutual_contact(``bool``): + True, if you both have each other's contact. + + is_deleted(``bool``): + True, if this user is deleted. + is_bot (``bool``): True, if this user is a bot. @@ -42,10 +54,10 @@ class User(Object): IETF language tag of the user's language. phone_number (``str``, *optional*): - User's or bot's phone number. + User's phone number. photo (:obj:`ChatPhoto `, *optional*): - User's or bot's current profile photo. + User's or bot's current profile photo. Suitable for downloads only. """ ID = 0xb0700001 @@ -53,6 +65,10 @@ class User(Object): def __init__( self, id: int, + is_self: bool, + is_contact: bool, + is_mutual_contact: bool, + is_deleted: bool, is_bot: bool, first_name: str, last_name: str = None, @@ -62,6 +78,10 @@ class User(Object): photo=None ): self.id = id + self.is_self = is_self + self.is_contact = is_contact + self.is_mutual_contact = is_mutual_contact + self.is_deleted = is_deleted self.is_bot = is_bot self.first_name = first_name self.last_name = last_name diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index a53295ce..8020bc71 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -21,6 +21,7 @@ import threading import time from .transport import * +from ..session.internals import DataCenter log = logging.getLogger(__name__) @@ -36,26 +37,36 @@ class Connection: 4: TCPIntermediateO } - def __init__(self, address: tuple, proxy: dict, mode: int = 1): - self.address = address + def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, mode: int = 1): + self.dc_id = dc_id + self.ipv6 = ipv6 self.proxy = proxy + self.address = DataCenter(dc_id, test_mode, ipv6) self.mode = self.MODES.get(mode, TCPAbridged) + self.lock = threading.Lock() self.connection = None def connect(self): for i in range(Connection.MAX_RETRIES): - self.connection = self.mode(self.proxy) + self.connection = self.mode(self.ipv6, self.proxy) try: log.info("Connecting...") self.connection.connect(self.address) - except OSError: + except OSError as e: + log.warning(e) # TODO: Remove self.connection.close() time.sleep(1) else: + log.info("Connected! DC{} - IPv{} - {}".format( + self.dc_id, + "6" if self.ipv6 else "4", + self.mode.__name__ + )) break else: + log.warning("Connection failed! Trying again...") raise TimeoutError def close(self): diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 5df8aacb..8560604f 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -33,8 +33,9 @@ log = logging.getLogger(__name__) class TCP(socks.socksocket): - def __init__(self, proxy: dict): - super().__init__() + def __init__(self, ipv6: bool, proxy: dict): + super().__init__(family=socket.AF_INET6 if ipv6 else socket.AF_INET) + self.settimeout(10) self.proxy_enabled = proxy.get("enabled", False) diff --git a/pyrogram/connection/transport/tcp/tcp_abridged.py b/pyrogram/connection/transport/tcp/tcp_abridged.py index 472f4799..5566b179 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged.py @@ -24,15 +24,13 @@ log = logging.getLogger(__name__) class TCPAbridged(TCP): - def __init__(self, proxy: dict): - super().__init__(proxy) + def __init__(self, ipv6: bool, proxy: dict): + super().__init__(ipv6, proxy) def connect(self, address: tuple): super().connect(address) 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 diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py index bba88e34..91ee8375 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py @@ -28,8 +28,9 @@ 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) + def __init__(self, ipv6: bool, proxy: dict): + super().__init__(ipv6, proxy) + self.encrypt = None self.decrypt = None @@ -54,8 +55,6 @@ class TCPAbridgedO(TCP): super().sendall(nonce) - log.info("Connected{}!".format(" with proxy" if self.proxy_enabled else "")) - def sendall(self, data: bytes, *args): length = len(data) // 4 diff --git a/pyrogram/connection/transport/tcp/tcp_full.py b/pyrogram/connection/transport/tcp/tcp_full.py index 1b131678..8704247b 100644 --- a/pyrogram/connection/transport/tcp/tcp_full.py +++ b/pyrogram/connection/transport/tcp/tcp_full.py @@ -26,14 +26,14 @@ log = logging.getLogger(__name__) class TCPFull(TCP): - def __init__(self, proxy: dict): - super().__init__(proxy) + def __init__(self, ipv6: bool, proxy: dict): + super().__init__(ipv6, proxy) + self.seq_no = None def connect(self, address: tuple): super().connect(address) self.seq_no = 0 - log.info("Connected{}!".format(" with proxy" if self.proxy_enabled else "")) def sendall(self, data: bytes, *args): # 12 = packet_length (4), seq_no (4), crc32 (4) (at the end) diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate.py b/pyrogram/connection/transport/tcp/tcp_intermediate.py index 4b2e2596..aa198db7 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate.py @@ -25,15 +25,13 @@ log = logging.getLogger(__name__) class TCPIntermediate(TCP): - def __init__(self, proxy: dict): - super().__init__(proxy) + def __init__(self, ipv6: bool, proxy: dict): + super().__init__(ipv6, proxy) def connect(self, address: tuple): super().connect(address) super().sendall(b"\xee" * 4) - log.info("Connected{}!".format(" with proxy" if self.proxy_enabled else "")) - def sendall(self, data: bytes, *args): super().sendall(pack(" bytes: - return tgcrypto.ige_encrypt(data, key, iv) + return tgcrypto.ige256_encrypt(data, key, iv) @classmethod def ige256_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes: - return tgcrypto.ige_decrypt(data, key, iv) + return tgcrypto.ige256_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)) + return tgcrypto.ctr256_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)) + return tgcrypto.ctr256_decrypt(data, key, iv, state or bytearray(1)) @staticmethod def xor(a: bytes, b: bytes) -> bytes: diff --git a/pyrogram/crypto/prime.py b/pyrogram/crypto/prime.py index 9338c206..8e9426ca 100644 --- a/pyrogram/crypto/prime.py +++ b/pyrogram/crypto/prime.py @@ -20,6 +20,18 @@ from random import randint class Prime: + CURRENT_DH_PRIME = int( + "C71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F" + "48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C37" + "20FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F64" + "2477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4" + "A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754" + "FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4" + "E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F" + "0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5B", + 16 + ) + # Recursive variant # @classmethod # def gcd(cls, a: int, b: int) -> int: diff --git a/pyrogram/crypto/rsa.py b/pyrogram/crypto/rsa.py index 9f02e2cc..10302dab 100644 --- a/pyrogram/crypto/rsa.py +++ b/pyrogram/crypto/rsa.py @@ -206,12 +206,8 @@ class RSA: @classmethod def encrypt(cls, data: bytes, fingerprint: int) -> bytes: - return int.to_bytes( - pow( - int.from_bytes(data, "big"), - cls.server_public_keys[fingerprint].e, - cls.server_public_keys[fingerprint].m - ), - 256, - "big" - ) + return pow( + int.from_bytes(data, "big"), + cls.server_public_keys[fingerprint].e, + cls.server_public_keys[fingerprint].m + ).to_bytes(256, "big") diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index 80956187..87817da1 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -26,7 +26,7 @@ from pyrogram.api import functions, types from pyrogram.api.core import Object, Long, Int from pyrogram.connection import Connection from pyrogram.crypto import AES, RSA, Prime -from .internals import MsgId, DataCenter +from .internals import MsgId log = logging.getLogger(__name__) @@ -34,21 +34,10 @@ log = logging.getLogger(__name__) class Auth: MAX_RETRIES = 5 - CURRENT_DH_PRIME = int( - "C71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F" - "48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C37" - "20FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F64" - "2477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4" - "A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754" - "FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4" - "E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F" - "0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5B", - 16 - ) - - def __init__(self, dc_id: int, test_mode: bool, proxy: dict): + def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict): self.dc_id = dc_id self.test_mode = test_mode + self.ipv6 = ipv6 self.proxy = proxy self.connection = None @@ -84,7 +73,7 @@ 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) + self.connection = Connection(self.dc_id, self.test_mode, self.ipv6, self.proxy) try: log.info("Start creating a new auth key on DC{}".format(self.dc_id)) @@ -122,8 +111,8 @@ class Auth: data = types.PQInnerData( res_pq.pq, - int.to_bytes(p, 4, "big"), - int.to_bytes(q, 4, "big"), + p.to_bytes(4, "big"), + q.to_bytes(4, "big"), nonce, server_nonce, new_nonce, @@ -142,8 +131,8 @@ class Auth: functions.ReqDHParams( nonce, server_nonce, - int.to_bytes(p, 4, "big"), - int.to_bytes(q, 4, "big"), + p.to_bytes(4, "big"), + q.to_bytes(4, "big"), public_key_fingerprint, encrypted_data ) @@ -151,8 +140,8 @@ class Auth: encrypted_answer = server_dh_params.encrypted_answer - server_nonce = int.to_bytes(server_nonce, 16, "little", signed=True) - new_nonce = int.to_bytes(new_nonce, 32, "little", signed=True) + server_nonce = server_nonce.to_bytes(16, "little", signed=True) + new_nonce = new_nonce.to_bytes(32, "little", signed=True) tmp_aes_key = ( sha1(new_nonce + server_nonce).digest() @@ -181,7 +170,7 @@ class Auth: # Step 6 g = server_dh_inner_data.g b = int.from_bytes(urandom(256), "big") - g_b = int.to_bytes(pow(g, b, dh_prime), 256, "big") + g_b = pow(g, b, dh_prime).to_bytes(256, "big") retry_id = 0 @@ -210,8 +199,8 @@ class Auth: # Step 7; Step 8 g_a = int.from_bytes(server_dh_inner_data.g_a, "big") - auth_key = int.to_bytes(pow(g_a, b, dh_prime), 256, "big") - server_nonce = int.to_bytes(server_nonce, 16, "little", signed=True) + auth_key = pow(g_a, b, dh_prime).to_bytes(256, "big") + server_nonce = server_nonce.to_bytes(16, "little", signed=True) # TODO: Handle errors @@ -219,7 +208,7 @@ class Auth: # Security checks ####################### - assert dh_prime == self.CURRENT_DH_PRIME + assert dh_prime == Prime.CURRENT_DH_PRIME log.debug("DH parameters check: OK") # https://core.telegram.org/mtproto/security_guidelines#g-a-and-g-b-validation @@ -246,7 +235,7 @@ class Auth: # 3rd message assert nonce == set_client_dh_params_answer.nonce assert server_nonce == set_client_dh_params_answer.server_nonce - server_nonce = int.to_bytes(server_nonce, 16, "little", signed=True) + server_nonce = server_nonce.to_bytes(16, "little", signed=True) log.debug("Nonce fields check: OK") # Step 9 diff --git a/pyrogram/session/internals/data_center.py b/pyrogram/session/internals/data_center.py index 232ca13b..d36e0613 100644 --- a/pyrogram/session/internals/data_center.py +++ b/pyrogram/session/internals/data_center.py @@ -34,5 +34,32 @@ class DataCenter: 121: "95.213.217.195" } - def __new__(cls, dc_id: int, test_mode: bool): - return (cls.TEST[dc_id], 80) if test_mode else (cls.PROD[dc_id], 443) + TEST_IPV6 = { + 1: "2001:b28:f23d:f001::e", + 2: "2001:67c:4e8:f002::e", + 3: "2001:b28:f23d:f003::e", + 121: "2a03:b0c0:3:d0::114:d001" + } + + PROD_IPV6 = { + 1: "2001:b28:f23d:f001::a", + 2: "2001:67c:4e8:f002::a", + 3: "2001:b28:f23d:f003::a", + 4: "2001:67c:4e8:f004::a", + 5: "2001:b28:f23f:f005::a", + 121: "2a03:b0c0:3:d0::114:d001" + } + + def __new__(cls, dc_id: int, test_mode: bool, ipv6: bool): + if ipv6: + return ( + (cls.TEST_IPV6[dc_id], 80) + if test_mode + else (cls.PROD_IPV6[dc_id], 443) + ) + else: + return ( + (cls.TEST[dc_id], 80) + if test_mode + else (cls.PROD[dc_id], 443) + ) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index ef7b565c..0d513430 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -31,10 +31,10 @@ from pyrogram import __copyright__, __license__, __version__ from pyrogram.api import functions, types, core from pyrogram.api.all import layer from pyrogram.api.core import Message, Object, MsgContainer, Long, FutureSalt, Int -from pyrogram.api.errors import Error, InternalServerError +from pyrogram.api.errors import Error, InternalServerError, AuthKeyDuplicated from pyrogram.connection import Connection from pyrogram.crypto import AES, KDF -from .internals import MsgId, MsgFactory, DataCenter +from .internals import MsgId, MsgFactory log = logging.getLogger(__name__) @@ -112,7 +112,7 @@ class Session: def start(self): while True: - self.connection = Connection(DataCenter(self.dc_id, self.client.test_mode), self.client.proxy) + self.connection = Connection(self.dc_id, self.client.test_mode, self.client.ipv6, self.client.proxy) try: self.connection.connect() @@ -156,7 +156,13 @@ class Session: self.ping_thread = Thread(target=self.ping, name="PingThread") self.ping_thread.start() - log.info("Connection inited: Layer {}".format(layer)) + log.info("Session initialized: Layer {}".format(layer)) + log.info("Device: {} - {}".format(self.client.device_model, self.client.app_version)) + log.info("System: {} ({})".format(self.client.system_version, self.client.lang_code.upper())) + + except AuthKeyDuplicated as e: + self.stop() + raise e except (OSError, TimeoutError, Error): self.stop() except Exception as e: @@ -193,6 +199,7 @@ class Session: i.join() self.net_worker_list.clear() + self.recv_queue.queue.clear() for i in self.results.values(): i.event.set() diff --git a/setup.py b/setup.py index 00f9be63..10789e98 100644 --- a/setup.py +++ b/setup.py @@ -16,10 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import os import re +import shutil from sys import argv -from setuptools import setup, find_packages +from setuptools import setup, find_packages, Command from compiler.api import compiler as api_compiler from compiler.docs import compiler as docs_compiler @@ -31,24 +33,109 @@ def read(file: str) -> list: return [i.strip() for i in r] -if len(argv) > 1 and argv[1] != "sdist": - api_compiler.start() - docs_compiler.start() +def get_version(): + with open("pyrogram/__init__.py", encoding="utf-8") as f: + return re.findall(r"__version__ = \"(.+)\"", f.read())[0] + + +def get_readme(): + # PyPI doesn"t like raw html + with open("README.rst", encoding="utf-8") as f: + readme = re.sub(r"\.\. \|.+\| raw:: html(?:\s{4}.+)+\n\n", "", f.read()) + return re.sub(r"\|header\|", "|logo|\n\n|description|\n\n|scheme| |tgcrypto|", readme) + + +class Clean(Command): + DIST = ["./build", "./dist", "./Pyrogram.egg-info"] + API = ["pyrogram/api/errors/exceptions", "pyrogram/api/functions", "pyrogram/api/types", "pyrogram/api/all.py"] + DOCS = ["docs/source/functions", "docs/source/types", "docs/build"] + ALL = DIST + API + DOCS + + description = "Clean generated files" + + user_options = [ + ("dist", None, "Clean distribution files"), + ("api", None, "Clean generated API files"), + ("docs", None, "Clean generated docs files"), + ("all", None, "Clean all generated files"), + ] + + def __init__(self, dist, **kw): + super().__init__(dist, **kw) + + self.dist = None + self.api = None + self.docs = None + self.all = None + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + paths = set() + + if self.dist: + paths.update(Clean.DIST) + + if self.api: + paths.update(Clean.API) + + if self.docs: + paths.update(Clean.DOCS) + + if self.all or not paths: + paths.update(Clean.ALL) + + for path in sorted(list(paths)): + try: + shutil.rmtree(path) if os.path.isdir(path) else os.remove(path) + except OSError: + print("skipping {}".format(path)) + else: + print("removing {}".format(path)) + + +class Generate(Command): + description = "Generate Pyrogram files" + + user_options = [ + ("api", None, "Generate API files"), + ("docs", None, "Generate docs files") + ] + + def __init__(self, dist, **kw): + super().__init__(dist, **kw) + + self.api = None + self.docs = None + + def initialize_options(self): + pass + + def finalize_options(self): + pass + + def run(self): + if self.api: + error_compiler.start() + api_compiler.start() + + if self.docs: + docs_compiler.start() + + +if len(argv) > 1 and argv[1] in ["bdist_wheel", "install"]: error_compiler.start() - -with open("pyrogram/__init__.py", encoding="utf-8") as f: - version = re.findall(r"__version__ = \"(.+)\"", f.read())[0] - -# PyPI doesn't like raw html -with open("README.rst", encoding="utf-8") as f: - readme = re.sub(r"\.\. \|.+\| raw:: html(?:\s{4}.+)+\n\n", "", f.read()) - readme = re.sub(r"\|header\|", "|logo|\n\n|description|\n\n|scheme| |tgcrypto|", readme) + api_compiler.start() setup( name="Pyrogram", - version=version, + version=get_version(), description="Telegram MTProto API Client Library for Python", - long_description=readme, + long_description=get_readme(), url="https://github.com/pyrogram", download_url="https://github.com/pyrogram/pyrogram/releases/latest", author="Dan Tès", @@ -85,5 +172,9 @@ setup( packages=find_packages(exclude=["compiler*"]), zip_safe=False, install_requires=read("requirements.txt"), - extras_require={"tgcrypto": ["tgcrypto>=1.0.4"]} + extras_require={"tgcrypto": ["tgcrypto==1.1.1"]}, + cmdclass={ + "clean": Clean, + "generate": Generate + } )