diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index cadf0bbb..92956c78 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -108,7 +108,7 @@ userStatusLastMonth#65899777 flags:# by_me:flags.0?true = UserStatus; chatEmpty#29562865 id:long = Chat; chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#6592a1a7 id:long title:string = Chat; -channel#7482147e 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:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true signature_profiles:flags2.12?true 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 stories_max_id:flags2.4?int color:flags2.7?PeerColor profile_color:flags2.8?PeerColor emoji_status:flags2.9?EmojiStatus level:flags2.10?int subscription_until_date:flags2.11?int bot_verification_icon:flags2.13?long send_paid_messages_stars:flags2.14?long = Chat; +channel#fe685355 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:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true signature_profiles:flags2.12?true autotranslation:flags2.15?true broadcast_messages_allowed:flags2.16?true monoforum:flags2.17?true forum_tabs:flags2.19?true 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 stories_max_id:flags2.4?int color:flags2.7?PeerColor profile_color:flags2.8?PeerColor emoji_status:flags2.9?EmojiStatus level:flags2.10?int subscription_until_date:flags2.11?int bot_verification_icon:flags2.13?long send_paid_messages_stars:flags2.14?long linked_monoforum_id:flags2.18?long = 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#2633421b 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 reactions_limit:flags.20?int = ChatFull; @@ -193,9 +193,10 @@ messageActionPaymentRefunded#41b3e202 flags:# peer:Peer currency:string total_am messageActionGiftStars#45d5b021 flags:# currency:string amount:long stars:long crypto_currency:flags.0?string crypto_amount:flags.0?long transaction_id:flags.1?string = MessageAction; messageActionPrizeStars#b00c47a2 flags:# unclaimed:flags.0?true stars:long transaction_id:string boost_peer:Peer giveaway_msg_id:int = MessageAction; messageActionStarGift#4717e8a4 flags:# name_hidden:flags.0?true saved:flags.2?true converted:flags.3?true upgraded:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true gift:StarGift message:flags.1?TextWithEntities convert_stars:flags.4?long upgrade_msg_id:flags.5?int upgrade_stars:flags.8?long from_id:flags.11?Peer peer:flags.12?Peer saved_id:flags.12?long = MessageAction; -messageActionStarGiftUnique#acdfcb81 flags:# upgrade:flags.0?true transferred:flags.1?true saved:flags.2?true refunded:flags.5?true gift:StarGift can_export_at:flags.3?int transfer_stars:flags.4?long from_id:flags.6?Peer peer:flags.7?Peer saved_id:flags.7?long = MessageAction; +messageActionStarGiftUnique#2e3ae60e flags:# upgrade:flags.0?true transferred:flags.1?true saved:flags.2?true refunded:flags.5?true gift:StarGift can_export_at:flags.3?int transfer_stars:flags.4?long from_id:flags.6?Peer peer:flags.7?Peer saved_id:flags.7?long resale_stars:flags.8?long can_transfer_at:flags.9?int can_resell_at:flags.10?int = MessageAction; messageActionPaidMessagesRefunded#ac1f1fcd count:int stars:long = MessageAction; -messageActionPaidMessagesPrice#bcd71419 stars:long = MessageAction; +messageActionPaidMessagesPrice#84b88578 flags:# broadcast_messages_allowed:flags.0?true stars:long = MessageAction; +messageActionConferenceCall#2ffe2f7a flags:# missed:flags.0?true active:flags.1?true video:flags.4?true call_id:long duration:flags.2?int other_participants:flags.3?Vector = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?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; @@ -340,7 +341,7 @@ updateBotCallbackQuery#b9cfc48d flags:# query_id:long user_id:long peer:Peer msg updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update; updateInlineBotCallbackQuery#691e9052 flags:# query_id:long user_id:long msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update; updateReadChannelOutbox#b75f99a9 channel_id:long max_id:int = Update; -updateDraftMessage#1b49ec6d flags:# peer:Peer top_msg_id:flags.0?int draft:DraftMessage = Update; +updateDraftMessage#edfc111e flags:# peer:Peer top_msg_id:flags.0?int saved_peer_id:flags.1?Peer draft:DraftMessage = Update; updateReadFeaturedStickers#571d2742 = Update; updateRecentStickers#9a422c20 = Update; updateConfig#a229dd06 = Update; @@ -356,10 +357,10 @@ updatePhoneCall#ab0f6b1e phone_call:PhoneCall = Update; updateLangPackTooLong#46560264 lang_code:string = Update; updateLangPack#56022f4d difference:LangPackDifference = Update; updateFavedStickers#e511996d = Update; -updateChannelReadMessagesContents#ea29055d flags:# channel_id:long top_msg_id:flags.0?int messages:Vector = Update; +updateChannelReadMessagesContents#25f324f7 flags:# channel_id:long top_msg_id:flags.0?int saved_peer_id:flags.1?Peer messages:Vector = Update; updateContactsReset#7084a7be = Update; updateChannelAvailableMessages#b23fc698 channel_id:long available_min_id:int = Update; -updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Update; +updateDialogUnreadMark#b658f23e flags:# unread:flags.0?true peer:DialogPeer saved_peer_id:flags.1?Peer = Update; updateMessagePoll#aca1657b flags:# poll_id:long poll:flags.0?Poll results:PollResults = Update; updateChatDefaultBannedRights#54c01850 peer:Peer default_banned_rights:ChatBannedRights version:int = Update; updateFolderPeers#19360dc0 folder_peers:Vector pts:int pts_count:int = Update; @@ -393,7 +394,7 @@ updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJ updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector = Update; updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector = Update; updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update; -updateMessageReactions#5e1b3cb8 flags:# peer:Peer msg_id:int top_msg_id:flags.0?int reactions:MessageReactions = Update; +updateMessageReactions#1e297bfa flags:# peer:Peer msg_id:int top_msg_id:flags.0?int saved_peer_id:flags.1?Peer reactions:MessageReactions = Update; updateAttachMenuBots#17b7a20b = Update; updateWebViewResultSent#1592b79d query_id:long = Update; updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update; @@ -440,6 +441,9 @@ updateStarsRevenueStatus#a584b019 peer:Peer status:StarsRevenueStatus = Update; updateBotPurchasedPaidMedia#283bd312 user_id:long payload:string qts:int = Update; updatePaidReactionPrivacy#8b725fce private:PaidReactionPrivacy = Update; updateSentPhoneCode#504aa18f sent_code:auth.SentCode = Update; +updateGroupCallChainBlocks#a477288f call:InputGroupCall sub_chain_id:int blocks:Vector next_offset:int = Update; +updateReadMonoForumInbox#77b0e372 channel_id:long saved_peer_id:Peer read_max_id:int = Update; +updateReadMonoForumOutbox#a4a79376 channel_id:long saved_peer_id:Peer read_max_id:int = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -614,7 +618,7 @@ messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMess webPageEmpty#211a1788 flags:# id:long url:flags.0?string = WebPage; webPagePending#b0d13e47 flags:# id:long url:flags.0?string date:int = WebPage; -webPage#e89c45b2 flags:# has_large_media:flags.13?true id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector = WebPage; +webPage#e89c45b2 flags:# has_large_media:flags.13?true video_cover_photo:flags.14?true id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector = WebPage; webPageNotModified#7311ca11 flags:# cached_page_views:flags.0?int = WebPage; authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true encrypted_requests_disabled:flags.3?true call_requests_disabled:flags.4?true unconfirmed:flags.5?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization; @@ -910,7 +914,7 @@ phoneCallDiscardReasonMissed#85e42301 = PhoneCallDiscardReason; phoneCallDiscardReasonDisconnect#e095c1a0 = PhoneCallDiscardReason; phoneCallDiscardReasonHangup#57adc690 = PhoneCallDiscardReason; phoneCallDiscardReasonBusy#faf7e8c9 = PhoneCallDiscardReason; -phoneCallDiscardReasonAllowGroupCall#afe2b839 encrypted_key:bytes = PhoneCallDiscardReason; +phoneCallDiscardReasonMigrateConferenceCall#9fbbf1f7 slug:string = PhoneCallDiscardReason; dataJSON#7d748d04 data:string = DataJSON; @@ -965,11 +969,11 @@ inputStickerSetItem#32da9e9c flags:# document:InputDocument emoji:string mask_co inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall; phoneCallEmpty#5366c915 id:long = PhoneCall; -phoneCallWaiting#eed42858 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long protocol:PhoneCallProtocol receive_date:flags.0?int conference_call:flags.8?InputGroupCall = PhoneCall; -phoneCallRequested#45361c63 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_hash:bytes protocol:PhoneCallProtocol conference_call:flags.8?InputGroupCall = PhoneCall; -phoneCallAccepted#22fd7181 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_b:bytes protocol:PhoneCallProtocol conference_call:flags.8?InputGroupCall = PhoneCall; -phoneCall#3ba5940c flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int custom_parameters:flags.7?DataJSON conference_call:flags.8?InputGroupCall = PhoneCall; -phoneCallDiscarded#f9d25503 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int conference_call:flags.8?InputGroupCall = PhoneCall; +phoneCallWaiting#c5226f17 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall; +phoneCallRequested#14b0ed0c flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall; +phoneCallAccepted#3660c311 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_b:bytes protocol:PhoneCallProtocol = PhoneCall; +phoneCall#30535af5 flags:# p2p_allowed:flags.5?true video:flags.6?true conference_supported:flags.8?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int custom_parameters:flags.7?DataJSON = PhoneCall; +phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall; phoneConnection#9cc123c7 flags:# tcp:flags.0?true id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; phoneConnectionWebrtc#635fe375 flags:# turn:flags.0?true stun:flags.1?true id:long ip:string ipv6:string port:int username:string password:string = PhoneConnection; @@ -1043,6 +1047,7 @@ channelAdminLogEventActionChangeEmojiStatus#3ea9feb1 prev_value:EmojiStatus new_ channelAdminLogEventActionChangeEmojiStickerSet#46d840ab prev_stickerset:InputStickerSet new_stickerset:InputStickerSet = ChannelAdminLogEventAction; channelAdminLogEventActionToggleSignatureProfiles#60a79c79 new_value:Bool = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantSubExtend#64642db3 prev_participant:ChannelParticipant new_participant:ChannelParticipant = ChannelAdminLogEventAction; +channelAdminLogEventActionToggleAutotranslation#c517f77e new_value:Bool = ChannelAdminLogEventAction; channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; @@ -1310,7 +1315,7 @@ statsGraph#8ea464b6 flags:# json:DataJSON zoom_token:flags.0?string = StatsGraph stats.broadcastStats#396ca5fc period:StatsDateRangeDays followers:StatsAbsValueAndPrev views_per_post:StatsAbsValueAndPrev shares_per_post:StatsAbsValueAndPrev reactions_per_post:StatsAbsValueAndPrev views_per_story:StatsAbsValueAndPrev shares_per_story:StatsAbsValueAndPrev reactions_per_story:StatsAbsValueAndPrev enabled_notifications:StatsPercentValue growth_graph:StatsGraph followers_graph:StatsGraph mute_graph:StatsGraph top_hours_graph:StatsGraph interactions_graph:StatsGraph iv_interactions_graph:StatsGraph views_by_source_graph:StatsGraph new_followers_by_source_graph:StatsGraph languages_graph:StatsGraph reactions_by_emotion_graph:StatsGraph story_interactions_graph:StatsGraph story_reactions_by_emotion_graph:StatsGraph recent_posts_interactions:Vector = stats.BroadcastStats; 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; +help.promoData#8a4d87a flags:# proxy:flags.0?true expires:int peer:flags.3?Peer psa_type:flags.1?string psa_message:flags.2?string pending_suggestions:Vector dismissed_suggestions:Vector custom_pending_suggestion:flags.4?PendingSuggestion chats:Vector users:Vector = 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; @@ -1349,9 +1354,11 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; stats.messageStats#7fe91c14 views_graph:StatsGraph reactions_by_emotion_graph:StatsGraph = stats.MessageStats; groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall; -groupCall#cdf8d3e3 flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true record_video_active:flags.11?true rtmp_stream:flags.12?true listeners_hidden:flags.13?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int conference_from_call:flags.14?long = GroupCall; +groupCall#553b0ba1 flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true record_video_active:flags.11?true rtmp_stream:flags.12?true listeners_hidden:flags.13?true conference:flags.14?true creator:flags.15?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int invite_link:flags.16?string = GroupCall; inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; +inputGroupCallSlug#fe06823f slug:string = InputGroupCall; +inputGroupCallInviteMessage#8c10603f msg_id:int = InputGroupCall; groupCallParticipant#eba636fe flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true video_joined:flags.15?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long video:flags.6?GroupCallParticipantVideo presentation:flags.14?GroupCallParticipantVideo = GroupCallParticipant; @@ -1492,7 +1499,10 @@ inputInvoiceStars#65f00ce3 purpose:InputStorePaymentPurpose = InputInvoice; inputInvoiceChatInviteSubscription#34e793f1 hash:string = InputInvoice; inputInvoiceStarGift#e8625e92 flags:# hide_name:flags.0?true include_upgrade:flags.2?true peer:InputPeer gift_id:long message:flags.1?TextWithEntities = InputInvoice; inputInvoiceStarGiftUpgrade#4d818d5d flags:# keep_original_details:flags.0?true stargift:InputSavedStarGift = InputInvoice; +inputInvoiceStarGiftTransfer#4a5f5bd9 stargift:InputSavedStarGift to_id:InputPeer = InputInvoice; inputInvoicePremiumGiftStars#dabab2ef flags:# user_id:InputUser months:int message:flags.0?TextWithEntities = InputInvoice; +inputInvoiceBusinessBotTransferStars#f4997e42 bot:InputUser stars:long = InputInvoice; +inputInvoiceStarGiftResale#63cbc38c slug:string to_id:InputPeer = InputInvoice; payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice; @@ -1639,8 +1649,9 @@ stories.storyViewsList#59d78fc5 flags:# count:int views_count:int forwards_count stories.storyViews#de9eed1d views:Vector users:Vector = stories.StoryViews; -inputReplyToMessage#22c0f6d5 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector quote_offset:flags.4?int = InputReplyTo; +inputReplyToMessage#b07038b0 flags:# reply_to_msg_id:int top_msg_id:flags.0?int reply_to_peer_id:flags.1?InputPeer quote_text:flags.2?string quote_entities:flags.3?Vector quote_offset:flags.4?int monoforum_peer_id:flags.5?InputPeer = InputReplyTo; inputReplyToStory#5881323a peer:InputPeer story_id:int = InputReplyTo; +inputReplyToMonoForum#69d66c45 monoforum_peer_id:InputPeer = InputReplyTo; exportedStoryLink#3fc9053b link:string = ExportedStoryLink; @@ -1713,6 +1724,7 @@ storyReactionPublicRepost#cfcd0f13 peer_id:Peer story:StoryItem = StoryReaction; stories.storyReactionsList#aa5f789c flags:# count:int reactions:Vector chats:Vector users:Vector next_offset:flags.0?string = stories.StoryReactionsList; savedDialog#bd87cb6c flags:# pinned:flags.2?true peer:Peer top_message:int = SavedDialog; +monoForumDialog#64407ea7 flags:# unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_reactions_count:int draft:flags.1?DraftMessage = SavedDialog; messages.savedDialogs#f83ae221 dialogs:Vector messages:Vector chats:Vector users:Vector = messages.SavedDialogs; messages.savedDialogsSlice#44ba9dd9 count:int dialogs:Vector messages:Vector chats:Vector users:Vector = messages.SavedDialogs; @@ -1852,7 +1864,7 @@ starsTransactionPeerAPI#f9677aad = StarsTransactionPeer; starsTopupOption#bd915c0 flags:# extended:flags.1?true stars:long store_product:flags.0?string currency:string amount:long = StarsTopupOption; -starsTransaction#a39fd94a flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true id:string stars:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int premium_gift_months:flags.20?int = StarsTransaction; +starsTransaction#a39fd94a flags:# refund:flags.3?true pending:flags.4?true failed:flags.6?true gift:flags.10?true reaction:flags.11?true stargift_upgrade:flags.18?true business_transfer:flags.21?true stargift_resale:flags.22?true id:string stars:StarsAmount date:int peer:StarsTransactionPeer title:flags.0?string description:flags.1?string photo:flags.2?WebDocument transaction_date:flags.5?int transaction_url:flags.5?string bot_payload:flags.7?bytes msg_id:flags.8?int extended_media:flags.9?Vector subscription_period:flags.12?int giveaway_post_id:flags.13?int stargift:flags.14?StarGift floodskip_number:flags.15?int starref_commission_permille:flags.16?int starref_peer:flags.17?Peer starref_amount:flags.17?StarsAmount paid_messages:flags.19?int premium_gift_months:flags.20?int = StarsTransaction; payments.starsStatus#6c9ce8ed flags:# balance:StarsAmount subscriptions:flags.1?Vector subscriptions_next_offset:flags.2?string subscriptions_missing_balance:flags.4?long history:flags.3?Vector next_offset:flags.0?string chats:Vector users:Vector = payments.StarsStatus; @@ -1890,8 +1902,8 @@ starsGiveawayOption#94ce852a flags:# extended:flags.0?true default:flags.1?true starsGiveawayWinnersOption#54236209 flags:# default:flags.0?true users:int per_user_stars:long = StarsGiveawayWinnersOption; -starGift#2cc73c8 flags:# limited:flags.0?true sold_out:flags.1?true birthday:flags.2?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int upgrade_stars:flags.3?long = StarGift; -starGiftUnique#5c62d151 flags:# id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector availability_issued:int availability_total:int gift_address:flags.3?string = StarGift; +starGift#c62aca28 flags:# limited:flags.0?true sold_out:flags.1?true birthday:flags.2?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int availability_resale:flags.4?long convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int upgrade_stars:flags.3?long resell_min_stars:flags.4?long title:flags.5?string = StarGift; +starGiftUnique#6411db89 flags:# id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector availability_issued:int availability_total:int gift_address:flags.3?string resell_stars:flags.4?long = StarGift; payments.starGiftsNotModified#a388a368 = payments.StarGifts; payments.starGifts#901689ea hash:int gifts:Vector = payments.StarGifts; @@ -1927,7 +1939,7 @@ botVerification#f93cd45c bot_id:long icon:long description:string = BotVerificat starGiftAttributeModel#39d99013 name:string document:Document rarity_permille:int = StarGiftAttribute; starGiftAttributePattern#13acff19 name:string document:Document rarity_permille:int = StarGiftAttribute; -starGiftAttributeBackdrop#94271762 name:string center_color:int edge_color:int pattern_color:int text_color:int rarity_permille:int = StarGiftAttribute; +starGiftAttributeBackdrop#d93d859c name:string backdrop_id:int center_color:int edge_color:int pattern_color:int text_color:int rarity_permille:int = StarGiftAttribute; starGiftAttributeOriginalDetails#e0bff26c flags:# sender_id:flags.0?Peer recipient_id:Peer date:int message:flags.1?TextWithEntities = StarGiftAttribute; payments.starGiftUpgradePreview#167bd90b sample_attributes:Vector = payments.StarGiftUpgradePreview; @@ -1939,12 +1951,13 @@ payments.uniqueStarGift#caa2f60b gift:StarGift users:Vector = payments.Uni messages.webPagePreview#b53e8b21 media:MessageMedia users:Vector = messages.WebPagePreview; -savedStarGift#6056dba5 flags:# name_hidden:flags.0?true unsaved:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true pinned_to_top:flags.12?true from_id:flags.1?Peer date:int gift:StarGift message:flags.2?TextWithEntities msg_id:flags.3?int saved_id:flags.11?long convert_stars:flags.4?long upgrade_stars:flags.6?long can_export_at:flags.7?int transfer_stars:flags.8?long = SavedStarGift; +savedStarGift#dfda0499 flags:# name_hidden:flags.0?true unsaved:flags.5?true refunded:flags.9?true can_upgrade:flags.10?true pinned_to_top:flags.12?true from_id:flags.1?Peer date:int gift:StarGift message:flags.2?TextWithEntities msg_id:flags.3?int saved_id:flags.11?long convert_stars:flags.4?long upgrade_stars:flags.6?long can_export_at:flags.7?int transfer_stars:flags.8?long can_transfer_at:flags.13?int can_resell_at:flags.14?int = SavedStarGift; payments.savedStarGifts#95f389b1 flags:# count:int chat_notifications_enabled:flags.1?Bool gifts:Vector next_offset:flags.0?string chats:Vector users:Vector = payments.SavedStarGifts; inputSavedStarGiftUser#69279795 msg_id:int = InputSavedStarGift; inputSavedStarGiftChat#f101aa7f peer:InputPeer saved_id:long = InputSavedStarGift; +inputSavedStarGiftSlug#2085c238 slug:string = InputSavedStarGift; payments.starGiftWithdrawalUrl#84aa3a9c url:string = payments.StarGiftWithdrawalUrl; @@ -1967,6 +1980,18 @@ sponsoredPeer#c69708d3 flags:# random_id:bytes peer:Peer sponsor_info:flags.0?st contacts.sponsoredPeersEmpty#ea32b4b1 = contacts.SponsoredPeers; contacts.sponsoredPeers#eb032884 peers:Vector chats:Vector users:Vector = contacts.SponsoredPeers; +starGiftAttributeIdModel#48aaae3c document_id:long = StarGiftAttributeId; +starGiftAttributeIdPattern#4a162433 document_id:long = StarGiftAttributeId; +starGiftAttributeIdBackdrop#1f01c757 backdrop_id:int = StarGiftAttributeId; + +starGiftAttributeCounter#2eb1b658 attribute:StarGiftAttributeId count:int = StarGiftAttributeCounter; + +payments.resaleStarGifts#947a12df flags:# count:int gifts:Vector next_offset:flags.0?string attributes:flags.1?Vector attributes_hash:flags.1?long chats:Vector counters:flags.2?Vector users:Vector = payments.ResaleStarGifts; + +stories.canSendStoryCount#c387c04e count_remains:int = stories.CanSendStoryCount; + +pendingSuggestion#e7e82e12 suggestion:string title:TextWithEntities description:TextWithEntities url:string = PendingSuggestion; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -2165,7 +2190,7 @@ messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; messages.sendMessage#fbf2340a flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long allow_paid_stars:flags.21?long = Updates; messages.sendMedia#a550cd78 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true invert_media:flags.16?true allow_paid_floodskip:flags.19?true peer:InputPeer reply_to:flags.0?InputReplyTo media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut effect:flags.18?long allow_paid_stars:flags.21?long = Updates; -messages.forwardMessages#bb9fa475 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true allow_paid_floodskip:flags.19?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer top_msg_id:flags.9?int schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut video_timestamp:flags.20?int allow_paid_stars:flags.21?long = Updates; +messages.forwardMessages#38f0188c flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true allow_paid_floodskip:flags.19?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer top_msg_id:flags.9?int reply_to:flags.22?InputReplyTo schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut video_timestamp:flags.20?int allow_paid_stars:flags.21?long = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; messages.report#fc78af9b peer:InputPeer id:Vector option:bytes message:string = ReportResult; @@ -2247,8 +2272,8 @@ messages.sendMultiMedia#1bf89d74 flags:# silent:flags.5?true background:flags.6? messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile; messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; messages.getSplitRanges#1cff7e08 = Vector; -messages.markDialogUnread#c286d98f flags:# unread:flags.0?true peer:InputDialogPeer = Bool; -messages.getDialogUnreadMarks#22e24e22 = Vector; +messages.markDialogUnread#8c5006f8 flags:# unread:flags.0?true parent_peer:flags.1?InputPeer peer:InputDialogPeer = Bool; +messages.getDialogUnreadMarks#21202222 flags:# parent_peer:flags.0?InputPeer = Vector; messages.clearAllDrafts#7e58ee9c = Bool; messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true unpin:flags.1?true pm_oneside:flags.2?true peer:InputPeer id:int = Updates; messages.sendVote#10ea6184 peer:InputPeer msg_id:int options:Vector = Updates; @@ -2278,7 +2303,7 @@ messages.getOldFeaturedStickers#7ed094a1 offset:int limit:int hash:long = messag messages.getReplies#22ddd30c peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage; messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool; -messages.unpinAllMessages#ee22b9a8 flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory; +messages.unpinAllMessages#62dd747 flags:# peer:InputPeer top_msg_id:flags.0?int saved_peer_id:flags.1?InputPeer = messages.AffectedHistory; messages.deleteChat#5bd0ee50 chat_id:long = Bool; messages.deletePhoneCallHistory#f9cbe409 flags:# revoke:flags.0?true = messages.AffectedFoundMessages; messages.checkHistoryImport#43fe19f3 import_head:string = messages.HistoryImportParsed; @@ -2309,8 +2334,8 @@ messages.setChatAvailableReactions#864b2581 flags:# peer:InputPeer available_rea 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.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.getUnreadReactions#bd7f90ac flags:# peer:InputPeer top_msg_id:flags.0?int saved_peer_id:flags.1?InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; +messages.readReactions#9ec44f93 flags:# peer:InputPeer top_msg_id:flags.0?int saved_peer_id:flags.1?InputPeer = 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; @@ -2342,9 +2367,9 @@ messages.getBotApp#34fdc5c3 app:InputBotApp hash:long = messages.BotApp; messages.requestAppWebView#53618bce flags:# write_allowed:flags.0?true compact:flags.7?true fullscreen:flags.8?true peer:InputPeer app:InputBotApp start_param:flags.1?string theme_params:flags.2?DataJSON platform:string = WebViewResult; messages.setChatWallPaper#8ffacae1 flags:# for_both:flags.3?true revert:flags.4?true peer:InputPeer wallpaper:flags.0?InputWallPaper settings:flags.2?WallPaperSettings id:flags.1?int = Updates; messages.searchEmojiStickerSets#92b4494c flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; -messages.getSavedDialogs#5381d21a flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.SavedDialogs; -messages.getSavedHistory#3d9a414d peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; -messages.deleteSavedHistory#6e98102b flags:# peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory; +messages.getSavedDialogs#1e91fc99 flags:# exclude_pinned:flags.0?true parent_peer:flags.1?InputPeer offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.SavedDialogs; +messages.getSavedHistory#998ab009 flags:# parent_peer:flags.0?InputPeer peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; +messages.deleteSavedHistory#4dc5085f flags:# parent_peer:flags.0?InputPeer peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory; messages.getPinnedSavedDialogs#d63d94e0 = messages.SavedDialogs; messages.toggleSavedDialogPin#ac81bbde flags:# pinned:flags.0?true peer:InputDialogPeer = Bool; messages.reorderPinnedSavedDialogs#8b716587 flags:# force:flags.0?true order:Vector = Bool; @@ -2379,6 +2404,8 @@ messages.savePreparedInlineMessage#f21f7f2f flags:# result:InputBotInlineResult messages.getPreparedInlineMessage#857ebdb8 bot:InputUser id:string = messages.PreparedInlineMessage; messages.searchStickers#29b1c66a flags:# emojis:flags.0?true q:string emoticon:string lang_code:Vector offset:int limit:int hash:long = messages.FoundStickers; messages.reportMessagesDelivery#5a6d7395 flags:# push:flags.0?true peer:InputPeer id:Vector = Bool; +messages.getSavedDialogsByID#6f6f9c96 flags:# parent_peer:flags.1?InputPeer ids:Vector = messages.SavedDialogs; +messages.readSavedHistory#ba4a3b5b parent_peer:InputPeer peer:InputPeer max_id:int = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference; @@ -2467,7 +2494,7 @@ channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates; channels.reorderUsernames#b45ced1d channel:InputChannel order:Vector = Bool; channels.toggleUsername#50f24105 channel:InputChannel username:string active:Bool = Bool; channels.deactivateAllUsernames#a245dd3 channel:InputChannel = Bool; -channels.toggleForum#a4298b29 channel:InputChannel enabled:Bool = Updates; +channels.toggleForum#3ff75734 channel:InputChannel enabled:Bool tabs:Bool = Updates; channels.createForumTopic#f40c0224 flags:# channel:InputChannel title:string icon_color:flags.0?int icon_emoji_id:flags.3?long random_id:long send_as:flags.2?InputPeer = Updates; channels.getForumTopics#de560d1 flags:# channel:InputChannel q:flags.0?string offset_date:int offset_id:int offset_topic:int limit:int = messages.ForumTopics; channels.getForumTopicsByID#b0831eb9 channel:InputChannel topics:Vector = messages.ForumTopics; @@ -2486,7 +2513,9 @@ channels.setBoostsToUnblockRestrictions#ad399cee channel:InputChannel boosts:int channels.setEmojiStickers#3cd930b7 channel:InputChannel stickerset:InputStickerSet = Bool; channels.restrictSponsoredMessages#9ae91519 channel:InputChannel restricted:Bool = Updates; channels.searchPosts#d19f987b hashtag:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; -channels.updatePaidMessagesPrice#fc84653f channel:InputChannel send_paid_messages_stars:long = Updates; +channels.updatePaidMessagesPrice#4b12327b flags:# broadcast_messages_allowed:flags.0?true channel:InputChannel send_paid_messages_stars:long = Updates; +channels.toggleAutotranslation#167fc0a1 channel:InputChannel enabled:Bool = Updates; +channels.getMessageAuthor#ece2a0e6 channel:InputChannel id:int = User; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -2567,6 +2596,8 @@ payments.getStarGiftWithdrawalUrl#d06e93a8 stargift:InputSavedStarGift password: payments.toggleChatStarGiftNotifications#60eaefa1 flags:# enabled:flags.0?true peer:InputPeer = Bool; payments.toggleStarGiftsPinnedToTop#1513e7b0 peer:InputPeer stargift:Vector = Bool; payments.canPurchaseStore#4fdc5ea7 purpose:InputStorePaymentPurpose = Bool; +payments.getResaleStarGifts#7a5fa236 flags:# sort_by_price:flags.1?true sort_by_num:flags.2?true attributes_hash:flags.0?long gift_id:long attributes:flags.3?Vector offset:string limit:int = payments.ResaleStarGifts; +payments.updateStarGiftPrice#3baea4e1 stargift:InputSavedStarGift resell_stars:long = Updates; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?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.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; @@ -2581,7 +2612,7 @@ stickers.deleteStickerSet#87704394 stickerset:InputStickerSet = Bool; stickers.replaceSticker#4696459a sticker:InputDocument new_sticker:InputStickerSetItem = messages.StickerSet; phone.getCallConfig#55451fa9 = DataJSON; -phone.requestCall#a6c4600c flags:# video:flags.0?true user_id:InputUser conference_call:flags.1?InputGroupCall random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall; +phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall; phone.acceptCall#3bd2b4a0 peer:InputPhoneCall g_b:bytes protocol:PhoneCallProtocol = phone.PhoneCall; phone.confirmCall#2efe1722 peer:InputPhoneCall g_a:bytes key_fingerprint:long protocol:PhoneCallProtocol = phone.PhoneCall; phone.receivedCall#17d54f61 peer:InputPhoneCall = Bool; @@ -2590,7 +2621,7 @@ phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhon phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; phone.createGroupCall#48cdc6d8 flags:# rtmp_stream:flags.2?true peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates; -phone.joinGroupCall#d61e1df3 flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string key_fingerprint:flags.3?long params:DataJSON = Updates; +phone.joinGroupCall#8fb53057 flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string public_key:flags.3?int256 block:flags.3?bytes params:DataJSON = Updates; phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates; phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; @@ -2611,7 +2642,12 @@ phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates; phone.getGroupCallStreamChannels#1ab21940 call:InputGroupCall = phone.GroupCallStreamChannels; phone.getGroupCallStreamRtmpUrl#deb3abbf peer:InputPeer revoke:Bool = phone.GroupCallStreamRtmpUrl; phone.saveCallLog#41248786 peer:InputPhoneCall file:InputFile = Bool; -phone.createConferenceCall#dfc909ab peer:InputPhoneCall key_fingerprint:long = phone.PhoneCall; +phone.createConferenceCall#7d0444bb flags:# muted:flags.0?true video_stopped:flags.2?true join:flags.3?true random_id:int public_key:flags.3?int256 block:flags.3?bytes params:flags.3?DataJSON = Updates; +phone.deleteConferenceCallParticipants#8ca60525 flags:# only_left:flags.0?true kick:flags.1?true call:InputGroupCall ids:Vector block:bytes = Updates; +phone.sendConferenceCallBroadcast#c6701900 call:InputGroupCall block:bytes = Updates; +phone.inviteConferenceCallParticipant#bcf22685 flags:# video:flags.0?true call:InputGroupCall user_id:InputUser = Updates; +phone.declineConferenceCallInvite#3c479971 msg_id:int = Updates; +phone.getGroupCallChainBlocks#ee9f88a6 call:InputGroupCall sub_chain_id:int offset:int limit:int = Updates; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; @@ -2644,7 +2680,7 @@ chatlists.hideChatlistUpdates#66e486fb chatlist:InputChatlist = Bool; chatlists.getLeaveChatlistSuggestions#fdbcd714 chatlist:InputChatlist = Vector; chatlists.leaveChatlist#74fae13a chatlist:InputChatlist peers:Vector = Updates; -stories.canSendStory#c7dfdfdd peer:InputPeer = Bool; +stories.canSendStory#30eb63f0 peer:InputPeer = stories.CanSendStoryCount; stories.sendStory#e4e6694b flags:# pinned:flags.2?true noforwards:flags.4?true fwd_modified:flags.7?true peer:InputPeer media:InputMedia media_areas:flags.5?Vector caption:flags.0?string entities:flags.1?Vector privacy_rules:Vector random_id:long period:flags.3?int fwd_from_id:flags.6?InputPeer fwd_from_story:flags.6?int = Updates; stories.editStory#b583ba46 flags:# peer:InputPeer id:int media:flags.0?InputMedia media_areas:flags.3?Vector caption:flags.1?string entities:flags.1?Vector privacy_rules:flags.2?Vector = Updates; stories.deleteStories#ae59db5f peer:InputPeer id:Vector = Vector; @@ -2687,4 +2723,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo; -// LAYER 201 +// LAYER 204 diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 39c037fb..24eeb19f 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -146,6 +146,7 @@ def pyrogram_api(): stop_transmission export_session_string set_parse_mode + ping """, conversation=""" Conversation @@ -364,16 +365,24 @@ def pyrogram_api(): get_chat_gifts hide_gift refund_star_payment + search_gifts_for_resale send_invoice send_paid_media send_paid_reaction send_payment_form send_gift + send_resold_gift + set_gift_resale_price + set_pinned_gifts show_gift transfer_gift upgrade_gift get_stars_balance """, + phone=""" + Phone + get_call_members + """, password=""" Password enable_cloud_password @@ -408,7 +417,11 @@ def pyrogram_api(): Telegram Business answer_pre_checkout_query answer_shipping_query + delete_business_messages get_business_connection + get_business_account_gifts + get_business_account_star_balance + transfer_business_account_stars """, authorization=""" Authorization @@ -420,12 +433,11 @@ def pyrogram_api(): resend_code sign_in sign_in_bot - sign_up + sign_in_qrcode get_password_hint check_password send_recovery_code recover_password - accept_terms_of_service log_out get_active_sessions """, @@ -483,6 +495,7 @@ def pyrogram_api(): BusinessWeeklyOpen BusinessWorkingHours User + Username Chat ChatPreview ChatPhoto @@ -505,6 +518,7 @@ def pyrogram_api(): PeerUser PeerChannel BotInfo + GroupCallMember ChatColor CollectibleItemInfo BotVerification @@ -603,6 +617,7 @@ def pyrogram_api(): Invoice LabeledPrice PaidMedia + PaidMessagePriceChanged PaymentForm PaymentInfo PaymentRefunded @@ -712,6 +727,7 @@ def pyrogram_api(): InputMessageContent InputMessageContent InputReplyToMessage + InputReplyToMonoforum InputReplyToStory InputTextMessageContent InputLocationMessageContent @@ -723,6 +739,7 @@ def pyrogram_api(): Authorization ActiveSession ActiveSessions + LoginToken SentCode TermsOfService """ diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index 2d75f9cb..ee599c5f 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -35,6 +35,7 @@ BOT_GAMES_DISABLED Bot games cannot be used in this type of chat BOT_GROUPS_BLOCKED This bot can't be added to groups BOT_INLINE_DISABLED The inline feature of the bot is disabled BOT_INVALID This is not a valid bot +BOT_INVOICE_INVALID The provided invoice is invalid 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 @@ -46,6 +47,7 @@ BROADCAST_CALLS_DISABLED Broadcast calls disabled BROADCAST_ID_INVALID The channel is invalid BROADCAST_PUBLIC_VOTERS_FORBIDDEN Polls with public voters cannot be sent in channels BROADCAST_REQUIRED The request can only be used with a channel +BUSINESS_BOT_MISSING Business bot missing BUTTON_DATA_INVALID The button callback data is invalid or too large BUTTON_ID_INVALID The button_id parameter is invalid BUTTON_TEXT_INVALID The specified button text is invalid @@ -503,3 +505,4 @@ BOOSTS_EMPTY You can't modify the icon of the General topic. BOOST_NOT_MODIFIED You're already boosting the specified channel. PAYMENT_REQUIRED The payment is required BOOST_PEER_INVALID The specified `boost_peer` is invalid. +STARS_AMOUNT_INVALID The specified `amount` is invalid. \ No newline at end of file diff --git a/compiler/errors/source/403_FORBIDDEN.tsv b/compiler/errors/source/403_FORBIDDEN.tsv index 44a76324..452ea59c 100644 --- a/compiler/errors/source/403_FORBIDDEN.tsv +++ b/compiler/errors/source/403_FORBIDDEN.tsv @@ -44,3 +44,4 @@ GROUPCALL_ALREADY_STARTED The groupcall has already started, you can join direct GROUPCALL_FORBIDDEN The group call has already ended LIVE_DISABLED Story is disabled server-side CHAT_GUEST_SEND_FORBIDDEN You need to join the discussion group before commenting +ALLOW_PAYMENT_REQUIRED_X Payment of {value} stars is required to perform this action \ No newline at end of file diff --git a/docs/source/api/enums/GiftForResaleOrder.rst b/docs/source/api/enums/GiftForResaleOrder.rst new file mode 100644 index 00000000..7d8d0b46 --- /dev/null +++ b/docs/source/api/enums/GiftForResaleOrder.rst @@ -0,0 +1,8 @@ +GiftAttributeType +================= + +.. autoclass:: pyrogram.enums.GiftForResaleOrder() + :members: + +.. raw:: html + :file: ./cleanup.html diff --git a/docs/source/api/enums/MessageOriginType.rst b/docs/source/api/enums/MessageOriginType.rst new file mode 100644 index 00000000..a48fb2a3 --- /dev/null +++ b/docs/source/api/enums/MessageOriginType.rst @@ -0,0 +1,8 @@ +MessageOriginType +================= + +.. autoclass:: pyrogram.enums.MessageOriginType() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index bf40b223..915319c5 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -18,7 +18,7 @@ # along with Pyrofork. If not, see . __fork_name__ = "PyroFork" -__version__ = "2.3.61" +__version__ = "2.3.67" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2022-present Mayuri-Chan " diff --git a/pyrogram/client.py b/pyrogram/client.py index bbad9c7e..e21414d4 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -39,6 +39,7 @@ import pyrogram from pyrogram import __version__, __license__ from pyrogram import enums from pyrogram import raw +from pyrogram import types from pyrogram import utils from pyrogram.crypto import aes from pyrogram.errors import CDNFileHashMismatch @@ -51,7 +52,7 @@ from pyrogram.handlers.handler import Handler from pyrogram.methods import Methods from pyrogram.session import Auth, Session from pyrogram.storage import FileStorage, MemoryStorage, Storage -from pyrogram.types import User, TermsOfService +from pyrogram.types import User from pyrogram.utils import ainput from .connection import Connection from .connection.transport import TCPAbridged @@ -130,6 +131,9 @@ class Client(Methods): Pass a session string to load the session in-memory. Implies ``in_memory=True``. + use_qrcode (``bool``, *optional*): + Pass True to login using a QR code. + in_memory (``bool``, *optional*): Pass True to start an in-memory session that will be discarded as soon as the client stops. In order to reconnect again using an in-memory session without having to login again, you can use @@ -229,6 +233,7 @@ class Client(Methods): PARENT_DIR = Path(sys.argv[0]).parent INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:joinchat/|\+))([\w-]+)$") + UPGRADED_GIFT_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:nft/|\+))([\w-]+)$") WORKERS = min(32, (os.cpu_count() or 0) + 4) # os.cpu_count() can be None WORKDIR = PARENT_DIR @@ -258,6 +263,7 @@ class Client(Methods): test_mode: Optional[bool] = False, bot_token: Optional[str] = None, session_string: Optional[str] = None, + use_qrcode: Optional[bool] = False, in_memory: Optional[bool] = None, mongodb: Optional[dict] = None, storage: Optional[Storage] = None, @@ -272,7 +278,7 @@ class Client(Methods): skip_updates: bool = True, takeout: bool = None, sleep_threshold: int = Session.SLEEP_THRESHOLD, - hide_password: Optional[bool] = False, + hide_password: Optional[bool] = True, max_concurrent_transmissions: int = MAX_CONCURRENT_TRANSMISSIONS, client_platform: "enums.ClientPlatform" = enums.ClientPlatform.OTHER, max_message_cache_size: int = MAX_CACHE_SIZE, @@ -295,6 +301,7 @@ class Client(Methods): self.test_mode = test_mode self.bot_token = bot_token self.session_string = session_string + self.use_qrcode = use_qrcode self.in_memory = in_memory self.mongodb = mongodb self.phone_number = phone_number @@ -403,6 +410,15 @@ class Client(Methods): if datetime.now() - self.last_update_time > timedelta(seconds=self.UPDATES_WATCHDOG_INTERVAL): await self.invoke(raw.functions.updates.GetState()) + async def _wait_for_update_login_token(self): + """ + Wait for an UpdateLoginToken update from Telegram. + """ + while True: + update, _, _ = await self.dispatcher.updates_queue.get() + if isinstance(update, raw.types.UpdateLoginToken): + break + async def authorize(self) -> User: if self.bot_token: return await self.sign_in_bot(self.bot_token) @@ -410,52 +426,60 @@ class Client(Methods): print(f"Welcome to Pyrogram (version {__version__})") print(f"Pyrogram is free software and comes with ABSOLUTELY NO WARRANTY. Licensed\n" f"under the terms of the {__license__}.\n") + if not self.use_qrcode: + while True: + try: + if not self.phone_number: + while True: + print("Enter 'qrcode' if you want to login with qrcode.") + value = await ainput("Enter phone number or bot token: ") + + if not value: + continue + + if value.lower() == "qrcode": + self.use_qrcode = True + break + + confirm = (await ainput(f'Is "{value}" correct? (y/N): ')).lower() + + if confirm == "y": + break + + if ":" in value: + self.bot_token = value + return await self.sign_in_bot(value) + else: + self.phone_number = value + + sent_code = await self.send_code(self.phone_number) + except BadRequest as e: + print(e.MESSAGE) + self.phone_number = None + self.bot_token = None + else: + break + + sent_code_descriptions = { + enums.SentCodeType.APP: "Telegram app", + enums.SentCodeType.SMS: "SMS", + 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]}") while True: - try: - if not self.phone_number: - while True: - value = await ainput("Enter phone number or bot token: ") - - if not value: - continue - - confirm = (await ainput(f'Is "{value}" correct? (y/N): ')).lower() - - if confirm == "y": - break - - if ":" in value: - self.bot_token = value - return await self.sign_in_bot(value) - else: - self.phone_number = value - - sent_code = await self.send_code(self.phone_number) - except BadRequest as e: - print(e.MESSAGE) - self.phone_number = None - self.bot_token = None - else: - break - - sent_code_descriptions = { - enums.SentCodeType.APP: "Telegram app", - enums.SentCodeType.SMS: "SMS", - 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]}") - - while True: - if not self.phone_code: + if not self.use_qrcode and not self.phone_code: self.phone_code = await ainput("Enter confirmation code: ") try: - signed_in = await self.sign_in(self.phone_number, sent_code.phone_code_hash, self.phone_code) + if self.use_qrcode: + signed_in = await self.sign_in_qrcode() + else: + signed_in = await self.sign_in(self.phone_number, sent_code.phone_code_hash, self.phone_code) except BadRequest as e: print(e.MESSAGE) self.phone_code = None @@ -494,33 +518,18 @@ class Client(Methods): print(e.MESSAGE) self.password = None else: + if self.use_qrcode and isinstance(signed_in, types.LoginToken): + time_out = signed_in.expires - datetime.timestamp(datetime.now()) + try: + await asyncio.wait_for(self._wait_for_update_login_token(), timeout=time_out) + except asyncio.TimeoutError: + print("QR code expired, Requesting new Qr code...") + continue break if isinstance(signed_in, User): return signed_in - while True: - first_name = await ainput("Enter first name: ") - last_name = await ainput("Enter last name (empty to skip): ") - - try: - signed_up = await self.sign_up( - self.phone_number, - sent_code.phone_code_hash, - first_name, - last_name - ) - except BadRequest as e: - print(e.MESSAGE) - else: - break - - if isinstance(signed_in, TermsOfService): - print("\n" + signed_in.text + "\n") - await self.accept_terms_of_service(signed_in.id) - - return signed_up - def set_parse_mode(self, parse_mode: Optional["enums.ParseMode"]): """Set the parse mode to be used globally by the client. diff --git a/pyrogram/dispatcher.py b/pyrogram/dispatcher.py index 5cb97b5e..c5f166d5 100644 --- a/pyrogram/dispatcher.py +++ b/pyrogram/dispatcher.py @@ -21,6 +21,7 @@ import asyncio import inspect import logging from collections import OrderedDict +from typing import Any import pyrogram from pyrogram import raw, types, utils @@ -337,59 +338,88 @@ class Dispatcher: async def handler_worker(self, lock: asyncio.Lock): while True: packet = await self.updates_queue.get() + if packet is None: break - await self._process_packet(packet, lock) - async def _process_packet( - self, - packet: tuple[raw.core.TLObject, dict[int, types.Update], dict[int, types.Update]], - lock: asyncio.Lock, + try: + await self._handle_packet(packet, lock) + except pyrogram.StopPropagation: + pass + except Exception as e: + log.exception(e) + finally: + self.updates_queue.task_done() + + async def _handle_packet(self, packet, lock: asyncio.Lock): + update, users, chats = packet + parser = self.update_parsers.get(type(update)) + + parsed_update, handler_type = ( + await parser(update, users, chats) + if parser is not None else (None, type(None)) + ) + async with lock: + await self._dispatch_to_handlers(update, users, chats, parsed_update, handler_type) + + + async def _dispatch_to_handlers( + self, update, users, chats, parsed_update, handler_type, + ): + for group in self.groups.values(): + for handler in group: + args = await self._match_handler( + handler, update, users, chats, parsed_update, handler_type, + ) + if args is None: + continue + + try: + await self._execute_handler(handler, *args) + except pyrogram.StopPropagation: + raise + except pyrogram.ContinuePropagation: + continue + except Exception as error: + if parsed_update is not None: + await self._handle_exception(parsed_update, error) + break + + async def _match_handler( + self, handler, update, users, chats, parsed_update, handler_type, ): try: - update, users, chats = packet - parser = self.update_parsers.get(type(update)) - - if parser is not None: - parsed_result = parser(update, users, chats) - if inspect.isawaitable(parsed_result): - parsed_update, handler_type = await parsed_result - else: - parsed_update, handler_type = parsed_result - else: - parsed_update, handler_type = (None, type(None)) - - async with lock: - for group in self.groups.values(): - for handler in group: - try: - if parsed_update is not None: - if isinstance(handler, handler_type) and await handler.check( - self.client, parsed_update - ): - await self._execute_callback(handler, parsed_update) - break - elif isinstance(handler, RawUpdateHandler): - await self._execute_callback(handler, update, users, chats) - break - except (pyrogram.StopPropagation, pyrogram.ContinuePropagation) as e: - if isinstance(e, pyrogram.StopPropagation): - raise - except Exception as exception: - if parsed_update is not None: - await self._handle_exception(parsed_update, exception) - except pyrogram.StopPropagation: - pass + if isinstance(handler, handler_type): + if await handler.check(self.client, parsed_update): + return (parsed_update,) + elif isinstance(handler, RawUpdateHandler): + if await handler.check(self.client, update): + return (update, users, chats) except Exception as e: log.exception(e) - finally: - self.updates_queue.task_done() - async def _handle_exception(self, parsed_update: types.Update, exception: Exception): + return None + + async def _execute_handler(self, handler, *args: Any): + if inspect.iscoroutinefunction(handler.callback): + await handler.callback(self.client, *args) + else: + await self.loop.run_in_executor( + self.client.executor, + handler.callback, + self.client, + *args + ) + + async def _handle_exception( + self, parsed_update: types.Update, exception: Exception, + ): handled_error = False for error_handler in self.error_handlers: try: - if await error_handler.check(self.client, parsed_update, exception): + if await error_handler.check( + self.client, parsed_update, exception, + ): handled_error = True break except pyrogram.StopPropagation: @@ -401,11 +431,3 @@ class Dispatcher: if not handled_error: log.exception("Unhandled exception: %s", exception) - - async def _execute_callback(self, handler: Handler, *args): - if inspect.iscoroutinefunction(handler.callback): - await handler.callback(self.client, *args) - else: - await self.client.loop.run_in_executor( - self.client.executor, handler.callback, self.client, *args - ) \ No newline at end of file diff --git a/pyrogram/enums/__init__.py b/pyrogram/enums/__init__.py index aac9db33..c67f64b7 100644 --- a/pyrogram/enums/__init__.py +++ b/pyrogram/enums/__init__.py @@ -27,6 +27,7 @@ from .chat_type import ChatType from .client_platform import ClientPlatform from .folder_color import FolderColor from .gift_attribute_type import GiftAttributeType +from .gift_for_resale_order import GiftForResaleOrder from .listerner_types import ListenerTypes from .message_entity_type import MessageEntityType from .message_media_type import MessageMediaType @@ -55,6 +56,7 @@ __all__ = [ 'ClientPlatform', 'FolderColor', 'GiftAttributeType', + 'GiftForResaleOrder', 'ListenerTypes', 'MessageEntityType', 'MessageMediaType', diff --git a/pyrogram/enums/chat_type.py b/pyrogram/enums/chat_type.py index 47d14f71..10cf8db6 100644 --- a/pyrogram/enums/chat_type.py +++ b/pyrogram/enums/chat_type.py @@ -39,3 +39,9 @@ class ChatType(AutoName): CHANNEL = auto() "Chat is a channel" + + FORUM = auto() + "Chat is a forum" + + MONOFORUM = auto() + "Chat is a monoforum" diff --git a/pyrogram/methods/auth/accept_terms_of_service.py b/pyrogram/enums/gift_for_resale_order.py similarity index 58% rename from pyrogram/methods/auth/accept_terms_of_service.py rename to pyrogram/enums/gift_for_resale_order.py index fa94fbc0..403b3691 100644 --- a/pyrogram/methods/auth/accept_terms_of_service.py +++ b/pyrogram/enums/gift_for_resale_order.py @@ -17,29 +17,19 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrofork. If not, see . -import pyrogram -from pyrogram import raw +from enum import auto + +from .auto_name import AutoName -class AcceptTermsOfService: - async def accept_terms_of_service( - self: "pyrogram.Client", - terms_of_service_id: str - ) -> bool: - """Accept the given terms of service. +class GiftForResaleOrder(AutoName): + """Describes order in which upgraded gifts for resale will be sorted. Used in :meth:`~pyrogram.Client.search_gifts_for_resale`.""" - .. include:: /_includes/usable-by/users.rst + PRICE = auto() + "The gifts will be sorted by their price from the lowest to the highest" - Parameters: - terms_of_service_id (``str``): - The terms of service identifier. - """ - r = await self.invoke( - raw.functions.help.AcceptTermsOfService( - id=raw.types.DataJSON( - data=terms_of_service_id - ) - ) - ) + CHANGE_DATE = auto() + "The gifts will be sorted by the last date when their price was changed from the newest to the oldest" - return bool(r) + NUMBER = auto() + "The gifts will be sorted by their number from the smallest to the largest" diff --git a/pyrogram/enums/message_service_type.py b/pyrogram/enums/message_service_type.py index a2fbc90a..5f25dd6b 100644 --- a/pyrogram/enums/message_service_type.py +++ b/pyrogram/enums/message_service_type.py @@ -132,3 +132,6 @@ class MessageServiceType(AutoName): SCREENSHOT_TAKEN = auto() "Screenshot taken" + + PAID_MESSAGE_PRICE_CHANGED = auto() + "Paid message price changed" diff --git a/pyrogram/filters.py b/pyrogram/filters.py index a7b17a60..a5f97560 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -1,3 +1,4 @@ + # Pyrofork - Telegram MTProto API Client Library for Python # Copyright (C) 2017-present Dan # Copyright (C) 2022-present Mayuri-Chan @@ -250,7 +251,7 @@ react = create(reaction_filter) # region forwarded_filter async def forwarded_filter(_, __, m: Message): - return bool(m.forward_date) + return bool(m.forward_origin) forwarded = create(forwarded_filter) @@ -889,7 +890,12 @@ def command(commands: Union[str, List[str]], prefixes: Union[str, List[str]] = " command_re = re.compile(r"([\"'])(.*?)(?. -from .accept_terms_of_service import AcceptTermsOfService from .check_password import CheckPassword from .connect import Connect from .disconnect import Disconnect @@ -31,12 +30,11 @@ from .send_code import SendCode from .send_recovery_code import SendRecoveryCode from .sign_in import SignIn from .sign_in_bot import SignInBot -from .sign_up import SignUp +from .sign_in_qrcode import SignInQrcode from .terminate import Terminate class Auth( - AcceptTermsOfService, CheckPassword, Connect, Disconnect, @@ -50,7 +48,7 @@ class Auth( SendRecoveryCode, SignIn, SignInBot, - SignUp, + SignInQrcode, Terminate ): pass diff --git a/pyrogram/methods/auth/sign_in.py b/pyrogram/methods/auth/sign_in.py index 90498aed..35af26f6 100644 --- a/pyrogram/methods/auth/sign_in.py +++ b/pyrogram/methods/auth/sign_in.py @@ -23,6 +23,7 @@ from typing import Union import pyrogram from pyrogram import raw from pyrogram import types +from pyrogram.errors import PhoneNumberUnoccupied log = logging.getLogger(__name__) @@ -49,15 +50,13 @@ class SignIn: The valid confirmation code you received (either as Telegram message or as SMS in your phone number). Returns: - :obj:`~pyrogram.types.User` | :obj:`~pyrogram.types.TermsOfService` | bool: On success, in case the - authorization completed, the user is returned. In case the phone number needs to be registered first AND the - terms of services accepted (with :meth:`~pyrogram.Client.accept_terms_of_service`), an object containing - them is returned. In case the phone number needs to be registered, but the terms of services don't need to - be accepted, False is returned instead. + :obj:`~pyrogram.types.User` | bool: On success, in case the + authorization completed, the user is returned. Raises: BadRequest: In case the arguments are invalid. SessionPasswordNeeded: In case a password is needed to sign in. + PhoneNumberUnoccupied: In case the phone number is not registered on Telegram. """ phone_number = phone_number.strip(" +") @@ -70,10 +69,7 @@ class SignIn: ) if isinstance(r, raw.types.auth.AuthorizationSignUpRequired): - if r.terms_of_service: - return types.TermsOfService._parse(terms_of_service=r.terms_of_service) - - return False + raise PhoneNumberUnoccupied("The phone number is not registered on Telegram. Please use official Telegram app to register it.") else: await self.storage.user_id(r.user.id) await self.storage.is_bot(False) diff --git a/pyrogram/methods/auth/sign_in_qrcode.py b/pyrogram/methods/auth/sign_in_qrcode.py new file mode 100644 index 00000000..0b255788 --- /dev/null +++ b/pyrogram/methods/auth/sign_in_qrcode.py @@ -0,0 +1,111 @@ +# Pyrofork - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# Copyright (C) 2022-present Mayuri-Chan +# +# This file is part of Pyrofork. +# +# Pyrofork is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrofork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrofork. If not, see . + +import logging +from base64 import b64encode +from typing import Union + +import pyrogram +from pyrogram import raw +from pyrogram import types +from pyrogram.session import Session, Auth + +log = logging.getLogger(__name__) + +class SignInQrcode: + async def sign_in_qrcode( + self: "pyrogram.Client" + ) -> Union["types.User", "types.LoginToken"]: + """Authorize a user in Telegram with a QR code. + + .. include:: /_includes/usable-by/users.rst + + Returns: + :obj:`~pyrogram.types.User` | :obj:`pyrogram.types.LoginToken`, in case the + authorization completed, the user is returned. In case the QR code is + not scanned, a login token is returned. + + Raises: + ImportError: In case the qrcode library is not installed. + SessionPasswordNeeded: In case a password is needed to sign in. + """ + + try: + import qrcode + except ImportError: + raise ImportError("qrcode is missing! " + "Please install it with `pip install qrcode`") + r = await self.session.invoke( + raw.functions.auth.ExportLoginToken( + api_id=self.api_id, + api_hash=self.api_hash, + except_ids=[] + ) + ) + if isinstance(r, raw.types.auth.LoginToken): + base64_token = b64encode(r.token).decode("utf-8") + login_url = f"tg://login?token={base64_token}" + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=10, + border=4, + ) + qr.add_data(login_url) + qr.make(fit=True) + + print("Scan the QR code with your Telegram app.") + qr.print_ascii() + + return types.LoginToken._parse(r) + if isinstance(r, raw.types.auth.LoginTokenSuccess): + await self.storage.user_id(r.authorization.user.id) + await self.storage.is_bot(False) + + return types.User._parse(self, r.authorization.user) + if isinstance(r, raw.types.auth.LoginTokenMigrateTo): + # pylint: disable=access-member-before-definition + await self.session.stop() + + await self.storage.dc_id(r.dc_id) + await self.storage.auth_key( + await Auth( + self, await self.storage.dc_id(), + await self.storage.test_mode() + ).create() + ) + self.session = Session( + self, await self.storage.dc_id(), + await self.storage.auth_key(), await self.storage.test_mode() + ) + + await self.session.start() + r = await self.session.invoke( + raw.functions.auth.ImportLoginToken( + token=r.token + ) + ) + if isinstance(r, raw.types.auth.LoginTokenSuccess): + await self.storage.user_id(r.authorization.user.id) + await self.storage.is_bot(False) + + return types.User._parse(self, r.authorization.user) + raise pyrogram.exceptions.RPCError( + "Unknown response type from Telegram API" + ) diff --git a/pyrogram/methods/auth/sign_up.py b/pyrogram/methods/auth/sign_up.py deleted file mode 100644 index c1eef80f..00000000 --- a/pyrogram/methods/auth/sign_up.py +++ /dev/null @@ -1,74 +0,0 @@ -# Pyrofork - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-present Dan -# Copyright (C) 2022-present Mayuri-Chan -# -# This file is part of Pyrofork. -# -# Pyrofork is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrofork is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrofork. If not, see . - -import logging - -import pyrogram -from pyrogram import raw -from pyrogram import types - -log = logging.getLogger(__name__) - - -class SignUp: - async def sign_up( - self: "pyrogram.Client", - phone_number: str, - phone_code_hash: str, - first_name: str, - last_name: str = "" - ) -> "types.User": - """Register a new user in Telegram. - - .. include:: /_includes/usable-by/users.rst - - Parameters: - phone_number (``str``): - Phone number in international format (includes the country prefix). - - phone_code_hash (``str``): - Code identifier taken from the result of :meth:`~pyrogram.Client.send_code`. - - first_name (``str``): - New user first name. - - last_name (``str``, *optional*): - New user last name. Defaults to "" (empty string, no last name). - - Returns: - :obj:`~pyrogram.types.User`: On success, the new registered user is returned. - - Raises: - BadRequest: In case the arguments are invalid. - """ - phone_number = phone_number.strip(" +") - - r = await self.invoke( - raw.functions.auth.SignUp( - phone_number=phone_number, - first_name=first_name, - last_name=last_name, - phone_code_hash=phone_code_hash - ) - ) - - await self.storage.user_id(r.user.id) - await self.storage.is_bot(False) - - return types.User._parse(self, r.user) diff --git a/pyrogram/methods/business/__init__.py b/pyrogram/methods/business/__init__.py index ac8140fb..cac6b40d 100644 --- a/pyrogram/methods/business/__init__.py +++ b/pyrogram/methods/business/__init__.py @@ -1,5 +1,6 @@ # Pyrogram - Telegram MTProto API Client Library for Python # Copyright (C) 2017-present Dan +# Copyright (C) 2022-present Mayuri-Chan # # This file is part of Pyrogram. # @@ -18,12 +19,20 @@ from .answer_pre_checkout_query import AnswerPreCheckoutQuery from .answer_shipping_query import AnswerShippingQuery +from .delete_business_messages import DeleteBusinessMessages from .get_business_connection import GetBusinessConnection +from .get_business_account_gifts import GetBusinessAccountGifts +from .get_business_account_star_balance import GetBusinessAccountStarBalance +from .transfer_business_account_stars import TransferBusinessAccountStars class TelegramBusiness( AnswerPreCheckoutQuery, AnswerShippingQuery, + DeleteBusinessMessages, GetBusinessConnection, + GetBusinessAccountGifts, + GetBusinessAccountStarBalance, + TransferBusinessAccountStars, ): pass diff --git a/pyrogram/methods/business/delete_business_messages.py b/pyrogram/methods/business/delete_business_messages.py new file mode 100644 index 00000000..12011e2b --- /dev/null +++ b/pyrogram/methods/business/delete_business_messages.py @@ -0,0 +1,71 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# Copyright (C) 2022-present Mayuri-Chan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Iterable, Union + +import pyrogram +from pyrogram import raw + + +class DeleteBusinessMessages: + async def delete_business_messages( + self: "pyrogram.Client", + business_connection_id: str, + message_ids: Union[int, Iterable[int]] + ) -> int: + """Delete messages on behalf of a business account. + + .. note:: + + Requires the `can_delete_sent_messages` business bot right to delete messages sent by the bot itself, + or the `can_delete_all_messages` business bot right to delete any message. + + .. include:: /_includes/usable-by/bots.rst + + Parameters: + business_connection_id (``str``): + Unique identifier of business connection on behalf of which to send the request. + + message_ids (``int`` | Iterable of ``int``): + An iterable of message identifiers to delete (integers) or a single message id. + All messages must be from the same chat. + + Returns: + ``int``: Amount of affected messages + + Example: + .. code-block:: python + + # Delete one message + await app.delete_business_messages(connection_id, message_id) + + # Delete multiple messages at once + await app.delete_business_messages(connection_id, list_of_message_ids) + """ + message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids] + + r = await self.invoke( + raw.functions.messages.DeleteMessages( + id=message_ids, + revoke=True + ), + business_connection_id=business_connection_id + ) + + return r.pts_count diff --git a/pyrogram/methods/business/get_business_account_gifts.py b/pyrogram/methods/business/get_business_account_gifts.py new file mode 100644 index 00000000..44f08898 --- /dev/null +++ b/pyrogram/methods/business/get_business_account_gifts.py @@ -0,0 +1,129 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# Copyright (C) 2022-present Mayuri-Chan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Optional + +import pyrogram +from pyrogram import raw, types + + +class GetBusinessAccountGifts: + async def get_business_account_gifts( + self: "pyrogram.Client", + business_connection_id: str, + exclude_unsaved: Optional[bool] = None, + exclude_saved: Optional[bool] = None, + exclude_unlimited: Optional[bool] = None, + exclude_limited: Optional[bool] = None, + exclude_upgraded: Optional[bool] = None, + sort_by_price: Optional[bool] = None, + limit: int = 0, + offset: str = "", + ): + """Return the gifts received and owned by a managed business account. + + .. note:: + + Requires the `can_view_gifts_and_stars` business bot right. + + .. include:: /_includes/usable-by/bots.rst + + Parameters: + business_connection_id (``str``): + Unique identifier of business connection on behalf of which to send the request. + + exclude_unsaved (``bool``, *optional*): + Pass True to exclude gifts that aren’t saved to the account’s profile page. + + exclude_saved (``bool``, *optional*): + Pass True to exclude gifts that are saved to the account’s profile page. + + exclude_unlimited (``bool``, *optional*): + Pass True to exclude gifts that can be purchased an unlimited number of times. + + exclude_limited (``bool``, *optional*): + Pass True to exclude gifts that can be purchased a limited number of times. + + exclude_upgraded (``bool``, *optional*): + Pass True to exclude upgraded gifts. + + sort_by_price (``bool``, *optional*): + Pass True to sort results by gift price instead of send date. Sorting is applied before pagination. + + offset (``str``, *optional*): + Offset of the first entry to return as received from the previous request. + + limit (``int``, *optional*): + The maximum number of gifts to be returned. + + Returns: + ``Generator``: A generator yielding :obj:`~pyrogram.types.Gift` objects. + + Example: + .. code-block:: python + + async for gift in app.get_business_account_gifts(connection_id): + print(gift) + """ + current = 0 + total = abs(limit) or (1 << 31) - 1 + limit = min(100, total) + + connection_info = await self.get_business_connection(business_connection_id) + + while True: + r = await self.invoke( + raw.functions.payments.GetSavedStarGifts( + peer=await self.resolve_peer(connection_info.user.id), + offset=offset, + limit=limit, + exclude_unsaved=exclude_unsaved, + exclude_saved=exclude_saved, + exclude_unlimited=exclude_unlimited, + exclude_limited=exclude_limited, + exclude_unique=exclude_upgraded, + sort_by_value=sort_by_price + ), + sleep_threshold=60, + business_connection_id=business_connection_id + ) + + users = {i.id: i for i in r.users} + chats = {i.id: i for i in r.chats} + + user_star_gifts = [ + await types.Gift._parse_saved(self, gift, users, chats) + for gift in r.gifts + ] + + if not user_star_gifts: + return + + for gift in user_star_gifts: + yield gift + + current += 1 + + if current >= total: + return + + offset = r.next_offset + + if not offset: + return diff --git a/pyrogram/methods/business/get_business_account_star_balance.py b/pyrogram/methods/business/get_business_account_star_balance.py new file mode 100644 index 00000000..90a81b5f --- /dev/null +++ b/pyrogram/methods/business/get_business_account_star_balance.py @@ -0,0 +1,61 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# Copyright (C) 2022-present Mayuri-Chan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Optional, Union + +import pyrogram +from pyrogram import raw + + +class GetBusinessAccountStarBalance: + async def get_business_account_star_balance( + self: "pyrogram.Client", + business_connection_id: str, + ) -> int: + """Return the amount of Telegram Stars owned by a managed business account. + + .. note:: + + Requires the `can_view_gifts_and_stars` business bot right. + + .. include:: /_includes/usable-by/bots.rst + + Parameters: + business_connection_id (``str``): + Unique identifier of business connection on behalf of which to send the request. + + Returns: + ``int``: On success, the current stars balance is returned. + + Example: + .. code-block:: python + + # Get stars balance + await app.get_business_account_star_balance("connection_id") + """ + connection_info = await self.get_business_connection(business_connection_id) + + r = await self.invoke( + raw.functions.payments.GetStarsStatus( + peer=await self.resolve_peer(connection_info.user.id), + ), + business_connection_id=business_connection_id + ) + + return r.balance.amount diff --git a/pyrogram/methods/business/transfer_business_account_stars.py b/pyrogram/methods/business/transfer_business_account_stars.py new file mode 100644 index 00000000..fbbdc2d0 --- /dev/null +++ b/pyrogram/methods/business/transfer_business_account_stars.py @@ -0,0 +1,72 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram +from pyrogram import raw + + +class TransferBusinessAccountStars: + async def transfer_business_account_stars( + self: "pyrogram.Client", + business_connection_id: str, + star_count: int, + ) -> bool: + """Transfers Telegram Stars from the business account balance to the bot’s balance. + + .. note:: + + Requires the `can_transfer_stars` business bot right. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + business_connection_id (``str``): + Unique identifier of the business connection. + + star_count (``int`` | ``str``): + Number of Telegram Stars to transfer, 1-10000. + + Returns: + ``bool``: On success, True is returned. + """ + # Why telegram won't let us just use InputPeerSelf :( + if self.me: + bot_id = self.me.id + else: + bot_id = ( + await self.invoke(raw.functions.users.GetUsers(id=[raw.types.InputPeerSelf()])) + )[0].id + + invoice = raw.types.InputInvoiceBusinessBotTransferStars( + bot=await self.resolve_peer(bot_id), stars=star_count + ) + + payment_form = await self.invoke( + raw.functions.payments.GetPaymentForm(invoice=invoice), + business_connection_id=business_connection_id, + ) + + await self.invoke( + raw.functions.payments.SendStarsForm( + form_id=payment_form.form_id, + invoice=invoice, + ), + business_connection_id=business_connection_id, + ) + + return True diff --git a/pyrogram/methods/decorators/on_error.py b/pyrogram/methods/decorators/on_error.py index d21d75bb..dc192b1e 100644 --- a/pyrogram/methods/decorators/on_error.py +++ b/pyrogram/methods/decorators/on_error.py @@ -24,26 +24,33 @@ from pyrogram.filters import Filter class OnError: - def on_error(self=None, errors=None) -> Callable: + def on_error( + self=None, + errors=None, + group: int = 0, + ) -> Callable: """Decorator for handling new errors. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the - :obj:`~pyrogram.handlers.MessageHandler`. + :obj:`~pyrogram.handlers.ErrorHandler`. Parameters: errors (:obj:`~Exception`, *optional*): Pass one or more errors to allow only a subset of errors to be passed in your function. + + group (``int``, *optional*): + The group identifier, defaults to 0. """ def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): - self.add_handler(pyrogram.handlers.ErrorHandler(func, errors), 0) + self.add_handler(pyrogram.handlers.ErrorHandler(func, errors), group) elif isinstance(self, Filter) or self is None: if not hasattr(func, "handlers"): func.handlers = [] - func.handlers.append((pyrogram.handlers.ErrorHandler(func, self), 0)) + func.handlers.append((pyrogram.handlers.ErrorHandler(func, self), group)) return func diff --git a/pyrogram/methods/messages/get_chat_history.py b/pyrogram/methods/messages/get_chat_history.py index fff96c7c..544b3cb2 100644 --- a/pyrogram/methods/messages/get_chat_history.py +++ b/pyrogram/methods/messages/get_chat_history.py @@ -33,7 +33,8 @@ async def get_chunk( from_message_id: int = 0, from_date: datetime = utils.zero_datetime(), min_id: int = 0, - max_id: int = 0 + max_id: int = 0, + reverse: Optional[bool] = None ): messages = await client.invoke( raw.functions.messages.GetHistory( @@ -48,6 +49,8 @@ async def get_chunk( ), sleep_threshold=60 ) + if reverse: + messages.messages.reverse() return await utils.parse_messages(client, messages, replies=0) @@ -61,7 +64,8 @@ class GetChatHistory: offset_id: int = 0, offset_date: datetime = utils.zero_datetime(), min_id: int = 0, - max_id: int = 0 + max_id: int = 0, + reverse: Optional[bool] = None ) -> Optional[AsyncGenerator["types.Message", None]]: """Get messages from a chat history. @@ -96,6 +100,9 @@ class GetChatHistory: max_id: (``int``, *optional*): The maximum message id. you will not get any message which have id greater than max_id. + reverse (``bool``, *optional*): + Pass True to retrieve the messages in reversed order (from older to most recent). + Returns: ``Generator``: A generator yielding :obj:`~pyrogram.types.Message` objects. @@ -118,7 +125,8 @@ class GetChatHistory: from_message_id=offset_id, from_date=offset_date, min_id=min_id, - max_id=max_id + max_id=max_id, + reverse=reverse ) if not messages: diff --git a/pyrogram/methods/messages/get_scheduled_messages.py b/pyrogram/methods/messages/get_scheduled_messages.py index 532627d1..0623944a 100644 --- a/pyrogram/methods/messages/get_scheduled_messages.py +++ b/pyrogram/methods/messages/get_scheduled_messages.py @@ -78,6 +78,6 @@ class GetScheduledMessages: r = await self.invoke(rpc, sleep_threshold=-1) - messages = await utils.parse_messages(self, r) + messages = await utils.parse_messages(self, r, is_scheduled=True) return messages if is_iterable else messages[0] if messages else None diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py index 99c67749..f77244cd 100644 --- a/pyrogram/methods/messages/send_animation.py +++ b/pyrogram/methods/messages/send_animation.py @@ -52,6 +52,7 @@ class SendAnimation: reply_to_message_id: int = None, reply_to_story_id: int = None, reply_to_chat_id: Union[int, str] = None, + reply_to_monoforum_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, schedule_date: datetime = None, @@ -145,6 +146,11 @@ class SendAnimation: for reply to message from another chat. You can also use chat public link in form of *t.me/* (str). + reply_to_monoforum_id (``int`` | ``str``, *optional*): + Unique identifier for the target user of monoforum. + for reply to message from monoforum. + for channel administrators only. + quote_text (``str``, *optional*): Text to quote. for reply_to_message only. @@ -226,6 +232,7 @@ class SendAnimation: reply_to_story_id=reply_to_story_id, message_thread_id=message_thread_id, reply_to_chat_id=reply_to_chat_id, + reply_to_monoforum_id=reply_to_monoforum_id, quote_text=quote_text, quote_entities=quote_entities, parse_mode=parse_mode diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py index 967958e0..752dee58 100644 --- a/pyrogram/methods/messages/send_audio.py +++ b/pyrogram/methods/messages/send_audio.py @@ -50,6 +50,7 @@ class SendAudio: reply_to_message_id: int = None, reply_to_story_id: int = None, reply_to_chat_id: Union[int, str] = None, + reply_to_monoforum_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, message_effect_id: int = None, @@ -137,6 +138,11 @@ class SendAudio: for reply to message from another chat. You can also use chat public link in form of *t.me/* (str). + reply_to_monoforum_id (``int`` | ``str``, *optional*): + Unique identifier for the target user of monoforum. + for reply to message from monoforum. + for channel administrators only. + quote_text (``str``, *optional*): Text to quote. for reply_to_message only. @@ -216,6 +222,7 @@ class SendAudio: reply_to_story_id=reply_to_story_id, message_thread_id=message_thread_id, reply_to_chat_id=reply_to_chat_id, + reply_to_monoforum_id=reply_to_monoforum_id, quote_text=quote_text, quote_entities=quote_entities, parse_mode=parse_mode diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py index 8fc8071c..05875f52 100644 --- a/pyrogram/methods/messages/send_cached_media.py +++ b/pyrogram/methods/messages/send_cached_media.py @@ -40,6 +40,7 @@ class SendCachedMedia: reply_to_message_id: int = None, reply_to_story_id: int = None, reply_to_chat_id: Union[int, str] = None, + reply_to_monoforum_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, schedule_date: datetime = None, @@ -104,6 +105,11 @@ class SendCachedMedia: for reply to message from another chat. You can also use chat public link in form of *t.me/* (str). + reply_to_monoforum_id (``int`` | ``str``, *optional*): + Unique identifier for the target user of monoforum. + for reply to message from monoforum. + for channel administrators only. + quote_text (``str``, *optional*): Text to quote. for reply_to_message only. @@ -144,6 +150,7 @@ class SendCachedMedia: reply_to_story_id=reply_to_story_id, message_thread_id=message_thread_id, reply_to_chat_id=reply_to_chat_id, + reply_to_monoforum_id=reply_to_monoforum_id, quote_text=quote_text, quote_entities=quote_entities, parse_mode=parse_mode diff --git a/pyrogram/methods/messages/send_contact.py b/pyrogram/methods/messages/send_contact.py index c50d03bd..bd96d7ef 100644 --- a/pyrogram/methods/messages/send_contact.py +++ b/pyrogram/methods/messages/send_contact.py @@ -38,6 +38,7 @@ class SendContact: business_connection_id: str = None, reply_to_message_id: int = None, reply_to_chat_id: Union[int, str] = None, + reply_to_monoforum_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, parse_mode: Optional["enums.ParseMode"] = None, @@ -95,6 +96,11 @@ class SendContact: for reply to message from another chat. You can also use chat public link in form of *t.me/* (str). + reply_to_monoforum_id (``int`` | ``str``, *optional*): + Unique identifier for the target user of monoforum. + for reply to message from monoforum. + for channel administrators only. + quote_text (``str``, *optional*): Text to quote. for reply_to_message only. @@ -139,6 +145,7 @@ class SendContact: reply_to_message_id=reply_to_message_id, message_thread_id=message_thread_id, reply_to_chat_id=reply_to_chat_id, + reply_to_monoforum_id=reply_to_monoforum_id, quote_text=quote_text, quote_entities=quote_entities, parse_mode=parse_mode diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py index d96025ed..0cd63a11 100644 --- a/pyrogram/methods/messages/send_dice.py +++ b/pyrogram/methods/messages/send_dice.py @@ -36,6 +36,7 @@ class SendDice: reply_to_message_id: int = None, reply_to_story_id: int = None, reply_to_chat_id: Union[int, str] = None, + reply_to_monoforum_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, parse_mode: Optional["enums.ParseMode"] = None, @@ -91,6 +92,11 @@ class SendDice: for reply to message from another chat. You can also use chat public link in form of *t.me/* (str). + reply_to_monoforum_id (``int`` | ``str``, *optional*): + Unique identifier for the target user of monoforum. + for reply to message from monoforum. + for channel administrators only. + quote_text (``str``, *optional*): Text to quote. for reply_to_message only. @@ -143,6 +149,7 @@ class SendDice: reply_to_story_id=reply_to_story_id, message_thread_id=message_thread_id, reply_to_chat_id=reply_to_chat_id, + reply_to_monoforum_id=reply_to_monoforum_id, quote_text=quote_text, quote_entities=quote_entities, parse_mode=parse_mode diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py index cc29985e..efb1da72 100644 --- a/pyrogram/methods/messages/send_document.py +++ b/pyrogram/methods/messages/send_document.py @@ -48,6 +48,7 @@ class SendDocument: reply_to_message_id: int = None, reply_to_story_id: int = None, reply_to_chat_id: Union[int, str] = None, + reply_to_monoforum_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, message_effect_id: int = None, @@ -129,6 +130,11 @@ class SendDocument: for reply to message from another chat. You can also use chat public link in form of *t.me/* (str). + reply_to_monoforum_id (``int`` | ``str``, *optional*): + Unique identifier for the target user of monoforum. + for reply to message from monoforum. + for channel administrators only. + quote_text (``str``, *optional*): Text to quote. for reply_to_message only. @@ -203,6 +209,7 @@ class SendDocument: reply_to_story_id=reply_to_story_id, message_thread_id=message_thread_id, reply_to_chat_id=reply_to_chat_id, + reply_to_monoforum_id=reply_to_monoforum_id, quote_text=quote_text, quote_entities=quote_entities, parse_mode=parse_mode diff --git a/pyrogram/methods/messages/send_location.py b/pyrogram/methods/messages/send_location.py index c8621a7d..0138f2a6 100644 --- a/pyrogram/methods/messages/send_location.py +++ b/pyrogram/methods/messages/send_location.py @@ -38,6 +38,7 @@ class SendLocation: business_connection_id: str = None, reply_to_message_id: int = None, reply_to_chat_id: Union[int, str] = None, + reply_to_monoforum_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, parse_mode: Optional["enums.ParseMode"] = None, @@ -89,6 +90,11 @@ class SendLocation: for reply to message from another chat. You can also use chat public link in form of *t.me/* (str). + reply_to_monoforum_id (``int`` | ``str``, *optional*): + Unique identifier for the target user of monoforum. + for reply to message from monoforum. + for channel administrators only. + reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message @@ -136,6 +142,7 @@ class SendLocation: reply_to_message_id=reply_to_message_id, message_thread_id=message_thread_id, reply_to_chat_id=reply_to_chat_id, + reply_to_monoforum_id=reply_to_monoforum_id, quote_text=quote_text, quote_entities=quote_entities, parse_mode=parse_mode diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index bbdeeac5..89867f0b 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -22,7 +22,12 @@ import os import re from datetime import datetime from pymediainfo import MediaInfo -from typing import Union, List, Optional +from typing import ( + Union, + List, + Optional, + Callable, +) import pyrogram from pyrogram import enums @@ -35,7 +40,6 @@ log = logging.getLogger(__name__) class SendMediaGroup: - # TODO: Add progress parameter async def send_media_group( self: "pyrogram.Client", chat_id: Union[int, str], @@ -52,6 +56,7 @@ class SendMediaGroup: reply_to_message_id: int = None, reply_to_story_id: int = None, reply_to_chat_id: Union[int, str] = None, + reply_to_monoforum_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, parse_mode: Optional["enums.ParseMode"] = None, @@ -59,7 +64,9 @@ class SendMediaGroup: protect_content: bool = None, allow_paid_broadcast: bool = None, message_effect_id: int = None, - invert_media: bool = None + invert_media: bool = None, + progress: Callable = None, + progress_args: tuple = (), ) -> List["types.Message"]: """Send a group of photos or videos as an album. @@ -89,7 +96,7 @@ class SendMediaGroup: reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - + reply_to_story_id (``int``, *optional*): Unique identifier for the target story. @@ -98,6 +105,11 @@ class SendMediaGroup: for reply to message from another chat. You can also use chat public link in form of *t.me/* (str). + reply_to_monoforum_id (``int`` | ``str``, *optional*): + Unique identifier for the target user of monoforum. + for reply to message from monoforum. + for channel administrators only. + quote_text (``str``, *optional*): Text to quote. for reply_to_message only. @@ -126,6 +138,28 @@ class SendMediaGroup: invert_media (``bool``, *optional*): Inverts the position of the media and caption. + progress (``Callable``, *optional*): + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. + + Other Parameters: + current (``int``): + The amount of bytes transmitted so far. + + total (``int``): + The total size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. + Returns: List of :obj:`~pyrogram.types.Message`: On success, a list of the sent messages is returned. @@ -152,23 +186,25 @@ class SendMediaGroup: reply_to_story_id=reply_to_story_id, message_thread_id=message_thread_id, reply_to_chat_id=reply_to_chat_id, + reply_to_monoforum_id=reply_to_monoforum_id, quote_text=quote_text, quote_entities=quote_entities, - parse_mode=parse_mode + parse_mode=parse_mode, ) for i in media: if isinstance(i, types.InputMediaPhoto): if isinstance(i.media, str): if os.path.isfile(i.media): + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) media = await self.invoke( 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=file, + spoiler=i.has_spoiler, + ), + ), ) media = raw.types.InputMediaPhoto( @@ -177,7 +213,7 @@ class SendMediaGroup: access_hash=media.photo.access_hash, file_reference=media.photo.file_reference ), - spoiler=i.has_spoiler + spoiler=i.has_spoiler, ) elif re.match("^https?://", i.media): media = await self.invoke( @@ -185,9 +221,9 @@ class SendMediaGroup: peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaPhotoExternal( url=i.media, - spoiler=i.has_spoiler - ) - ) + spoiler=i.has_spoiler, + ), + ), ) media = raw.types.InputMediaPhoto( @@ -196,19 +232,20 @@ class SendMediaGroup: access_hash=media.photo.access_hash, file_reference=media.photo.file_reference ), - spoiler=i.has_spoiler + spoiler=i.has_spoiler, ) else: media = utils.get_input_media_from_file_id(i.media, FileType.PHOTO) else: + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) media = await self.invoke( 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=file, + spoiler=i.has_spoiler, + ), + ), ) media = raw.types.InputMediaPhoto( @@ -217,7 +254,7 @@ class SendMediaGroup: access_hash=media.photo.access_hash, file_reference=media.photo.file_reference ), - spoiler=i.has_spoiler + spoiler=i.has_spoiler, ) elif ( isinstance(i, types.InputMediaVideo) @@ -241,22 +278,25 @@ class SendMediaGroup: w=i.width, h=i.height ), - raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) + raw.types.DocumentAttributeFilename(file_name=i.file_name or os.path.basename(i.media)), ] if is_animation: attributes.append(raw.types.DocumentAttributeAnimated()) + + thumb = await self.save_file(i.thumb) + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( - file=await self.save_file(i.media), - thumb=await self.save_file(i.thumb), + file=file, + thumb=thumb, spoiler=i.has_spoiler, mime_type=self.guess_mime_type(i.media) or "video/mp4", nosound_video=is_animation, - attributes=attributes - ) - ) + attributes=attributes, + ), + ), ) media = raw.types.InputMediaDocument( @@ -265,7 +305,7 @@ class SendMediaGroup: access_hash=media.document.access_hash, file_reference=media.document.file_reference ), - spoiler=i.has_spoiler + spoiler=i.has_spoiler, ) elif re.match("^https?://", i.media): media = await self.invoke( @@ -273,9 +313,9 @@ class SendMediaGroup: peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaDocumentExternal( url=i.media, - spoiler=i.has_spoiler - ) - ) + spoiler=i.has_spoiler, + ), + ), ) media = raw.types.InputMediaDocument( @@ -284,17 +324,19 @@ class SendMediaGroup: access_hash=media.document.access_hash, file_reference=media.document.file_reference ), - spoiler=i.has_spoiler + spoiler=i.has_spoiler, ) else: media = utils.get_input_media_from_file_id(i.media, FileType.VIDEO) else: + thumb = await self.save_file(i.thumb) + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( - file=await self.save_file(i.media), - thumb=await self.save_file(i.thumb), + file=file, + thumb=thumb, spoiler=i.has_spoiler, mime_type=self.guess_mime_type(getattr(i.media, "name", "video.mp4")) or "video/mp4", attributes=[ @@ -304,10 +346,10 @@ class SendMediaGroup: w=i.width, h=i.height ), - raw.types.DocumentAttributeFilename(file_name=getattr(i.media, "name", "video.mp4")) - ] - ) - ) + raw.types.DocumentAttributeFilename(file_name=i.file_name or getattr(i.media, "name", "video.mp4")), + ], + ), + ), ) media = raw.types.InputMediaDocument( @@ -316,127 +358,135 @@ class SendMediaGroup: access_hash=media.document.access_hash, file_reference=media.document.file_reference ), - spoiler=i.has_spoiler + spoiler=i.has_spoiler, ) elif isinstance(i, types.InputMediaAudio): if isinstance(i.media, str): if os.path.isfile(i.media): + thumb = await self.save_file(i.thumb) + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) 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(i.media) or "audio/mpeg", - file=await self.save_file(i.media), - thumb=await self.save_file(i.thumb), + file=file, + thumb=thumb, attributes=[ raw.types.DocumentAttributeAudio( duration=i.duration, performer=i.performer, title=i.title ), - raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) - ] - ) - ) + raw.types.DocumentAttributeFilename(file_name=i.file_name or os.path.basename(i.media)), + ], + ), + ), ) media = raw.types.InputMediaDocument( id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + file_reference=media.document.file_reference, + ), ) 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 - ) - ) + url=i.media, + ), + ), ) media = raw.types.InputMediaDocument( id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + file_reference=media.document.file_reference, + ), ) else: media = utils.get_input_media_from_file_id(i.media, FileType.AUDIO) else: + thumb = await self.save_file(i.thumb) + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) 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(getattr(i.media, "name", "audio.mp3")) or "audio/mpeg", - file=await self.save_file(i.media), - thumb=await self.save_file(i.thumb), + file=file, + thumb=thumb, attributes=[ raw.types.DocumentAttributeAudio( duration=i.duration, performer=i.performer, title=i.title ), - raw.types.DocumentAttributeFilename(file_name=getattr(i.media, "name", "audio.mp3")) - ] - ) - ) + raw.types.DocumentAttributeFilename(file_name=i.file_name or getattr(i.media, "name", "audio.mp3")), + ], + ), + ), ) media = raw.types.InputMediaDocument( id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + file_reference=media.document.file_reference, + ), ) elif isinstance(i, types.InputMediaDocument): if isinstance(i.media, str): if os.path.isfile(i.media): + thumb = await self.save_file(i.thumb) + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) 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(i.media) or "application/zip", - file=await self.save_file(i.media), - thumb=await self.save_file(i.thumb), + file=file, + thumb=thumb, attributes=[ - raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) - ] - ) - ) + raw.types.DocumentAttributeFilename(file_name=i.file_name or os.path.basename(i.media)), + ], + ), + ), ) media = raw.types.InputMediaDocument( id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + file_reference=media.document.file_reference, + ), ) 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 - ) - ) + url=i.media, + ), + ), ) media = raw.types.InputMediaDocument( id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + file_reference=media.document.file_reference, + ), ) else: media = utils.get_input_media_from_file_id(i.media, FileType.DOCUMENT) else: + thumb = await self.save_file(i.thumb) + file = await self.save_file(i.media, progress=progress, progress_args=progress_args) media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), @@ -444,21 +494,21 @@ class SendMediaGroup: mime_type=self.guess_mime_type( getattr(i.media, "name", "file.zip") ) or "application/zip", - file=await self.save_file(i.media), - thumb=await self.save_file(i.thumb), + file=file, + thumb=thumb, attributes=[ - raw.types.DocumentAttributeFilename(file_name=getattr(i.media, "name", "file.zip")) - ] - ) - ) + raw.types.DocumentAttributeFilename(file_name=i.file_name or getattr(i.media, "name", "file.zip")), + ], + ), + ), ) media = raw.types.InputMediaDocument( id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + file_reference=media.document.file_reference, + ), ) else: raise ValueError(f"{i.__class__.__name__} is not a supported type for send_media_group") @@ -467,8 +517,8 @@ class SendMediaGroup: raw.types.InputSingleMedia( media=media, random_id=self.rnd_id(), - **(await utils.parse_text_entities(self, i.caption, i.parse_mode, i.caption_entities)) - ) + **(await utils.parse_text_entities(self, i.caption, i.parse_mode, i.caption_entities)), + ), ) rpc = raw.functions.messages.SendMultiMedia( @@ -480,20 +530,20 @@ class SendMediaGroup: noforwards=protect_content, allow_paid_floodskip=allow_paid_broadcast, effect=message_effect_id, - invert_media=invert_media + invert_media=invert_media, ) - + if business_connection_id is not None: r = await self.invoke( raw.functions.InvokeWithBusinessConnection( connection_id=business_connection_id, query=rpc ), - sleep_threshold=60 + sleep_threshold=60, ) else: r = await self.invoke(rpc, sleep_threshold=60) - + return await utils.parse_messages( self, raw.types.messages.Messages( @@ -505,7 +555,7 @@ class SendMediaGroup: r.updates )], users=r.users, - chats=r.chats + chats=r.chats, ), - business_connection_id=business_connection_id + business_connection_id=business_connection_id, ) diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py index c8547f30..dd12e323 100644 --- a/pyrogram/methods/messages/send_message.py +++ b/pyrogram/methods/messages/send_message.py @@ -39,6 +39,7 @@ class SendMessage: reply_to_message_id: int = None, reply_to_story_id: int = None, reply_to_chat_id: int = None, + reply_to_monoforum_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, schedule_date: datetime = None, @@ -100,6 +101,11 @@ class SendMessage: for reply to message from another chat. You can also use chat public link in form of *t.me/* (str). + reply_to_monoforum_id (``int`` | ``str``, *optional*): + Unique identifier for the target user of the monoforum. + for reply to message from a monoforum. + for channel administrators only. + quote_text (``str``, *optional*): Text to quote. for reply_to_message only. @@ -174,6 +180,7 @@ class SendMessage: reply_to_story_id=reply_to_story_id, message_thread_id=message_thread_id, reply_to_chat_id=reply_to_chat_id, + reply_to_monoforum_id=reply_to_monoforum_id, quote_text=quote_text, quote_entities=quote_entities, parse_mode=parse_mode diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index 711d893d..be48619a 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -46,6 +46,7 @@ class SendPhoto: reply_to_message_id: int = None, reply_to_story_id: int = None, reply_to_chat_id: Union[int, str] = None, + reply_to_monoforum_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, schedule_date: datetime = None, @@ -122,6 +123,11 @@ class SendPhoto: for reply to message from another chat. You can also use chat public link in form of *t.me/* (str). + reply_to_monoforum_id (``int`` | ``str``, *optional*): + Unique identifier for the target user of monoforum. + for reply to message from monoforum. + for channel administrators only. + quote_text (``str``, *optional*): Text to quote. for reply_to_message only. @@ -203,6 +209,7 @@ class SendPhoto: reply_to_story_id=reply_to_story_id, message_thread_id=message_thread_id, reply_to_chat_id=reply_to_chat_id, + reply_to_monoforum_id=reply_to_monoforum_id, quote_text=quote_text, quote_entities=quote_entities, parse_mode=parse_mode diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py index f3e60cd2..7af70846 100644 --- a/pyrogram/methods/messages/send_sticker.py +++ b/pyrogram/methods/messages/send_sticker.py @@ -44,6 +44,7 @@ class SendSticker: reply_to_message_id: int = None, reply_to_story_id: int = None, reply_to_chat_id: Union[int, str] = None, + reply_to_monoforum_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, parse_mode: Optional["enums.ParseMode"] = None, @@ -104,6 +105,11 @@ class SendSticker: for reply to message from another chat. You can also use chat public link in form of *t.me/* (str). + reply_to_monoforum_id (``int`` | ``str``, *optional*): + Unique identifier for the target user of monoforum. + for reply to message from monoforum. + for channel administrators only. + quote_text (``str``, *optional*): Text to quote. for reply_to_message only. @@ -178,6 +184,7 @@ class SendSticker: reply_to_story_id=reply_to_story_id, message_thread_id=message_thread_id, reply_to_chat_id=reply_to_chat_id, + reply_to_monoforum_id=reply_to_monoforum_id, quote_text=quote_text, quote_entities=quote_entities, parse_mode=parse_mode diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index af2a539d..0702f34d 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -50,6 +50,7 @@ class SendVideo: reply_to_message_id: int = None, reply_to_story_id: int = None, reply_to_chat_id: Union[int, str] = None, + reply_to_monoforum_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, cover: Union[str, BinaryIO] = None, @@ -58,6 +59,7 @@ class SendVideo: protect_content: bool = None, allow_paid_broadcast: bool = None, message_effect_id: int = None, + view_once: bool = None, invert_media: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", @@ -150,6 +152,11 @@ class SendVideo: for reply to message from another chat. You can also use chat public link in form of *t.me/* (str). + reply_to_monoforum_id (``int`` | ``str``, *optional*): + Unique identifier for the target user of monoforum. + for reply to message from monoforum. + for channel administrators only. + quote_text (``str``, *optional*): Text to quote. for reply_to_message only. @@ -180,6 +187,10 @@ class SendVideo: message_effect_id (``int`` ``64-bit``, *optional*): Unique identifier of the message effect to be added to the message; for private chats only. + view_once (``bool``, *optional*): + Self-Destruct Timer. + If True, the photo will self-destruct after it was viewed. + invert_media (``bool``, *optional*): Inverts the position of the video and caption. @@ -246,6 +257,7 @@ class SendVideo: reply_to_story_id=reply_to_story_id, message_thread_id=message_thread_id, reply_to_chat_id=reply_to_chat_id, + reply_to_monoforum_id=reply_to_monoforum_id, quote_text=quote_text, quote_entities=quote_entities, parse_mode=parse_mode @@ -298,7 +310,7 @@ class SendVideo: media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(video) or "video/mp4", file=file, - ttl_seconds=ttl_seconds, + ttl_seconds=(1 << 31) - 1 if view_once else ttl_seconds, spoiler=has_spoiler, thumb=thumb, attributes=[ @@ -316,13 +328,13 @@ class SendVideo: elif re.match("^https?://", video): media = raw.types.InputMediaDocumentExternal( url=video, - ttl_seconds=ttl_seconds, + ttl_seconds=(1 << 31) - 1 if view_once else ttl_seconds, spoiler=has_spoiler, video_cover=vidcover_file, video_timestamp=start_timestamp ) else: - media = utils.get_input_media_from_file_id(video, FileType.VIDEO, ttl_seconds=ttl_seconds) + media = utils.get_input_media_from_file_id(video, FileType.VIDEO, ttl_seconds=(1 << 31) - 1 if view_once else ttl_seconds) media.spoiler = has_spoiler else: thumb = await self.save_file(thumb) @@ -330,7 +342,7 @@ class SendVideo: media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(file_name or video.name) or "video/mp4", file=file, - ttl_seconds=ttl_seconds, + ttl_seconds=(1 << 31) - 1 if view_once else ttl_seconds, spoiler=has_spoiler, thumb=thumb, attributes=[ diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py index aa0a8def..bea7380e 100644 --- a/pyrogram/methods/messages/send_video_note.py +++ b/pyrogram/methods/messages/send_video_note.py @@ -45,6 +45,7 @@ class SendVideoNote: reply_to_message_id: int = None, reply_to_story_id: int = None, reply_to_chat_id: Union[int, str] = None, + reply_to_monoforum_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, parse_mode: Optional["enums.ParseMode"] = None, @@ -118,6 +119,11 @@ class SendVideoNote: for reply to message from another chat. You can also use chat public link in form of *t.me/* (str). + reply_to_monoforum_id (``int`` | ``str``, *optional*): + Unique identifier for the target user of monoforum. + for reply to message from monoforum. + for channel administrators only. + quote_text (``str``, *optional*): Text to quote. for reply_to_message only. @@ -197,6 +203,7 @@ class SendVideoNote: reply_to_story_id=reply_to_story_id, message_thread_id=message_thread_id, reply_to_chat_id=reply_to_chat_id, + reply_to_monoforum_id=reply_to_monoforum_id, quote_text=quote_text, quote_entities=quote_entities, parse_mode=parse_mode diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py index 2bcbb0ca..1ec3751b 100644 --- a/pyrogram/methods/messages/send_voice.py +++ b/pyrogram/methods/messages/send_voice.py @@ -46,6 +46,7 @@ class SendVoice: reply_to_message_id: int = None, reply_to_story_id: int = None, reply_to_chat_id: Union[int, str] = None, + reply_to_monoforum_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, schedule_date: datetime = None, @@ -121,6 +122,11 @@ class SendVoice: for reply to message from another chat. You can also use chat public link in form of *t.me/* (str). + reply_to_monoforum_id (``int`` | ``str``, *optional*): + Unique identifier for the target user of monoforum. + for reply to message from monoforum. + for channel administrators only. + quote_text (``str``, *optional*): Text to quote. for reply_to_message only. @@ -189,6 +195,7 @@ class SendVoice: reply_to_story_id=reply_to_story_id, message_thread_id=message_thread_id, reply_to_chat_id=reply_to_chat_id, + reply_to_monoforum_id=reply_to_monoforum_id, quote_text=quote_text, quote_entities=quote_entities, parse_mode=parse_mode diff --git a/pyrogram/methods/messages/send_web_page.py b/pyrogram/methods/messages/send_web_page.py index be79610e..257b7bd7 100644 --- a/pyrogram/methods/messages/send_web_page.py +++ b/pyrogram/methods/messages/send_web_page.py @@ -39,6 +39,7 @@ class SendWebPage: reply_to_message_id: int = None, reply_to_story_id: int = None, reply_to_chat_id: Union[int, str] = None, + reply_to_monoforum_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, schedule_date: datetime = None, @@ -106,6 +107,11 @@ class SendWebPage: for reply to message from another chat. You can also use chat public link in form of *t.me/* (str). + reply_to_monoforum_id (``int`` | ``str``, *optional*): + Unique identifier for the target user of monoforum. + for reply to message from monoforum. + for channel administrators only. + quote_text (``str``, *optional*): Text to quote. for reply_to_message only. @@ -161,6 +167,7 @@ class SendWebPage: reply_to_story_id=reply_to_story_id, message_thread_id=message_thread_id, reply_to_chat_id=reply_to_chat_id, + reply_to_monoforum_id=reply_to_monoforum_id, quote_text=quote_text, quote_entities=quote_entities, parse_mode=parse_mode diff --git a/pyrogram/methods/payments/__init__.py b/pyrogram/methods/payments/__init__.py index d67a26aa..b4185a50 100644 --- a/pyrogram/methods/payments/__init__.py +++ b/pyrogram/methods/payments/__init__.py @@ -31,11 +31,15 @@ from .get_chat_gifts_count import GetChatGiftsCount from .get_chat_gifts import GetChatGifts from .hide_gift import HideGift from .refund_stars_payment import RefundStarPayment +from .search_gifts_for_resale import SearchGiftsForResale from .send_invoice import SendInvoice from .send_paid_media import SendPaidMedia from .send_paid_reaction import SendPaidReaction from .send_payment_form import SendPaymentForm from .send_gift import SendGift +from .send_resold_gift import SendResoldGift +from .set_gift_resale_price import SetGiftResalePrice +from .set_pinned_gifts import SetPinnedGifts from .show_gift import ShowGift from .transfer_gift import TransferGift from .upgrade_gift import UpgradeGift @@ -55,11 +59,15 @@ class Payments( GetChatGifts, HideGift, RefundStarPayment, + SearchGiftsForResale, SendPaidReaction, SendPaidMedia, SendInvoice, SendPaymentForm, SendGift, + SendResoldGift, + SetGiftResalePrice, + SetPinnedGifts, ShowGift, TransferGift, UpgradeGift diff --git a/pyrogram/methods/payments/get_upgraded_gift.py b/pyrogram/methods/payments/get_upgraded_gift.py index 613be8dd..b91dc65d 100644 --- a/pyrogram/methods/payments/get_upgraded_gift.py +++ b/pyrogram/methods/payments/get_upgraded_gift.py @@ -47,7 +47,7 @@ class GetUpgradedGift: # Get information about upgraded gift by slug gift = await client.get_upgraded_gift("SignetRing-903") """ - match = re.match(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:nft/|\+))([\w-]+)$", link) + match = self.UPGRADED_GIFT_RE.match(link) if match: slug = match.group(1) diff --git a/pyrogram/methods/payments/search_gifts_for_resale.py b/pyrogram/methods/payments/search_gifts_for_resale.py new file mode 100644 index 00000000..c576766c --- /dev/null +++ b/pyrogram/methods/payments/search_gifts_for_resale.py @@ -0,0 +1,110 @@ +# Pyrofork - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# Copyright (C) 2022-present Mayuri-Chan +# +# This file is part of Pyrofork. +# +# Pyrofork is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrofork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrofork. If not, see . + +from typing import List, Optional + +import pyrogram +from pyrogram import enums, raw, types + + +class SearchGiftsForResale: + async def search_gifts_for_resale( + self: "pyrogram.Client", + gift_id: int, + order: "enums.GiftForResaleOrder" = enums.GiftForResaleOrder.CHANGE_DATE, + attributes: Optional[List["types.UpgradedGiftAttributeId"]] = None, + limit: int = 0, + offset: str = "" + ): + """Get upgraded gifts that can be bought from other owners. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + gift_id (``int``): + Identifier of the regular gift that was upgraded to a unique gift. + + order (:obj:`~pyrogram.enums.GiftForResaleOrder`): + Order in which the results will be sorted. + + attributes (List of :obj:`~pyrogram.types.UpgradedGiftAttributeId`, *optional*): + Attributes used to filter received gifts. + If multiple attributes of the same type are specified, then all of them are allowed. + If none attributes of specific type are specified, then all values for this attribute type are allowed. + + limit (``int``, *optional*): + The maximum number of gifts to return. Default is 0 (no limit). + + offset (``str``, *optional*): + The offset from which to start returning results. Default is "" (no offset). + + Returns: + ``Generator``: A generator yielding :obj:`~pyrogram.types.Gift` objects. + + Example: + .. code-block:: python + + async for gift in app.search_gifts_for_resale(gift_id=123456): + print(gift) + + # Buy first gift from resale market + async for gift in app.search_gifts_for_resale(gift_id=123456, limit=1): + await app.send_resold_gift(gift_link=gift.link, new_owner_chat_id="me") # or just use await gift.buy() + """ + current = 0 + total = abs(limit) or (1 << 31) - 1 + limit = min(100, total) + + while True: + r = await self.invoke( + raw.functions.payments.GetResaleStarGifts( + gift_id=gift_id, + offset=offset, + limit=limit, + sort_by_price=order == enums.GiftForResaleOrder.PRICE, + sort_by_num=order == enums.GiftForResaleOrder.NUMBER, + attributes=[attr.write() for attr in attributes] if attributes else None, + + ), + sleep_threshold=60 + ) + + users = {i.id: i for i in r.users} + chats = {i.id: i for i in r.chats} + + gifts = [ + await types.Gift._parse(self, gift, users, chats) + for gift in r.gifts + ] + + if not gifts: + return + + for gift in gifts: + yield gift + + current += 1 + + if current >= total: + return + + offset = r.next_offset + + if not offset: + return diff --git a/pyrogram/methods/payments/send_resold_gift.py b/pyrogram/methods/payments/send_resold_gift.py new file mode 100644 index 00000000..21f66489 --- /dev/null +++ b/pyrogram/methods/payments/send_resold_gift.py @@ -0,0 +1,89 @@ +# Pyrofork - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# Copyright (C) 2022-present Mayuri-Chan +# +# This file is part of Pyrofork. +# +# Pyrofork is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrofork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrofork. If not, see . + + +from typing import Optional, Union + +import pyrogram +from pyrogram import raw, types, utils + + +class SendResoldGift: + async def send_resold_gift( + self: "pyrogram.Client", + gift_link: str, + new_owner_chat_id: Union[int, str], + ) -> Optional["types.Message"]: + """Send an upgraded gift that is available for resale to another user or channel chat. + + .. note:: + + Gifts already owned by the current user must be transferred using :meth:`~pyrogram.Client.transfer_gift` and can't be passed to this method. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + gift_link (``str``): + Link to the gift. + + new_owner_chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat you want to transfer the star gift to. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + Returns: + :obj:`~pyrogram.types.Message`: On success, the sent message is returned. + + Example: + .. code-block:: python + + # Transfer gift to another user + await app.send_resold_gift(gift_link="https://t.me/nft/NekoHelmet-9215", new_owner_chat_id=123) + """ + match = self.UPGRADED_GIFT_RE.match(gift_link) + + if not match: + raise ValueError( + "Invalid gift link provided." + ) + + peer = await self.resolve_peer(new_owner_chat_id) + + invoice = raw.types.InputInvoiceStarGiftResale( + slug=match.group(1), + to_id=peer + ) + + r = await self.invoke( + raw.functions.payments.SendStarsForm( + form_id=(await self.invoke( + raw.functions.payments.GetPaymentForm( + invoice=invoice + ), + )).form_id, + invoice=invoice + ), + ) + + messages = await utils.parse_messages( + client=self, + messages=r.updates if isinstance(r, raw.types.payments.PaymentResult) else r + ) + + return messages[0] if messages else None diff --git a/pyrogram/methods/payments/set_gift_resale_price.py b/pyrogram/methods/payments/set_gift_resale_price.py new file mode 100644 index 00000000..52ce7d6e --- /dev/null +++ b/pyrogram/methods/payments/set_gift_resale_price.py @@ -0,0 +1,82 @@ +# Pyrofork - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# Copyright (C) 2022-present Mayuri-Chan +# +# This file is part of Pyrofork. +# +# Pyrofork is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrofork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrofork. If not, see . + +import re + +import pyrogram +from pyrogram import raw + + +class SetGiftResalePrice: + async def set_gift_resale_price( + self: "pyrogram.Client", + owned_gift_id: str, + resale_star_count: int + ) -> bool: + """Change resale price of a unique gift owned by the current user. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + owned_gift_id (``str``): + Unique identifier of the target gift. + For a user gift, you can use the message ID (int) of the gift message. + For a channel gift, you can use the packed format `chatID_savedID` (str). + For a upgraded gift, you can use the gift link. + + resale_star_count (``int``): + The new price for the unique gift. Pass 0 to disallow gift resale. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + # Change resale price of a unique gift + await app.set_gift_resale_price(owned_gift_id="123456", resale_star_count=100) + """ + if not isinstance(owned_gift_id, str): + raise ValueError(f"owned_gift_id has to be str, but {type(owned_gift_id)} was provided") + + saved_gift_match = re.match(r"^(-\d+)_(\d+)$", owned_gift_id) + slug_match = self.UPGRADED_GIFT_RE.match(owned_gift_id) + + if saved_gift_match: + stargift = raw.types.InputSavedStarGiftChat( + peer=await self.resolve_peer(saved_gift_match.group(1)), + saved_id=int(saved_gift_match.group(2)) + ) + elif slug_match: + stargift = raw.types.InputSavedStarGiftSlug( + slug=slug_match.group(1) + ) + else: + stargift = raw.types.InputSavedStarGiftUser( + msg_id=int(owned_gift_id) + ) + + await self.invoke( + raw.functions.payments.UpdateStarGiftPrice( + stargift=stargift, + resell_stars=resale_star_count + ) + ) + + return True diff --git a/pyrogram/methods/payments/set_pinned_gifts.py b/pyrogram/methods/payments/set_pinned_gifts.py new file mode 100644 index 00000000..ce7318b9 --- /dev/null +++ b/pyrogram/methods/payments/set_pinned_gifts.py @@ -0,0 +1,95 @@ +# Pyrofork - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# Copyright (C) 2022-present Mayuri-Chan +# +# This file is part of Pyrofork. +# +# Pyrofork is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrofork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrofork. If not, see . + +import re +from typing import List, Union + +import pyrogram +from pyrogram import raw + + +class SetPinnedGifts: + async def set_pinned_gifts( + self: "pyrogram.Client", + owner_id: Union[int, str], + owned_gift_ids: List[str], + ) -> bool: + """Change the list of pinned gifts on the current user. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + owner_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + owned_gift_ids (List of ``str``): + New list of pinned gifts. + All gifts must be upgraded and saved on the profile page first. + For a user gift, you can use the message ID (int) of the gift message. + For a channel gift, you can use the packed format `chatID_savedID` (str). + For a upgraded gift, you can use the gift link. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + # Set pinned gifts in user profile + await app.set_pinned_gifts(received_gift_ids=["123", "456"]) + """ + stargifts = [] + + for gift in owned_gift_ids: + if not isinstance(gift, str): + raise ValueError(f"gift id has to be str, but {type(gift)} was provided") + + saved_gift_match = re.match(r"^(\d+)_(\d+)$", str(gift)) + slug_match = self.UPGRADED_GIFT_RE.match(str(gift)) + + if saved_gift_match: + stargifts.append( + raw.types.InputSavedStarGiftChat( + peer=await self.resolve_peer(saved_gift_match.group(1)), + saved_id=int(saved_gift_match.group(2)) + ) + ) + elif slug_match: + stargifts.append( + raw.types.InputSavedStarGiftSlug( + slug=slug_match.group(1) + ) + ) + else: + stargifts.append( + raw.types.InputSavedStarGiftUser( + msg_id=int(gift) + ) + ) + + r = await self.invoke( + raw.functions.payments.ToggleStarGiftsPinnedToTop( + peer=await self.resolve_peer(owner_id), + stargift=stargifts + ) + ) + + return r diff --git a/pyrogram/methods/phone/__init__.py b/pyrogram/methods/phone/__init__.py new file mode 100644 index 00000000..d2cff63d --- /dev/null +++ b/pyrogram/methods/phone/__init__.py @@ -0,0 +1,25 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from .get_call_members import GetCallMembers + + +class Phone( + GetCallMembers +): + pass diff --git a/pyrogram/methods/phone/get_call_members.py b/pyrogram/methods/phone/get_call_members.py new file mode 100644 index 00000000..8fabc1d9 --- /dev/null +++ b/pyrogram/methods/phone/get_call_members.py @@ -0,0 +1,103 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union, AsyncGenerator + +import pyrogram +from pyrogram import types, raw + + +class GetCallMembers: + async def get_call_members( + self: "pyrogram.Client", + chat_id: Union[int, str], + limit: int = 0 + ) -> AsyncGenerator["types.GroupCallMember", None]: + """Get the members list of a chat call. + + A chat can be either a basic group or a supergroup. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + limit (``int``, *optional*): + Limits the number of members to be retrieved. + + Returns: + ``Generator``: On success, a generator yielding :obj:`~pyrogram.types.GroupCallMember` objects is returned. + + Example: + .. code-block:: python + + # Get members + async for member in app.get_call_members(chat_id): + print(member) + """ + peer = await self.resolve_peer(chat_id) + + if isinstance(peer, raw.types.InputPeerChannel): + r = await self.invoke(raw.functions.channels.GetFullChannel(channel=peer)) + elif isinstance(peer, raw.types.InputPeerChat): + r = await self.invoke(raw.functions.messages.GetFullChat(chat_id=peer.chat_id)) + else: + raise ValueError("Target chat should be group, supergroup or channel.") + + full_chat = r.full_chat + + if not getattr(full_chat, "call", None): + raise ValueError("There is no active call in this chat.") + + current = 0 + offset = "" + total = abs(limit) or (1 << 31) - 1 + limit = min(20, total) + + while True: + r = await self.invoke( + raw.functions.phone.GetGroupParticipants( + call=full_chat.call, + ids=[], + sources=[], + offset=offset, + limit=limit + ), + sleep_threshold=60 + ) + + users = {u.id: u for u in r.users} + chats = {c.id: c for c in r.chats} + members = [ + types.GroupCallMember._parse(self, member, users, chats) + for member in r.participants + ] + + if not members: + return + + offset = r.next_offset + + for member in members: + yield member + + current += 1 + + if current >= total: + return diff --git a/pyrogram/methods/utilities/__init__.py b/pyrogram/methods/utilities/__init__.py index 22d97434..28ff9eaa 100644 --- a/pyrogram/methods/utilities/__init__.py +++ b/pyrogram/methods/utilities/__init__.py @@ -19,6 +19,7 @@ from .add_handler import AddHandler from .export_session_string import ExportSessionString +from .ping import Ping from .remove_handler import RemoveHandler from .remove_error_handler import RemoveErrorHandler from .restart import Restart @@ -32,6 +33,7 @@ from .stop_transmission import StopTransmission class Utilities( AddHandler, ExportSessionString, + Ping, RemoveHandler, RemoveErrorHandler, Restart, diff --git a/pyrogram/methods/utilities/ping.py b/pyrogram/methods/utilities/ping.py new file mode 100644 index 00000000..f85838c9 --- /dev/null +++ b/pyrogram/methods/utilities/ping.py @@ -0,0 +1,46 @@ +# Pyrofork - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# Copyright (C) 2022-present Mayuri-Chan +# +# This file is part of Pyrofork. +# +# Pyrofork is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrofork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrofork. If not, see . + +from time import time + +import pyrogram +from pyrogram import raw + + +class Ping: + async def ping(self: "pyrogram.Client"): + """Measure the round-trip time (RTT) to the Telegram server. + + The ping method sends a request to the Telegram server and measures the time it takes to receive a response. + This can be useful for monitoring network latency and ensuring a stable connection to the server. + + Returns: + float: The round-trip time in milliseconds (ms). + + Example: + .. code-block:: python + + latency = await app.ping() + print(f"Ping: {latency} ms") + """ + start_time = time() + await self.invoke( + raw.functions.ping.Ping(ping_id=self.rnd_id()), + ) + return round((time() - start_time) * 1000.0, 3) diff --git a/pyrogram/types/authorization/__init__.py b/pyrogram/types/authorization/__init__.py index 0358badd..4f739486 100644 --- a/pyrogram/types/authorization/__init__.py +++ b/pyrogram/types/authorization/__init__.py @@ -19,12 +19,14 @@ from .active_session import ActiveSession from .active_sessions import ActiveSessions +from .login_token import LoginToken from .sent_code import SentCode from .terms_of_service import TermsOfService __all__ = [ "ActiveSession", "ActiveSessions", + "LoginToken", "SentCode", "TermsOfService", ] diff --git a/pyrogram/types/authorization/login_token.py b/pyrogram/types/authorization/login_token.py new file mode 100644 index 00000000..a869f539 --- /dev/null +++ b/pyrogram/types/authorization/login_token.py @@ -0,0 +1,46 @@ +# Pyrofork - Telegram MTProto API Client Library for Python +# Copyright (C) 2022-present Mayuri-Chan +# +# This file is part of Pyrofork. +# +# Pyrofork is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrofork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrofork. If not, see . + +from ..object import Object + +from pyrogram import raw + + +class LoginToken(Object): + """Contains info on a login token. + + Parameters: + token (``str``): + The login token. + + expires (``int``): + The expiration date of the token in UNIX format. + """ + + def __init__(self, *, token: str, expires: int): + super().__init__() + + self.token = token + self.expires = expires + + @staticmethod + def _parse(login_token: "raw.base.LoginToken") -> "LoginToken": + return LoginToken( + token=login_token.token, + expires=login_token.expires, + ) diff --git a/pyrogram/types/bots_and_keyboards/keyboard_button.py b/pyrogram/types/bots_and_keyboards/keyboard_button.py index cf40bf2b..82c30b87 100644 --- a/pyrogram/types/bots_and_keyboards/keyboard_button.py +++ b/pyrogram/types/bots_and_keyboards/keyboard_button.py @@ -17,7 +17,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrofork. If not, see . -from pyrogram import raw, types +from pyrogram import enums, raw, types from ..object import Object from typing import Union @@ -99,15 +99,21 @@ class KeyboardButton(Object): if isinstance(b, raw.types.KeyboardButtonRequestPeer): if isinstance(b.peer_type, raw.types.RequestPeerTypeBroadcast): + user_privileges = getattr(b.peer_type, "user_admin_rights", None) + bot_privileges = getattr(b.peer_type, "bot_admin_rights", None) return KeyboardButton( text=b.text, request_chat=types.RequestPeerTypeChannel( is_creator=b.peer_type.creator, is_username=b.peer_type.has_username, - max=b.max_quantity + max=b.max_quantity, + user_privileges=user_privileges, + bot_privileges=bot_privileges ) ) if isinstance(b.peer_type, raw.types.RequestPeerTypeChat): + user_privileges = getattr(b.peer_type, "user_admin_rights", None) + bot_privileges = getattr(b.peer_type, "bot_admin_rights", None) return KeyboardButton( text=b.text, request_chat=types.RequestPeerTypeChat( @@ -115,7 +121,9 @@ class KeyboardButton(Object): is_bot_participant=b.peer_type.bot_participant, is_username=b.peer_type.has_username, is_forum=b.peer_type.forum, - max=b.max_quantity + max=b.max_quantity, + user_privileges=user_privileges, + bot_privileges=bot_privileges ) ) @@ -135,13 +143,52 @@ class KeyboardButton(Object): elif self.request_location: return raw.types.KeyboardButtonRequestGeoLocation(text=self.text) elif self.request_chat: + user_privileges = self.request_chat.user_privileges + bot_privileges = self.request_chat.bot_privileges + + user_admin_rights = raw.types.ChatAdminRights( + change_info=user_privileges.can_change_info, + post_messages=user_privileges.can_post_messages, + post_stories=user_privileges.can_post_stories, + edit_messages=user_privileges.can_edit_messages, + edit_stories=user_privileges.can_post_stories, + delete_messages=user_privileges.can_delete_messages, + delete_stories=user_privileges.can_delete_stories, + ban_users=user_privileges.can_restrict_members, + invite_users=user_privileges.can_invite_users, + pin_messages=user_privileges.can_pin_messages, + add_admins=user_privileges.can_promote_members, + anonymous=user_privileges.is_anonymous, + manage_call=user_privileges.can_manage_video_chats, + other=user_privileges.can_manage_chat + ) if user_privileges else None + + bot_admin_rights = raw.types.ChatAdminRights( + change_info=bot_privileges.can_change_info, + post_messages=bot_privileges.can_post_messages, + post_stories=bot_privileges.can_post_stories, + edit_messages=bot_privileges.can_edit_messages, + edit_stories=bot_privileges.can_post_stories, + delete_messages=bot_privileges.can_delete_messages, + delete_stories=bot_privileges.can_delete_stories, + ban_users=bot_privileges.can_restrict_members, + invite_users=bot_privileges.can_invite_users, + pin_messages=bot_privileges.can_pin_messages, + add_admins=bot_privileges.can_promote_members, + anonymous=bot_privileges.is_anonymous, + manage_call=bot_privileges.can_manage_video_chats, + other=bot_privileges.can_manage_chat + ) if bot_privileges else None + if isinstance(self.request_chat, types.RequestPeerTypeChannel): return raw.types.InputKeyboardButtonRequestPeer( text=self.text, button_id=self.request_chat.button_id, peer_type=raw.types.RequestPeerTypeBroadcast( creator=self.request_chat.is_creator, - has_username=self.request_chat.is_username + has_username=self.request_chat.is_username, + user_admin_rights=user_admin_rights, + bot_admin_rights=bot_admin_rights ), max_quantity=self.request_chat.max, name_requested=self.request_chat.is_name_requested, @@ -155,7 +202,9 @@ class KeyboardButton(Object): creator=self.request_chat.is_creator, bot_participant=self.request_chat.is_bot_participant, has_username=self.request_chat.is_username, - forum=self.request_chat.is_forum + forum=self.request_chat.is_forum, + user_admin_rights=user_admin_rights, + bot_admin_rights=bot_admin_rights ), max_quantity=self.request_chat.max, name_requested=self.request_chat.is_name_requested, diff --git a/pyrogram/types/bots_and_keyboards/request_peer_type_channel.py b/pyrogram/types/bots_and_keyboards/request_peer_type_channel.py index c7283ee7..d21e77a0 100644 --- a/pyrogram/types/bots_and_keyboards/request_peer_type_channel.py +++ b/pyrogram/types/bots_and_keyboards/request_peer_type_channel.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrofork. If not, see . +from pyrogram import types + from ..object import Object @@ -47,17 +49,25 @@ class RequestPeerTypeChannel(Object): is_photo_requested (``bool``, *optional*): If True, Channel photo is requested. default True. + + user_privileges (:obj:`~pyrogram.types.ChatPrivileges`, *optional*): + Privileged actions that an user administrator is able to take. + + bot_privileges (:obj:`~pyrogram.types.ChatPrivileges`, *optional*): + Privileged actions that a bot administrator is able to take. """ # TODO user_admin_rights, bot_admin_rights def __init__( self, - button_id: int=0, - is_creator: bool=None, - is_username: bool=None, - max: int=1, - is_name_requested: bool=True, - is_username_requested: bool=True, - is_photo_requested: bool=True + button_id: int = 0, + is_creator: bool = None, + is_username: bool = None, + max: int = 1, + is_name_requested: bool = True, + is_username_requested: bool = True, + is_photo_requested: bool = True, + user_privileges: "types.ChatPrivileges" = None, + bot_privileges: "types.ChatPrivileges" = None ): super().__init__() @@ -68,3 +78,6 @@ class RequestPeerTypeChannel(Object): self.is_name_requested = is_name_requested self.is_username_requested = is_username_requested self.is_photo_requested = is_photo_requested + self.user_privileges = user_privileges + self.bot_privileges = bot_privileges + diff --git a/pyrogram/types/bots_and_keyboards/request_peer_type_chat.py b/pyrogram/types/bots_and_keyboards/request_peer_type_chat.py index 59d87300..cc800e4f 100644 --- a/pyrogram/types/bots_and_keyboards/request_peer_type_chat.py +++ b/pyrogram/types/bots_and_keyboards/request_peer_type_chat.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrofork. If not, see . +from pyrogram import types + from ..object import Object @@ -53,19 +55,27 @@ class RequestPeerTypeChat(Object): is_photo_requested (``bool``, *optional*): If True, Chat photo is requested. default True. + + user_privileges (:obj:`~pyrogram.types.ChatPrivileges`, *optional*): + Privileged actions that an user administrator is able to take. + + bot_privileges (:obj:`~pyrogram.types.ChatPrivileges`, *optional*): + Privileged actions that a bot administrator is able to take. """ # TODO user_admin_rights, bot_admin_rights def __init__( self, - button_id: int=0, - is_creator: bool=None, - is_bot_participant: bool=None, - is_username: bool=None, - is_forum: bool=None, - max: int=1, - is_name_requested: bool=True, - is_username_requested: bool=True, - is_photo_requested: bool=True + button_id: int = 0, + is_creator: bool = None, + is_bot_participant: bool = None, + is_username: bool = None, + is_forum: bool = None, + max: int = 1, + is_name_requested: bool = True, + is_username_requested: bool = True, + is_photo_requested: bool = True, + user_privileges: "types.ChatPrivileges" = None, + bot_privileges: "types.ChatPrivileges" = None ): super().__init__() @@ -78,3 +88,5 @@ class RequestPeerTypeChat(Object): self.is_name_requested = is_name_requested self.is_username_requested = is_username_requested self.is_photo_requested = is_photo_requested + self.user_privileges = user_privileges + self.bot_privileges = bot_privileges diff --git a/pyrogram/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py index ffd5d70d..24feac1f 100644 --- a/pyrogram/types/input_media/input_media_audio.py +++ b/pyrogram/types/input_media/input_media_audio.py @@ -62,6 +62,10 @@ class InputMediaAudio(InputMedia): title (``str``, *optional*): Title of the audio + + file_name (``str``, *optional*): + File name of the audio sent. + Defaults to file's path basename. """ def __init__( @@ -73,7 +77,8 @@ class InputMediaAudio(InputMedia): caption_entities: List[MessageEntity] = None, duration: int = 0, performer: str = "", - title: str = "" + title: str = "", + file_name: str = None ): super().__init__(media, caption, parse_mode, caption_entities) @@ -81,3 +86,4 @@ class InputMediaAudio(InputMedia): self.duration = duration self.performer = performer self.title = title + self.file_name = file_name diff --git a/pyrogram/types/input_media/input_media_document.py b/pyrogram/types/input_media/input_media_document.py index 766d17f2..feca4953 100644 --- a/pyrogram/types/input_media/input_media_document.py +++ b/pyrogram/types/input_media/input_media_document.py @@ -51,6 +51,10 @@ class InputMediaDocument(InputMedia): 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*. + + file_name (``str``, *optional*): + File name of the document sent. + Defaults to file's path basename. """ def __init__( @@ -59,8 +63,10 @@ class InputMediaDocument(InputMedia): thumb: str = None, caption: str = "", parse_mode: Optional["enums.ParseMode"] = None, - caption_entities: List[MessageEntity] = None + caption_entities: List[MessageEntity] = None, + file_name: str = None ): super().__init__(media, caption, parse_mode, caption_entities) self.thumb = thumb + self.file_name = file_name diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py index 0b4c6aa8..bc7a716b 100644 --- a/pyrogram/types/input_media/input_media_video.py +++ b/pyrogram/types/input_media/input_media_video.py @@ -62,6 +62,10 @@ class InputMediaVideo(InputMedia): duration (``int``, *optional*): Video duration. + file_name (``str``, *optional*): + File name of the video sent. + Defaults to file's path basename. + supports_streaming (``bool``, *optional*): Pass True, if the uploaded video is suitable for streaming. @@ -79,6 +83,7 @@ class InputMediaVideo(InputMedia): width: int = 0, height: int = 0, duration: int = 0, + file_name: str = None, supports_streaming: bool = True, has_spoiler: bool = None, ): @@ -88,5 +93,6 @@ class InputMediaVideo(InputMedia): self.width = width self.height = height self.duration = duration + self.file_name = file_name self.supports_streaming = supports_streaming self.has_spoiler = has_spoiler diff --git a/pyrogram/types/input_message_content/__init__.py b/pyrogram/types/input_message_content/__init__.py index 1a271da1..ae897b0d 100644 --- a/pyrogram/types/input_message_content/__init__.py +++ b/pyrogram/types/input_message_content/__init__.py @@ -19,6 +19,7 @@ from .input_message_content import InputMessageContent from .input_reply_to_message import InputReplyToMessage +from .input_reply_to_monoforum import InputReplyToMonoforum from .input_reply_to_story import InputReplyToStory from .input_text_message_content import InputTextMessageContent from .input_location_message_content import InputLocationMessageContent @@ -29,6 +30,7 @@ from .input_invoice_message_content import InputInvoiceMessageContent __all__ = [ "InputMessageContent", "InputReplyToMessage", + "InputReplyToMonoforum", "InputReplyToStory", "InputTextMessageContent", "InputLocationMessageContent", diff --git a/pyrogram/types/input_message_content/input_reply_to_monoforum.py b/pyrogram/types/input_message_content/input_reply_to_monoforum.py new file mode 100644 index 00000000..701e2900 --- /dev/null +++ b/pyrogram/types/input_message_content/input_reply_to_monoforum.py @@ -0,0 +1,43 @@ +# Pyrofork - Telegram MTProto API Client Library for Python +# Copyright (C) 2022-present Mayuri-Chan +# +# This file is part of Pyrofork. +# +# Pyrofork is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrofork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrofork. If not, see . + +from pyrogram import raw +from ..object import Object + + +class InputReplyToMonoforum(Object): + """Contains information about a target replied monoforum. + + + Parameters: + monoforum_peer (:obj:`~pyrogram.raw.types.InputPeer`): + An InputPeer. + """ + + def __init__( + self, *, + monoforum_peer: "raw.types.InputPeer" + ): + super().__init__() + + self.monoforum_peer = monoforum_peer + + def write(self): + return raw.types.InputReplyToMonoForum( + monoforum_peer_id=self.monoforum_peer + ).write() diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index d5cb3f94..bb6007c2 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -480,6 +480,7 @@ class Message(Object, Update): gift_code: "types.GiftCode" = None, gift: "types.Gift" = None, screenshot_taken: "types.ScreenshotTaken" = None, + paid_message_price_changed: "types.PaidMessagePriceChanged" = None, invoice: "types.Invoice" = None, story: Union["types.MessageStory", "types.Story"] = None, alternative_videos: List["types.AlternativeVideo"] = None, @@ -596,6 +597,7 @@ class Message(Object, Update): self.gift_code = gift_code self.gift = gift self.screenshot_taken = screenshot_taken + self.paid_message_price_changed = paid_message_price_changed self.invoice = invoice self.story = story self.video = video @@ -760,6 +762,7 @@ class Message(Object, Update): gift_code = None gift = None screenshot_taken = None + paid_message_price_changed = None service_type = None chat_join_type = None @@ -808,7 +811,7 @@ class Message(Object, Update): service_type = enums.MessageServiceType.BOT_ALLOWED elif isinstance(action, raw.types.MessageActionRequestedPeer) or isinstance(action, raw.types.MessageActionRequestedPeerSentMe): chats_shared = await types.RequestedChats._parse(client, action) - service_type = enums.MessageServiceType.ChatShared + service_type = enums.MessageServiceType.CHAT_SHARED elif isinstance(action, raw.types.MessageActionTopicCreate): forum_topic_created = types.ForumTopicCreated._parse(message) service_type = enums.MessageServiceType.FORUM_TOPIC_CREATED @@ -881,6 +884,9 @@ class Message(Object, Update): elif isinstance(action, raw.types.MessageActionScreenshotTaken): screenshot_taken = types.ScreenshotTaken() service_type = enums.MessageServiceType.SCREENSHOT_TAKEN + elif isinstance(action, raw.types.MessageActionPaidMessagesPrice): + paid_message_price_changed = types.PaidMessagePriceChanged._parse(action) + service_type = enums.MessageServiceType.PAID_MESSAGE_PRICE_CHANGED parsed_message = Message( id=message.id, @@ -926,6 +932,7 @@ class Message(Object, Update): contact_registered=contact_registered, gift_code=gift_code, screenshot_taken=screenshot_taken, + paid_message_price_changed=paid_message_price_changed, raw=message, chat_join_type=chat_join_type, client=client @@ -970,7 +977,7 @@ class Message(Object, Update): else: parsed_message.message_thread_id = message.reply_to.reply_to_msg_id parsed_message.is_topic_message = True - elif parsed_message.chat.is_forum and parsed_message.message_thread_id is None: + elif parsed_message.chat.type == enums.ChatType.FORUM and parsed_message.message_thread_id is None: parsed_message.message_thread_id = 1 parsed_message.is_topic_message = True @@ -1299,7 +1306,7 @@ class Message(Object, Update): pass else: parsed_message.reply_to_story = reply_to_story - if parsed_message.chat.is_forum and parsed_message.message_thread_id is None: + if parsed_message.chat.type == enums.ChatType.FORUM and parsed_message.message_thread_id is None: parsed_message.message_thread_id = 1 parsed_message.is_topic_message = True @@ -1314,9 +1321,14 @@ class Message(Object, Update): self.chat.type in (enums.ChatType.GROUP, enums.ChatType.SUPERGROUP, enums.ChatType.CHANNEL) and self.chat.username ): + if self.chat.type == enums.ChatType.SUPERGROUP and self.message_thread_id: + return f"https://t.me/{self.chat.username}/{self.message_thread_id}/{self.id}" return f"https://t.me/{self.chat.username}/{self.id}" - else: - return f"https://t.me/c/{utils.get_channel_id(self.chat.id)}/{self.id}" + if self.chat.type == enums.ChatType.PRIVATE: + return f"tg://openmessage?user_id={self.from_user.id}&message_id={self.id}" + if self.message_thread_id: + return f"https://t.me/c/{utils.get_channel_id(self.chat.id)}/{self.message_thread_id}/{self.id}" + return f"https://t.me/c/{utils.get_channel_id(self.chat.id)}/{self.id}" @property def content(self) -> str: @@ -2779,7 +2791,9 @@ class Message(Object, Update): allow_paid_broadcast: bool = None, message_effect_id: int = None, parse_mode: Optional["enums.ParseMode"] = None, - invert_media: bool = None + invert_media: bool = None, + progress: Callable = None, + progress_args: tuple = (), ) -> List["types.Message"]: """Bound method *reply_media_group* of :obj:`~pyrogram.types.Message`. @@ -2840,6 +2854,28 @@ class Message(Object, Update): invert_media (``bool``, *optional*): Inverts the position of the media and caption. + progress (``Callable``, *optional*): + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. + + Other Parameters: + current (``int``): + The amount of bytes transmitted so far. + + total (``int``): + The total size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. + Returns: On success, a :obj:`~pyrogram.types.Messages` object is returned containing all the single messages sent. @@ -2878,7 +2914,9 @@ class Message(Object, Update): quote_entities=quote_entities, allow_paid_broadcast=allow_paid_broadcast, message_effect_id=message_effect_id, - invert_media=invert_media + invert_media=invert_media, + progress=progress, + progress_args=progress_args, ) async def reply_photo( @@ -2896,6 +2934,8 @@ class Message(Object, Update): reply_in_chat_id: Union[int, str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, + schedule_date: datetime = None, + protect_content: bool = None, allow_paid_broadcast: bool = None, message_effect_id: int = None, view_once: bool = None, @@ -2985,6 +3025,12 @@ class Message(Object, Update): List of special entities that appear in quote_text, which can be specified instead of *parse_mode*. for reply_to_message only. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): + Date when the message will be automatically sent. + + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + allow_paid_broadcast (``bool``, *optional*): Pass True to allow the message to ignore regular broadcast limits for a small fee; for bots @@ -3059,6 +3105,8 @@ class Message(Object, Update): reply_to_chat_id=reply_to_chat_id, quote_text=quote_text, quote_entities=quote_entities, + schedule_date=schedule_date, + protect_content=protect_content, allow_paid_broadcast=allow_paid_broadcast, message_effect_id=message_effect_id, view_once=view_once, @@ -3611,9 +3659,11 @@ class Message(Object, Update): quote_entities: List["types.MessageEntity"] = None, allow_paid_broadcast: bool = None, message_effect_id: int = None, + view_once: bool = None, cover: Union[str, BinaryIO] = None, start_timestamp: int = None, schedule_date: datetime = None, + protect_content: bool = None, invert_media: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", @@ -3731,9 +3781,15 @@ class Message(Object, Update): schedule_date (:py:obj:`~datetime.datetime`, *optional*): Date when the message will be automatically sent. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + message_effect_id (``int`` ``64-bit``, *optional*): Unique identifier of the message effect to be added to the message; for private chats only. + view_once (``bool``, *optional*): + Pass True to send the video as a view-once message. + invert_media (``bool``, *optional*): Pass True to invert the video and caption position. @@ -3813,9 +3869,11 @@ class Message(Object, Update): quote_entities=quote_entities, allow_paid_broadcast=allow_paid_broadcast, message_effect_id=message_effect_id, + view_once=view_once, cover=cover, start_timestamp=start_timestamp, schedule_date=schedule_date, + protect_content=protect_content, invert_media=invert_media, reply_markup=reply_markup, progress=progress, diff --git a/pyrogram/types/messages_and_media/wallpaper.py b/pyrogram/types/messages_and_media/wallpaper.py index e5ea024b..44a7a33e 100644 --- a/pyrogram/types/messages_and_media/wallpaper.py +++ b/pyrogram/types/messages_and_media/wallpaper.py @@ -54,7 +54,7 @@ class Wallpaper(Object): self, id: int, slug: str, - document: "types.Document", + document: "types.Document" = None, is_creator: bool = None, is_default: bool = None, is_pattern: bool = None, @@ -73,7 +73,9 @@ class Wallpaper(Object): @staticmethod def _parse(client: "pyrogram.Client", wallpaper: "raw.base.WallPaper") -> "Wallpaper": - doc = wallpaper.document + doc = None + if not isinstance(wallpaper, raw.types.WallPaperNoFile): + doc = wallpaper.document attributes = {type(i): i for i in doc.attributes} file_name = getattr( @@ -84,7 +86,7 @@ class Wallpaper(Object): return Wallpaper( id=wallpaper.id, slug=wallpaper.slug, - document=types.Document._parse(client, doc, file_name), + document=types.Document._parse(client, doc, file_name) if doc is not None else None, is_creator=getattr(wallpaper, "creator", None), is_default=getattr(wallpaper, "default", None), is_pattern=getattr(wallpaper, "pattern", None), diff --git a/pyrogram/types/payments/__init__.py b/pyrogram/types/payments/__init__.py index 7dc9e6bd..dbb02e4e 100644 --- a/pyrogram/types/payments/__init__.py +++ b/pyrogram/types/payments/__init__.py @@ -27,6 +27,7 @@ from .input_stars_transaction import InputStarsTransaction from .invoice import Invoice from .labeled_price import LabeledPrice from .paid_media import PaidMedia +from .paid_message_price_changed import PaidMessagePriceChanged from .payment_form import PaymentForm from .payment_info import PaymentInfo from .payment_refunded import PaymentRefunded @@ -46,6 +47,7 @@ __all__ = [ "Invoice", "LabeledPrice", "PaidMedia", + "PaidMessagePriceChanged", "PaymentForm", "PaymentInfo", "PaymentRefunded", diff --git a/pyrogram/types/payments/gift.py b/pyrogram/types/payments/gift.py index 348a937d..2af51f66 100644 --- a/pyrogram/types/payments/gift.py +++ b/pyrogram/types/payments/gift.py @@ -77,13 +77,16 @@ class Gift(Object): User who sent the star gift. owner (:obj:`~pyrogram.types.Chat`, *optional*): - Current gift owner. + Only available if the nfts is in telegram. owner_name (``str``, *optional*): Name of the user who received the star gift. owner_address (``str``, *optional*): - Address of the gift owner in TON blockchain. + Only available if the nfts is in ton network. + + ton_address (``str``, *optional*): + Only available if the nfts is in ton network. price (``int``, *optional*): Price of this gift in stars. @@ -141,7 +144,7 @@ class Gift(Object): raw (:obj:`~pyrogram.raw.base.StarGift`, *optional*): The raw object as received from the server. - + link (``str``, *property*): A link to the gift. For unique gifts only. @@ -163,6 +166,7 @@ class Gift(Object): owner: Optional["types.Chat"] = None, owner_name: Optional[str] = None, owner_address: Optional[str] = None, + ton_address: Optional[str] = None, price: Optional[int] = None, convert_price: Optional[int] = None, upgrade_price: Optional[int] = None, @@ -201,6 +205,7 @@ class Gift(Object): self.owner = owner self.owner_name = owner_name self.owner_address = owner_address + self.ton_address = ton_address self.price = price self.convert_price = convert_price self.upgrade_price = upgrade_price @@ -233,7 +238,7 @@ class Gift(Object): return await Gift._parse_unique(client, gift, users, chats) elif isinstance(gift, raw.types.StarGiftSaved): return await Gift._parse_saved(client, gift, users, chats) - + @staticmethod async def _parse_regular( client, @@ -277,9 +282,15 @@ class Gift(Object): ) or None, available_amount=getattr(star_gift, "availability_issued", None), total_amount=getattr(star_gift, "availability_total", None), - owner=types.Chat._parse_chat(client, users.get(owner_id) or chats.get(owner_id)), + owner=( + types.Chat._parse_chat(client, users.get(owner_id) or + chats.get(owner_id)) + if owner_id is not None + else None + ), owner_name=getattr(star_gift, "owner_name", None), owner_address=getattr(star_gift, "owner_address", None), + ton_address=getattr(star_gift, "gift_address", None), is_upgraded=True, raw=star_gift, client=client diff --git a/pyrogram/types/payments/paid_message_price_changed.py b/pyrogram/types/payments/paid_message_price_changed.py new file mode 100644 index 00000000..d248df2d --- /dev/null +++ b/pyrogram/types/payments/paid_message_price_changed.py @@ -0,0 +1,49 @@ +# Pyrofork - Telegram MTProto API Client Library for Python +# Copyright (C) 2022-present Mayuri-Chan +# +# This file is part of Pyrofork. +# +# Pyrofork is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrofork is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrofork. If not, see . + +from typing import List, Union + +from pyrogram import raw +from pyrogram import types +from ..object import Object + + +class PaidMessagePriceChanged(Object): + """A PaidMessagePriceChanged. + + Parameters: + stars_amount (``int``): + Amount of stars. + + extended_media (List of :obj:`~pyrogram.types.Animation` | :obj:`~pyrogram.types.ExtendedMediaPreview` | :obj:`~pyrogram.types.Photo` | :obj:`~pyrogram.types.Video`, *optional*): + Extended media. + """ + def __init__( + self, + *, + stars_amount: int, + ): + super().__init__() + + self.stars_amount = stars_amount + + @staticmethod + def _parse(action: "raw.types.MessageActionPaidMessagesPrice") -> "PaidMessagePriceChanged": + return PaidMessagePriceChanged( + stars_amount=action.stars + ) diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py index 9a992d55..bc6c6ddc 100644 --- a/pyrogram/types/user_and_chats/__init__.py +++ b/pyrogram/types/user_and_chats/__init__.py @@ -43,6 +43,7 @@ from .chat_reactions import ChatReactions from .dialog import Dialog from .emoji_status import EmojiStatus from .folder import Folder +from .group_call_member import GroupCallMember from .invite_link_importer import InviteLinkImporter from .restriction import Restriction from .user import User @@ -106,5 +107,6 @@ __all__ = [ "ChatPrivileges", "ChatJoiner", "EmojiStatus", + "GroupCallMember", "ChatReactions" ] diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 776a8809..cd42e983 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -56,9 +56,6 @@ class Chat(Object): is_support (``bool``): True, if this chat is part of the Telegram support team. Users and bots only. - is_forum (``bool``, *optional*): - True, if the supergroup chat is a forum - is_participants_hidden (``bool``, *optional*): True, if the chat members are hidden. Returned only in :meth:`~pyrogram.Client.get_chat`. @@ -83,6 +80,9 @@ class Chat(Object): is_gifts_available (``bool``, *optional*): True, if star gifts can be received by this chat. + is_auto_translation_enabled (``bool``, *optional*): + True, if automatic translation is enabled in chat. + title (``str``, *optional*): Title, for supergroups, channels and basic group chats. @@ -167,6 +167,10 @@ class Chat(Object): The linked discussion group (in case of channels) or the linked channel (in case of supergroups). Returned only in :meth:`~pyrogram.Client.get_chat`. + linked_forum (:obj:`~pyrogram.types.Chat`, *optional*): + The linked monoforum (in case of channels) or the linked channel (in case of monoforum). + Returned only in :meth:`~pyrogram.Client.get_chat`. + send_as_chat (:obj:`~pyrogram.types.Chat`, *optional*): The default "send_as" chat. Returned only in :meth:`~pyrogram.Client.get_chat`. @@ -228,7 +232,6 @@ class Chat(Object): is_scam: bool = None, is_fake: bool = None, is_support: bool = None, - is_forum: bool = None, is_participants_hidden: bool = None, is_join_request: bool = None, is_join_to_send: bool = None, @@ -236,6 +239,7 @@ class Chat(Object): is_paid_reactions_available: bool = None, is_slowmode_enabled: bool = None, is_gifts_available: bool = None, + is_auto_translation_enabled: bool = None, title: str = None, username: str = None, first_name: str = None, @@ -259,6 +263,7 @@ class Chat(Object): permissions: "types.ChatPermissions" = None, distance: int = None, linked_chat: "types.Chat" = None, + linked_forum: "types.Chat" = None, send_as_chat: "types.Chat" = None, available_reactions: Optional["types.ChatReactions"] = None, usernames: List["types.Username"] = None, @@ -282,7 +287,6 @@ class Chat(Object): self.is_scam = is_scam self.is_fake = is_fake self.is_support = is_support - self.is_forum = is_forum self.is_participants_hidden = is_participants_hidden self.is_join_request = is_join_request self.is_join_to_send = is_join_to_send @@ -290,6 +294,7 @@ class Chat(Object): self.is_paid_reactions_available = is_paid_reactions_available self.is_slowmode_enabled = is_slowmode_enabled self.is_gifts_available = is_gifts_available + self.is_auto_translation_enabled = is_auto_translation_enabled self.title = title self.username = username self.first_name = first_name @@ -313,6 +318,7 @@ class Chat(Object): self.permissions = permissions self.distance = distance self.linked_chat = linked_chat + self.linked_forum = linked_forum self.send_as_chat = send_as_chat self.available_reactions = available_reactions self.usernames = usernames @@ -389,6 +395,16 @@ class Chat(Object): restriction_reason = getattr(channel, "restriction_reason", []) user_name = getattr(channel, "username", None) active_usernames = getattr(channel, "usernames", []) + if getattr(channel, "monoforum", None): + chat_type = enums.ChatType.MONOFORUM + elif getattr(channel, "forum", None): + chat_type = enums.ChatType.FORUM + elif getattr(channel, "megagroup", None): + chat_type = enums.ChatType.SUPERGROUP + elif getattr(channel, "broadcast", None): + chat_type = enums.ChatType.CHANNEL + else: + chat_type = enums.ChatType.GROUP usernames = None if len(active_usernames) >= 1: usernames = [] @@ -405,13 +421,12 @@ class Chat(Object): return Chat( id=peer_id, - type=enums.ChatType.SUPERGROUP if getattr(channel, "megagroup", None) else enums.ChatType.CHANNEL, + type=chat_type, is_verified=getattr(channel, "verified", None), is_restricted=getattr(channel, "restricted", None), is_creator=getattr(channel, "creator", None), is_scam=getattr(channel, "scam", None), is_fake=getattr(channel, "fake", None), - is_forum=getattr(channel, "forum", None), is_join_request=getattr(channel, "join_request", None), is_join_to_send=getattr(channel, "join_to_send", None), is_slowmode_enabled=getattr(channel, "slowmode_enabled", None), @@ -535,14 +550,20 @@ class Chat(Object): parsed_chat.is_antispam = full_chat.antispam parsed_chat.is_paid_reactions_available = full_chat.paid_reactions_available parsed_chat.is_gifts_available = getattr(full_chat, "stargifts_available", None) + parsed_chat.is_auto_translation_enabled = getattr(full_chat, "auto_translation", None) parsed_chat.gifts_count = getattr(full_chat, "stargifts_count", None) parsed_chat.folder_id = getattr(full_chat, "folder_id", None) linked_chat_raw = chats.get(full_chat.linked_chat_id, None) + linked_forum_raw = chats.get(getattr(chat_raw, "linked_monoforum_id"), None) + if linked_chat_raw: parsed_chat.linked_chat = Chat._parse_channel_chat(client, linked_chat_raw) + if linked_forum_raw: + parsed_chat.linked_forum = Chat._parse_channel_chat(client, linked_forum_raw) + default_send_as = full_chat.default_send_as if default_send_as: diff --git a/pyrogram/types/user_and_chats/group_call_member.py b/pyrogram/types/user_and_chats/group_call_member.py new file mode 100644 index 00000000..551db102 --- /dev/null +++ b/pyrogram/types/user_and_chats/group_call_member.py @@ -0,0 +1,149 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from datetime import datetime +from typing import Dict + +import pyrogram +from pyrogram import raw, types, utils +from ..object import Object + + +class GroupCallMember(Object): + """Contains information about one member of a group call. + + Parameters: + chat (:obj:`~pyrogram.types.Chat`, *optional*): + Information about the user or chat. + + date (:py:obj:`~datetime.datetime`, *optional*): + Date when this participant join this group call. + + active_date (:py:obj:`~datetime.datetime`, *optional*): + Date when this participant last active in this group call. + + volume (``int``, *optional*): + Volume, if not set the volume is set to 100%. + + can_self_unmute (``bool``, *optional*): + Whether the participant can unmute themselves. + + is_muted (``bool``, *optional*): + Whether the participant is muted. + + is_left (``bool``, *optional*): + Whether the participant has left. + + is_just_joined (``bool``, *optional*): + Whether the participant has just joined. + + is_muted_by_you (``bool``, *optional*): + Whether this participant was muted by the current user. + + is_volume_by_admin (``bool``, *optional*): + Whether our volume can only changed by an admin. + + is_self (``bool``, *optional*): + Whether this participant is the current user. + + is_video_joined (``bool``, *optional*): + Whether this participant is currently broadcasting video. + + is_hand_raised (``bool``, *optional*): + Whether this participant is raised hand. + + is_video_enabled (``bool``, *optional*): + Whether this participant is currently broadcasting video. + + is_screen_sharing_enabled (``bool``, *optional*): + Whether this participant is currently shared screen. + """ + + def __init__( + self, + *, + client: "pyrogram.Client" = None, + chat: "types.Chat" = None, + date: datetime = None, + active_date: datetime = None, + volume: int = None, + can_self_unmute: bool = None, + is_muted: bool = None, + is_left: bool = None, + is_just_joined: bool = None, + is_muted_by_you: bool = None, + is_volume_by_admin: bool = None, + is_self: bool = None, + is_video_joined: bool = None, + is_hand_raised: bool = None, + is_video_enabled: bool = None, + is_screen_sharing_enabled: bool = None + ): + super().__init__(client) + + self.chat = chat + self.date = date + self.active_date = active_date + self.volume = volume + self.can_self_unmute = can_self_unmute + self.is_muted = is_muted + self.is_left = is_left + self.is_just_joined = is_just_joined + self.is_muted_by_you = is_muted_by_you + self.is_volume_by_admin = is_volume_by_admin + self.is_self = is_self + self.is_video_joined = is_video_joined + self.is_hand_raised = is_hand_raised + self.is_video_enabled = is_video_enabled + self.is_screen_sharing_enabled = is_screen_sharing_enabled + + @staticmethod + def _parse( + client: "pyrogram.Client", + member: "raw.types.GroupCallParticipant", + users: Dict[int, "raw.base.User"], + chats: Dict[int, "raw.base.Chat"] + ) -> "GroupCallMember": + peer = member.peer + peer_id = utils.get_raw_peer_id(peer) + + parsed_chat = types.Chat._parse_chat( + client, + users[peer_id] if isinstance(peer, raw.types.PeerUser) else chats[peer_id], + ) + + parsed_chat.bio = getattr(member, "about", None) + + return GroupCallMember( + chat=parsed_chat, + date=utils.timestamp_to_datetime(member.date), + active_date=utils.timestamp_to_datetime(member.active_date), + volume=getattr(member, "volume", None), + can_self_unmute=member.can_self_unmute, + is_muted=member.muted, + is_left=member.left, + is_just_joined=member.just_joined, + is_muted_by_you=member.muted_by_you, + is_volume_by_admin=member.volume_by_admin, + is_self=member.is_self, + is_video_joined=member.video_joined, + is_hand_raised=bool(getattr(member, "raise_hand_rating", None)), + is_video_enabled=bool(getattr(member, "video", None)), + is_screen_sharing_enabled=bool(getattr(member, "presentation", None)), + client=client + ) diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py index a52ac4a4..4c5efcaf 100644 --- a/pyrogram/types/user_and_chats/user.py +++ b/pyrogram/types/user_and_chats/user.py @@ -79,6 +79,9 @@ class User(Object, Update): is_deleted(``bool``, *optional*): True, if this user is deleted. + is_frozen(``bool``, *optional*): + True, if this user is frozen. + is_bot (``bool``, *optional*): True, if this user is a bot. @@ -168,6 +171,10 @@ class User(Object, Update): active_users (``int``, *optional*): Bot's active users count. + + frozen_icon (``int``, *optional*): + Frozen account icon. + This field is available only in case *is_frozen* is True. """ def __init__( @@ -179,6 +186,7 @@ class User(Object, Update): is_contact: bool = None, is_mutual_contact: bool = None, is_deleted: bool = None, + is_frozen: bool = None, is_bot: bool = None, is_verified: bool = None, is_restricted: bool = None, @@ -203,7 +211,8 @@ class User(Object, Update): restrictions: List["types.Restriction"] = None, reply_color: "types.ChatColor" = None, profile_color: "types.ChatColor" = None, - active_users: int = None + active_users: int = None, + frozen_icon: int = None ): super().__init__(client) @@ -212,6 +221,7 @@ class User(Object, Update): self.is_contact = is_contact self.is_mutual_contact = is_mutual_contact self.is_deleted = is_deleted + self.is_frozen = is_frozen self.is_bot = is_bot self.is_verified = is_verified self.is_restricted = is_restricted @@ -237,6 +247,7 @@ class User(Object, Update): self.reply_color = reply_color self.profile_color = profile_color self.active_users = active_users + self.frozen_icon = frozen_icon @property def full_name(self) -> str: @@ -268,6 +279,8 @@ class User(Object, Update): if user_name is None and usernames is not None and len(usernames) > 0: user_name = usernames[0].username usernames.pop(0) + + frozen_icon = getattr(user, "bot_verification_icon", None) return User( id=user.id, @@ -275,6 +288,7 @@ class User(Object, Update): is_contact=user.contact, is_mutual_contact=user.mutual_contact, is_deleted=user.deleted, + is_frozen=True if frozen_icon else False, is_bot=user.bot, is_verified=user.verified, is_restricted=user.restricted, @@ -298,6 +312,7 @@ class User(Object, Update): reply_color=types.ChatColor._parse(getattr(user, "color", None)), profile_color=types.ChatColor._parse_profile_color(getattr(user, "profile_color", None)), active_users=active_users, + frozen_icon=frozen_icon, client=client ) diff --git a/pyrogram/utils.py b/pyrogram/utils.py index 09e3a7ed..fc0e9abd 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -103,7 +103,8 @@ async def parse_messages( client, messages: "raw.types.messages.Messages", replies: int = 1, - business_connection_id: str = None + business_connection_id: str = None, + is_scheduled: bool = False ) -> List["types.Message"]: users = {i.id: i for i in messages.users} chats = {i.id: i for i in messages.chats} @@ -120,64 +121,76 @@ async def parse_messages( parsed_messages.append(await types.Message._parse(client, message, users, chats, topics, replies=0, business_connection_id=business_connection_id)) if replies: - messages_with_replies = { - i.id: i.reply_to.reply_to_msg_id - for i in messages.messages - if ( - not isinstance(i, raw.types.MessageEmpty) - and i.reply_to - and isinstance(i.reply_to, raw.types.MessageReplyHeader) - and i.reply_to.reply_to_msg_id is not None - ) - } + if not is_scheduled: + messages_with_replies = { + i.id: i.reply_to.reply_to_msg_id + for i in messages.messages + if ( + not isinstance(i, raw.types.MessageEmpty) + and i.reply_to + and isinstance(i.reply_to, raw.types.MessageReplyHeader) + and i.reply_to.reply_to_msg_id is not None + ) + } - message_reply_to_story = { - i.id: {'user_id': i.reply_to.user_id, 'story_id': i.reply_to.story_id} - for i in messages.messages - if not isinstance(i, raw.types.MessageEmpty) and i.reply_to and isinstance(i.reply_to, raw.types.MessageReplyStoryHeader) - } + message_reply_to_story = { + i.id: {'user_id': i.reply_to.user_id, 'story_id': i.reply_to.story_id} + for i in messages.messages + if not isinstance(i, raw.types.MessageEmpty) and i.reply_to and isinstance(i.reply_to, raw.types.MessageReplyStoryHeader) + } - if messages_with_replies: - # We need a chat id, but some messages might be empty (no chat attribute available) - # Scan until we find a message with a chat available (there must be one, because we are fetching replies) - for m in parsed_messages: - if m.chat: - chat_id = m.chat.id - break - else: - chat_id = 0 + if messages_with_replies: + # We need a chat id, but some messages might be empty (no chat attribute available) + # Scan until we find a message with a chat available (there must be one, because we are fetching replies) + for m in parsed_messages: + if m.chat: + chat_id = m.chat.id + break + else: + chat_id = 0 - reply_messages = await client.get_messages( - chat_id, - reply_to_message_ids=messages_with_replies.keys(), - replies=replies - 1 - ) - - for message in parsed_messages: - reply_id = messages_with_replies.get(message.id, None) - - for reply in reply_messages: - if reply.id == reply_id: - if not reply.forum_topic_created: - message.reply_to_message = reply - if message_reply_to_story: - for m in parsed_messages: - if m.chat: - chat_id = m.chat.id - break - else: - chat_id = 0 - - reply_messages = {} - for msg_id in message_reply_to_story.keys(): - reply_messages[msg_id] = await client.get_stories( - message_reply_to_story[msg_id]['user_id'], - message_reply_to_story[msg_id]['story_id'] + reply_messages = await client.get_messages( + chat_id, + reply_to_message_ids=messages_with_replies.keys(), + replies=replies - 1 ) + for message in parsed_messages: + reply_id = messages_with_replies.get(message.id, None) + + for reply in reply_messages: + if reply.id == reply_id: + if not reply.forum_topic_created: + message.reply_to_message = reply + if message_reply_to_story: + for m in parsed_messages: + if m.chat: + chat_id = m.chat.id + break + else: + chat_id = 0 + + reply_messages = {} + for msg_id in message_reply_to_story.keys(): + reply_messages[msg_id] = await client.get_stories( + message_reply_to_story[msg_id]['user_id'], + message_reply_to_story[msg_id]['story_id'] + ) + + for message in parsed_messages: + if message.id in reply_messages: + message.reply_to_story = reply_messages[message.id] + else: for message in parsed_messages: - if message.id in reply_messages: - message.reply_to_story = reply_messages[message.id] + if ( + message.reply_to_message_id + and not message.external_reply + ): + message.reply_to_message = await client.get_messages( + message.chat.id, + message_ids=message.reply_to_message_id, + replies=replies - 1 + ) return types.List(parsed_messages) @@ -474,6 +487,7 @@ async def get_reply_to( reply_to_message_id: int = None, reply_to_story_id: int = None, message_thread_id: int = None, + reply_to_monoforum_id: Union[int,str] = None, reply_to_chat_id: Union[int,str] = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, @@ -482,7 +496,12 @@ async def get_reply_to( ): reply_to = None reply_to_chat = None - if reply_to_message_id or message_thread_id: + if reply_to_monoforum_id: + peer = await client.resolve_peer(reply_to_monoforum_id) + reply_to = types.InputReplyToMonoforum( + monoforum_peer=peer + ) + elif reply_to_message_id or message_thread_id: text, entities = (await parse_text_entities(client, quote_text, parse_mode, quote_entities)).values() if reply_to_chat_id is not None: reply_to_chat = await client.resolve_peer(reply_to_chat_id) @@ -494,7 +513,7 @@ async def get_reply_to( quote_entities=entities, quote_offset=quote_offset, ) - if reply_to_story_id: + elif reply_to_story_id: peer = await client.resolve_peer(chat_id) reply_to = types.InputReplyToStory( peer=peer,