diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 7fd74edb3d..e4f1ac0e9c 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -36,15 +36,15 @@ inputFile#f52ff27f id:long parts:int name:string md5_checksum:string = InputFile inputFileBig#fa4f0bb5 id:long parts:int name:string = InputFile; inputMediaEmpty#9664f57f = InputMedia; -inputMediaUploadedPhoto#1e287d04 flags:# spoiler:flags.2?true file:InputFile stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; -inputMediaPhoto#b3ba0635 flags:# spoiler:flags.1?true id:InputPhoto ttl_seconds:flags.0?int = 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#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia; -inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true force_file:flags.4?true spoiler:flags.5?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; -inputMediaDocument#33473058 flags:# spoiler:flags.2?true id:InputDocument ttl_seconds:flags.0?int query:flags.1?string = InputMedia; +inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true force_file:flags.4?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; +inputMediaDocument#33473058 flags:# id:InputDocument ttl_seconds:flags.0?int query:flags.1?string = InputMedia; inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia; -inputMediaPhotoExternal#e5bbfe1a flags:# spoiler:flags.1?true url:string ttl_seconds:flags.0?int = InputMedia; -inputMediaDocumentExternal#fb52dc99 flags:# spoiler:flags.1?true url:string ttl_seconds:flags.0?int = InputMedia; +inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia; +inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaGame#d33f43f3 id:InputGame = InputMedia; inputMediaInvoice#8eb5a6d5 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:flags.1?string extended_media:flags.2?InputMedia = InputMedia; inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia; @@ -52,7 +52,7 @@ inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector s inputMediaDice#e66fbf7b emoticon:string = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; -inputChatUploadedPhoto#bdcdaec0 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.3?VideoSize = InputChatPhoto; +inputChatUploadedPhoto#c642724e flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = InputChatPhoto; inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto; inputGeoPointEmpty#e4c123d6 = InputGeoPoint; @@ -88,10 +88,10 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#d3bc4b7a id:long = User; -user#8f97c628 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector = User; +user#8f97c628 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; -userProfilePhoto#82d1f706 flags:# has_video:flags.0?true personal:flags.2?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; +userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; userStatusEmpty#9d05049 = UserStatus; userStatusOnline#edb93949 expires:int = UserStatus; @@ -106,8 +106,8 @@ chatForbidden#6592a1a7 id:long title:string = Chat; channel#83259464 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; -chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull; -channelFull#f2355507 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions = ChatFull; +chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull; +channelFull#f2355507 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -124,11 +124,11 @@ message#38116ee0 flags:# out:flags.1?true mentioned:flags.4?true media_unread:fl messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; -messageMediaPhoto#695150d7 flags:# spoiler:flags.3?true photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; +messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; messageMediaContact#70322949 phone_number:string first_name:string last_name:string vcard:string user_id:long = MessageMedia; messageMediaUnsupported#9f84f49e = MessageMedia; -messageMediaDocument#9cb070d7 flags:# nopremium:flags.3?true spoiler:flags.4?true document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia; +messageMediaDocument#9cb070d7 flags:# nopremium:flags.3?true document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia; messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia; messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia; messageMediaGame#fdb19008 game:Game = MessageMedia; @@ -156,7 +156,7 @@ messageActionPaymentSent#96163f56 flags:# recurring_init:flags.2?true recurring_ messageActionPhoneCall#80e11a7f flags:# video:flags.2?true call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction; messageActionScreenshotTaken#4792929b = MessageAction; messageActionCustomAction#fae69f56 message:string = MessageAction; -messageActionBotAllowed#c516d679 flags:# attach_menu:flags.1?true domain:flags.0?string app:flags.2?BotApp = MessageAction; +messageActionBotAllowed#abe9affe domain:string = MessageAction; messageActionSecureValuesSentMe#1b287353 values:Vector credentials:SecureCredentialsEncrypted = MessageAction; messageActionSecureValuesSent#d95c6154 types:Vector = MessageAction; messageActionContactSignUp#f3f25f76 = MessageAction; @@ -169,13 +169,9 @@ messageActionSetChatTheme#aa786345 emoticon:string = MessageAction; messageActionChatJoinedByRequest#ebbca3cb = MessageAction; messageActionWebViewDataSentMe#47dd8079 text:string data:string = MessageAction; messageActionWebViewDataSent#b4c38cb5 text:string = MessageAction; -messageActionGiftPremium#c83d6aec flags:# currency:string amount:long months:int crypto_currency:flags.0?string crypto_amount:flags.0?long = MessageAction; +messageActionGiftPremium#aba0f5c6 currency:string amount:long months:int = MessageAction; messageActionTopicCreate#d999256 flags:# title:string icon_color:int icon_emoji_id:flags.0?long = MessageAction; messageActionTopicEdit#c0944820 flags:# title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool hidden:flags.3?Bool = MessageAction; -messageActionSuggestProfilePhoto#57de635e photo:Photo = MessageAction; -messageActionRequestedPeer#fe77345d button_id:int peer:Peer = MessageAction; -messageActionSetChatWallPaper#bc44a927 wallpaper:WallPaper = MessageAction; -messageActionSetSameChatWallPaper#c0787d6d wallpaper:WallPaper = MessageAction; dialog#d58a08c6 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 unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -194,9 +190,8 @@ geoPointEmpty#1117dd5f = GeoPoint; geoPoint#b2a2f663 flags:# long:double lat:double access_hash:long accuracy_radius:flags.0?int = GeoPoint; auth.sentCode#5e002502 flags:# type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode; -auth.sentCodeSuccess#2390fe44 authorization:auth.Authorization = auth.SentCode; -auth.authorization#2ea2c0d4 flags:# setup_password_required:flags.1?true otherwise_relogin_days:flags.1?int tmp_sessions:flags.0?int future_auth_token:flags.2?bytes user:User = auth.Authorization; +auth.authorization#33fb7bb8 flags:# setup_password_required:flags.1?true otherwise_relogin_days:flags.1?int tmp_sessions:flags.0?int user:User = auth.Authorization; auth.authorizationSignUpRequired#44747e9a flags:# terms_of_service:flags.0?help.TermsOfService = auth.Authorization; auth.exportedAuthorization#b434e2b8 id:long bytes:bytes = auth.ExportedAuthorization; @@ -227,7 +222,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason; inputReportReasonIllegalDrugs#a8eb2be = ReportReason; inputReportReasonPersonalDetails#9ec7863d = ReportReason; -userFull#93eadb53 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper = UserFull; +userFull#c4b1fc3f flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true id:long about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -285,6 +280,7 @@ updateChatUserTyping#83487af0 chat_id:long from_id:Peer action:SendMessageAction updateChatParticipants#7761198 participants:ChatParticipants = Update; updateUserStatus#e5bdf8de user_id:long status:UserStatus = Update; updateUserName#a7848924 user_id:long first_name:string last_name:string usernames:Vector = Update; +updateUserPhoto#f227868c user_id:long date:int photo:UserProfilePhoto previous:Bool = Update; updateNewEncryptedMessage#12bcbd9a message:EncryptedMessage qts:int = Update; updateEncryptedChatTyping#1710f156 chat_id:int = Update; updateEncryption#b4a2e88d chat:EncryptedChat date:int = Update; @@ -365,7 +361,7 @@ updateGroupCallParticipants#f2ebdb4e call:InputGroupCall participants:Vector = Update; @@ -385,9 +381,6 @@ updateMoveStickerSetToTop#86fccf85 flags:# masks:flags.0?true emojis:flags.1?tru updateMessageExtendedMedia#5a73a98c peer:Peer msg_id:int extended_media:MessageExtendedMedia = Update; updateChannelPinnedTopic#192efbe3 flags:# pinned:flags.0?true channel_id:long topic_id:int = Update; updateChannelPinnedTopics#fe198602 flags:# channel_id:long order:flags.0?Vector = Update; -updateUser#20529438 user_id:long = Update; -updateAutoSaveSettings#ec05b097 = Update; -updateGroupInvitePrivacyForbidden#ccf08ad6 user_id:long = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -414,7 +407,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 this_port_only:flags.5?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption; -config#cc1a241e flags:# default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true force_try_ipv6:flags.14?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 edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int channels_read_media_period:int tmp_sessions:flags.0?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 base_lang_pack_version:flags.2?int reactions_default:flags.15?Reaction autologin_token:flags.16?string = Config; +config#232566ac 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 pfs_enabled:flags.13?true force_try_ipv6:flags.14?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 pinned_infolder_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 base_lang_pack_version:flags.2?int reactions_default:flags.15?Reaction = Config; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; @@ -532,7 +525,7 @@ documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true supports_strea documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute; documentAttributeFilename#15590068 file_name:string = DocumentAttribute; documentAttributeHasStickers#9801d2f7 = DocumentAttribute; -documentAttributeCustomEmoji#fd149899 flags:# free:flags.0?true text_color:flags.1?true alt:string stickerset:InputStickerSet = DocumentAttribute; +documentAttributeCustomEmoji#fd149899 flags:# free:flags.0?true alt:string stickerset:InputStickerSet = DocumentAttribute; messages.stickersNotModified#f1749a22 = messages.Stickers; messages.stickers#30a6ec7e hash:long stickers:Vector = messages.Stickers; @@ -595,7 +588,7 @@ keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton; keyboardButtonCallback#35bbdb6b flags:# requires_password:flags.0?true text:string data:bytes = KeyboardButton; keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton; keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton; -keyboardButtonSwitchInline#93b9fbb5 flags:# same_peer:flags.0?true text:string query:string peer_types:flags.1?Vector = KeyboardButton; +keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton; keyboardButtonGame#50f41ccf text:string = KeyboardButton; keyboardButtonBuy#afd93fbb text:string = KeyboardButton; keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton; @@ -605,13 +598,12 @@ inputKeyboardButtonUserProfile#e988037b text:string user_id:InputUser = Keyboard keyboardButtonUserProfile#308660c1 text:string user_id:long = KeyboardButton; keyboardButtonWebView#13767230 text:string url:string = KeyboardButton; keyboardButtonSimpleWebView#a0c0505c text:string url:string = KeyboardButton; -keyboardButtonRequestPeer#d0b468c text:string button_id:int peer_type:RequestPeerType = KeyboardButton; keyboardButtonRow#77608b83 buttons:Vector = KeyboardButtonRow; replyKeyboardHide#a03e5b85 flags:# selective:flags.2?true = ReplyMarkup; replyKeyboardForceReply#86b40b08 flags:# single_use:flags.1?true selective:flags.2?true placeholder:flags.3?string = ReplyMarkup; -replyKeyboardMarkup#85dd99d1 flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true persistent:flags.4?true rows:Vector placeholder:flags.3?string = ReplyMarkup; +replyKeyboardMarkup#85dd99d1 flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector placeholder:flags.3?string = ReplyMarkup; replyInlineMarkup#48a30254 rows:Vector = ReplyMarkup; messageEntityUnknown#bb92ba95 offset:int length:int = MessageEntity; @@ -700,7 +692,7 @@ botInlineMessageMediaInvoice#354a9b09 flags:# shipping_address_requested:flags.1 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; -messages.botResults#e021f2f6 flags:# gallery:flags.0?true query_id:long next_offset:flags.1?string switch_pm:flags.2?InlineBotSwitchPM switch_webview:flags.3?InlineBotWebView results:Vector cache_time:int users:Vector = messages.BotResults; +messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_offset:flags.1?string switch_pm:flags.2?InlineBotSwitchPM results:Vector cache_time:int users:Vector = messages.BotResults; exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink; @@ -717,10 +709,9 @@ auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType; auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType; auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType; auth.sentCodeTypeMissedCall#82006484 prefix:string length:int = auth.SentCodeType; -auth.sentCodeTypeEmailCode#f450f59b flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true email_pattern:string length:int reset_available_period:flags.3?int reset_pending_date:flags.4?int = auth.SentCodeType; +auth.sentCodeTypeEmailCode#5a159841 flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true email_pattern:string length:int next_phone_login_date:flags.2?int = auth.SentCodeType; auth.sentCodeTypeSetUpEmailRequired#a5491dea flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true = auth.SentCodeType; auth.sentCodeTypeFragmentSms#d9565c39 url:string length:int = auth.SentCodeType; -auth.sentCodeTypeFirebaseSms#e57b1432 flags:# nonce:flags.0?bytes receipt:flags.1?string push_timeout:flags.1?int length:int = auth.SentCodeType; messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer; @@ -767,7 +758,6 @@ messages.stickerSetInstallResultArchive#35e410a8 sets:Vector stickerSetCovered#6410a5d2 set:StickerSet cover:Document = StickerSetCovered; stickerSetMultiCovered#3407e51b set:StickerSet covers:Vector = StickerSetCovered; stickerSetFullCovered#40d13c0e set:StickerSet packs:Vector keywords:Vector documents:Vector = StickerSetCovered; -stickerSetNoCovered#77b15d1c set:StickerSet = StickerSetCovered; maskCoords#aed6dbb2 n:int x:double y:double zoom:double = MaskCoords; @@ -880,7 +870,7 @@ account.tmpPassword#db64fd34 tmp_password:bytes valid_until:int = account.TmpPas shippingOption#b6213cdf id:string title:string prices:Vector = ShippingOption; -inputStickerSetItem#32da9e9c flags:# document:InputDocument emoji:string mask_coords:flags.0?MaskCoords keywords:flags.1?string = InputStickerSetItem; +inputStickerSetItem#ffa0a496 flags:# document:InputDocument emoji:string mask_coords:flags.0?MaskCoords = InputStickerSetItem; inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall; @@ -939,7 +929,7 @@ channelAdminLogEventActionDiscardGroupCall#db9f9140 call:InputGroupCall = Channe channelAdminLogEventActionParticipantMute#f92424d2 participant:GroupCallParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantUnmute#e64429c0 participant:GroupCallParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionToggleGroupCallSetting#56d6a247 join_muted:Bool = ChannelAdminLogEventAction; -channelAdminLogEventActionParticipantJoinByInvite#fe9fc158 flags:# via_chatlist:flags.0?true invite:ExportedChatInvite = ChannelAdminLogEventAction; +channelAdminLogEventActionParticipantJoinByInvite#5cdada77 invite:ExportedChatInvite = ChannelAdminLogEventAction; channelAdminLogEventActionExportedInviteDelete#5a50fca4 invite:ExportedChatInvite = ChannelAdminLogEventAction; channelAdminLogEventActionExportedInviteRevoke#410a134e invite:ExportedChatInvite = ChannelAdminLogEventAction; channelAdminLogEventActionExportedInviteEdit#e90ebb59 prev_invite:ExportedChatInvite new_invite:ExportedChatInvite = ChannelAdminLogEventAction; @@ -1121,7 +1111,7 @@ statsURL#47a971e0 url:string = StatsURL; chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true other:flags.12?true manage_topics:flags.13?true = ChatAdminRights; -chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true manage_topics:flags.18?true send_photos:flags.19?true send_videos:flags.20?true send_roundvideos:flags.21?true send_audios:flags.22?true send_voices:flags.23?true send_docs:flags.24?true send_plain:flags.25?true until_date:int = ChatBannedRights; +chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true manage_topics:flags.18?true until_date:int = ChatBannedRights; inputWallPaper#e630b979 id:long access_hash:long = InputWallPaper; inputWallPaperSlug#72091c80 slug:string = InputWallPaper; @@ -1130,7 +1120,7 @@ inputWallPaperNoFile#967a462e id:long = InputWallPaper; account.wallPapersNotModified#1c199183 = account.WallPapers; account.wallPapers#cdc3858c hash:long wallpapers:Vector = account.WallPapers; -codeSettings#ad253d78 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true allow_missed_call:flags.5?true allow_firebase:flags.7?true logout_tokens:flags.6?Vector token:flags.8?string app_sandbox:flags.8?Bool = CodeSettings; +codeSettings#8a6469c2 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true allow_missed_call:flags.5?true logout_tokens:flags.6?Vector = CodeSettings; wallPaperSettings#1dc1bca4 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int third_background_color:flags.5?int fourth_background_color:flags.6?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings; @@ -1207,7 +1197,6 @@ payments.bankCardData#3e24e573 title:string open_urls:Vector = dialogFilter#7438f7e8 flags:# contacts:flags.0?true non_contacts:flags.1?true groups:flags.2?true broadcasts:flags.3?true bots:flags.4?true exclude_muted:flags.11?true exclude_read:flags.12?true exclude_archived:flags.13?true id:int title:string emoticon:flags.25?string pinned_peers:Vector include_peers:Vector exclude_peers:Vector = DialogFilter; dialogFilterDefault#363293ae = DialogFilter; -dialogFilterChatlist#d64a04a8 flags:# has_my_invites:flags.26?true id:int title:string emoticon:flags.25?string pinned_peers:Vector include_peers:Vector = DialogFilter; dialogFilterSuggested#77744d4a filter:DialogFilter description:string = DialogFilterSuggested; @@ -1229,8 +1218,6 @@ help.promoDataEmpty#98f6ac75 expires:int = help.PromoData; help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector users:Vector psa_type:flags.1?string psa_message:flags.2?string = help.PromoData; videoSize#de33b094 flags:# type:string w:int h:int size:int video_start_ts:flags.0?double = VideoSize; -videoSizeEmojiMarkup#f85c413c emoji_id:long background_colors:Vector = VideoSize; -videoSizeStickerMarkup#da082fe stickerset:InputStickerSet sticker_id:long background_colors:Vector = VideoSize; statsGroupTopPoster#9d04af9b user_id:long messages:int avg_chars:int = StatsGroupTopPoster; @@ -1279,7 +1266,6 @@ inlineQueryPeerTypePM#833c0fac = InlineQueryPeerType; inlineQueryPeerTypeChat#d766c50a = InlineQueryPeerType; inlineQueryPeerTypeMegagroup#5ec4be43 = InlineQueryPeerType; inlineQueryPeerTypeBroadcast#6334ee9a = InlineQueryPeerType; -inlineQueryPeerTypeBotPM#e3b2d0c = InlineQueryPeerType; messages.historyImport#1662af0b id:long = messages.HistoryImport; @@ -1287,7 +1273,7 @@ messages.historyImportParsed#5e0fb7b9 flags:# pm:flags.0?true group:flags.1?true messages.affectedFoundMessages#ef8d3e6c pts:int pts_count:int offset:int messages:Vector = messages.AffectedFoundMessages; -chatInviteImporter#8c5adfd9 flags:# requested:flags.0?true via_chatlist:flags.3?true user_id:long date:int about:flags.2?string approved_by:flags.1?long = ChatInviteImporter; +chatInviteImporter#8c5adfd9 flags:# requested:flags.0?true user_id:long date:int about:flags.2?string approved_by:flags.1?long = ChatInviteImporter; messages.exportedChatInvites#bdc62dcc count:int invites:Vector users:Vector = messages.ExportedChatInvites; @@ -1324,7 +1310,7 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult; account.resetPasswordOk#e926d63e = account.ResetPasswordResult; -sponsoredMessage#fc25b828 flags:# recommended:flags.5?true show_peer_photo:flags.6?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector sponsor_info:flags.7?string additional_info:flags.8?string = SponsoredMessage; +sponsoredMessage#3a836df8 flags:# recommended:flags.5?true show_peer_photo:flags.6?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector = SponsoredMessage; messages.sponsoredMessages#c9ee1d87 flags:# posts_between:flags.0?int messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; messages.sponsoredMessagesEmpty#1839490f = messages.SponsoredMessages; @@ -1356,7 +1342,10 @@ availableReaction#c077ec01 flags:# inactive:flags.0?true premium:flags.2?true re messages.availableReactionsNotModified#9f071957 = messages.AvailableReactions; messages.availableReactions#768e3aad hash:int reactions:Vector = messages.AvailableReactions; -messagePeerReaction#8c79b63c flags:# big:flags.0?true unread:flags.1?true peer_id:Peer date:int reaction:Reaction = MessagePeerReaction; +messages.translateNoResult#67ca4737 = messages.TranslatedText; +messages.translateResultText#a214f7d0 text:string = messages.TranslatedText; + +messagePeerReaction#b156fe9c flags:# big:flags.0?true unread:flags.1?true peer_id:Peer reaction:Reaction = MessagePeerReaction; groupCallStreamChannel#80eb48af channel:int scale:int last_timestamp_ms:long = GroupCallStreamChannel; @@ -1368,7 +1357,7 @@ attachMenuBotIconColor#4576f3f0 name:string color:int = AttachMenuBotIconColor; attachMenuBotIcon#b2a7386b flags:# name:string icon:Document colors:flags.0?Vector = AttachMenuBotIcon; -attachMenuBot#c8aa2cd2 flags:# inactive:flags.0?true has_settings:flags.1?true request_write_access:flags.2?true bot_id:long short_name:string peer_types:Vector icons:Vector = AttachMenuBot; +attachMenuBot#c8aa2cd2 flags:# inactive:flags.0?true has_settings:flags.1?true bot_id:long short_name:string peer_types:Vector icons:Vector = AttachMenuBot; attachMenuBotsNotModified#f1d88a5c = AttachMenuBots; attachMenuBots#3c4301c0 hash:long bots:Vector users:Vector = AttachMenuBots; @@ -1411,7 +1400,7 @@ messages.transcribedAudio#93752c52 flags:# pending:flags.0?true transcription_id help.premiumPromo#5334759c status_text:string status_entities:Vector video_sections:Vector videos:Vector period_options:Vector users:Vector = help.PremiumPromo; -inputStorePaymentPremiumSubscription#a6751e66 flags:# restore:flags.0?true upgrade:flags.1?true = InputStorePaymentPurpose; +inputStorePaymentPremiumSubscription#a6751e66 flags:# restore:flags.0?true = InputStorePaymentPurpose; inputStorePaymentGiftPremium#616f7fe8 user_id:InputUser currency:string amount:long = InputStorePaymentPurpose; premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption; @@ -1447,7 +1436,7 @@ emailVerificationApple#96d074fd token:string = EmailVerification; account.emailVerified#2b96cd1b email:string = account.EmailVerified; account.emailVerifiedLogin#e1bb0d61 email:string sent_code:auth.SentCode = account.EmailVerified; -premiumSubscriptionOption#5f2d1df2 flags:# current:flags.1?true can_purchase_upgrade:flags.2?true transaction:flags.3?string months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumSubscriptionOption; +premiumSubscriptionOption#b6f11ebe flags:# current:flags.1?true can_purchase_upgrade:flags.2?true months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumSubscriptionOption; sendAsPeer#b81c7034 flags:# premium_required:flags.0?true peer:Peer = SendAsPeer; @@ -1467,60 +1456,6 @@ defaultHistoryTTL#43b46b20 period:int = DefaultHistoryTTL; exportedContactToken#41bf109b url:string expires:int = ExportedContactToken; -requestPeerTypeUser#5f3b8a00 flags:# bot:flags.0?Bool premium:flags.1?Bool = RequestPeerType; -requestPeerTypeChat#c9f06e1b flags:# creator:flags.0?true bot_participant:flags.5?true has_username:flags.3?Bool forum:flags.4?Bool user_admin_rights:flags.1?ChatAdminRights bot_admin_rights:flags.2?ChatAdminRights = RequestPeerType; -requestPeerTypeBroadcast#339bef6c flags:# creator:flags.0?true has_username:flags.3?Bool user_admin_rights:flags.1?ChatAdminRights bot_admin_rights:flags.2?ChatAdminRights = RequestPeerType; - -emojiListNotModified#481eadfa = EmojiList; -emojiList#7a1e11d1 hash:long document_id:Vector = EmojiList; - -emojiGroup#7a9abda9 title:string icon_emoji_id:long emoticons:Vector = EmojiGroup; - -messages.emojiGroupsNotModified#6fb4ad87 = messages.EmojiGroups; -messages.emojiGroups#881fb94b hash:int groups:Vector = messages.EmojiGroups; - -textWithEntities#751f3146 text:string entities:Vector = TextWithEntities; - -messages.translateResult#33db32f8 result:Vector = messages.TranslatedText; - -autoSaveSettings#c84834ce flags:# photos:flags.0?true videos:flags.1?true video_max_size:flags.2?long = AutoSaveSettings; - -autoSaveException#81602d47 peer:Peer settings:AutoSaveSettings = AutoSaveException; - -account.autoSaveSettings#4c3e069d users_settings:AutoSaveSettings chats_settings:AutoSaveSettings broadcasts_settings:AutoSaveSettings exceptions:Vector chats:Vector users:Vector = account.AutoSaveSettings; - -help.appConfigNotModified#7cde641d = help.AppConfig; -help.appConfig#dd18782e hash:int config:JSONValue = help.AppConfig; - -inputBotAppID#a920bd7a id:long access_hash:long = InputBotApp; -inputBotAppShortName#908c0407 bot_id:InputUser short_name:string = InputBotApp; - -botAppNotModified#5da674b7 = BotApp; -botApp#95fcd1d6 flags:# id:long access_hash:long short_name:string title:string description:string photo:Photo document:flags.0?Document hash:long = BotApp; - -messages.botApp#eb50adf5 flags:# inactive:flags.0?true request_write_access:flags.1?true app:BotApp = messages.BotApp; - -appWebViewResultUrl#3c1b4f0d url:string = AppWebViewResult; - -inlineBotWebView#b57295d5 text:string url:string = InlineBotWebView; - -readParticipantDate#4a4ff172 user_id:long date:int = ReadParticipantDate; - -inputChatlistDialogFilter#f3e0da33 filter_id:int = InputChatlist; - -exportedChatlistInvite#c5181ac flags:# title:string url:string peers:Vector = ExportedChatlistInvite; - -chatlists.exportedChatlistInvite#10e6e3a6 filter:DialogFilter invite:ExportedChatlistInvite = chatlists.ExportedChatlistInvite; - -chatlists.exportedInvites#10ab6dc7 invites:Vector chats:Vector users:Vector = chatlists.ExportedInvites; - -chatlists.chatlistInviteAlready#fa87f659 filter_id:int missing_peers:Vector already_peers:Vector chats:Vector users:Vector = chatlists.ChatlistInvite; -chatlists.chatlistInvite#1dcd839d flags:# title:string emoticon:flags.0?string peers:Vector chats:Vector users:Vector = chatlists.ChatlistInvite; - -chatlists.chatlistUpdates#93bd878d missing_peers:Vector chats:Vector users:Vector = chatlists.ChatlistUpdates; - -bots.botInfo#e8a775b0 name:string about:string description:string = bots.BotInfo; - ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1551,8 +1486,6 @@ auth.importLoginToken#95ac5ce4 token:bytes = auth.LoginToken; auth.acceptLoginToken#e894ad4d token:bytes = Authorization; auth.checkRecoveryPassword#d36bf79 code:string = Bool; auth.importWebTokenAuthorization#2db873a9 api_id:int api_hash:string web_auth_token:string = auth.Authorization; -auth.requestFirebaseSms#89464b50 flags:# phone_number:string phone_code_hash:string safety_net_token:flags.0?string ios_push_secret:flags.1?string = Bool; -auth.resetLoginEmail#7e960193 phone_number:string phone_code_hash:string = auth.SentCode; account.registerDevice#ec86017a flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector = Bool; account.unregisterDevice#6a0d3206 token_type:int token:string other_uids:Vector = Bool; @@ -1603,7 +1536,7 @@ account.getContactSignUpNotification#9f07c728 = Bool; account.setContactSignUpNotification#cff43f61 silent:Bool = Bool; account.getNotifyExceptions#53577479 flags:# compare_sound:flags.1?true peer:flags.0?InputNotifyPeer = Updates; account.getWallPaper#fc8ddbea wallpaper:InputWallPaper = WallPaper; -account.uploadWallPaper#e39a8f03 flags:# for_chat:flags.0?true file:InputFile mime_type:string settings:WallPaperSettings = WallPaper; +account.uploadWallPaper#dd853661 file:InputFile mime_type:string settings:WallPaperSettings = WallPaper; account.saveWallPaper#6c5a5b37 wallpaper:InputWallPaper unsave:Bool settings:WallPaperSettings = Bool; account.installWallPaper#feed5769 wallpaper:InputWallPaper settings:WallPaperSettings = Bool; account.resetWallPapers#bb3b9804 = Bool; @@ -1636,11 +1569,6 @@ account.getRecentEmojiStatuses#f578105 hash:long = account.EmojiStatuses; account.clearRecentEmojiStatuses#18201aae = Bool; account.reorderUsernames#ef500eab order:Vector = Bool; account.toggleUsername#58d6b376 username:string active:Bool = Bool; -account.getDefaultProfilePhotoEmojis#e2750328 hash:long = EmojiList; -account.getDefaultGroupPhotoEmojis#915860ae hash:long = EmojiList; -account.getAutoSaveSettings#adcbbcda = account.AutoSaveSettings; -account.saveAutoSaveSettings#d69b8361 flags:# users:flags.0?true chats:flags.1?true broadcasts:flags.2?true peer:flags.3?InputPeer settings:AutoSaveSettings = Bool; -account.deleteAutoSaveExceptions#53bc0020 = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; @@ -1723,7 +1651,7 @@ messages.getDocumentByHash#b1f2061f sha256:bytes size:long mime_type:string = Do messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; -messages.setInlineBotResults#bb12a419 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM switch_webview:flags.4?InlineBotWebView = Bool; +messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool; messages.sendInlineBotResult#d3fbdccb flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int = Updates; @@ -1812,7 +1740,7 @@ messages.getChatInviteImporters#df04dd4e flags:# requested:flags.0?true peer:Inp messages.setHistoryTTL#b80e5fe4 peer:InputPeer period:int = Updates; messages.checkHistoryImportPeer#5dc60f03 peer:InputPeer = messages.CheckedHistoryImportPeer; messages.setChatTheme#e63be13f peer:InputPeer emoticon:string = Updates; -messages.getMessageReadParticipants#31c1c44f peer:InputPeer msg_id:int = Vector; +messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector; messages.getSearchResultsCalendar#49f0bde9 peer:InputPeer filter:MessagesFilter offset_id:int offset_date:int = messages.SearchResultsCalendar; messages.getSearchResultsPositions#6e9583a3 peer:InputPeer filter:MessagesFilter offset_id:int limit:int = messages.SearchResultsPositions; messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPeer user_id:InputUser = Updates; @@ -1825,16 +1753,16 @@ messages.getMessageReactionsList#461b3f48 flags:# peer:InputPeer id:int reaction messages.setChatAvailableReactions#feb16771 peer:InputPeer available_reactions:ChatReactions = Updates; messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions; messages.setDefaultReaction#4f47a016 reaction:Reaction = Bool; -messages.translateText#63183030 flags:# peer:flags.0?InputPeer id:flags.0?Vector text:flags.1?Vector to_lang:string = messages.TranslatedText; +messages.translateText#24ce6dee flags:# peer:flags.0?InputPeer msg_id:flags.0?int text:flags.1?string from_lang:flags.2?string to_lang:string = messages.TranslatedText; messages.getUnreadReactions#3223495b flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; messages.readReactions#54aa7f8e flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory; messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = messages.Messages; messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots; messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot; -messages.toggleBotInAttachMenu#69f59d69 flags:# write_allowed:flags.0?true bot:InputUser enabled:Bool = Bool; +messages.toggleBotInAttachMenu#1aee33af bot:InputUser enabled:Bool = Bool; messages.requestWebView#178b480b flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON platform:string reply_to_msg_id:flags.0?int top_msg_id:flags.9?int send_as:flags.13?InputPeer = WebViewResult; messages.prolongWebView#7ff34309 flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int top_msg_id:flags.9?int send_as:flags.13?InputPeer = Bool; -messages.requestSimpleWebView#299bec8e flags:# from_switch_webview:flags.1?true bot:InputUser url:string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult; +messages.requestSimpleWebView#299bec8e flags:# bot:InputUser url:string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult; messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent; messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates; messages.transcribeAudio#269e9a49 peer:InputPeer msg_id:int = messages.TranscribedAudio; @@ -1849,25 +1777,15 @@ messages.clearRecentReactions#9dfeefb4 = Bool; messages.getExtendedMedia#84f80814 peer:InputPeer id:Vector = Updates; messages.setDefaultHistoryTTL#9eb51445 period:int = Bool; messages.getDefaultHistoryTTL#658b7188 = DefaultHistoryTTL; -messages.sendBotRequestedPeer#fe38d01b peer:InputPeer msg_id:int button_id:int requested_peer:InputPeer = Updates; -messages.getEmojiGroups#7488ce5b hash:int = messages.EmojiGroups; -messages.getEmojiStatusGroups#2ecd56cd hash:int = messages.EmojiGroups; -messages.getEmojiProfilePhotoGroups#21a548f3 hash:int = messages.EmojiGroups; -messages.searchCustomEmoji#2c11c0d7 emoticon:string hash:long = EmojiList; -messages.togglePeerTranslations#e47cb579 flags:# disabled:flags.0?true peer:InputPeer = Bool; -messages.getBotApp#34fdc5c3 app:InputBotApp hash:long = messages.BotApp; -messages.requestAppWebView#8c5a3b3c flags:# write_allowed:flags.0?true peer:InputPeer app:InputBotApp start_param:flags.1?string theme_params:flags.2?DataJSON platform:string = AppWebViewResult; -messages.setChatWallPaper#8ffacae1 flags:# peer:InputPeer wallpaper:flags.0?InputWallPaper settings:flags.2?WallPaperSettings id:flags.1?int = Updates; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; -photos.updateProfilePhoto#9e82039 flags:# fallback:flags.0?true bot:flags.1?InputUser id:InputPhoto = photos.Photo; -photos.uploadProfilePhoto#388a3b5 flags:# fallback:flags.3?true bot:flags.5?InputUser file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.4?VideoSize = photos.Photo; +photos.updateProfilePhoto#72d4742c id:InputPhoto = photos.Photo; +photos.uploadProfilePhoto#89f30f69 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo; photos.deletePhotos#87cf7f2f id:Vector = Vector; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos; -photos.uploadContactProfilePhoto#e14c4a71 flags:# suggest:flags.3?true save:flags.4?true user_id:InputUser file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.5?VideoSize = photos.Photo; upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool; upload.getFile#be5335be flags:# precise:flags.0?true cdn_supported:flags.1?true location:InputFileLocation offset:long limit:int = upload.File; @@ -1890,7 +1808,7 @@ help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls; help.getTermsOfServiceUpdate#2ca51fd1 = help.TermsOfServiceUpdate; help.acceptTermsOfService#ee72f79a id:DataJSON = Bool; help.getDeepLinkInfo#3fedc75f path:string = help.DeepLinkInfo; -help.getAppConfig#61e3f854 hash:int = help.AppConfig; +help.getAppConfig#98914110 = JSONValue; help.saveAppLog#6f02f748 events:Vector = Bool; help.getPassportConfig#c661ad08 hash:int = help.PassportConfig; help.getSupportName#d360e72c = help.SupportName; @@ -1910,7 +1828,7 @@ channels.getParticipants#77ced9d0 channel:InputChannel filter:ChannelParticipant channels.getParticipant#a0ab6cc6 channel:InputChannel participant:InputPeer = channels.ChannelParticipant; channels.getChannels#a7f6bbb id:Vector = messages.Chats; channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull; -channels.createChannel#91006707 flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true forum:flags.5?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string ttl_period:flags.4?int = Updates; +channels.createChannel#91006707 flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string ttl_period:flags.4?int = Updates; channels.editAdmin#d33c8902 channel:InputChannel user_id:InputUser admin_rights:ChatAdminRights rank:string = Updates; channels.editTitle#566decd0 channel:InputChannel title:string = Updates; channels.editPhoto#f12e57c9 channel:InputChannel photo:InputChatPhoto = Updates; @@ -1956,7 +1874,6 @@ channels.deleteTopicHistory#34435f2d channel:InputChannel top_msg_id:int = messa channels.reorderPinnedForumTopics#2950a18f flags:# force:flags.0?true channel:InputChannel order:Vector = Updates; channels.toggleAntiSpam#68f3e4eb channel:InputChannel enabled:Bool = Updates; channels.reportAntiSpamFalsePositive#a850a693 channel:InputChannel msg_id:int = Bool; -channels.toggleParticipantsHidden#6a6e7854 channel:InputChannel enabled:Bool = Updates; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -1967,10 +1884,6 @@ bots.setBotMenuButton#4504d54f user_id:InputUser button:BotMenuButton = Bool; bots.getBotMenuButton#9c60eb28 user_id:InputUser = BotMenuButton; bots.setBotBroadcastDefaultAdminRights#788464e1 admin_rights:ChatAdminRights = Bool; bots.setBotGroupDefaultAdminRights#925ec9ea admin_rights:ChatAdminRights = Bool; -bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:flags.3?string about:flags.0?string description:flags.1?string = Bool; -bots.getBotInfo#dcd914fd flags:# bot:flags.0?InputUser lang_code:string = bots.BotInfo; -bots.reorderUsernames#9709b1c2 bot:InputUser order:Vector = Bool; -bots.toggleUsername#53ca973 bot:InputUser username:string active:Bool = Bool; payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm; payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt; @@ -1984,16 +1897,13 @@ payments.assignAppStoreTransaction#80ed747d receipt:bytes purpose:InputStorePaym payments.assignPlayMarketTransaction#dffd50d3 receipt:DataJSON purpose:InputStorePaymentPurpose = Updates; payments.canPurchasePremium#9fc19eb6 purpose:InputStorePaymentPurpose = Bool; -stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; +stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet; stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet; -stickers.setStickerSetThumb#a76a5392 flags:# stickerset:InputStickerSet thumb:flags.0?InputDocument thumb_document_id:flags.1?long = messages.StickerSet; +stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet; stickers.checkShortName#284b3639 short_name:string = Bool; stickers.suggestShortName#4dafc503 title:string = stickers.SuggestedShortName; -stickers.changeSticker#f5537ebc flags:# sticker:InputDocument emoji:flags.0?string mask_coords:flags.1?MaskCoords keywords:flags.2?string = messages.StickerSet; -stickers.renameStickerSet#124b1c00 stickerset:InputStickerSet title:string = messages.StickerSet; -stickers.deleteStickerSet#87704394 stickerset:InputStickerSet = Bool; phone.getCallConfig#55451fa9 = DataJSON; phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall; @@ -2034,6 +1944,7 @@ langpack.getLanguages#42c6978f lang_pack:string = Vector; langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLanguage; folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; +folders.deleteFolder#1c295881 folder_id:int = Updates; stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; @@ -2041,16 +1952,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -chatlists.exportChatlistInvite#8472478e chatlist:InputChatlist title:string peers:Vector = chatlists.ExportedChatlistInvite; -chatlists.deleteExportedInvite#719c5c5e chatlist:InputChatlist slug:string = Bool; -chatlists.editExportedInvite#653db63d flags:# chatlist:InputChatlist slug:string title:flags.1?string peers:flags.2?Vector = ExportedChatlistInvite; -chatlists.getExportedInvites#ce03da83 chatlist:InputChatlist = chatlists.ExportedInvites; -chatlists.checkChatlistInvite#41c10fff slug:string = chatlists.ChatlistInvite; -chatlists.joinChatlistInvite#a6b1e39a slug:string peers:Vector = Updates; -chatlists.getChatlistUpdates#89419521 chatlist:InputChatlist = chatlists.ChatlistUpdates; -chatlists.joinChatlistUpdates#e089f8f5 chatlist:InputChatlist peers:Vector = Updates; -chatlists.hideChatlistUpdates#66e486fb chatlist:InputChatlist = Bool; -chatlists.getLeaveChatlistSuggestions#fdbcd714 chatlist:InputChatlist = Vector; -chatlists.leaveChatlist#74fae13a chatlist:InputChatlist peers:Vector = Updates; - -// LAYER 158 +// LAYER 150 \ No newline at end of file diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index 08ecb7a676..1ad4c6a593 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -28,7 +28,6 @@ BOT_INLINE_DISABLED The inline feature of the bot is disabled BOT_INVALID This is not a valid bot BOT_METHOD_INVALID The method can't be used by bots BOT_MISSING This method can only be run by a bot -BOT_ONESIDE_NOT_AVAIL Bots can't pin messages for one side only in private chats BOT_PAYMENTS_DISABLED This method can only be run by a bot BOT_POLLS_DISABLED Sending polls by bots has been disabled BOT_RESPONSE_TIMEOUT The bot did not answer to the callback query in time @@ -39,7 +38,6 @@ BROADCAST_REQUIRED The request can only be used with a channel BUTTON_DATA_INVALID The button callback data is invalid or too large BUTTON_TYPE_INVALID The type of one of the buttons you provided is invalid BUTTON_URL_INVALID The button url is invalid -BUTTON_USER_PRIVACY_RESTRICTED The privacy settings of the user specified in a keyboard button do not allow creating such button CALL_ALREADY_ACCEPTED The call is already accepted CALL_ALREADY_DECLINED The call is already declined CALL_PEER_INVALID The provided call peer object is invalid @@ -102,7 +100,6 @@ ENCRYPTION_ALREADY_DECLINED The secret chat is already declined ENCRYPTION_DECLINED The secret chat was declined ENCRYPTION_ID_INVALID The provided secret chat id is invalid ENTITIES_TOO_LONG The entity provided contains data that is too long, or you passed too many entities to this message -ENTITY_BOUNDS_INVALID The message entity bounds are invalid ENTITY_MENTION_USER_INVALID The mentioned entity is not an user ERROR_TEXT_EMPTY The provided error message is empty EXPIRE_DATE_INVALID The expiration date is invalid @@ -156,7 +153,6 @@ INPUT_USER_DEACTIVATED The target user has been deleted/deactivated INVITE_HASH_EMPTY The invite hash is empty INVITE_HASH_EXPIRED The chat invite link is no longer valid INVITE_HASH_INVALID The invite link hash is invalid -INVITE_REQUEST_SENT The request to join this chat or channel has been successfully sent INVITE_REVOKED_MISSING The action required a chat invite link to be revoked first LANG_PACK_INVALID The provided language pack is invalid LASTNAME_INVALID The last name is invalid @@ -300,8 +296,8 @@ STICKER_INVALID The provided sticker is invalid STICKER_PNG_DIMENSIONS The sticker png dimensions are invalid STICKER_PNG_NOPNG Stickers must be png files but the provided image was not a png STICKER_TGS_NOTGS A tgs sticker file was expected, but something else was provided -STICKER_THUMB_PNG_NOPNG A png sticker thumbnail file was expected, but something else was provided STICKER_VIDEO_NOWEBM A webm video file was expected, but something else was provided +STICKER_THUMB_PNG_NOPNG A png sticker thumbnail file was expected, but something else was provided TAKEOUT_INVALID The takeout id is invalid TAKEOUT_REQUIRED The method must be invoked inside a takeout session TEMP_AUTH_KEY_EMPTY The temporary auth key provided is empty @@ -357,4 +353,5 @@ WEBDOCUMENT_URL_EMPTY The web document URL is empty WEBDOCUMENT_URL_INVALID The web document URL is invalid WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media -YOU_BLOCKED_USER You blocked this user \ No newline at end of file +YOU_BLOCKED_USER You blocked this user +ENTITY_BOUNDS_INVALID The message entity bounds are invalid diff --git a/compiler/errors/source/403_FORBIDDEN.tsv b/compiler/errors/source/403_FORBIDDEN.tsv index 027f2e852e..69777cd249 100644 --- a/compiler/errors/source/403_FORBIDDEN.tsv +++ b/compiler/errors/source/403_FORBIDDEN.tsv @@ -15,7 +15,6 @@ INLINE_BOT_REQUIRED The action must be performed through an inline bot callback MESSAGE_AUTHOR_REQUIRED You are not the author of this message MESSAGE_DELETE_FORBIDDEN You don't have rights to delete messages in this chat, most likely because you are not the author of them POLL_VOTE_REQUIRED Cast a vote in the poll before calling this method -PREMIUM_ACCOUNT_REQUIRED This action requires a premium account RIGHT_FORBIDDEN You don't have enough rights for this action, or you tried to set one or more admin rights that can't be applied to this kind of chat (channel or supergroup) SENSITIVE_CHANGE_FORBIDDEN Your sensitive content settings can't be changed at this time TAKEOUT_REQUIRED The method must be invoked inside a takeout session @@ -25,4 +24,5 @@ USER_INVALID The provided user is invalid USER_IS_BLOCKED The user is blocked USER_NOT_MUTUAL_CONTACT The provided user is not a mutual contact USER_PRIVACY_RESTRICTED The user's privacy settings is preventing you to perform this action -USER_RESTRICTED You are limited/restricted. You can't perform this action \ No newline at end of file +USER_RESTRICTED You are limited/restricted. You can't perform this action +PREMIUM_ACCOUNT_REQUIRED This action requires a premium account \ No newline at end of file diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 9a6a44accb..a0d9ce7848 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.106" +__version__ = "2.2.2" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " diff --git a/pyrogram/client.py b/pyrogram/client.py index c74634ea8e..f60ad63fb8 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -26,13 +26,12 @@ import shutil import sys from concurrent.futures.thread import ThreadPoolExecutor -from datetime import datetime, timedelta from hashlib import sha256 from importlib import import_module from io import StringIO, BytesIO from mimetypes import MimeTypes from pathlib import Path -from typing import Union, List, Optional, Callable, AsyncGenerator +from typing import Union, List, Optional, Callable, AsyncGenerator, Type import pyrogram from pyrogram import __version__, __license__ @@ -44,7 +43,7 @@ from pyrogram.errors import ( SessionPasswordNeeded, VolumeLocNotFound, ChannelPrivate, - BadRequest + AuthBytesInvalid, BadRequest ) from pyrogram.handlers.handler import Handler from pyrogram.methods import Methods @@ -52,6 +51,8 @@ from pyrogram.storage import FileStorage, MemoryStorage from pyrogram.types import User, TermsOfService from pyrogram.utils import ainput +from .connection import Connection +from .connection.transport import TCP, TCPAbridged from .dispatcher import Dispatcher from .file_id import FileId, FileType, ThumbnailSource from .mime_types import mime_types @@ -172,11 +173,6 @@ class Client(Methods): Pass True to hide the password when typing it during the login. Defaults to False, because ``getpass`` (the library used) is known to be problematic in some terminal environments. - - max_concurrent_transmissions (``bool``, *optional*): - Set the maximum amount of concurrent transmissions (uploads & downloads). - A value that is too high may result in network related issues. - Defaults to 1. """ APP_VERSION = f"Pyrogram {__version__}" @@ -191,11 +187,6 @@ class Client(Methods): WORKERS = min(32, (os.cpu_count() or 0) + 4) # os.cpu_count() can be None WORKDIR = PARENT_DIR - # Interval of seconds in which the updates watchdog will kick in - UPDATES_WATCHDOG_INTERVAL = 5 * 60 - - MAX_CONCURRENT_TRANSMISSIONS = 1 - mimetypes = MimeTypes() mimetypes.readfp(StringIO(mime_types)) @@ -224,8 +215,7 @@ def __init__( no_updates: bool = None, takeout: bool = None, sleep_threshold: int = Session.SLEEP_THRESHOLD, - hide_password: bool = False, - max_concurrent_transmissions: int = MAX_CONCURRENT_TRANSMISSIONS + hide_password: bool = False ): super().__init__() @@ -253,7 +243,6 @@ def __init__( self.takeout = takeout self.sleep_threshold = sleep_threshold self.hide_password = hide_password - self.max_concurrent_transmissions = max_concurrent_transmissions self.executor = ThreadPoolExecutor(self.workers, thread_name_prefix="Handler") @@ -264,6 +253,9 @@ def __init__( else: self.storage = FileStorage(self.name, self.workdir) + self.connection_factory = Connection + self.protocol_factory = TCPAbridged + self.dispatcher = Dispatcher(self) self.rnd_id = MsgId @@ -275,9 +267,6 @@ def __init__( self.media_sessions = {} self.media_sessions_lock = asyncio.Lock() - self.save_file_semaphore = asyncio.Semaphore(self.max_concurrent_transmissions) - self.get_file_semaphore = asyncio.Semaphore(self.max_concurrent_transmissions) - self.is_connected = None self.is_initialized = None @@ -289,13 +278,6 @@ def __init__( self.message_cache = Cache(10000) - # Sometimes, for some reason, the server will stop sending updates and will only respond to pings. - # This watchdog will invoke updates.GetState in order to wake up the server and enable it sending updates again - # after some idle time has been detected. - self.updates_watchdog_task = None - self.updates_watchdog_event = asyncio.Event() - self.last_update_time = datetime.now() - self.loop = asyncio.get_event_loop() def __enter__(self): @@ -316,18 +298,6 @@ async def __aexit__(self, *args): except ConnectionError: pass - async def updates_watchdog(self): - while True: - try: - await asyncio.wait_for(self.updates_watchdog_event.wait(), self.UPDATES_WATCHDOG_INTERVAL) - except asyncio.TimeoutError: - pass - else: - break - - if datetime.now() - self.last_update_time > timedelta(seconds=self.UPDATES_WATCHDOG_INTERVAL): - await self.invoke(raw.functions.updates.GetState()) - async def authorize(self) -> User: if self.bot_token: return await self.sign_in_bot(self.bot_token) @@ -370,7 +340,6 @@ async def authorize(self) -> User: enums.SentCodeType.CALL: "phone call", enums.SentCodeType.FLASH_CALL: "phone flash call", enums.SentCodeType.FRAGMENT_SMS: "Fragment SMS", - enums.SentCodeType.EMAIL_CODE: "email code" } print(f"The confirmation code has been sent via {sent_code_descriptions[sent_code.type]}") @@ -499,29 +468,17 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra if isinstance(peer, raw.types.User): peer_id = peer.id access_hash = peer.access_hash - username = ( - peer.username.lower() if peer.username - else peer.usernames[0].username.lower() if peer.usernames - else None - ) + username = (peer.username or "").lower() or None phone_number = peer.phone peer_type = "bot" if peer.bot else "user" elif isinstance(peer, (raw.types.Chat, raw.types.ChatForbidden)): peer_id = -peer.id access_hash = 0 peer_type = "group" - elif isinstance(peer, raw.types.Channel): - peer_id = utils.get_channel_id(peer.id) - access_hash = peer.access_hash - username = ( - peer.username.lower() if peer.username - else peer.usernames[0].username.lower() if peer.usernames - else None - ) - peer_type = "channel" if peer.broadcast else "supergroup" - elif isinstance(peer, raw.types.ChannelForbidden): + elif isinstance(peer, (raw.types.Channel, raw.types.ChannelForbidden)): peer_id = utils.get_channel_id(peer.id) access_hash = peer.access_hash + username = (getattr(peer, "username", None) or "").lower() or None peer_type = "channel" if peer.broadcast else "supergroup" else: continue @@ -533,8 +490,6 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra return is_min async def handle_updates(self, updates): - self.last_update_time = datetime.now() - if isinstance(updates, (raw.types.Updates, raw.types.UpdatesCombined)): is_min = any(( await self.fetch_peers(updates.users), @@ -557,7 +512,7 @@ async def handle_updates(self, updates): pts_count = getattr(update, "pts_count", None) if isinstance(update, raw.types.UpdateChannelTooLong): - log.info(update) + log.warning(update) if isinstance(update, raw.types.UpdateNewChannelMessage) and is_min: message = update.message @@ -815,93 +770,204 @@ async def get_file( progress: Callable = None, progress_args: tuple = () ) -> Optional[AsyncGenerator[bytes, None]]: - async with self.get_file_semaphore: - file_type = file_id.file_type + dc_id = file_id.dc_id - if file_type == FileType.CHAT_PHOTO: - if file_id.chat_id > 0: - peer = raw.types.InputPeerUser( - user_id=file_id.chat_id, - access_hash=file_id.chat_access_hash + async with self.media_sessions_lock: + session = self.media_sessions.get(dc_id, None) + + if session is None: + if dc_id != await self.storage.dc_id(): + session = Session( + self, dc_id, await Auth(self, dc_id, await self.storage.test_mode()).create(), + await self.storage.test_mode(), is_media=True ) - else: - if file_id.chat_access_hash == 0: - peer = raw.types.InputPeerChat( - chat_id=-file_id.chat_id + await session.start() + + for _ in range(3): + exported_auth = await self.invoke( + raw.functions.auth.ExportAuthorization( + dc_id=dc_id + ) ) + + try: + await session.invoke( + raw.functions.auth.ImportAuthorization( + id=exported_auth.id, + bytes=exported_auth.bytes + ) + ) + except AuthBytesInvalid: + continue + else: + break else: - peer = raw.types.InputPeerChannel( - channel_id=utils.get_channel_id(file_id.chat_id), - access_hash=file_id.chat_access_hash - ) + await session.stop() + raise AuthBytesInvalid + else: + session = Session( + self, dc_id, await self.storage.auth_key(), + await self.storage.test_mode(), is_media=True + ) + await session.start() - location = raw.types.InputPeerPhotoFileLocation( - peer=peer, - photo_id=file_id.media_id, - big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG - ) - elif file_type == FileType.PHOTO: - location = raw.types.InputPhotoFileLocation( - id=file_id.media_id, - access_hash=file_id.access_hash, - file_reference=file_id.file_reference, - thumb_size=file_id.thumbnail_size + self.media_sessions[dc_id] = session + + file_type = file_id.file_type + + if file_type == FileType.CHAT_PHOTO: + if file_id.chat_id > 0: + peer = raw.types.InputPeerUser( + user_id=file_id.chat_id, + access_hash=file_id.chat_access_hash ) else: - location = raw.types.InputDocumentFileLocation( - id=file_id.media_id, - access_hash=file_id.access_hash, - file_reference=file_id.file_reference, - thumb_size=file_id.thumbnail_size - ) + if file_id.chat_access_hash == 0: + peer = raw.types.InputPeerChat( + chat_id=-file_id.chat_id + ) + else: + peer = raw.types.InputPeerChannel( + channel_id=utils.get_channel_id(file_id.chat_id), + access_hash=file_id.chat_access_hash + ) - current = 0 - total = abs(limit) or (1 << 31) - 1 - chunk_size = 1024 * 1024 - offset_bytes = abs(offset) * chunk_size + location = raw.types.InputPeerPhotoFileLocation( + peer=peer, + photo_id=file_id.media_id, + big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG + ) + elif file_type == FileType.PHOTO: + location = raw.types.InputPhotoFileLocation( + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + thumb_size=file_id.thumbnail_size + ) + else: + location = raw.types.InputDocumentFileLocation( + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + thumb_size=file_id.thumbnail_size + ) - dc_id = file_id.dc_id + current = 0 + total = abs(limit) or (1 << 31) - 1 + chunk_size = 1024 * 1024 + offset_bytes = abs(offset) * chunk_size - session = Session( - self, dc_id, - await Auth(self, dc_id, await self.storage.test_mode()).create() - if dc_id != await self.storage.dc_id() - else await self.storage.auth_key(), - await self.storage.test_mode(), - is_media=True + try: + r = await session.invoke( + raw.functions.upload.GetFile( + location=location, + offset=offset_bytes, + limit=chunk_size + ), + sleep_threshold=30 ) - try: - await session.start() + if isinstance(r, raw.types.upload.File): + while True: + chunk = r.bytes - if dc_id != await self.storage.dc_id(): - exported_auth = await self.invoke( - raw.functions.auth.ExportAuthorization( - dc_id=dc_id + yield chunk + + current += 1 + offset_bytes += chunk_size + + if progress: + func = functools.partial( + progress, + min(offset_bytes, file_size) + if file_size != 0 + else offset_bytes, + file_size, + *progress_args ) + + if inspect.iscoroutinefunction(progress): + await func() + else: + await self.loop.run_in_executor(self.executor, func) + + if len(chunk) < chunk_size or current >= total: + break + + r = await session.invoke( + raw.functions.upload.GetFile( + location=location, + offset=offset_bytes, + limit=chunk_size + ), + sleep_threshold=30 ) - await session.invoke( - raw.functions.auth.ImportAuthorization( - id=exported_auth.id, - bytes=exported_auth.bytes + elif isinstance(r, raw.types.upload.FileCdnRedirect): + async with self.media_sessions_lock: + cdn_session = self.media_sessions.get(r.dc_id, None) + + if cdn_session is None: + cdn_session = Session( + self, r.dc_id, await Auth(self, r.dc_id, await self.storage.test_mode()).create(), + await self.storage.test_mode(), is_media=True, is_cdn=True ) - ) - r = await session.invoke( - raw.functions.upload.GetFile( - location=location, - offset=offset_bytes, - limit=chunk_size - ), - sleep_threshold=30 - ) + await cdn_session.start() + + self.media_sessions[r.dc_id] = cdn_session - if isinstance(r, raw.types.upload.File): + try: while True: - chunk = r.bytes + r2 = await cdn_session.invoke( + raw.functions.upload.GetCdnFile( + file_token=r.file_token, + offset=offset_bytes, + limit=chunk_size + ) + ) - yield chunk + if isinstance(r2, raw.types.upload.CdnFileReuploadNeeded): + try: + await session.invoke( + raw.functions.upload.ReuploadCdnFile( + file_token=r.file_token, + request_token=r2.request_token + ) + ) + except VolumeLocNotFound: + break + else: + continue + + chunk = r2.bytes + + # https://core.telegram.org/cdn#decrypting-files + decrypted_chunk = aes.ctr256_decrypt( + chunk, + r.encryption_key, + bytearray( + r.encryption_iv[:-4] + + (offset_bytes // 16).to_bytes(4, "big") + ) + ) + + hashes = await session.invoke( + raw.functions.upload.GetCdnFileHashes( + file_token=r.file_token, + offset=offset_bytes + ) + ) + + # https://core.telegram.org/cdn#verifying-files + for i, h in enumerate(hashes): + cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] + CDNFileHashMismatch.check( + h.hash == sha256(cdn_chunk).digest(), + "h.hash == sha256(cdn_chunk).digest()" + ) + + yield decrypted_chunk current += 1 offset_bytes += chunk_size @@ -909,9 +975,7 @@ async def get_file( if progress: func = functools.partial( progress, - min(offset_bytes, file_size) - if file_size != 0 - else offset_bytes, + min(offset_bytes, file_size) if file_size != 0 else offset_bytes, file_size, *progress_args ) @@ -923,104 +987,12 @@ async def get_file( if len(chunk) < chunk_size or current >= total: break - - r = await session.invoke( - raw.functions.upload.GetFile( - location=location, - offset=offset_bytes, - limit=chunk_size - ), - sleep_threshold=30 - ) - - elif isinstance(r, raw.types.upload.FileCdnRedirect): - cdn_session = Session( - self, r.dc_id, await Auth(self, r.dc_id, await self.storage.test_mode()).create(), - await self.storage.test_mode(), is_media=True, is_cdn=True - ) - - try: - await cdn_session.start() - - while True: - r2 = await cdn_session.invoke( - raw.functions.upload.GetCdnFile( - file_token=r.file_token, - offset=offset_bytes, - limit=chunk_size - ) - ) - - if isinstance(r2, raw.types.upload.CdnFileReuploadNeeded): - try: - await session.invoke( - raw.functions.upload.ReuploadCdnFile( - file_token=r.file_token, - request_token=r2.request_token - ) - ) - except VolumeLocNotFound: - break - else: - continue - - chunk = r2.bytes - - # https://core.telegram.org/cdn#decrypting-files - decrypted_chunk = aes.ctr256_decrypt( - chunk, - r.encryption_key, - bytearray( - r.encryption_iv[:-4] - + (offset_bytes // 16).to_bytes(4, "big") - ) - ) - - hashes = await session.invoke( - raw.functions.upload.GetCdnFileHashes( - file_token=r.file_token, - offset=offset_bytes - ) - ) - - # https://core.telegram.org/cdn#verifying-files - for i, h in enumerate(hashes): - cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] - CDNFileHashMismatch.check( - h.hash == sha256(cdn_chunk).digest(), - "h.hash == sha256(cdn_chunk).digest()" - ) - - yield decrypted_chunk - - current += 1 - offset_bytes += chunk_size - - if progress: - func = functools.partial( - progress, - min(offset_bytes, file_size) if file_size != 0 else offset_bytes, - file_size, - *progress_args - ) - - if inspect.iscoroutinefunction(progress): - await func() - else: - await self.loop.run_in_executor(self.executor, func) - - if len(chunk) < chunk_size or current >= total: - break - except Exception as e: - raise e - finally: - await cdn_session.stop() - except pyrogram.StopTransmission: - raise - except Exception as e: - log.exception(e) - finally: - await session.stop() + except Exception as e: + raise e + except pyrogram.StopTransmission: + raise + except Exception as e: + log.exception(e) def guess_mime_type(self, filename: str) -> Optional[str]: return self.mimetypes.guess_type(filename)[0] diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 1107673f1a..05d2e07af1 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -18,7 +18,7 @@ import asyncio import logging -from typing import Optional +from typing import Optional, Type from .transport import TCP, TCPAbridged from ..session.internals import DataCenter @@ -29,19 +29,30 @@ class Connection: MAX_CONNECTION_ATTEMPTS = 3 - def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, media: bool = False): + def __init__( + self, + dc_id: int, + test_mode: bool, + ipv6: bool, + alt_port: bool, + proxy: dict, + media: bool = False, + protocol_factory: Type[TCP] = TCPAbridged + ) -> None: self.dc_id = dc_id self.test_mode = test_mode self.ipv6 = ipv6 + self.alt_port = alt_port self.proxy = proxy self.media = media + self.protocol_factory = protocol_factory - self.address = DataCenter(dc_id, test_mode, ipv6, media) - self.protocol: TCP = None + self.address = DataCenter(dc_id, test_mode, ipv6, alt_port, media) + self.protocol: Optional[TCP] = None - async def connect(self): + async def connect(self) -> None: for i in range(Connection.MAX_CONNECTION_ATTEMPTS): - self.protocol = TCPAbridged(self.ipv6, self.proxy) + self.protocol = self.protocol_factory(ipv6=self.ipv6, proxy=self.proxy) try: log.info("Connecting...") @@ -61,11 +72,11 @@ async def connect(self): log.warning("Connection failed! Trying again...") raise ConnectionError - async def close(self): + async def close(self) -> None: await self.protocol.close() log.info("Disconnected") - async def send(self, data: bytes): + async def send(self, data: bytes) -> None: await self.protocol.send(data) async def recv(self) -> Optional[bytes]: diff --git a/pyrogram/connection/transport/tcp/__init__.py b/pyrogram/connection/transport/tcp/__init__.py index 3e23a88379..bae35e8825 100644 --- a/pyrogram/connection/transport/tcp/__init__.py +++ b/pyrogram/connection/transport/tcp/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .tcp import TCP +from .tcp import TCP, Proxy from .tcp_abridged import TCPAbridged from .tcp_abridged_o import TCPAbridgedO from .tcp_full import TCPFull diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 82ef033be4..8913123628 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -21,89 +21,134 @@ import logging import socket from concurrent.futures import ThreadPoolExecutor +from typing import Tuple, Dict, TypedDict, Optional import socks log = logging.getLogger(__name__) +proxy_type_by_scheme: Dict[str, int] = { + "SOCKS4": socks.SOCKS4, + "SOCKS5": socks.SOCKS5, + "HTTP": socks.HTTP, +} + + +class Proxy(TypedDict): + scheme: str + hostname: str + port: int + username: Optional[str] + password: Optional[str] + class TCP: TIMEOUT = 10 - def __init__(self, ipv6: bool, proxy: dict): - self.socket = None + def __init__(self, ipv6: bool, proxy: Proxy) -> None: + self.ipv6 = ipv6 + self.proxy = proxy - self.reader = None - self.writer = None + self.reader: Optional[asyncio.StreamReader] = None + self.writer: Optional[asyncio.StreamWriter] = None self.lock = asyncio.Lock() self.loop = asyncio.get_event_loop() - self.proxy = proxy + async def _connect_via_proxy( + self, + destination: Tuple[str, int] + ) -> None: + scheme = self.proxy.get("scheme") + if scheme is None: + raise ValueError("No scheme specified") - if proxy: - hostname = proxy.get("hostname") - - try: - ip_address = ipaddress.ip_address(hostname) - except ValueError: - self.socket = socks.socksocket(socket.AF_INET) - else: - if isinstance(ip_address, ipaddress.IPv6Address): - self.socket = socks.socksocket(socket.AF_INET6) - else: - self.socket = socks.socksocket(socket.AF_INET) + proxy_type = proxy_type_by_scheme.get(scheme.upper()) + if proxy_type is None: + raise ValueError(f"Unknown proxy type {scheme}") - self.socket.set_proxy( - proxy_type=getattr(socks, proxy.get("scheme").upper()), - addr=hostname, - port=proxy.get("port", None), - username=proxy.get("username", None), - password=proxy.get("password", None) - ) + hostname = self.proxy.get("hostname") + port = self.proxy.get("port") + username = self.proxy.get("username") + password = self.proxy.get("password") - self.socket.settimeout(TCP.TIMEOUT) - - log.info("Using proxy %s", hostname) + try: + ip_address = ipaddress.ip_address(hostname) + except ValueError: + is_proxy_ipv6 = False else: - self.socket = socket.socket( - socket.AF_INET6 if ipv6 - else socket.AF_INET - ) - - self.socket.setblocking(False) - - async def connect(self, address: tuple): + is_proxy_ipv6 = isinstance(ip_address, ipaddress.IPv6Address) + + proxy_family = socket.AF_INET6 if is_proxy_ipv6 else socket.AF_INET + sock = socks.socksocket(proxy_family) + + sock.set_proxy( + proxy_type=proxy_type, + addr=hostname, + port=port, + username=username, + password=password + ) + sock.settimeout(TCP.TIMEOUT) + + await self.loop.sock_connect( + sock=sock, + address=destination + ) + + sock.setblocking(False) + + self.reader, self.writer = await asyncio.open_connection( + sock=sock + ) + + async def _connect_via_direct( + self, + destination: Tuple[str, int] + ) -> None: + host, port = destination + family = socket.AF_INET6 if self.ipv6 else socket.AF_INET + self.reader, self.writer = await asyncio.open_connection( + host=host, + port=port, + family=family + ) + + async def _connect(self, destination: Tuple[str, int]) -> None: if self.proxy: - with ThreadPoolExecutor(1) as executor: - await self.loop.run_in_executor(executor, self.socket.connect, address) + await self._connect_via_proxy(destination) else: - try: - await asyncio.wait_for(asyncio.get_event_loop().sock_connect(self.socket, address), TCP.TIMEOUT) - except asyncio.TimeoutError: # Re-raise as TimeoutError. asyncio.TimeoutError is deprecated in 3.11 - raise TimeoutError("Connection timed out") + await self._connect_via_direct(destination) + + async def connect(self, address: Tuple[str, int]) -> None: + try: + await asyncio.wait_for(self._connect(address), TCP.TIMEOUT) + except asyncio.TimeoutError: # Re-raise as TimeoutError. asyncio.TimeoutError is deprecated in 3.11 + raise TimeoutError("Connection timed out") - self.reader, self.writer = await asyncio.open_connection(sock=self.socket) + async def close(self) -> None: + if self.writer is None: + return None - async def close(self): try: - if self.writer is not None: - self.writer.close() - await asyncio.wait_for(self.writer.wait_closed(), TCP.TIMEOUT) + self.writer.close() + await asyncio.wait_for(self.writer.wait_closed(), TCP.TIMEOUT) except Exception as e: log.info("Close exception: %s %s", type(e).__name__, e) - async def send(self, data: bytes): + async def send(self, data: bytes) -> None: + if self.writer is None: + return None + async with self.lock: try: - if self.writer is not None: - self.writer.write(data) - await self.writer.drain() + self.writer.write(data) + await self.writer.drain() except Exception as e: log.info("Send exception: %s %s", type(e).__name__, e) raise OSError(e) - async def recv(self, length: int = 0): + async def recv(self, length: int = 0) -> Optional[bytes]: data = b"" while len(data) < length: diff --git a/pyrogram/connection/transport/tcp/tcp_abridged.py b/pyrogram/connection/transport/tcp/tcp_abridged.py index 77d44cf41c..4cb4c1b2a3 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged.py @@ -17,22 +17,22 @@ # along with Pyrogram. If not, see . import logging -from typing import Optional +from typing import Optional, Tuple -from .tcp import TCP +from .tcp import TCP, Proxy log = logging.getLogger(__name__) class TCPAbridged(TCP): - def __init__(self, ipv6: bool, proxy: dict): + def __init__(self, ipv6: bool, proxy: Proxy) -> None: super().__init__(ipv6, proxy) - async def connect(self, address: tuple): + async def connect(self, address: Tuple[str, int]) -> None: await super().connect(address) await super().send(b"\xef") - async def send(self, data: bytes, *args): + async def send(self, data: bytes, *args) -> None: length = len(data) // 4 await super().send( diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py index 6f57ab1154..20efb5ec3f 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py @@ -18,11 +18,11 @@ import logging import os -from typing import Optional +from typing import Optional, Tuple import pyrogram from pyrogram.crypto import aes -from .tcp import TCP +from .tcp import TCP, Proxy log = logging.getLogger(__name__) @@ -30,13 +30,13 @@ class TCPAbridgedO(TCP): RESERVED = (b"HEAD", b"POST", b"GET ", b"OPTI", b"\xee" * 4) - def __init__(self, ipv6: bool, proxy: dict): + def __init__(self, ipv6: bool, proxy: Proxy) -> None: super().__init__(ipv6, proxy) self.encrypt = None self.decrypt = None - async def connect(self, address: tuple): + async def connect(self, address: Tuple[str, int]) -> None: await super().connect(address) while True: @@ -55,7 +55,7 @@ async def connect(self, address: tuple): await super().send(nonce) - async def send(self, data: bytes, *args): + async def send(self, data: bytes, *args) -> None: length = len(data) // 4 data = (bytes([length]) if length <= 126 else b"\x7f" + length.to_bytes(3, "little")) + data payload = await self.loop.run_in_executor(pyrogram.crypto_executor, aes.ctr256_encrypt, data, *self.encrypt) diff --git a/pyrogram/connection/transport/tcp/tcp_full.py b/pyrogram/connection/transport/tcp/tcp_full.py index 8bd89000c8..ad9d981718 100644 --- a/pyrogram/connection/transport/tcp/tcp_full.py +++ b/pyrogram/connection/transport/tcp/tcp_full.py @@ -19,24 +19,24 @@ import logging from binascii import crc32 from struct import pack, unpack -from typing import Optional +from typing import Optional, Tuple -from .tcp import TCP +from .tcp import TCP, Proxy log = logging.getLogger(__name__) class TCPFull(TCP): - def __init__(self, ipv6: bool, proxy: dict): + def __init__(self, ipv6: bool, proxy: Proxy) -> None: super().__init__(ipv6, proxy) - self.seq_no = None + self.seq_no: Optional[int] = None - async def connect(self, address: tuple): + async def connect(self, address: Tuple[str, int]) -> None: await super().connect(address) self.seq_no = 0 - async def send(self, data: bytes, *args): + async def send(self, data: bytes, *args) -> None: data = pack(" None: super().__init__(ipv6, proxy) - async def connect(self, address: tuple): + async def connect(self, address: Tuple[str, int]) -> None: await super().connect(address) await super().send(b"\xee" * 4) - async def send(self, data: bytes, *args): + async def send(self, data: bytes, *args) -> None: await super().send(pack(" Optional[bytes]: diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py index 48b2d44520..3f47bdfe0a 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py @@ -19,10 +19,10 @@ import logging import os from struct import pack, unpack -from typing import Optional +from typing import Optional, Tuple from pyrogram.crypto import aes -from .tcp import TCP +from .tcp import TCP, Proxy log = logging.getLogger(__name__) @@ -30,13 +30,13 @@ class TCPIntermediateO(TCP): RESERVED = (b"HEAD", b"POST", b"GET ", b"OPTI", b"\xee" * 4) - def __init__(self, ipv6: bool, proxy: dict): + def __init__(self, ipv6: bool, proxy: Proxy) -> None: super().__init__(ipv6, proxy) self.encrypt = None self.decrypt = None - async def connect(self, address: tuple): + async def connect(self, address: Tuple[str, int]) -> None: await super().connect(address) while True: @@ -55,7 +55,7 @@ async def connect(self, address: tuple): await super().send(nonce) - async def send(self, data: bytes, *args): + async def send(self, data: bytes, *args) -> None: await super().send( aes.ctr256_encrypt( pack(". +import bisect from hashlib import sha256 from io import BytesIO from os import urandom +from typing import List from pyrogram.errors import SecurityCheckMismatch from pyrogram.raw.core import Message, Long from . import aes +from ..session.internals import MsgId + +STORED_MSG_IDS_MAX_SIZE = 1000 * 2 def kdf(auth_key: bytes, msg_key: bytes, outgoing: bool) -> tuple: @@ -54,7 +59,8 @@ def unpack( b: BytesIO, session_id: bytes, auth_key: bytes, - auth_key_id: bytes + auth_key_id: bytes, + stored_msg_ids: List[int] ) -> Message: SecurityCheckMismatch.check(b.read(8) == auth_key_id, "b.read(8) == auth_key_id") @@ -97,4 +103,26 @@ def unpack( # https://core.telegram.org/mtproto/security_guidelines#checking-msg-id SecurityCheckMismatch.check(message.msg_id % 2 != 0, "message.msg_id % 2 != 0") + if len(stored_msg_ids) > STORED_MSG_IDS_MAX_SIZE: + del stored_msg_ids[:STORED_MSG_IDS_MAX_SIZE // 2] + + if stored_msg_ids: + if message.msg_id < stored_msg_ids[0]: + raise SecurityCheckMismatch("The msg_id is lower than all the stored values") + + if message.msg_id in stored_msg_ids: + raise SecurityCheckMismatch("The msg_id is equal to any of the stored values") + + time_diff = (message.msg_id - MsgId()) / 2 ** 32 + + if time_diff > 30: + raise SecurityCheckMismatch("The msg_id belongs to over 30 seconds in the future. " + "Most likely the client time has to be synchronized.") + + if time_diff < -300: + raise SecurityCheckMismatch("The msg_id belongs to over 300 seconds in the past. " + "Most likely the client time has to be synchronized.") + + bisect.insort(stored_msg_ids, message.msg_id) + return message diff --git a/pyrogram/crypto/rsa.py b/pyrogram/crypto/rsa.py index 25c2322957..3fd7d67ba3 100644 --- a/pyrogram/crypto/rsa.py +++ b/pyrogram/crypto/rsa.py @@ -153,25 +153,25 @@ int("010001", 16) # Exponent ), - # -7395192255793472640 - 0x995effd323b5db80 - (1 << 64): PublicKey( # CDN DC-121 + # 6427105915145367799 + 0x15931aac70e0d30f7 - (1 << 64): PublicKey( # CDN DC-121 # -----BEGIN RSA PUBLIC KEY----- - # MIIBCgKCAQEA4tWHcGJlElkxuxKQJwFjJaulmVHgdxNA3wgI2E8XbNnA88y51Xog - # V5m8BEYuTSP4llXZY4ZSJW5VlFXnmsJT/hmjyeFqqTajyAW6nb9vwZX291QvqD/1 - # ZCFBy7TLvCM0lbNIEhcLMf33ZV8AetLAd+uRLF6QHosys5w0iJ7x+UbGwDxyfeic - # 8EJJnsKaXrUOwRycMRN+V/zDySa0EYl1u1EB1MDX1/jIV1IQEbLvdBH4vsVTVEdW - # KHlzOcFzT9qX/g8XibCPiHLJvqQb8hVibvs9NaANyClcBEt3mOucG1/46Lilkc/K - # d4nlCcohk0jIHNp8symUzNWRPUGmTs3SPwIDAQAB + # MIIBCgKCAQEA+Lf3PvgE1yxbJUCMaEAkV0QySTVpnaDjiednB5RbtNWjCeqSVakY + # HbqqGMIIv5WCGdFdrqOfMNcNSstPtSU6R9UmRw6tquOIykpSuUOje9H+4XVIKquj + # yL2ISdK+4ZOMl4hCMkqauw4bP1Sbr03vZRQbU6qEA04V4j879BAyBVhr3WG9+Zi+ + # t5XfGSTgSExPYEl8rZNHYNV5RB+BuroVH2HLTOpT/mJVfikYpgjfWF5ldezV4Wo9 + # LSH0cZGSFIaeJl8d0A8Eiy5B9gtBO8mL+XfQRKOOmr7a4BM4Ro2de5rr2i2od7hY + # Xd3DO9FRSl4y1zA8Am48Rfd95WHF3N/OmQIDAQAB # -----END RSA PUBLIC KEY----- int( - "E2D587706265125931BB129027016325ABA59951E0771340DF0808D84F176CD9" - "C0F3CCB9D57A205799BC04462E4D23F89655D9638652256E559455E79AC253FE" - "19A3C9E16AA936A3C805BA9DBF6FC195F6F7542FA83FF5642141CBB4CBBC2334" - "95B34812170B31FDF7655F007AD2C077EB912C5E901E8B32B39C34889EF1F946" - "C6C03C727DE89CF042499EC29A5EB50EC11C9C31137E57FCC3C926B4118975BB" - "5101D4C0D7D7F8C857521011B2EF7411F8BEC55354475628797339C1734FDA97" - "FE0F1789B08F8872C9BEA41BF215626EFB3D35A00DC8295C044B7798EB9C1B5F" - "F8E8B8A591CFCA7789E509CA219348C81CDA7CB32994CCD5913D41A64ECDD23F", + "F8B7F73EF804D72C5B25408C6840245744324935699DA0E389E76707945BB4D5" + "A309EA9255A9181DBAAA18C208BF958219D15DAEA39F30D70D4ACB4FB5253A47" + "D526470EADAAE388CA4A52B943A37BD1FEE175482AABA3C8BD8849D2BEE1938C" + "978842324A9ABB0E1B3F549BAF4DEF65141B53AA84034E15E23F3BF410320558" + "6BDD61BDF998BEB795DF1924E0484C4F60497CAD934760D579441F81BABA151F" + "61CB4CEA53FE62557E2918A608DF585E6575ECD5E16A3D2D21F471919214869E" + "265F1DD00F048B2E41F60B413BC98BF977D044A38E9ABEDAE01338468D9D7B9A" + "EBDA2DA877B8585DDDC33BD1514A5E32D7303C026E3C45F77DE561C5DCDFCE99", 16 ), # Modulus int("010001", 16) # Exponent @@ -199,54 +199,6 @@ 16 ), # Modulus int("010001", 16) # Exponent - ), - - # -3997872768018684475 - 0xc884b3e62d09e5c5 - (1 << 64): PublicKey( # CDN DC-201 - # -----BEGIN RSA PUBLIC KEY----- - # MIIBCgKCAQEAug6fETVb7NkXYYu5ueZuM0pqw1heuqUrZNYomQN0lS0o7i6mAWwb - # 1/FiscFK+y4LQSSEx+oUzXAhjmll9fmb4e7PbUiXo8MuXO0Rj3e5416DXfTiOYGW - # XlFRV0aQzu8agy1epKwkFDidnmy7g5rJJV0q1+3eR+Jk2OEc/B6lMAOv3fBU6xhE - # ZByN9gqc6fvkNo13PQ8JYZUSGttzLlYy76uFmvFBhRsJU+LNQ2+bsTHwafSffVYl - # Z2boJOblvqbRWe453CzssaSWywGXOQmWvVbEe7F8q1ki/s7S8BxYWrhSLJ6bsu9V - # ZWnIHD9vB34QF8IABPRE93mhCOHBqJxSBQIDAQAB - # -----END RSA PUBLIC KEY----- - int( - "BA0E9F11355BECD917618BB9B9E66E334A6AC3585EBAA52B64D628990374952D" - "28EE2EA6016C1BD7F162B1C14AFB2E0B412484C7EA14CD70218E6965F5F99BE1" - "EECF6D4897A3C32E5CED118F77B9E35E835DF4E23981965E5151574690CEEF1A" - "832D5EA4AC2414389D9E6CBB839AC9255D2AD7EDDE47E264D8E11CFC1EA53003" - "AFDDF054EB1844641C8DF60A9CE9FBE4368D773D0F096195121ADB732E5632EF" - "AB859AF141851B0953E2CD436F9BB131F069F49F7D56256766E824E6E5BEA6D1" - "59EE39DC2CECB1A496CB0197390996BD56C47BB17CAB5922FECED2F01C585AB8" - "522C9E9BB2EF556569C81C3F6F077E1017C20004F444F779A108E1C1A89C5205", - 16 - ), # Modulus - int("010001", 16) # Exponent - ), - - # -4960899639492471258 - 0xbb27580fd5b01626 - (1 << 64): PublicKey( # CDN DC-203 - # -----BEGIN RSA PUBLIC KEY----- - # MIIBCgKCAQEAv/L6td+mj7Dl81NHfu+Xf1KNtvZPR1tS5xFqkiUson1u7D2ulK05 - # jM8HKvpV1o+1HPPqhaXhasvsX90u3TIHRQ0zuJKJxKAiZo3GK7phHozjAJ9VUFbO - # 7jKAa5BTE9tXgA5ZwJAiQWb3U6ykwRzk3fFRe5WaW7xfVUiepxyWGdr1eecoWCfB - # af1TCXfcS7vcyljNT03pwt2YyS5iXE5IB5wBB5yqSSm4GYtWWR67UjIsXBd77TRp - # foLGpfOdUHxBz4ZSj8D76m1zlpID5J2pF6bH4+ZCz0SUpv3j7bE8WFlvgMfwEPhw - # xMYidRGayq9YlLlYd4D+Yoq0U6jS3MWTRQIDAQAB - # -----END RSA PUBLIC KEY----- - int( - "BFF2FAB5DFA68FB0E5F353477EEF977F528DB6F64F475B52E7116A92252CA27D" - "6EEC3DAE94AD398CCF072AFA55D68FB51CF3EA85A5E16ACBEC5FDD2EDD320745" - "0D33B89289C4A022668DC62BBA611E8CE3009F555056CEEE32806B905313DB57" - "800E59C090224166F753ACA4C11CE4DDF1517B959A5BBC5F55489EA71C9619DA" - "F579E7285827C169FD530977DC4BBBDCCA58CD4F4DE9C2DD98C92E625C4E4807" - "9C01079CAA4929B8198B56591EBB52322C5C177BED34697E82C6A5F39D507C41" - "CF86528FC0FBEA6D73969203E49DA917A6C7E3E642CF4494A6FDE3EDB13C5859" - "6F80C7F010F870C4C62275119ACAAF5894B9587780FE628AB453A8D2DCC59345", - 16 - ), # Modulus - int("010001", 16) # Exponent ) } diff --git a/pyrogram/enums/sent_code_type.py b/pyrogram/enums/sent_code_type.py index 474ed6b0f3..e3ec61120a 100644 --- a/pyrogram/enums/sent_code_type.py +++ b/pyrogram/enums/sent_code_type.py @@ -40,6 +40,3 @@ class SentCodeType(AutoName): FRAGMENT_SMS = raw.types.auth.SentCodeTypeFragmentSms "The code was sent via Fragment SMS." - - EMAIL_CODE = raw.types.auth.SentCodeTypeEmailCode - "The code was sent via email." diff --git a/pyrogram/filters.py b/pyrogram/filters.py index b52dfe601d..ac2dbf20fd 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -425,17 +425,6 @@ async def dice_filter(_, __, m: Message): """Filter messages that contain :obj:`~pyrogram.types.Dice` objects.""" -# endregion - -# region media_spoiler -async def media_spoiler_filter(_, __, m: Message): - return bool(m.has_media_spoiler) - - -media_spoiler = create(media_spoiler_filter) -"""Filter media messages that contain a spoiler.""" - - # endregion # region private_filter @@ -742,7 +731,6 @@ async def linked_channel_filter(_, __, m: Message): linked_channel = create(linked_channel_filter) """Filter messages that are automatically forwarded from the linked channel to the group chat.""" - # endregion diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index 453a62af17..b99a3c43b5 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -94,133 +94,137 @@ async def save_file( Raises: RPCError: In case of a Telegram RPC error. """ - async with self.save_file_semaphore: - if path is None: - return None + if path is None: + return None - async def worker(session): - while True: - data = await queue.get() + async def worker(session): + while True: + data = await queue.get() - if data is None: - return + if data is None: + return - try: - await session.invoke(data) - except Exception as e: - log.exception(e) + try: + await session.invoke(data) + except Exception as e: + log.exception(e) - part_size = 512 * 1024 + part_size = 512 * 1024 - if isinstance(path, (str, PurePath)): - fp = open(path, "rb") - elif isinstance(path, io.IOBase): - fp = path - else: - raise ValueError("Invalid file. Expected a file path as string or a binary (not text) file pointer") + if isinstance(path, (str, PurePath)): + fp = open(path, "rb") + elif isinstance(path, io.IOBase): + fp = path + else: + raise ValueError("Invalid file. Expected a file path as string or a binary (not text) file pointer") - file_name = getattr(fp, "name", "file.jpg") + file_name = getattr(fp, "name", "file.jpg") - fp.seek(0, os.SEEK_END) - file_size = fp.tell() - fp.seek(0) + fp.seek(0, os.SEEK_END) + file_size = fp.tell() + fp.seek(0) - if file_size == 0: - raise ValueError("File size equals to 0 B") + if file_size == 0: + raise ValueError("File size equals to 0 B") - file_size_limit_mib = 4000 if self.me.is_premium else 2000 + file_size_limit_mib = 4000 if self.me.is_premium else 2000 - if file_size > file_size_limit_mib * 1024 * 1024: - raise ValueError(f"Can't upload files bigger than {file_size_limit_mib} MiB") + if file_size > file_size_limit_mib * 1024 * 1024: + raise ValueError(f"Can't upload files bigger than {file_size_limit_mib} MiB") - file_total_parts = int(math.ceil(file_size / part_size)) - is_big = file_size > 10 * 1024 * 1024 - workers_count = 4 if is_big else 1 - is_missing_part = file_id is not None - file_id = file_id or self.rnd_id() - md5_sum = md5() if not is_big and not is_missing_part else None - session = Session( + file_total_parts = int(math.ceil(file_size / part_size)) + is_big = file_size > 10 * 1024 * 1024 + pool_size = 3 if is_big else 1 + workers_count = 4 if is_big else 1 + is_missing_part = file_id is not None + file_id = file_id or self.rnd_id() + md5_sum = md5() if not is_big and not is_missing_part else None + pool = [ + Session( self, await self.storage.dc_id(), await self.storage.auth_key(), await self.storage.test_mode(), is_media=True - ) - workers = [self.loop.create_task(worker(session)) for _ in range(workers_count)] - queue = asyncio.Queue(1) + ) for _ in range(pool_size) + ] + workers = [self.loop.create_task(worker(session)) for session in pool for _ in range(workers_count)] + queue = asyncio.Queue(16) - try: + try: + for session in pool: await session.start() - fp.seek(part_size * file_part) - - while True: - chunk = fp.read(part_size) + fp.seek(part_size * file_part) - if not chunk: - if not is_big and not is_missing_part: - md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) - break - - if is_big: - rpc = raw.functions.upload.SaveBigFilePart( - file_id=file_id, - file_part=file_part, - file_total_parts=file_total_parts, - bytes=chunk - ) - else: - rpc = raw.functions.upload.SaveFilePart( - file_id=file_id, - file_part=file_part, - bytes=chunk - ) - - await queue.put(rpc) - - if is_missing_part: - return + while True: + chunk = fp.read(part_size) + if not chunk: if not is_big and not is_missing_part: - md5_sum.update(chunk) - - file_part += 1 - - if progress: - func = functools.partial( - progress, - min(file_part * part_size, file_size), - file_size, - *progress_args - ) - - if inspect.iscoroutinefunction(progress): - await func() - else: - await self.loop.run_in_executor(self.executor, func) - except StopTransmission: - raise - except Exception as e: - log.exception(e) - else: - if is_big: - return raw.types.InputFileBig( - id=file_id, - parts=file_total_parts, - name=file_name, + md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) + break + if is_big: + rpc = raw.functions.upload.SaveBigFilePart( + file_id=file_id, + file_part=file_part, + file_total_parts=file_total_parts, + bytes=chunk ) else: - return raw.types.InputFile( - id=file_id, - parts=file_total_parts, - name=file_name, - md5_checksum=md5_sum + rpc = raw.functions.upload.SaveFilePart( + file_id=file_id, + file_part=file_part, + bytes=chunk ) - finally: - for _ in workers: - await queue.put(None) - await asyncio.gather(*workers) + await queue.put(rpc) + + if is_missing_part: + return + + if not is_big and not is_missing_part: + md5_sum.update(chunk) + file_part += 1 + + if progress: + func = functools.partial( + progress, + min(file_part * part_size, file_size), + file_size, + *progress_args + ) + + if inspect.iscoroutinefunction(progress): + await func() + else: + await self.loop.run_in_executor(self.executor, func) + except StopTransmission: + raise + except Exception as e: + log.exception(e) + else: + if is_big: + return raw.types.InputFileBig( + id=file_id, + parts=file_total_parts, + name=file_name, + + ) + else: + return raw.types.InputFile( + id=file_id, + parts=file_total_parts, + name=file_name, + md5_checksum=md5_sum + ) + finally: + for _ in workers: + await queue.put(None) + + await asyncio.gather(*workers) + + for session in pool: await session.stop() - if isinstance(path, (str, PurePath)): - fp.close() + if isinstance(path, (str, PurePath)): + fp.close() diff --git a/pyrogram/methods/auth/initialize.py b/pyrogram/methods/auth/initialize.py index 7188b66817..1e7915e00e 100644 --- a/pyrogram/methods/auth/initialize.py +++ b/pyrogram/methods/auth/initialize.py @@ -16,7 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import asyncio import logging import pyrogram @@ -47,6 +46,4 @@ async def initialize( await self.dispatcher.start() - self.updates_watchdog_task = asyncio.create_task(self.updates_watchdog()) - self.is_initialized = True diff --git a/pyrogram/methods/auth/terminate.py b/pyrogram/methods/auth/terminate.py index d5fd949cba..5ecb6758ef 100644 --- a/pyrogram/methods/auth/terminate.py +++ b/pyrogram/methods/auth/terminate.py @@ -41,7 +41,7 @@ async def terminate( if self.takeout_id: await self.invoke(raw.functions.account.FinishTakeoutSession()) - log.info("Takeout session %s finished", self.takeout_id) + log.warning("Takeout session %s finished", self.takeout_id) await self.storage.save() await self.dispatcher.stop() @@ -51,11 +51,4 @@ async def terminate( self.media_sessions.clear() - self.updates_watchdog_event.set() - - if self.updates_watchdog_task is not None: - await self.updates_watchdog_task - - self.updates_watchdog_event.clear() - self.is_initialized = False diff --git a/pyrogram/methods/bots/send_inline_bot_result.py b/pyrogram/methods/bots/send_inline_bot_result.py index f29d8eb9cd..0f31880636 100644 --- a/pyrogram/methods/bots/send_inline_bot_result.py +++ b/pyrogram/methods/bots/send_inline_bot_result.py @@ -30,7 +30,7 @@ async def send_inline_bot_result( result_id: str, disable_notification: bool = None, reply_to_message_id: int = None - ) -> "raw.base.Updates": + ): """Send an inline bot result. Bot results can be retrieved using :meth:`~pyrogram.Client.get_inline_bot_results` @@ -56,7 +56,7 @@ async def send_inline_bot_result( If the message is a reply, ID of the original message. Returns: - :obj:`~pyrogram.raw.base.Updates`: Currently, on success, a raw result is returned. + :obj:`~pyrogram.types.Message`: On success, the sent inline result message is returned. Example: .. code-block:: python diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py index 7ab424a4f2..77fa673ae6 100644 --- a/pyrogram/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.py @@ -16,10 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import asyncio -import io import os import re +import io +import asyncio +import io import pyrogram from pyrogram import raw @@ -79,6 +80,8 @@ async def edit_inline_media( caption = media.caption parse_mode = media.parse_mode + is_photo = isinstance(media, types.InputMediaPhoto) + is_bytes_io = isinstance(media.media, io.BytesIO) is_uploaded_file = is_bytes_io or os.path.isfile(media.media) @@ -96,16 +99,15 @@ async def edit_inline_media( else: filename_attribute = [] - if isinstance(media, types.InputMediaPhoto): + + if is_photo: if is_uploaded_file: media = raw.types.InputMediaUploadedPhoto( - file=await self.save_file(media.media), - spoiler=media.has_spoiler + file=await self.save_file(media.media) ) elif is_external_url: media = raw.types.InputMediaPhotoExternal( - url=media.media, - spoiler=media.has_spoiler + url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO) @@ -115,20 +117,18 @@ async def edit_inline_media( mime_type=(None if is_bytes_io else self.guess_mime_type(media.media)) or "video/mp4", thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), - spoiler=media.has_spoiler, attributes=[ - raw.types.DocumentAttributeVideo( - supports_streaming=media.supports_streaming or None, - duration=media.duration, - w=media.width, - h=media.height - ) - ] + filename_attribute + raw.types.DocumentAttributeVideo( + supports_streaming=media.supports_streaming or None, + duration=media.duration, + w=media.width, + h=media.height + ) + ] + filename_attribute ) elif is_external_url: media = raw.types.InputMediaDocumentExternal( - url=media.media, - spoiler=media.has_spoiler + url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO) @@ -139,12 +139,12 @@ async def edit_inline_media( thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ - raw.types.DocumentAttributeAudio( - duration=media.duration, - performer=media.performer, - title=media.title - ) - ] + filename_attribute + raw.types.DocumentAttributeAudio( + duration=media.duration, + performer=media.performer, + title=media.title + ) + ] + filename_attribute ) elif is_external_url: media = raw.types.InputMediaDocumentExternal( @@ -158,22 +158,20 @@ async def edit_inline_media( mime_type=(None if is_bytes_io else self.guess_mime_type(media.media)) or "video/mp4", thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), - spoiler=media.has_spoiler, attributes=[ - raw.types.DocumentAttributeVideo( - supports_streaming=True, - duration=media.duration, - w=media.width, - h=media.height - ), - raw.types.DocumentAttributeAnimated() - ] + filename_attribute, + raw.types.DocumentAttributeVideo( + supports_streaming=True, + duration=media.duration, + w=media.width, + h=media.height + ), + raw.types.DocumentAttributeAnimated() + ] + filename_attribute, nosound_video=True ) elif is_external_url: media = raw.types.InputMediaDocumentExternal( - url=media.media, - spoiler=media.has_spoiler + url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION) @@ -198,6 +196,7 @@ async def edit_inline_media( session = await get_session(self, dc_id) + if is_uploaded_file: uploaded_media = await self.invoke( raw.functions.messages.UploadMedia( @@ -211,15 +210,13 @@ async def edit_inline_media( id=uploaded_media.photo.id, access_hash=uploaded_media.photo.access_hash, file_reference=uploaded_media.photo.file_reference - ), - spoiler=getattr(media, "has_spoiler", None) - ) if isinstance(media, types.InputMediaPhoto) else raw.types.InputMediaDocument( + ) + ) if is_photo else raw.types.InputMediaDocument( id=raw.types.InputDocument( id=uploaded_media.document.id, access_hash=uploaded_media.document.access_hash, - file_reference=uploaded_media.document.file_reference - ), - spoiler=getattr(media, "has_spoiler", None) + file_reference=uploaded_media.document.file_reference + ) ) else: actual_media = media diff --git a/pyrogram/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py index 5a34f13875..16efb5b858 100644 --- a/pyrogram/methods/messages/edit_message_media.py +++ b/pyrogram/methods/messages/edit_message_media.py @@ -16,9 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io import os import re +import io from typing import Union import pyrogram @@ -93,40 +93,36 @@ async def edit_message_media( if isinstance(media, types.InputMediaPhoto): if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): - uploaded_media = await self.invoke( + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedPhoto( - file=await self.save_file(media.media), - spoiler=media.has_spoiler + file=await self.save_file(media.media) ) ) ) media = raw.types.InputMediaPhoto( id=raw.types.InputPhoto( - id=uploaded_media.photo.id, - access_hash=uploaded_media.photo.access_hash, - file_reference=uploaded_media.photo.file_reference - ), - spoiler=media.has_spoiler + id=media.photo.id, + access_hash=media.photo.access_hash, + file_reference=media.photo.file_reference + ) ) elif re.match("^https?://", media.media): media = raw.types.InputMediaPhotoExternal( - url=media.media, - spoiler=media.has_spoiler + url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO) elif isinstance(media, types.InputMediaVideo): if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): - uploaded_media = await self.invoke( + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "video/mp4", thumb=await self.save_file(media.thumb), - spoiler=media.has_spoiler, file=await self.save_file(media.media), attributes=[ raw.types.DocumentAttributeVideo( @@ -145,16 +141,14 @@ async def edit_message_media( media = raw.types.InputMediaDocument( id=raw.types.InputDocument( - id=uploaded_media.document.id, - access_hash=uploaded_media.document.access_hash, - file_reference=uploaded_media.document.file_reference - ), - spoiler=media.has_spoiler + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference + ) ) elif re.match("^https?://", media.media): media = raw.types.InputMediaDocumentExternal( - url=media.media, - spoiler=media.has_spoiler + url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO) @@ -196,13 +190,12 @@ async def edit_message_media( media = utils.get_input_media_from_file_id(media.media, FileType.AUDIO) elif isinstance(media, types.InputMediaAnimation): if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): - uploaded_media = await self.invoke( + media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "video/mp4", thumb=await self.save_file(media.thumb), - spoiler=media.has_spoiler, file=await self.save_file(media.media), attributes=[ raw.types.DocumentAttributeVideo( @@ -222,16 +215,14 @@ async def edit_message_media( media = raw.types.InputMediaDocument( id=raw.types.InputDocument( - id=uploaded_media.document.id, - access_hash=uploaded_media.document.access_hash, - file_reference=uploaded_media.document.file_reference - ), - spoiler=media.has_spoiler + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference + ) ) elif re.match("^https?://", media.media): media = raw.types.InputMediaDocumentExternal( - url=media.media, - spoiler=media.has_spoiler + url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION) diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py index bac16bac9f..ec85dc0569 100644 --- a/pyrogram/methods/messages/send_animation.py +++ b/pyrogram/methods/messages/send_animation.py @@ -39,7 +39,6 @@ async def send_animation( unsave: bool = False, parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, - has_spoiler: bool = None, duration: int = 0, width: int = 0, height: int = 0, @@ -89,9 +88,6 @@ async def send_animation( caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. - has_spoiler (``bool``, *optional*): - Pass True if the animation needs to be covered with a spoiler animation. - duration (``int``, *optional*): Duration of sent animation in seconds. @@ -184,7 +180,6 @@ async def progress(current, total): mime_type=self.guess_mime_type(animation) or "video/mp4", file=file, thumb=thumb, - spoiler=has_spoiler, attributes=[ raw.types.DocumentAttributeVideo( supports_streaming=True, @@ -198,8 +193,7 @@ async def progress(current, total): ) elif re.match("^https?://", animation): media = raw.types.InputMediaDocumentExternal( - url=animation, - spoiler=has_spoiler + url=animation ) else: media = utils.get_input_media_from_file_id(animation, FileType.ANIMATION) @@ -210,7 +204,6 @@ async def progress(current, total): mime_type=self.guess_mime_type(file_name or animation.name) or "video/mp4", file=file, thumb=thumb, - spoiler=has_spoiler, attributes=[ raw.types.DocumentAttributeVideo( supports_streaming=True, diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index a8b905de23..0dfbbaa236 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -100,8 +100,7 @@ async def send_media_group( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedPhoto( - file=await self.save_file(i.media), - spoiler=i.has_spoiler + file=await self.save_file(i.media) ) ) ) @@ -111,16 +110,14 @@ async def send_media_group( id=media.photo.id, access_hash=media.photo.access_hash, file_reference=media.photo.file_reference - ), - spoiler=i.has_spoiler + ) ) elif re.match("^https?://", i.media): media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaPhotoExternal( - url=i.media, - spoiler=i.has_spoiler + url=i.media ) ) ) @@ -130,8 +127,7 @@ async def send_media_group( id=media.photo.id, access_hash=media.photo.access_hash, file_reference=media.photo.file_reference - ), - spoiler=i.has_spoiler + ) ) else: media = utils.get_input_media_from_file_id(i.media, FileType.PHOTO) @@ -140,8 +136,7 @@ async def send_media_group( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedPhoto( - file=await self.save_file(i.media), - spoiler=i.has_spoiler + file=await self.save_file(i.media) ) ) ) @@ -151,8 +146,7 @@ async def send_media_group( id=media.photo.id, access_hash=media.photo.access_hash, file_reference=media.photo.file_reference - ), - spoiler=i.has_spoiler + ) ) elif isinstance(i, types.InputMediaVideo): if isinstance(i.media, str): @@ -163,7 +157,6 @@ async def send_media_group( media=raw.types.InputMediaUploadedDocument( file=await self.save_file(i.media), thumb=await self.save_file(i.thumb), - spoiler=i.has_spoiler, mime_type=self.guess_mime_type(i.media) or "video/mp4", attributes=[ raw.types.DocumentAttributeVideo( @@ -183,16 +176,14 @@ async def send_media_group( id=media.document.id, access_hash=media.document.access_hash, file_reference=media.document.file_reference - ), - spoiler=i.has_spoiler + ) ) elif re.match("^https?://", i.media): media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaDocumentExternal( - url=i.media, - spoiler=i.has_spoiler + url=i.media ) ) ) @@ -202,8 +193,7 @@ async def send_media_group( id=media.document.id, access_hash=media.document.access_hash, file_reference=media.document.file_reference - ), - spoiler=i.has_spoiler + ) ) else: media = utils.get_input_media_from_file_id(i.media, FileType.VIDEO) @@ -214,7 +204,6 @@ async def send_media_group( media=raw.types.InputMediaUploadedDocument( file=await self.save_file(i.media), thumb=await self.save_file(i.thumb), - spoiler=i.has_spoiler, mime_type=self.guess_mime_type(getattr(i.media, "name", "video.mp4")) or "video/mp4", attributes=[ raw.types.DocumentAttributeVideo( @@ -234,8 +223,7 @@ async def send_media_group( id=media.document.id, access_hash=media.document.access_hash, file_reference=media.document.file_reference - ), - spoiler=i.has_spoiler + ) ) elif isinstance(i, types.InputMediaAudio): if isinstance(i.media, str): diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index 61298a5c68..994f0c9322 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -37,7 +37,6 @@ async def send_photo( caption: str = "", parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, - has_spoiler: bool = None, ttl_seconds: int = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -79,9 +78,6 @@ async def send_photo( caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. - has_spoiler (``bool``, *optional*): - Pass True if the photo needs to be covered with a spoiler animation. - ttl_seconds (``int``, *optional*): Self-Destruct Timer. If you set a timer, the photo will self-destruct in *ttl_seconds* @@ -153,14 +149,12 @@ async def send_photo( file = await self.save_file(photo, progress=progress, progress_args=progress_args) media = raw.types.InputMediaUploadedPhoto( file=file, - ttl_seconds=ttl_seconds, - spoiler=has_spoiler, + ttl_seconds=ttl_seconds ) elif re.match("^https?://", photo): media = raw.types.InputMediaPhotoExternal( url=photo, - ttl_seconds=ttl_seconds, - spoiler=has_spoiler + ttl_seconds=ttl_seconds ) else: media = utils.get_input_media_from_file_id(photo, FileType.PHOTO, ttl_seconds=ttl_seconds) @@ -168,8 +162,7 @@ async def send_photo( file = await self.save_file(photo, progress=progress, progress_args=progress_args) media = raw.types.InputMediaUploadedPhoto( file=file, - ttl_seconds=ttl_seconds, - spoiler=has_spoiler + ttl_seconds=ttl_seconds ) while True: diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index e869dd172d..c20530641c 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -38,7 +38,6 @@ async def send_video( caption: str = "", parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, - has_spoiler: bool = None, ttl_seconds: int = None, duration: int = 0, width: int = 0, @@ -86,9 +85,6 @@ async def send_video( caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. - has_spoiler (``bool``, *optional*): - Pass True if the video needs to be covered with a spoiler animation. - ttl_seconds (``int``, *optional*): Self-Destruct Timer. If you set a timer, the video will self-destruct in *ttl_seconds* @@ -189,7 +185,6 @@ async def progress(current, total): mime_type=self.guess_mime_type(video) or "video/mp4", file=file, ttl_seconds=ttl_seconds, - spoiler=has_spoiler, thumb=thumb, attributes=[ raw.types.DocumentAttributeVideo( @@ -204,8 +199,7 @@ async def progress(current, total): elif re.match("^https?://", video): media = raw.types.InputMediaDocumentExternal( url=video, - ttl_seconds=ttl_seconds, - spoiler=has_spoiler + ttl_seconds=ttl_seconds ) else: media = utils.get_input_media_from_file_id(video, FileType.VIDEO, ttl_seconds=ttl_seconds) @@ -216,7 +210,6 @@ async def progress(current, total): mime_type=self.guess_mime_type(file_name or video.name) or "video/mp4", file=file, ttl_seconds=ttl_seconds, - spoiler=has_spoiler, thumb=thumb, attributes=[ raw.types.DocumentAttributeVideo( diff --git a/pyrogram/methods/utilities/start.py b/pyrogram/methods/utilities/start.py index d8314da182..19a7eb7cf3 100644 --- a/pyrogram/methods/utilities/start.py +++ b/pyrogram/methods/utilities/start.py @@ -63,7 +63,7 @@ async def main(): if not await self.storage.is_bot() and self.takeout: self.takeout_id = (await self.invoke(raw.functions.account.InitTakeoutSession())).id - log.info("Takeout session %s initiated", self.takeout_id) + log.warning("Takeout session %s initiated", self.takeout_id) await self.invoke(raw.functions.updates.GetState()) except (Exception, KeyboardInterrupt): diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index 46722a8c40..7edb7f3c99 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -131,7 +131,7 @@ async def parse(self, text: str): for tag, entities in parser.tag_entities.items(): unclosed_tags.append(f"<{tag}> (x{len(entities)})") - log.info("Unclosed tags: %s", ", ".join(unclosed_tags)) + log.warning("Unclosed tags: %s", ", ".join(unclosed_tags)) entities = [] diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index c5d9cd9a50..8c01211926 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -22,6 +22,7 @@ from hashlib import sha1 from io import BytesIO from os import urandom +from typing import Optional import pyrogram from pyrogram import raw @@ -37,13 +38,21 @@ class Auth: MAX_RETRIES = 5 - def __init__(self, client: "pyrogram.Client", dc_id: int, test_mode: bool): + def __init__( + self, + client: "pyrogram.Client", + dc_id: int, + test_mode: bool + ): self.dc_id = dc_id self.test_mode = test_mode self.ipv6 = client.ipv6 + self.alt_port = client.alt_port self.proxy = client.proxy + self.connection_factory = client.connection_factory + self.protocol_factory = client.protocol_factory - self.connection = None + self.connection: Optional[Connection] = None @staticmethod def pack(data: TLObject) -> bytes: @@ -76,7 +85,15 @@ async def create(self): # 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(self.dc_id, self.test_mode, self.ipv6, self.proxy) + self.connection = self.connection_factory( + dc_id=self.dc_id, + test_mode=self.test_mode, + ipv6=self.ipv6, + alt_port=self.alt_port, + proxy=self.proxy, + media=False, + protocol_factory=self.protocol_factory + ) try: log.info("Start creating a new auth key on DC%s", self.dc_id) @@ -278,4 +295,4 @@ async def create(self): else: return auth_key finally: - await self.connection.close() + self.connection.close() diff --git a/pyrogram/session/internals/data_center.py b/pyrogram/session/internals/data_center.py index d314626352..4fce19aa24 100644 --- a/pyrogram/session/internals/data_center.py +++ b/pyrogram/session/internals/data_center.py @@ -31,8 +31,7 @@ class DataCenter: 2: "149.154.167.51", 3: "149.154.175.100", 4: "149.154.167.91", - 5: "91.108.56.130", - 203: "91.105.192.100" + 5: "91.108.56.130" } PROD_MEDIA = { @@ -51,8 +50,7 @@ class DataCenter: 2: "2001:67c:4e8:f002::a", 3: "2001:b28:f23d:f003::a", 4: "2001:67c:4e8:f004::a", - 5: "2001:b28:f23f:f005::a", - 203: "2a0a:f280:0203:000a:5000:0000:0000:0100" + 5: "2001:b28:f23f:f005::a" } PROD_IPV6_MEDIA = { diff --git a/pyrogram/session/internals/msg_id.py b/pyrogram/session/internals/msg_id.py index da2e264ff6..58e3087c51 100644 --- a/pyrogram/session/internals/msg_id.py +++ b/pyrogram/session/internals/msg_id.py @@ -27,9 +27,9 @@ class MsgId: offset = 0 def __new__(cls) -> int: - now = int(time.time()) + now = time.time() cls.offset = (cls.offset + 4) if now == cls.last_time else 0 - msg_id = (now * 2 ** 32) + cls.offset + msg_id = int(now * 2 ** 32) + cls.offset cls.last_time = now return msg_id diff --git a/pyrogram/session/internals/seq_no.py b/pyrogram/session/internals/seq_no.py index 79501d9863..0abc4a2f72 100644 --- a/pyrogram/session/internals/seq_no.py +++ b/pyrogram/session/internals/seq_no.py @@ -16,15 +16,19 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from threading import Lock + class SeqNo: def __init__(self): self.content_related_messages_sent = 0 + self.lock = Lock() def __call__(self, is_content_related: bool) -> int: - seq_no = (self.content_related_messages_sent * 2) + (1 if is_content_related else 0) + with self.lock: + seq_no = (self.content_related_messages_sent * 2) + (1 if is_content_related else 0) - if is_content_related: - self.content_related_messages_sent += 1 + if is_content_related: + self.content_related_messages_sent += 1 - return seq_no + return seq_no diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 0ed967a1a2..c345f39512 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -17,12 +17,11 @@ # along with Pyrogram. If not, see . import asyncio -import bisect import logging import os from hashlib import sha1 from io import BytesIO - +from typing import Optional import pyrogram from pyrogram import raw from pyrogram.connection import Connection @@ -33,7 +32,7 @@ ) from pyrogram.raw.all import layer from pyrogram.raw.core import TLObject, MsgContainer, Int, FutureSalts -from .internals import MsgId, MsgFactory +from .internals import MsgFactory log = logging.getLogger(__name__) @@ -45,19 +44,12 @@ def __init__(self): class Session: - START_TIMEOUT = 2 + START_TIMEOUT = 1 WAIT_TIMEOUT = 15 SLEEP_THRESHOLD = 10 - MAX_RETRIES = 10 - ACKS_THRESHOLD = 10 + MAX_RETRIES = 5 + ACKS_THRESHOLD = 8 PING_INTERVAL = 5 - STORED_MSG_IDS_MAX_SIZE = 1000 * 2 - - TRANSPORT_ERRORS = { - 404: "auth key not found", - 429: "transport flood", - 444: "invalid DC" - } def __init__( self, @@ -75,7 +67,7 @@ def __init__( self.is_media = is_media self.is_cdn = is_cdn - self.connection = None + self.connection: Optional[Connection] = None self.auth_key_id = sha1(auth_key).digest()[-8:] @@ -101,12 +93,14 @@ def __init__( async def start(self): while True: - self.connection = Connection( - self.dc_id, - self.test_mode, - self.client.ipv6, - self.client.proxy, - self.is_media + self.connection = self.client.connection_factory( + dc_id=self.dc_id, + test_mode=self.test_mode, + ipv6=self.client.ipv6, + alt_port=self.client.alt_port, + proxy=self.client.proxy, + media=self.is_media, + protocol_factory=self.client.protocol_factory ) try: @@ -157,8 +151,6 @@ async def start(self): async def stop(self): self.is_started.clear() - self.stored_msg_ids.clear() - self.ping_task_event.set() if self.ping_task is not None: @@ -166,11 +158,14 @@ async def stop(self): self.ping_task_event.clear() - await self.connection.close() + self.connection.close() if self.recv_task: await self.recv_task + for i in self.results.values(): + i.event.set() + if not self.is_media and callable(self.client.disconnect_handler): try: await self.client.disconnect_handler(self.client) @@ -184,14 +179,19 @@ async def restart(self): await self.start() async def handle_packet(self, packet): - data = await self.loop.run_in_executor( - pyrogram.crypto_executor, - mtproto.unpack, - BytesIO(packet), - self.session_id, - self.auth_key, - self.auth_key_id - ) + try: + data = await self.loop.run_in_executor( + pyrogram.crypto_executor, + mtproto.unpack, + BytesIO(packet), + self.session_id, + self.auth_key, + self.auth_key_id, + self.stored_msg_ids + ) + except SecurityCheckMismatch as e: + log.warning("Discarding packet: %s", e) + return messages = ( data.body.messages @@ -208,33 +208,6 @@ async def handle_packet(self, packet): else: self.pending_acks.add(msg.msg_id) - try: - if len(self.stored_msg_ids) > Session.STORED_MSG_IDS_MAX_SIZE: - del self.stored_msg_ids[:Session.STORED_MSG_IDS_MAX_SIZE // 2] - - if self.stored_msg_ids: - if msg.msg_id < self.stored_msg_ids[0]: - raise SecurityCheckMismatch("The msg_id is lower than all the stored values") - - if msg.msg_id in self.stored_msg_ids: - raise SecurityCheckMismatch("The msg_id is equal to any of the stored values") - - time_diff = (msg.msg_id - MsgId()) / 2 ** 32 - - if time_diff > 30: - raise SecurityCheckMismatch("The msg_id belongs to over 30 seconds in the future. " - "Most likely the client time has to be synchronized.") - - if time_diff < -300: - raise SecurityCheckMismatch("The msg_id belongs to over 300 seconds in the past. " - "Most likely the client time has to be synchronized.") - except SecurityCheckMismatch as e: - log.info("Discarding packet: %s", e) - await self.connection.close() - return - else: - bisect.insort(self.stored_msg_ids, msg.msg_id) - if isinstance(msg.body, (raw.types.MsgDetailedInfo, raw.types.MsgNewDetailedInfo)): self.pending_acks.add(msg.body.answer_msg_id) continue @@ -298,12 +271,7 @@ async def recv_worker(self): if packet is None or len(packet) == 4: if packet: - error_code = -Int.read(BytesIO(packet)) - - log.warning( - "Server sent transport error: %s (%s)", - error_code, Session.TRANSPORT_ERRORS.get(error_code, "unknown error") - ) + log.warning('Server sent "%s"', Int.read(BytesIO(packet))) if self.is_started.is_set(): self.loop.create_task(self.restart()) @@ -344,26 +312,23 @@ async def send(self, data: TLObject, wait_response: bool = True, timeout: float await asyncio.wait_for(self.results[msg_id].event.wait(), timeout) except asyncio.TimeoutError: pass - - result = self.results.pop(msg_id).value + finally: + result = self.results.pop(msg_id).value if result is None: raise TimeoutError("Request timed out") - - if isinstance(result, raw.types.RpcError): + elif isinstance(result, raw.types.RpcError): if isinstance(data, (raw.functions.InvokeWithoutUpdates, raw.functions.InvokeWithTakeout)): data = data.query RPCError.raise_it(result, type(data)) - - if isinstance(result, raw.types.BadMsgNotification): - log.warning("%s: %s", BadMsgNotification.__name__, BadMsgNotification(result.error_code)) - - if isinstance(result, raw.types.BadServerSalt): + elif isinstance(result, raw.types.BadMsgNotification): + raise BadMsgNotification(result.error_code) + elif isinstance(result, raw.types.BadServerSalt): self.salt = result.new_server_salt return await self.send(data, wait_response, timeout) - - return result + else: + return result async def invoke( self, diff --git a/pyrogram/storage/file_storage.py b/pyrogram/storage/file_storage.py index aebe917671..986787cd95 100644 --- a/pyrogram/storage/file_storage.py +++ b/pyrogram/storage/file_storage.py @@ -38,13 +38,13 @@ def update(self): version = self.version() if version == 1: - with self.conn: + with self.lock, self.conn: self.conn.execute("DELETE FROM peers") version += 1 if version == 2: - with self.conn: + with self.lock, self.conn: self.conn.execute("ALTER TABLE sessions ADD api_id INTEGER") version += 1 @@ -63,7 +63,10 @@ async def open(self): self.update() with self.conn: - self.conn.execute("VACUUM") + try: # Python 3.6.0 (exactly this version) is bugged and won't successfully execute the vacuum + self.conn.execute("VACUUM") + except sqlite3.OperationalError: + pass async def delete(self): os.remove(self.database) diff --git a/pyrogram/storage/sqlite_storage.py b/pyrogram/storage/sqlite_storage.py index e28b9b746e..15e5ddc0c5 100644 --- a/pyrogram/storage/sqlite_storage.py +++ b/pyrogram/storage/sqlite_storage.py @@ -19,6 +19,7 @@ import inspect import sqlite3 import time +from threading import Lock from typing import List, Tuple, Any from pyrogram import raw @@ -97,9 +98,10 @@ def __init__(self, name: str): super().__init__(name) self.conn = None # type: sqlite3.Connection + self.lock = Lock() def create(self): - with self.conn: + with self.lock, self.conn: self.conn.executescript(SCHEMA) self.conn.execute( @@ -117,20 +119,24 @@ async def open(self): async def save(self): await self.date(int(time.time())) - self.conn.commit() + + with self.lock: + self.conn.commit() async def close(self): - self.conn.close() + with self.lock: + self.conn.close() async def delete(self): raise NotImplementedError async def update_peers(self, peers: List[Tuple[int, int, str, str, str]]): - self.conn.executemany( - "REPLACE INTO peers (id, access_hash, type, username, phone_number)" - "VALUES (?, ?, ?, ?, ?)", - peers - ) + with self.lock: + self.conn.executemany( + "REPLACE INTO peers (id, access_hash, type, username, phone_number)" + "VALUES (?, ?, ?, ?, ?)", + peers + ) async def get_peer_by_id(self, peer_id: int): r = self.conn.execute( @@ -179,7 +185,7 @@ def _get(self): def _set(self, value: Any): attr = inspect.stack()[2].function - with self.conn: + with self.lock, self.conn: self.conn.execute( f"UPDATE sessions SET {attr} = ?", (value,) @@ -215,7 +221,7 @@ def version(self, value: int = object): "SELECT number FROM version" ).fetchone()[0] else: - with self.conn: + with self.lock, self.conn: self.conn.execute( "UPDATE version SET number = ?", (value,) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 62a85b7593..ef249771fd 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -136,9 +136,6 @@ class Message(Object, Update): has_protected_content (``bool``, *optional*): True, if the message can't be forwarded. - has_media_spoiler (``bool``, *optional*): - True, if the message media is covered by a spoiler animation. - text (``str``, *optional*): For text messages, the actual UTF-8 text of the message, 0-4096 characters. If the message contains entities (bold, italic, ...) you can access *text.markdown* or @@ -335,7 +332,6 @@ def __init__( media_group_id: str = None, author_signature: str = None, has_protected_content: bool = None, - has_media_spoiler: bool = None, text: Str = None, entities: List["types.MessageEntity"] = None, caption_entities: List["types.MessageEntity"] = None, @@ -412,7 +408,6 @@ def __init__( self.media_group_id = media_group_id self.author_signature = author_signature self.has_protected_content = has_protected_content - self.has_media_spoiler = has_media_spoiler self.text = text self.entities = entities self.caption_entities = caption_entities @@ -663,13 +658,11 @@ async def _parse( media = message.media media_type = None - has_media_spoiler = None if media: if isinstance(media, raw.types.MessageMediaPhoto): photo = types.Photo._parse(client, media.photo, media.ttl_seconds) media_type = enums.MessageMediaType.PHOTO - has_media_spoiler = media.spoiler elif isinstance(media, raw.types.MessageMediaGeo): location = types.Location._parse(client, media.geo) media_type = enums.MessageMediaType.LOCATION @@ -698,7 +691,6 @@ async def _parse( video_attributes = attributes.get(raw.types.DocumentAttributeVideo, None) animation = types.Animation._parse(client, doc, video_attributes, file_name) media_type = enums.MessageMediaType.ANIMATION - has_media_spoiler = media.spoiler elif raw.types.DocumentAttributeSticker in attributes: sticker = await types.Sticker._parse(client, doc, attributes) media_type = enums.MessageMediaType.STICKER @@ -711,7 +703,6 @@ async def _parse( else: video = types.Video._parse(client, doc, video_attributes, file_name, media.ttl_seconds) media_type = enums.MessageMediaType.VIDEO - has_media_spoiler = media.spoiler elif raw.types.DocumentAttributeAudio in attributes: audio_attributes = attributes[raw.types.DocumentAttributeAudio] @@ -786,7 +777,6 @@ async def _parse( ), author_signature=message.post_author, has_protected_content=message.noforwards, - has_media_spoiler=has_media_spoiler, forward_from=forward_from, forward_sender_name=forward_sender_name, forward_from_chat=forward_from_chat, @@ -989,7 +979,6 @@ async def reply_animation( caption: str = "", parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, - has_spoiler: bool = None, duration: int = 0, width: int = 0, height: int = 0, @@ -1043,9 +1032,6 @@ async def reply_animation( caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. - has_spoiler (``bool``, *optional*): - Pass True if the animation needs to be covered with a spoiler animation. - duration (``int``, *optional*): Duration of sent animation in seconds. @@ -1114,7 +1100,6 @@ async def reply_animation( caption=caption, parse_mode=parse_mode, caption_entities=caption_entities, - has_spoiler=has_spoiler, duration=duration, width=width, height=height, @@ -1891,7 +1876,6 @@ async def reply_photo( caption: str = "", parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, - has_spoiler: bool = None, ttl_seconds: int = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -1942,9 +1926,6 @@ async def reply_photo( caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. - has_spoiler (``bool``, *optional*): - Pass True if the photo needs to be covered with a spoiler animation. - ttl_seconds (``int``, *optional*): Self-Destruct Timer. If you set a timer, the photo will self-destruct in *ttl_seconds* @@ -2003,7 +1984,6 @@ async def reply_photo( caption=caption, parse_mode=parse_mode, caption_entities=caption_entities, - has_spoiler=has_spoiler, ttl_seconds=ttl_seconds, disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, @@ -2362,7 +2342,6 @@ async def reply_video( caption: str = "", parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, - has_spoiler: bool = None, ttl_seconds: int = None, duration: int = 0, width: int = 0, @@ -2418,9 +2397,6 @@ async def reply_video( caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. - has_spoiler (``bool``, *optional*): - Pass True if the video needs to be covered with a spoiler animation. - ttl_seconds (``int``, *optional*): Self-Destruct Timer. If you set a timer, the video will self-destruct in *ttl_seconds* @@ -2497,7 +2473,6 @@ async def reply_video( caption=caption, parse_mode=parse_mode, caption_entities=caption_entities, - has_spoiler=has_spoiler, ttl_seconds=ttl_seconds, duration=duration, width=width, diff --git a/pyrogram/utils.py b/pyrogram/utils.py index f7fe59706d..f2daba2ba2 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -198,7 +198,8 @@ def unpack_inline_message_id(inline_message_id: str) -> "raw.base.InputBotInline ) -MIN_CHANNEL_ID = -1002147483647 +MIN_CHANNEL_ID_OLD = -1002147483647 +MIN_CHANNEL_ID = -1009999999999 MAX_CHANNEL_ID = -1000000000000 MIN_CHAT_ID = -2147483647 MAX_USER_ID_OLD = 2147483647