From 551b34bc704896be95d5ff9cec33434eb0260dad Mon Sep 17 00:00:00 2001 From: Aliwoto Date: Tue, 26 Mar 2024 04:47:07 +0330 Subject: [PATCH] Upgrade the library to the latest mtproto layer (176). Signed-off-by: Aliwoto --- .github/workflows/pypi_release.yml | 6 +- .github/workflows/python.yml | 9 +- compiler/api/source/main_api.tl | 144 +++++++-- compiler/docs/compiler.py | 114 ++++++- compiler/errors/source/400_BAD_REQUEST.tsv | 19 +- compiler/errors/source/403_FORBIDDEN.tsv | 12 +- compiler/errors/source/406_NOT_ACCEPTABLE.tsv | 2 + compiler/errors/source/420_FLOOD.tsv | 4 +- .../source/500_INTERNAL_SERVER_ERROR.tsv | 4 +- .../errors/source/503_SERVICE_UNAVAILABLE.tsv | 2 +- pyrogram/__init__.py | 8 +- pyrogram/client.py | 83 ++++- pyrogram/dispatcher.py | 95 +++++- pyrogram/enums/__init__.py | 4 + pyrogram/enums/business_schedule.py | 33 ++ pyrogram/enums/folder_color.py | 47 +++ pyrogram/enums/message_service_type.py | 6 + pyrogram/filters.py | 19 +- pyrogram/handlers/story_handler.py | 2 +- pyrogram/methods/auth/send_code.py | 54 +++- .../methods/bots/send_inline_bot_result.py | 21 +- pyrogram/methods/chats/__init__.py | 6 +- pyrogram/methods/chats/close_forum_topic.py | 2 +- pyrogram/methods/chats/create_forum_topic.py | 2 +- pyrogram/methods/chats/delete_folder.py | 2 +- pyrogram/methods/chats/delete_forum_topic.py | 2 +- pyrogram/methods/chats/edit_forum_topic.py | 2 +- pyrogram/methods/chats/export_folder_link.py | 2 +- pyrogram/methods/chats/get_chat_event_log.py | 2 +- pyrogram/methods/chats/get_chat_members.py | 2 +- pyrogram/methods/chats/get_dialogs.py | 15 +- pyrogram/methods/chats/get_folders.py | 55 ++-- pyrogram/methods/chats/get_forum_topics.py | 71 ++++- .../methods/chats/get_forum_topics_by_id.py | 13 +- pyrogram/methods/chats/get_send_as_chats.py | 6 +- .../methods/chats/get_similar_channels.py | 2 +- pyrogram/methods/chats/join_folder.py | 2 +- pyrogram/methods/chats/leave_folder.py | 2 +- pyrogram/methods/chats/promote_chat_member.py | 3 + pyrogram/methods/chats/set_chat_ttl.py | 12 +- pyrogram/methods/chats/toggle_folder_tags.py | 50 +++ pyrogram/methods/chats/toggle_forum_topics.py | 2 +- pyrogram/methods/chats/toggle_join_to_send.py | 65 ++++ .../chats/update_chat_notifications.py | 2 +- pyrogram/methods/chats/update_color.py | 2 +- pyrogram/methods/chats/update_folder.py | 11 +- .../methods/decorators/on_callback_query.py | 8 +- .../decorators/on_chat_join_request.py | 8 +- .../decorators/on_chat_member_updated.py | 8 +- .../decorators/on_chosen_inline_result.py | 8 +- .../methods/decorators/on_deleted_messages.py | 8 +- pyrogram/methods/decorators/on_disconnect.py | 4 +- .../methods/decorators/on_edited_message.py | 8 +- .../methods/decorators/on_inline_query.py | 8 +- pyrogram/methods/decorators/on_message.py | 8 +- pyrogram/methods/decorators/on_poll.py | 8 +- pyrogram/methods/decorators/on_raw_update.py | 6 +- pyrogram/methods/decorators/on_story.py | 10 +- pyrogram/methods/decorators/on_user_status.py | 8 +- .../invite_links/get_chat_invite_link.py | 2 +- pyrogram/methods/messages/__init__.py | 8 + pyrogram/methods/messages/copy_media_group.py | 6 +- pyrogram/methods/messages/download_media.py | 19 +- .../methods/messages/edit_message_media.py | 6 +- .../methods/messages/edit_message_text.py | 6 + pyrogram/methods/messages/forward_messages.py | 10 + pyrogram/methods/messages/get_history.py | 79 +++++ .../methods/messages/get_message_by_link.py | 118 +++++++ .../messages/get_scheduled_messages.py | 61 ++++ pyrogram/methods/messages/get_stickers.py | 64 ++++ pyrogram/methods/messages/send_audio.py | 10 +- .../methods/messages/send_cached_media.py | 6 +- pyrogram/methods/messages/send_media_group.py | 8 +- pyrogram/methods/messages/send_message.py | 6 +- pyrogram/methods/messages/send_reaction.py | 8 +- pyrogram/methods/messages/send_video.py | 7 + pyrogram/methods/messages/send_video_note.py | 14 +- pyrogram/methods/messages/send_voice.py | 21 +- pyrogram/methods/messages/send_web_page.py | 46 ++- pyrogram/methods/messages/vote_poll.py | 2 +- pyrogram/methods/stories/__init__.py | 2 - .../methods/stories/increment_story_views.py | 12 +- pyrogram/methods/stories/send_story.py | 7 +- pyrogram/methods/users/__init__.py | 2 + pyrogram/methods/users/block_user.py | 6 +- pyrogram/methods/users/check_username.py | 66 ++++ pyrogram/methods/users/unblock_user.py | 6 +- .../methods/utilities/stop_transmission.py | 3 +- pyrogram/parser/html.py | 4 +- pyrogram/parser/markdown.py | 59 +++- pyrogram/parser/parser.py | 6 +- pyrogram/parser/utils.py | 4 +- pyrogram/py.typed | 0 pyrogram/raw/core/primitives/vector.py | 7 + pyrogram/session/session.py | 23 +- pyrogram/storage/file_storage.py | 21 +- pyrogram/storage/sqlite_storage.py | 62 +++- pyrogram/storage/storage.py | 42 ++- pyrogram/types/bots_and_keyboards/__init__.py | 2 + .../bots_and_keyboards/keyboard_button.py | 183 ++++++++++- .../request_channel_info.py | 2 +- .../bots_and_keyboards/request_chat_info.py | 2 +- .../bots_and_keyboards/request_poll_info.py | 2 +- .../bots_and_keyboards/request_user_info.py | 2 +- .../bots_and_keyboards/requested_chats.py | 77 +++++ .../types/input_media/input_media_video.py | 6 + .../types/input_message_content/__init__.py | 6 +- pyrogram/types/messages_and_media/__init__.py | 2 +- .../types/messages_and_media/forum_topic.py | 10 +- .../types/messages_and_media/gift_code.py | 2 +- pyrogram/types/messages_and_media/giveaway.py | 2 +- .../messages_and_media/giveaway_result.py | 2 +- pyrogram/types/messages_and_media/message.py | 287 ++++++++++++++---- pyrogram/types/messages_and_media/my_boost.py | 6 +- pyrogram/types/messages_and_media/poll.py | 37 --- pyrogram/types/messages_and_media/story.py | 62 ++-- .../types/messages_and_media/video_note.py | 11 +- pyrogram/types/messages_and_media/voice.py | 10 +- pyrogram/types/messages_and_media/web_page.py | 24 +- pyrogram/types/object.py | 22 +- pyrogram/types/update.py | 6 +- pyrogram/types/user_and_chats/__init__.py | 12 +- .../types/user_and_chats/business_info.py | 81 +++++ .../types/user_and_chats/business_message.py | 111 +++++++ .../user_and_chats/business_recipients.py | 78 +++++ .../user_and_chats/business_weekly_open.py | 49 +++ .../user_and_chats/business_working_hours.py | 62 ++++ pyrogram/types/user_and_chats/chat.py | 12 +- pyrogram/types/user_and_chats/chat_event.py | 2 +- pyrogram/types/user_and_chats/folder.py | 50 ++- pyrogram/types/user_and_chats/user.py | 9 +- pyrogram/utils.py | 12 +- setup.py | 4 +- tests/parser/test_markdown.py | 24 ++ 134 files changed, 2748 insertions(+), 485 deletions(-) create mode 100644 pyrogram/enums/business_schedule.py create mode 100644 pyrogram/enums/folder_color.py create mode 100644 pyrogram/methods/chats/toggle_folder_tags.py create mode 100644 pyrogram/methods/chats/toggle_join_to_send.py create mode 100644 pyrogram/methods/messages/get_history.py create mode 100644 pyrogram/methods/messages/get_message_by_link.py create mode 100644 pyrogram/methods/messages/get_scheduled_messages.py create mode 100644 pyrogram/methods/messages/get_stickers.py create mode 100644 pyrogram/methods/users/check_username.py create mode 100644 pyrogram/py.typed create mode 100644 pyrogram/types/bots_and_keyboards/requested_chats.py create mode 100644 pyrogram/types/user_and_chats/business_info.py create mode 100644 pyrogram/types/user_and_chats/business_message.py create mode 100644 pyrogram/types/user_and_chats/business_recipients.py create mode 100644 pyrogram/types/user_and_chats/business_weekly_open.py create mode 100644 pyrogram/types/user_and_chats/business_working_hours.py diff --git a/.github/workflows/pypi_release.yml b/.github/workflows/pypi_release.yml index bb3dc069d..3c5e4b5d0 100644 --- a/.github/workflows/pypi_release.yml +++ b/.github/workflows/pypi_release.yml @@ -13,17 +13,17 @@ jobs: steps: - name: Checkout source - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.x" - name: Install dependencies run: | python -m pip install --upgrade pip - pip install tox==4.8.0 + pip install tox - name: Generate API run: | diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 8d3f5844a..35b3243bf 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -10,20 +10,20 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip - pip install tox==4.8.0 + pip install tox - name: Generate API run: | @@ -33,4 +33,3 @@ jobs: - name: Run tests run: | tox - diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 2186bf7d6..053eace8a 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -90,7 +90,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#d3bc4b7a id:long = User; -user#215c4438 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector stories_max_id:flags2.5?int color:flags2.8?PeerColor profile_color:flags2.9?PeerColor = User; +user#215c4438 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true close_friend:flags2.2?true stories_hidden:flags2.3?true stories_unavailable:flags2.4?true contact_require_premium:flags2.10?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector stories_max_id:flags2.5?int color:flags2.8?PeerColor profile_color:flags2.9?PeerColor = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#82d1f706 flags:# has_video:flags.0?true personal:flags.2?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; @@ -98,9 +98,9 @@ userProfilePhoto#82d1f706 flags:# has_video:flags.0?true personal:flags.2?true p userStatusEmpty#9d05049 = UserStatus; userStatusOnline#edb93949 expires:int = UserStatus; userStatusOffline#8c703f was_online:int = UserStatus; -userStatusRecently#e26f42f1 = UserStatus; -userStatusLastWeek#7bf09fc = UserStatus; -userStatusLastMonth#77ebc742 = UserStatus; +userStatusRecently#7b197dc8 flags:# by_me:flags.0?true = UserStatus; +userStatusLastWeek#541a1d1a flags:# by_me:flags.0?true = UserStatus; +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; @@ -109,7 +109,7 @@ channel#aadfc8f flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5 channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull; -channelFull#f2bcb6f flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper = ChatFull; +channelFull#44c054a7 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -122,7 +122,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#76bec211 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true id:int from_id:flags.8?Peer peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; +message#a66c7efc flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int = Message; messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -183,6 +183,7 @@ messageActionSetChatWallPaper#5060a3f4 flags:# same:flags.0?true for_both:flags. messageActionGiftCode#678c2e09 flags:# via_giveaway:flags.0?true unclaimed:flags.2?true boost_peer:flags.1?Peer months:int slug:string currency:flags.2?string amount:flags.2?long crypto_currency:flags.3?string crypto_amount:flags.3?long = MessageAction; messageActionGiveawayLaunch#332ba9ed = MessageAction; messageActionGiveawayResults#2a9fadc5 winners_count:int unclaimed_count:int = MessageAction; +messageActionBoostApply#cc02aa6d boosts:int = 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; @@ -234,7 +235,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason; inputReportReasonIllegalDrugs#a8eb2be = ReportReason; inputReportReasonPersonalDetails#9ec7863d = ReportReason; -userFull#b9b12c6c flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper stories:flags.25?PeerStories = UserFull; +userFull#22ff3e85 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true stories_pinned_available:flags.26?true blocked_my_stories_from:flags.27?true wallpaper_overridden:flags.28?true contact_require_premium:flags.29?true read_dates_private:flags.30?true flags2:# id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper stories:flags.25?PeerStories business_work_hours:flags2.0?BusinessWorkHours business_location:flags2.1?BusinessLocation business_greeting_message:flags2.2?BusinessGreetingMessage business_away_message:flags2.3?BusinessAwayMessage = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -408,6 +409,13 @@ updateBotMessageReaction#ac21d3ce peer:Peer msg_id:int date:int actor:Peer old_r updateBotMessageReactions#9cb7759 peer:Peer msg_id:int date:int reactions:Vector qts:int = Update; updateSavedDialogPinned#aeaf9e74 flags:# pinned:flags.0?true peer:DialogPeer = Update; updatePinnedSavedDialogs#686c85a6 flags:# order:flags.0?Vector = Update; +updateSavedReactionTags#39c67432 = Update; +updateSmsJob#f16269d4 job_id:string = Update; +updateQuickReplies#f9470ab2 quick_replies:Vector = Update; +updateNewQuickReply#f53da717 quick_reply:QuickReply = Update; +updateDeleteQuickReply#53e6f1ec shortcut_id:int = Update; +updateQuickReplyMessage#3e050d0f message:Message = Update; +updateDeleteQuickReplyMessages#566fe7cd shortcut_id:int messages:Vector = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -987,6 +995,7 @@ channelAdminLogEventActionChangePeerColor#5796e780 prev_value:PeerColor new_valu channelAdminLogEventActionChangeProfilePeerColor#5e477b25 prev_value:PeerColor new_value:PeerColor = ChannelAdminLogEventAction; channelAdminLogEventActionChangeWallpaper#31bb5d52 prev_value:WallPaper new_value:WallPaper = ChannelAdminLogEventAction; channelAdminLogEventActionChangeEmojiStatus#3ea9feb1 prev_value:EmojiStatus new_value:EmojiStatus = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeEmojiStickerSet#46d840ab prev_stickerset:InputStickerSet new_stickerset:InputStickerSet = ChannelAdminLogEventAction; channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; @@ -1233,9 +1242,9 @@ bankCardOpenUrl#f568028a url:string name:string = BankCardOpenUrl; payments.bankCardData#3e24e573 title:string open_urls:Vector = payments.BankCardData; -dialogFilter#7438f7e8 flags:# contacts:flags.0?true non_contacts:flags.1?true groups:flags.2?true broadcasts:flags.3?true bots:flags.4?true exclude_muted:flags.11?true exclude_read:flags.12?true exclude_archived:flags.13?true id:int title:string emoticon:flags.25?string pinned_peers:Vector include_peers:Vector exclude_peers:Vector = DialogFilter; +dialogFilter#5fb5523b flags:# contacts:flags.0?true non_contacts:flags.1?true groups:flags.2?true broadcasts:flags.3?true bots:flags.4?true exclude_muted:flags.11?true exclude_read:flags.12?true exclude_archived:flags.13?true id:int title:string emoticon:flags.25?string color:flags.27?int pinned_peers:Vector include_peers:Vector exclude_peers:Vector = DialogFilter; dialogFilterDefault#363293ae = DialogFilter; -dialogFilterChatlist#d64a04a8 flags:# has_my_invites:flags.26?true id:int title:string emoticon:flags.25?string pinned_peers:Vector include_peers:Vector = DialogFilter; +dialogFilterChatlist#9fe28ea4 flags:# has_my_invites:flags.26?true id:int title:string emoticon:flags.25?string color:flags.27?int pinned_peers:Vector include_peers:Vector = DialogFilter; dialogFilterSuggested#77744d4a filter:DialogFilter description:string = DialogFilterSuggested; @@ -1266,7 +1275,7 @@ statsGroupTopInviter#535f779d user_id:long invitations:int = StatsGroupTopInvite stats.megagroupStats#ef7ff916 period:StatsDateRangeDays members:StatsAbsValueAndPrev messages:StatsAbsValueAndPrev viewers:StatsAbsValueAndPrev posters:StatsAbsValueAndPrev growth_graph:StatsGraph members_graph:StatsGraph new_members_by_source_graph:StatsGraph languages_graph:StatsGraph messages_graph:StatsGraph actions_graph:StatsGraph top_hours_graph:StatsGraph weekdays_graph:StatsGraph top_posters:Vector top_admins:Vector top_inviters:Vector users:Vector = stats.MegagroupStats; -globalPrivacySettings#734c4ccb flags:# archive_and_mute_new_noncontact_peers:flags.0?true keep_archived_unmuted:flags.1?true keep_archived_folders:flags.2?true = GlobalPrivacySettings; +globalPrivacySettings#734c4ccb flags:# archive_and_mute_new_noncontact_peers:flags.0?true keep_archived_unmuted:flags.1?true keep_archived_folders:flags.2?true hide_read_marks:flags.3?true new_noncontact_peers_require_premium:flags.4?true = GlobalPrivacySettings; help.countryCode#4203c5ef flags:# country_code:string prefixes:flags.0?Vector patterns:flags.1?Vector = help.CountryCode; @@ -1282,7 +1291,7 @@ messages.messageViews#b6c4f543 views:Vector chats:Vector use messages.discussionMessage#a6341782 flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector users:Vector = messages.DiscussionMessage; messageReplyHeader#afbc09db flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true quote:flags.9?true reply_to_msg_id:flags.4?int reply_to_peer_id:flags.0?Peer reply_from:flags.5?MessageFwdHeader reply_media:flags.8?MessageMedia reply_to_top_id:flags.1?int quote_text:flags.6?string quote_entities:flags.7?Vector quote_offset:flags.10?int = MessageReplyHeader; -messageReplyStoryHeader#9c98bfc1 user_id:long story_id:int = MessageReplyHeader; +messageReplyStoryHeader#e5af939 peer:Peer story_id:int = MessageReplyHeader; messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; @@ -1374,7 +1383,7 @@ auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut reactionCount#a3d1cb80 flags:# chosen_order:flags.0?int reaction:Reaction count:int = ReactionCount; -messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector recent_reactions:flags.1?Vector = MessageReactions; +messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true reactions_as_tags:flags.3?true results:Vector recent_reactions:flags.1?Vector = MessageReactions; messages.messageReactionsList#31bd492d flags:# count:int reactions:Vector chats:Vector users:Vector next_offset:flags.0?string = messages.MessageReactionsList; @@ -1561,7 +1570,7 @@ storyViews#8d595cd6 flags:# has_viewers:flags.1?true views_count:int forwards_co storyItemDeleted#51e6ee4f id:int = StoryItem; storyItemSkipped#ffadc913 flags:# close_friends:flags.8?true id:int date:int expire_date:int = StoryItem; -storyItem#af6365a1 flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true out:flags.16?true id:int date:int fwd_from:flags.17?StoryFwdHeader expire_date:int caption:flags.0?string entities:flags.1?Vector media:MessageMedia media_areas:flags.14?Vector privacy:flags.2?Vector views:flags.3?StoryViews sent_reaction:flags.15?Reaction = StoryItem; +storyItem#79b26a24 flags:# pinned:flags.5?true public:flags.7?true close_friends:flags.8?true min:flags.9?true noforwards:flags.10?true edited:flags.11?true contacts:flags.12?true selected_contacts:flags.13?true out:flags.16?true id:int date:int from_id:flags.18?Peer fwd_from:flags.17?StoryFwdHeader expire_date:int caption:flags.0?string entities:flags.1?Vector media:MessageMedia media_areas:flags.14?Vector privacy:flags.2?Vector views:flags.3?StoryViews sent_reaction:flags.15?Reaction = StoryItem; stories.allStoriesNotModified#1158fe3e flags:# state:string stealth_mode:StoriesStealthMode = stories.AllStories; stories.allStories#6efc5e81 flags:# has_more:flags.0?true count:int state:string peer_stories:Vector chats:Vector users:Vector stealth_mode:StoriesStealthMode = stories.AllStories; @@ -1577,7 +1586,7 @@ 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; -inputReplyToStory#15b0f283 user_id:InputUser story_id:int = InputReplyTo; +inputReplyToStory#5881323a peer:InputPeer story_id:int = InputReplyTo; exportedStoryLink#3fc9053b link:string = ExportedStoryLink; @@ -1634,7 +1643,7 @@ peerColor#b54b5acf flags:# color:flags.0?int background_emoji_id:flags.1?long = help.peerColorSet#26219a58 colors:Vector = help.PeerColorSet; help.peerColorProfileSet#767d61eb palette_colors:Vector bg_colors:Vector story_colors:Vector = help.PeerColorSet; -help.peerColorOption#ef8430ab flags:# hidden:flags.0?true color_id:int colors:flags.1?help.PeerColorSet dark_colors:flags.2?help.PeerColorSet channel_min_level:flags.3?int = help.PeerColorOption; +help.peerColorOption#adec6ebe flags:# hidden:flags.0?true color_id:int colors:flags.1?help.PeerColorSet dark_colors:flags.2?help.PeerColorSet channel_min_level:flags.3?int group_min_level:flags.4?int = help.PeerColorOption; help.peerColorsNotModified#2ba1f5ce = help.PeerColors; help.peerColors#f8ed08 hash:int colors:Vector = help.PeerColors; @@ -1651,6 +1660,60 @@ messages.savedDialogs#f83ae221 dialogs:Vector messages:Vector messages:Vector chats:Vector users:Vector = messages.SavedDialogs; messages.savedDialogsNotModified#c01f6fe8 count:int = messages.SavedDialogs; +savedReactionTag#cb6ff828 flags:# reaction:Reaction title:flags.0?string count:int = SavedReactionTag; + +messages.savedReactionTagsNotModified#889b59ef = messages.SavedReactionTags; +messages.savedReactionTags#3259950a tags:Vector hash:long = messages.SavedReactionTags; + +outboxReadDate#3bb842ac date:int = OutboxReadDate; + +smsjobs.eligibleToJoin#dc8b44cf terms_url:string monthly_sent_sms:int = smsjobs.EligibilityToJoin; + +smsjobs.status#2aee9191 flags:# allow_international:flags.0?true recent_sent:int recent_since:int recent_remains:int total_sent:int total_since:int last_gift_slug:flags.1?string terms_url:string = smsjobs.Status; + +smsJob#e6a1eeb8 job_id:string phone_number:string text:string = SmsJob; + +businessWeeklyOpen#120b1ab9 start_minute:int end_minute:int = BusinessWeeklyOpen; + +businessWorkHours#8c92b098 flags:# open_now:flags.0?true timezone_id:string weekly_open:Vector = BusinessWorkHours; + +businessLocation#ac5c1af7 flags:# geo_point:flags.0?GeoPoint address:string = BusinessLocation; + +inputBusinessRecipients#6f8b32aa flags:# existing_chats:flags.0?true new_chats:flags.1?true contacts:flags.2?true non_contacts:flags.3?true exclude_selected:flags.5?true users:flags.4?Vector = InputBusinessRecipients; + +businessRecipients#21108ff7 flags:# existing_chats:flags.0?true new_chats:flags.1?true contacts:flags.2?true non_contacts:flags.3?true exclude_selected:flags.5?true users:flags.4?Vector = BusinessRecipients; + +businessAwayMessageScheduleAlways#c9b9e2b9 = BusinessAwayMessageSchedule; +businessAwayMessageScheduleOutsideWorkHours#c3f2f501 = BusinessAwayMessageSchedule; +businessAwayMessageScheduleCustom#cc4d9ecc start_date:int end_date:int = BusinessAwayMessageSchedule; + +inputBusinessGreetingMessage#194cb3b shortcut_id:int recipients:InputBusinessRecipients no_activity_days:int = InputBusinessGreetingMessage; + +businessGreetingMessage#e519abab shortcut_id:int recipients:BusinessRecipients no_activity_days:int = BusinessGreetingMessage; + +inputBusinessAwayMessage#832175e0 flags:# offline_only:flags.0?true shortcut_id:int schedule:BusinessAwayMessageSchedule recipients:InputBusinessRecipients = InputBusinessAwayMessage; + +businessAwayMessage#ef156a5c flags:# offline_only:flags.0?true shortcut_id:int schedule:BusinessAwayMessageSchedule recipients:BusinessRecipients = BusinessAwayMessage; + +timezone#ff9289f5 id:string name:string utc_offset:int = Timezone; + +help.timezonesListNotModified#970708cc = help.TimezonesList; +help.timezonesList#7b74ed71 timezones:Vector hash:int = help.TimezonesList; + +quickReply#697102b shortcut_id:int shortcut:string top_message:int count:int = QuickReply; + +inputQuickReplyShortcut#24596d41 shortcut:string = InputQuickReplyShortcut; +inputQuickReplyShortcutId#1190cf1 shortcut_id:int = InputQuickReplyShortcut; + +messages.quickReplies#c68d6695 quick_replies:Vector messages:Vector chats:Vector users:Vector = messages.QuickReplies; +messages.quickRepliesNotModified#5f91eb5b = messages.QuickReplies; + +connectedBot#e7e999e7 flags:# can_reply:flags.0?true bot_id:long recipients:BusinessRecipients = ConnectedBot; + +account.connectedBots#17d7f87b connected_bots:Vector users:Vector = account.ConnectedBots; + +messages.dialogFilters#2ad93719 flags:# tags_enabled:flags.0?true filters:Vector = messages.DialogFilters; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1662,7 +1725,7 @@ invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X; invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X; auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode; -auth.signUp#80eee427 phone_number:string phone_code_hash:string first_name:string last_name:string = auth.Authorization; +auth.signUp#aac7b717 flags:# no_joined_notifications:flags.0?true phone_number:string phone_code_hash:string first_name:string last_name:string = auth.Authorization; auth.signIn#8d52a951 flags:# phone_number:string phone_code_hash:string phone_code:flags.0?string email_verification:flags.1?EmailVerification = auth.Authorization; auth.logOut#3e72ba19 = auth.LoggedOut; auth.resetAuthorizations#9fab0d1a = Bool; @@ -1776,10 +1839,17 @@ account.updateColor#7cefa15d flags:# for_profile:flags.1?true color:flags.2?int account.getDefaultBackgroundEmojis#a60ab9ce hash:long = EmojiList; account.getChannelDefaultEmojiStatuses#7727a7d5 hash:long = account.EmojiStatuses; account.getChannelRestrictedStatusEmojis#35a9e0d5 hash:long = EmojiList; +account.updateBusinessWorkHours#4b00e066 flags:# business_work_hours:flags.0?BusinessWorkHours = Bool; +account.updateBusinessLocation#9e6b131a flags:# geo_point:flags.1?InputGeoPoint address:flags.0?string = Bool; +account.updateBusinessGreetingMessage#66cdafc4 flags:# message:flags.0?InputBusinessGreetingMessage = Bool; +account.updateBusinessAwayMessage#a26a7fa5 flags:# message:flags.0?InputBusinessAwayMessage = Bool; +account.updateConnectedBot#9c2d527d flags:# can_reply:flags.0?true deleted:flags.1?true bot:InputUser recipients:InputBusinessRecipients = Updates; +account.getConnectedBots#4ea4c80f = account.ConnectedBots; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; users.setSecureValueErrors#90c894b5 id:InputUser errors:Vector = Bool; +users.getIsPremiumRequiredToContact#a622aa10 id:Vector = Vector; contacts.getContactIDs#7adc669d hash:long = Vector; contacts.getStatuses#c4a353ee = Vector; @@ -1810,15 +1880,15 @@ contacts.setBlocked#94c65c76 flags:# my_stories_from:flags.0?true id:Vector = messages.Messages; messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.Dialogs; messages.getHistory#4423e6c5 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; -messages.search#a7b4e929 flags:# peer:InputPeer q:string from_id:flags.0?InputPeer saved_peer_id:flags.2?InputPeer top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; +messages.search#29ee847a flags:# peer:InputPeer q:string from_id:flags.0?InputPeer saved_peer_id:flags.2?InputPeer saved_reaction:flags.3?Vector top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages; messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory; messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; -messages.sendMessage#280d096f 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 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 = Updates; -messages.sendMedia#72ccc23d 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 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 = Updates; -messages.forwardMessages#c661bbc4 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 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 = Updates; +messages.sendMessage#dff8042c 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 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 = Updates; +messages.sendMedia#7bd66041 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 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 = Updates; +messages.forwardMessages#d5039208 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 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 = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; messages.report#8953ab4e peer:InputPeer id:Vector reason:ReportReason message:string = Bool; @@ -1861,9 +1931,9 @@ messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; messages.setInlineBotResults#bb12a419 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM switch_webview:flags.4?InlineBotWebView = Bool; -messages.sendInlineBotResult#f7bc68ba flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to:flags.0?InputReplyTo random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendInlineBotResult#3ebee86a flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to:flags.0?InputReplyTo random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut = Updates; messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; -messages.editMessage#48f71778 flags:# no_webpage:flags.1?true invert_media:flags.16?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int = Updates; +messages.editMessage#dfd14005 flags:# no_webpage:flags.1?true invert_media:flags.16?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int quick_reply_shortcut_id:flags.17?int = Updates; messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true invert_media:flags.16?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer; messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool; @@ -1896,7 +1966,7 @@ messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool; messages.getUnreadMentions#f107e790 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.readMentions#36e5bf4d flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory; messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages; -messages.sendMultiMedia#456e8987 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 peer:InputPeer reply_to:flags.0?InputReplyTo multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMultiMedia#c964709 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 peer:InputPeer reply_to:flags.0?InputReplyTo multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer quick_reply_shortcut:flags.17?InputQuickReplyShortcut = Updates; 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; @@ -1923,7 +1993,7 @@ messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector = Updates; messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector = Updates; messages.getPollVotes#b86e380e flags:# peer:InputPeer id:int option:flags.0?bytes offset:flags.1?string limit:int = messages.VotesList; messages.toggleStickerSets#b5052fea flags:# uninstall:flags.0?true archive:flags.1?true unarchive:flags.2?true stickersets:Vector = Bool; -messages.getDialogFilters#f19ed96d = Vector; +messages.getDialogFilters#efd48c89 = messages.DialogFilters; messages.getSuggestedDialogFilters#a29cd42c = Vector; messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool; messages.updateDialogFiltersOrder#c563c1e4 order:Vector = Bool; @@ -2001,6 +2071,19 @@ messages.deleteSavedHistory#6e98102b flags:# peer:InputPeer max_id:int min_date: 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; +messages.getSavedReactionTags#3637e05b flags:# peer:flags.0?InputPeer hash:long = messages.SavedReactionTags; +messages.updateSavedReactionTag#60297dec flags:# reaction:Reaction title:flags.0?string = Bool; +messages.getDefaultTagReactions#bdf93428 hash:long = messages.Reactions; +messages.getOutboxReadDate#8c4bfe5d peer:InputPeer msg_id:int = OutboxReadDate; +messages.getQuickReplies#d483f2a8 hash:long = messages.QuickReplies; +messages.reorderQuickReplies#60331907 order:Vector = Bool; +messages.checkQuickReplyShortcut#f1d0fbd3 shortcut:string = Bool; +messages.editQuickReplyShortcut#5c003cef shortcut_id:int shortcut:string = Bool; +messages.deleteQuickReplyShortcut#3cc04740 shortcut_id:int = Bool; +messages.getQuickReplyMessages#94a495c3 flags:# shortcut_id:int id:flags.0?Vector hash:long = messages.Messages; +messages.sendQuickReplyMessages#33153ad4 peer:InputPeer shortcut_id:int = Updates; +messages.deleteQuickReplyMessages#e105e910 shortcut_id:int id:Vector = Updates; +messages.toggleDialogFilterTags#fd2dda49 enabled:Bool = 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; @@ -2045,6 +2128,7 @@ help.getCountriesList#735787a8 lang_code:string hash:int = help.CountriesList; help.getPremiumPromo#b81b93d4 = help.PremiumPromo; help.getPeerColors#da80f42f hash:int = help.PeerColors; help.getPeerProfileColors#abcfa9fd hash:int = help.PeerColors; +help.getTimezonesList#49b30240 hash:int = help.TimezonesList; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector = messages.AffectedMessages; @@ -2106,6 +2190,8 @@ channels.updateColor#d8aa3671 flags:# for_profile:flags.1?true channel:InputChan channels.toggleViewForumAsMessages#9738bb15 channel:InputChannel enabled:Bool = Updates; channels.getChannelRecommendations#83b70d97 channel:InputChannel = messages.Chats; channels.updateEmojiStatus#f0d3e6a8 channel:InputChannel emoji_status:EmojiStatus = Updates; +channels.setBoostsToUnblockRestrictions#ad399cee channel:InputChannel boosts:int = Updates; +channels.setEmojiStickers#3cd930b7 channel:InputChannel stickerset:InputStickerSet = Bool; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -2243,4 +2329,12 @@ premium.applyBoost#6b7da746 flags:# slots:flags.0?Vector peer:InputPeer = p premium.getBoostsStatus#42f1f61 peer:InputPeer = premium.BoostsStatus; premium.getUserBoosts#39854d1f peer:InputPeer user_id:InputUser = premium.BoostsList; -// LAYER 170 \ No newline at end of file +smsjobs.isEligibleToJoin#edc39d0 = smsjobs.EligibilityToJoin; +smsjobs.join#a74ece2d = Bool; +smsjobs.leave#9898ad73 = Bool; +smsjobs.updateSettings#93fa0bf flags:# allow_international:flags.0?true = Bool; +smsjobs.getStatus#10a698e8 = smsjobs.Status; +smsjobs.getSmsJob#778d902f job_id:string = SmsJob; +smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool; + +// LAYER 176 \ No newline at end of file diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 12a4d9722..1b9e4b1b4 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -176,6 +176,8 @@ def get_title_list(s: str) -> list: send_chat_action delete_messages get_messages + get_scheduled_messages + get_stickers get_media_group get_chat_history get_chat_history_count @@ -195,6 +197,9 @@ def get_title_list(s: str) -> list: get_discussion_replies get_discussion_replies_count get_custom_emoji_stickers + send_web_page + start_bot + update_color """, chats=""" Chats @@ -237,6 +242,25 @@ def get_title_list(s: str) -> list: get_send_as_chats set_send_as_chat set_chat_protected_content + close_forum_topic + create_forum_topic + delete_forum_topic + edit_forum_topic + get_forum_topics + get_forum_topics_by_id + update_color + update_chat_notifications + toggle_forum_topics + delete_folder + export_folder_link + get_folders + update_folder + get_similar_channels + join_folder + leave_folder + toggle_join_to_send + toggle_folder_tags + set_chat_ttl """, users=""" Users @@ -253,6 +277,8 @@ def get_title_list(s: str) -> list: get_common_chats get_default_emoji_statuses set_emoji_status + update_status + check_username """, invite_links=""" Invite Links @@ -330,6 +356,32 @@ def get_title_list(s: str) -> list: invoke resolve_peer save_file + """, + stories=""" + Stories + can_send_story + copy_story + delete_stories + edit_story_caption + edit_story_media + edit_story_privacy + forward_story + get_all_stories + get_chat_stories + get_pinned_stories + get_stories_archive + get_stories + hide_stories + increment_story_views + pin_stories + read_stories + send_story + """, + premium=""" + Premium + apply_boost + get_boosts + get_boosts_status """ ) @@ -371,7 +423,13 @@ def get_title_list(s: str) -> list: categories = dict( users_chats=""" Users & Chats + BusinessInfo + BusinessMessage + BusinessRecipients + BusinessWeeklyOpen + BusinessWorkingHours User + Username Chat ChatPreview ChatPhoto @@ -388,6 +446,8 @@ def get_title_list(s: str) -> list: Dialog Restriction EmojiStatus + Folder + ChatColor """, messages_media=""" Messages & Media @@ -418,6 +478,12 @@ def get_title_list(s: str) -> list: WebAppData MessageReactions ChatReactions + Story + MyBoost + BoostsStatus + Giveaway + GiveawayResult + GiftCode """, bot_keyboards=""" Bot keyboards @@ -437,6 +503,11 @@ def get_title_list(s: str) -> list: MenuButtonWebApp MenuButtonDefault SentWebAppMessage + ForumTopic + RequestChannelInfo + RequestChatInfo + RequestUserInfo + RequestPollInfo """, bot_commands=""" Bot commands @@ -557,6 +628,7 @@ def get_title_list(s: str) -> list: Message.reply_video Message.reply_video_note Message.reply_voice + Message.reply_web_page Message.get_media_group Message.react """, @@ -567,18 +639,22 @@ def get_title_list(s: str) -> list: Chat.set_title Chat.set_description Chat.set_photo + Chat.set_ttl Chat.ban_member Chat.unban_member Chat.restrict_member Chat.promote_member + Chat.join + Chat.leave + Chat.export_invite_link Chat.get_member Chat.get_members Chat.add_members - Chat.join - Chat.leave Chat.mark_unread Chat.set_protected_content Chat.unpin_all_messages + Chat.mute + Chat.unmute """, user=""" User @@ -586,6 +662,7 @@ def get_title_list(s: str) -> list: User.unarchive User.block User.unblock + User.get_common_chats """, callback_query=""" Callback Query @@ -603,6 +680,39 @@ def get_title_list(s: str) -> list: ChatJoinRequest ChatJoinRequest.approve ChatJoinRequest.decline + """, + story=""" + Story + Story.reply_text + Story.reply_animation + Story.reply_audio + Story.reply_cached_media + Story.reply_media_group + Story.reply_photo + Story.reply_sticker + Story.reply_video + Story.reply_video_note + Story.reply_voice + Story.copy + Story.delete + Story.edit_media + Story.edit_caption + Story.edit_privacy + Story.react + Story.forward + Story.download + Story.read + """, + folder=""" + Folder + Folder.delete + Folder.update + Folder.include_chat + Folder.exclude_chat + Folder.update_color + Folder.pin_chat + Folder.remove_chat + Folder.export_link """ ) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index db07ca1e8..6fa64ba16 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -57,8 +57,8 @@ CHANNEL_TOO_LARGE The channel is too large CHAT_ABOUT_NOT_MODIFIED The chat about text was not modified because you tried to edit it using the same content CHAT_ABOUT_TOO_LONG The chat about text is too long CHAT_ADMIN_REQUIRED The method requires chat admin privileges -CHAT_FORWARDS_RESTRICTED The chat restricts forwarding content CHAT_DISCUSSION_UNALLOWED The chat discussion is not allowed +CHAT_FORWARDS_RESTRICTED The chat restricts forwarding content CHAT_ID_EMPTY The provided chat id is empty CHAT_ID_INVALID The chat id being used is invalid or not known yet. Make sure you see the chat before interacting with it CHAT_INVALID The chat is invalid @@ -72,6 +72,7 @@ CHAT_TOO_BIG The chat is too big for this action CODE_EMPTY The provided code is empty CODE_HASH_INVALID The provided code hash invalid CODE_INVALID The provided code is invalid (i.e. from email) +COLOR_INVALID The provided color is invalid CONNECTION_API_ID_INVALID The provided API id is invalid CONNECTION_APP_VERSION_EMPTY App version is empty CONNECTION_DEVICE_MODEL_EMPTY The device model is empty @@ -127,6 +128,7 @@ FILE_REFERENCE_EMPTY The file id contains an empty file reference, you must obta FILE_REFERENCE_EXPIRED The file id contains an expired file reference, you must obtain a valid one by fetching the message from the origin context FILE_REFERENCE_INVALID The file id contains an invalid file reference, you must obtain a valid one by fetching the message from the origin context FILTER_ID_INVALID The specified filter ID is invalid +FILTER_INCLUDE_EMPTY The filter include is empty FIRSTNAME_INVALID The first name is invalid FOLDER_ID_EMPTY The folder you tried to delete was already empty FOLDER_ID_INVALID The folder id is invalid @@ -135,6 +137,7 @@ FROM_MESSAGE_BOT_DISABLED Bots can't use fromMessage min constructors FROM_PEER_INVALID The from peer value is invalid GAME_BOT_INVALID You cannot send that game with the current bot GEO_POINT_INVALID Invalid geo point provided +GIFT_SLUG_EXPIRED The gift slug is expired GIF_CONTENT_TYPE_INVALID GIF content-type invalid GIF_ID_INVALID The provided gif/animation id is invalid GRAPH_INVALID_RELOAD Invalid graph token provided, please reload the stats and provide the updated token @@ -161,6 +164,8 @@ INVITE_HASH_EXPIRED The chat invite link is no longer valid INVITE_HASH_INVALID The invite link hash is invalid INVITE_REQUEST_SENT The request to join this chat or channel has been successfully sent INVITE_REVOKED_MISSING The action required a chat invite link to be revoked first +INVITE_SLUG_EMPTY The invite slug is empty +INVITE_SLUG_EXPIRED The invite slug is expired LANG_PACK_INVALID The provided language pack is invalid LASTNAME_INVALID The last name is invalid LIMIT_INVALID The limit parameter is invalid @@ -171,7 +176,7 @@ MAX_QTS_INVALID The provided QTS is invalid MD5_CHECKSUM_INVALID The file's checksum did not match the md5_checksum parameter MEDIA_CAPTION_TOO_LONG The media caption is too long MEDIA_EMPTY The media you tried to send is invalid -MEDIA_FILE_INVALID The media file is invalid +MEDIA_FILE_INVALID The provided media file is invalid MEDIA_INVALID The media is invalid MEDIA_NEW_INVALID The new media to edit the message with is invalid MEDIA_PREV_INVALID The previous media cannot be edited with anything else @@ -258,10 +263,12 @@ QUIZ_CORRECT_ANSWERS_EMPTY The correct answers of the quiz are empty QUIZ_CORRECT_ANSWERS_TOO_MUCH The quiz contains too many correct answers QUIZ_CORRECT_ANSWER_INVALID The correct answers of the quiz are invalid QUIZ_MULTIPLE_INVALID A quiz can't have multiple answers +QUOTE_TEXT_INVALID The quote text is invalid RANDOM_ID_EMPTY The random ID is empty RANDOM_ID_INVALID The provided random ID is invalid RANDOM_LENGTH_INVALID The random length is invalid RANGES_INVALID Invalid range provided +REACTIONS_TOO_MANY Non-premium users, can set up only one reaction per message REACTION_EMPTY The reaction provided is empty REACTION_INVALID Invalid reaction provided (only valid emoji are allowed) REFLECTOR_NOT_AVAILABLE The call reflector is not available @@ -269,6 +276,7 @@ REPLY_MARKUP_BUY_EMPTY Reply markup for buy button empty REPLY_MARKUP_GAME_EMPTY The provided reply markup for the game is empty REPLY_MARKUP_INVALID The provided reply markup is invalid REPLY_MARKUP_TOO_LONG The reply markup is too long +REPLY_MESSAGE_ID_INVALID The reply message id is invalid RESULTS_TOO_MUCH The result contains too many items RESULT_ID_DUPLICATE The result contains items with duplicated identifiers RESULT_ID_EMPTY Result ID empty @@ -310,7 +318,9 @@ STICKER_PNG_NOPNG Stickers must be png files but the provided image was not a pn STICKER_TGS_NOTGS A tgs sticker file was expected, but something else was provided STICKER_THUMB_PNG_NOPNG A png sticker thumbnail file was expected, but something else was provided STICKER_VIDEO_NOWEBM A webm video file was expected, but something else was provided +STORIES_NEVER_CREATED You have never created any stories STORIES_TOO_MUCH Too many stories in the current account +STORY_PERIOD_INVALID The story period is invalid TAKEOUT_INVALID The takeout id is invalid TAKEOUT_REQUIRED The method must be invoked inside a takeout session TEMP_AUTH_KEY_EMPTY The temporary auth key provided is empty @@ -357,6 +367,7 @@ USER_IS_BOT A bot cannot send messages to other bots or to itself USER_KICKED This user was kicked from this chat USER_NOT_MUTUAL_CONTACT The user is not a mutual contact USER_NOT_PARTICIPANT The user is not a member of this chat +USER_PUBLIC_MISSING The accounts username is missing VIDEO_CONTENT_TYPE_INVALID The video content type is invalid (i.e.: not streamable) VIDEO_FILE_INVALID The video file is invalid VOICE_MESSAGES_FORBIDDEN Voice messages are restricted @@ -372,6 +383,4 @@ WEBDOCUMENT_URL_EMPTY The web document URL is empty WEBDOCUMENT_URL_INVALID The web document URL is invalid WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media -YOU_BLOCKED_USER You blocked this user -STORIES_NEVER_CREATED You have never created any stories -MEDIA_FILE_INVALID The provided media file is invalid \ No newline at end of file +YOU_BLOCKED_USER You blocked this user \ No newline at end of file diff --git a/compiler/errors/source/403_FORBIDDEN.tsv b/compiler/errors/source/403_FORBIDDEN.tsv index 8f08a721b..6f83d8052 100644 --- a/compiler/errors/source/403_FORBIDDEN.tsv +++ b/compiler/errors/source/403_FORBIDDEN.tsv @@ -4,15 +4,16 @@ CHANNEL_PUBLIC_GROUP_NA The channel/supergroup is not available CHAT_ADMIN_INVITE_REQUIRED You don't have rights to invite other users CHAT_ADMIN_REQUIRED The method requires chat admin privileges CHAT_FORBIDDEN You cannot write in this chat +CHAT_GUEST_SEND_FORBIDDEN You need to join the discussion group before commenting +CHAT_SEND_AUDIOS_FORBIDDEN You can't send audio messages in this chat +CHAT_SEND_GAME_FORBIDDEN You can't send a game to this chat CHAT_SEND_GIFS_FORBIDDEN You can't send animations in this chat CHAT_SEND_INLINE_FORBIDDEN You cannot use inline bots to send messages in this chat CHAT_SEND_MEDIA_FORBIDDEN You can't send media messages in this chat -CHAT_SEND_POLL_FORBIDDEN You can't send polls in this chat -CHAT_SEND_STICKERS_FORBIDDEN You can't send stickers in this chat -CHAT_SEND_AUDIOS_FORBIDDEN You can't send audio messages in this chat -CHAT_SEND_GAME_FORBIDDEN You can't send a game to this chat CHAT_SEND_PHOTOS_FORBIDDEN You can't send photos in this chat CHAT_SEND_PLAIN_FORBIDDEN You can't send non-media (text) messages in this chat +CHAT_SEND_POLL_FORBIDDEN You can't send polls in this chat +CHAT_SEND_STICKERS_FORBIDDEN You can't send stickers in this chat CHAT_SEND_VIDEOS_FORBIDDEN You can't send videos in this chat CHAT_SEND_VOICES_FORBIDDEN You can't send voice recordings in this chat CHAT_WRITE_FORBIDDEN You don't have rights to send messages in this chat @@ -24,6 +25,7 @@ MESSAGE_AUTHOR_REQUIRED You are not the author of this message MESSAGE_DELETE_FORBIDDEN You don't have rights to delete messages in this chat, most likely because you are not the author of them POLL_VOTE_REQUIRED Cast a vote in the poll before calling this method PREMIUM_ACCOUNT_REQUIRED This action requires a premium account +PRIVACY_PREMIUM_REQUIRED The user has restricted from sending messages or this action requires a premium account RIGHT_FORBIDDEN You don't have enough rights for this action, or you tried to set one or more admin rights that can't be applied to this kind of chat (channel or supergroup) SENSITIVE_CHANGE_FORBIDDEN Your sensitive content settings can't be changed at this time TAKEOUT_REQUIRED The method must be invoked inside a takeout session @@ -33,4 +35,4 @@ USER_INVALID The provided user is invalid USER_IS_BLOCKED The user is blocked USER_NOT_MUTUAL_CONTACT The provided user is not a mutual contact USER_PRIVACY_RESTRICTED The user's privacy settings is preventing you to perform this action -USER_RESTRICTED You are limited/restricted. You can't perform this action \ No newline at end of file +USER_RESTRICTED You are limited/restricted. You can't perform this action diff --git a/compiler/errors/source/406_NOT_ACCEPTABLE.tsv b/compiler/errors/source/406_NOT_ACCEPTABLE.tsv index c2df2384d..0d1f38aa3 100644 --- a/compiler/errors/source/406_NOT_ACCEPTABLE.tsv +++ b/compiler/errors/source/406_NOT_ACCEPTABLE.tsv @@ -1,10 +1,12 @@ id message AUTH_KEY_DUPLICATED The same authorization key (session file) was used in more than one place simultaneously. You must delete your session file and log in again with your phone number or bot token CHANNEL_PRIVATE The channel/supergroup is not accessible +CHANNEL_TOO_LARGE Сhannel is too large to be deleted. Contact support for removal FILEREF_UPGRADE_NEEDED The file reference has expired and you must use a refreshed one by obtaining the original media message FRESH_CHANGE_ADMINS_FORBIDDEN You were just elected admin, you can't add or modify other admins yet FRESH_CHANGE_PHONE_FORBIDDEN You can't change your phone number because your session was logged-in recently FRESH_RESET_AUTHORISATION_FORBIDDEN You can't terminate other authorized sessions because the current was logged-in recently +GIFTCODE_NOT_ALLOWED Giftcode not allowed PHONE_NUMBER_INVALID The phone number is invalid PHONE_PASSWORD_FLOOD You have tried to log-in too many times STICKERSET_INVALID The sticker set is invalid diff --git a/compiler/errors/source/420_FLOOD.tsv b/compiler/errors/source/420_FLOOD.tsv index 575cc2f5b..b45fb3f63 100644 --- a/compiler/errors/source/420_FLOOD.tsv +++ b/compiler/errors/source/420_FLOOD.tsv @@ -2,5 +2,7 @@ id message 2FA_CONFIRM_WAIT_X A wait of {value} seconds is required because this account is active and protected by a 2FA password FLOOD_TEST_PHONE_WAIT_X A wait of {value} seconds is required in the test servers FLOOD_WAIT_X A wait of {value} seconds is required -SLOWMODE_WAIT_X A wait of {value} seconds is required to send messages in this chat. +PREMIUM_SUB_ACTIVE_UNTIL_X A wait of {value} seconds is required +SLOWMODE_WAIT_X A wait of {value} seconds is required to send messages in this chat +STORY_SEND_FLOOD_X A wait of {value} seconds is required to continue posting stories TAKEOUT_INIT_DELAY_X You have to confirm the data export request using one of your mobile devices or wait {value} seconds \ No newline at end of file diff --git a/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv index 0e8d68a55..acf254b02 100644 --- a/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv +++ b/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv @@ -8,6 +8,7 @@ CHAT_OCCUPY_USERNAME_FAILED Failure to occupy chat username due to Telegram havi CHP_CALL_FAIL Telegram is having internal problems. Please try again later ENCRYPTION_OCCUPY_ADMIN_FAILED Failed occupying memory for admin info due to Telegram having internal problems. Please try again later ENCRYPTION_OCCUPY_FAILED Internal server error while accepting secret chat +FILE_WRITE_FAILED Telegram is having internal problems. Please try again later FOLDER_DEAC_AUTOFIX_ALL Telegram is having internal problems. Please try again later GROUPCALL_ADD_PARTICIPANTS_FAILED Failure while adding voice chat member due to Telegram having internal problems. Please try again later GROUPED_ID_OCCUPY_FAILED Telegram is having internal problems. Please try again later @@ -42,5 +43,4 @@ UNKNOWN_METHOD The method you tried to call cannot be called on non-CDN DCs UPLOAD_NO_VOLUME Telegram is having internal problems. Please try again later VOLUME_LOC_NOT_FOUND Telegram is having internal problems. Please try again later WORKER_BUSY_TOO_LONG_RETRY Server workers are too busy right now due to Telegram having internal problems. Please try again later -WP_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later -FILE_WRITE_FAILED Telegram is having internal problems. Please try again later \ No newline at end of file +WP_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later \ No newline at end of file diff --git a/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv b/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv index 4db95a10e..0164c93a8 100644 --- a/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv +++ b/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv @@ -1,4 +1,4 @@ id message ApiCallError Telegram is having internal problems. Please try again later. -Timeout Telegram is having internal problems. Please try again later. Timedout Telegram is having internal problems. Please try again later. +Timeout Telegram is having internal problems. Please try again later. \ No newline at end of file diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index d9186bc0c..404b21a6f 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -14,9 +14,9 @@ # 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 +# along with Pyrogram. If not, see . -__version__ = "2.0.140" +__version__ = "2.0.141" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " @@ -35,8 +35,8 @@ class ContinuePropagation(StopAsyncIteration): pass -from . import emoji, enums, filters, handlers, raw, types +from . import raw, types, filters, handlers, emoji, enums from .client import Client -from .sync import compose, idle +from .sync import idle, compose crypto_executor = ThreadPoolExecutor(1, thread_name_prefix="CryptoWorker") diff --git a/pyrogram/client.py b/pyrogram/client.py index f99808abd..6ccdbdd31 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -88,10 +88,18 @@ class Client(Methods): Operating System version. Defaults to *platform.system() + " " + platform.release()*. + lang_pack (``str``, *optional*): + Name of the language pack used on the client. + Defaults to "" (empty string). + lang_code (``str``, *optional*): Code of the language used on the client, in ISO 639-1 standard. Defaults to "en". + system_lang_code (``str``, *optional*): + Code of the language used on the system, in ISO 639-1 standard. + Defaults to "en". + ipv6 (``bool``, *optional*): Pass True to connect to Telegram using IPv6. Defaults to False (IPv4). @@ -155,6 +163,17 @@ class Client(Methods): Useful for batch programs that don't need to deal with updates. Defaults to False (updates enabled and received). + skip_updates (``bool``, *optional*): + Pass True to skip pending updates that arrived while the client was offline. + Defaults to True. + + allow_states_update (``bool``, *optional*): + Pass True to allow updating states in the db; please consider this might have + a performance impact; since we will be sending query to the database a lot, + consider disabling this option if you do not want to fetch updates in your bot's + offline time. + Defaults to True. + takeout (``bool``, *optional*): Pass True to let the client use a takeout session instead of a normal one, implies *no_updates=True*. Useful for exporting Telegram data. Methods invoked inside a takeout session (such as get_chat_history, @@ -173,21 +192,31 @@ class Client(Methods): Defaults to False, because ``getpass`` (the library used) is known to be problematic in some terminal environments. - max_concurrent_transmissions (``bool``, *optional*): + max_concurrent_transmissions (``int``, *optional*): Set the maximum amount of concurrent transmissions (uploads & downloads). A value that is too high may result in network related issues. Defaults to 1. + max_message_cache_size (``int``, *optional*): + Set the maximum size of the message cache. + Defaults to 10000. + storage_engine (:obj:`~pyrogram.storage.Storage`, *optional*): Pass an instance of your own implementation of session storage engine. Useful when you want to store your session in databases like Mongo, Redis, etc. + + init_connection_params (:obj:`~pyrogram.raw.base.JSONValue`, *optional*): + Additional initConnection parameters. + For now, only the tz_offset field is supported, for specifying timezone offset in seconds. """ APP_VERSION = f"Pyrogram {__version__}" DEVICE_MODEL = f"{platform.python_implementation()} {platform.python_version()}" SYSTEM_VERSION = f"{platform.system()} {platform.release()}" + LANG_PACK = "" LANG_CODE = "en" + SYSTEM_LANG_CODE = "en" PARENT_DIR = Path(sys.argv[0]).parent @@ -196,9 +225,10 @@ class Client(Methods): WORKDIR = PARENT_DIR # Interval of seconds in which the updates watchdog will kick in - UPDATES_WATCHDOG_INTERVAL = 5 * 60 + UPDATES_WATCHDOG_INTERVAL = 15 * 60 MAX_CONCURRENT_TRANSMISSIONS = 1 + MAX_MESSAGE_CACHE_SIZE = 10000 mimetypes = MimeTypes() mimetypes.readfp(StringIO(mime_types)) @@ -211,7 +241,9 @@ def __init__( app_version: str = APP_VERSION, device_model: str = DEVICE_MODEL, system_version: str = SYSTEM_VERSION, + lang_pack: str = LANG_PACK, lang_code: str = LANG_CODE, + system_lang_code: str = SYSTEM_LANG_CODE, ipv6: bool = False, proxy: dict = None, test_mode: bool = False, @@ -226,11 +258,15 @@ def __init__( plugins: dict = None, parse_mode: "enums.ParseMode" = enums.ParseMode.DEFAULT, no_updates: bool = None, + skip_updates: bool = True, + allow_states_update: bool = True, takeout: bool = None, sleep_threshold: int = Session.SLEEP_THRESHOLD, hide_password: bool = False, max_concurrent_transmissions: int = MAX_CONCURRENT_TRANSMISSIONS, - storage_engine: Storage = None + max_message_cache_size: int = MAX_MESSAGE_CACHE_SIZE, + storage_engine: Storage = None, + init_connection_params: "raw.base.JSONValue" = None ): super().__init__() @@ -240,7 +276,9 @@ def __init__( self.app_version = app_version self.device_model = device_model self.system_version = system_version + self.lang_pack = lang_pack.lower() self.lang_code = lang_code.lower() + self.system_lang_code = system_lang_code.lower() self.ipv6 = ipv6 self.proxy = proxy self.test_mode = test_mode @@ -255,10 +293,14 @@ def __init__( self.plugins = plugins self.parse_mode = parse_mode self.no_updates = no_updates + self.skip_updates = skip_updates + self.allow_states_update = allow_states_update self.takeout = takeout self.sleep_threshold = sleep_threshold self.hide_password = hide_password self.max_concurrent_transmissions = max_concurrent_transmissions + self.max_message_cache_size = max_message_cache_size + self.init_connection_params = init_connection_params self.executor = ThreadPoolExecutor(self.workers, thread_name_prefix="Handler") @@ -294,7 +336,7 @@ def __init__( self.me: Optional[User] = None - self.message_cache = Cache(10000) + self.message_cache = Cache(self.max_message_cache_size) # Sometimes, for some reason, the server will stop sending updates and will only respond to pings. # This watchdog will invoke updates.GetState in order to wake up the server and enable it sending updates again @@ -695,6 +737,17 @@ async def handle_updates(self, updates): pts = getattr(update, "pts", None) pts_count = getattr(update, "pts_count", None) + if pts and self.allow_states_update: + await self.storage.update_state( + ( + utils.get_channel_id(channel_id) if channel_id else 0, + pts, + None, + updates.date, + updates.seq + ) + ) + if isinstance(update, raw.types.UpdateChannelTooLong): log.info(update) @@ -725,6 +778,17 @@ async def handle_updates(self, updates): self.dispatcher.updates_queue.put_nowait((update, users, chats)) elif isinstance(updates, (raw.types.UpdateShortMessage, raw.types.UpdateShortChatMessage)): + if self.allow_states_update: + await self.storage.update_state( + ( + 0, + updates.pts, + None, + updates.date, + None + ) + ) + diff = await self.invoke( raw.functions.updates.GetDifference( pts=updates.pts - updates.pts_count, @@ -934,6 +998,9 @@ async def handle_download(self, packet): if isinstance(e, asyncio.CancelledError): raise e + if isinstance(e, pyrogram.errors.FloodWait): + raise e + return None else: if in_memory: @@ -1165,14 +1232,10 @@ async def get_file( await cdn_session.stop() except pyrogram.StopTransmission: raise + except pyrogram.errors.FloodWait: + raise except Exception as e: log.exception(e) - finally: - if session: - try: - await session.stop() - self.media_sessions.pop(session.dc_id) - except: pass def guess_mime_type(self, filename: str) -> Optional[str]: return self.mimetypes.guess_type(filename)[0] diff --git a/pyrogram/dispatcher.py b/pyrogram/dispatcher.py index a0ded834e..9448ea330 100644 --- a/pyrogram/dispatcher.py +++ b/pyrogram/dispatcher.py @@ -22,12 +22,14 @@ from collections import OrderedDict import pyrogram +from pyrogram import errors from pyrogram import utils +from pyrogram import raw from pyrogram.handlers import ( CallbackQueryHandler, MessageHandler, EditedMessageHandler, DeletedMessagesHandler, UserStatusHandler, RawUpdateHandler, InlineQueryHandler, PollHandler, - ChosenInlineResultHandler, ChatMemberUpdatedHandler, ChatJoinRequestHandler, - StoryHandler, ConversationHandler + ChosenInlineResultHandler, ChatMemberUpdatedHandler, ChatJoinRequestHandler, StoryHandler, + ConversationHandler ) from pyrogram.raw.types import ( UpdateNewMessage, UpdateNewChannelMessage, UpdateNewScheduledMessage, @@ -170,6 +172,95 @@ async def start(self): log.info("Started %s HandlerTasks", self.client.workers) + if not self.client.skip_updates: + states = await self.client.storage.update_state() + + if not states: + log.info("No states found, skipping recovery.") + return + + message_updates_counter = 0 + other_updates_counter = 0 + + for state in states: + id, local_pts, _, local_date, _ = state + + prev_pts = 0 + + while True: + try: + diff = await self.client.invoke( + raw.functions.updates.GetChannelDifference( + channel=await self.client.resolve_peer(id), + filter=raw.types.ChannelMessagesFilterEmpty(), + pts=local_pts, + limit=10000 + ) if id < 0 else + raw.functions.updates.GetDifference( + pts=local_pts, + date=local_date, + qts=0 + ) + ) + except (errors.ChannelPrivate, errors.ChannelInvalid): + break + + if isinstance(diff, raw.types.updates.DifferenceEmpty): + break + elif isinstance(diff, raw.types.updates.DifferenceTooLong): + break + elif isinstance(diff, raw.types.updates.Difference): + local_pts = diff.state.pts + elif isinstance(diff, raw.types.updates.DifferenceSlice): + local_pts = diff.intermediate_state.pts + local_date = diff.intermediate_state.date + + if prev_pts == local_pts: + break + + prev_pts = local_pts + elif isinstance(diff, raw.types.updates.ChannelDifferenceEmpty): + break + elif isinstance(diff, raw.types.updates.ChannelDifferenceTooLong): + break + elif isinstance(diff, raw.types.updates.ChannelDifference): + local_pts = diff.pts + + users = {i.id: i for i in getattr(diff, "users", [])} + chats = {i.id: i for i in getattr(diff, "chats", [])} + + for message in diff.new_messages: + message_updates_counter += 1 + self.updates_queue.put_nowait( + ( + raw.types.UpdateNewMessage( + message=message, + pts=local_pts, + pts_count=-1 + ) if id == self.client.me.id else + raw.types.UpdateNewChannelMessage( + message=message, + pts=local_pts, + pts_count=-1 + ), + users, + chats + ) + ) + + for update in diff.other_updates: + other_updates_counter += 1 + self.updates_queue.put_nowait( + (update, users, chats) + ) + + if isinstance(diff, (raw.types.updates.Difference, raw.types.updates.ChannelDifference)): + break + + await self.client.storage.update_state(id) + + log.info("Recovered %s messages and %s updates.", message_updates_counter, other_updates_counter) + async def stop(self): if not self.client.no_updates: for i in range(self.client.workers): diff --git a/pyrogram/enums/__init__.py b/pyrogram/enums/__init__.py index b0609761e..41d065fbe 100644 --- a/pyrogram/enums/__init__.py +++ b/pyrogram/enums/__init__.py @@ -16,11 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from .business_schedule import BusinessSchedule from .chat_action import ChatAction from .chat_event_action import ChatEventAction from .chat_member_status import ChatMemberStatus from .chat_members_filter import ChatMembersFilter from .chat_type import ChatType +from .folder_color import FolderColor from .message_entity_type import MessageEntityType from .message_media_type import MessageMediaType from .message_service_type import MessageServiceType @@ -35,11 +37,13 @@ from .user_status import UserStatus __all__ = [ + 'BusinessSchedule', 'ChatAction', 'ChatEventAction', 'ChatMemberStatus', 'ChatMembersFilter', 'ChatType', + 'FolderColor', 'MessageEntityType', 'MessageMediaType', 'MessageServiceType', diff --git a/pyrogram/enums/business_schedule.py b/pyrogram/enums/business_schedule.py new file mode 100644 index 000000000..3c44e39b7 --- /dev/null +++ b/pyrogram/enums/business_schedule.py @@ -0,0 +1,33 @@ +# 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 pyrogram import raw +from .auto_name import AutoName + + +class BusinessSchedule(AutoName): + """Business away enumeration used in :obj:`~pyrogram.types.BusinessInfo`.""" + + ALWAYS = raw.types.BusinessAwayMessageScheduleAlways + "Send always" + + OUTSIDE_WORK_HOURS = raw.types.BusinessAwayMessageScheduleOutsideWorkHours + "Outside of Business Hours" + + CUSTOM = raw.types.BusinessAwayMessageScheduleCustom + "Custom Schedule" \ No newline at end of file diff --git a/pyrogram/enums/folder_color.py b/pyrogram/enums/folder_color.py new file mode 100644 index 000000000..6b2ebb3a3 --- /dev/null +++ b/pyrogram/enums/folder_color.py @@ -0,0 +1,47 @@ +# 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 .auto_name import AutoName + + +class FolderColor(AutoName): + """Folder color enumeration used in :obj:`~pyrogram.types.Folder`.""" + + NO_COLOR = None + "No color." + + RED = 0 + "Red color." + + ORANGE = 1 + "Orange color." + + VIOLET = 2 + "Violet color." + + GREEN = 3 + "Green color." + + CYAN = 4 + "Cyan color." + + BLUE = 5 + "Blue color." + + PINK = 6 + "Pink color." \ No newline at end of file diff --git a/pyrogram/enums/message_service_type.py b/pyrogram/enums/message_service_type.py index 6914e79a4..8329b9178 100644 --- a/pyrogram/enums/message_service_type.py +++ b/pyrogram/enums/message_service_type.py @@ -101,3 +101,9 @@ class MessageServiceType(AutoName): REQUESTED_CHAT = auto() "Requested chat" + + CHAT_TTL_CHANGED = auto() + "Chat TTL changed" + + BOOST_APPLY = auto() + "Boost apply" \ No newline at end of file diff --git a/pyrogram/filters.py b/pyrogram/filters.py index 9d566b2c1..c49f71815 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -230,11 +230,11 @@ async def text_filter(_, __, m: Message): # region reply_filter async def reply_filter(_, __, m: Message): - return bool(m.reply_to_message_id) + return bool(m.reply_to_message_id or m.reply_to_story_id) reply = create(reply_filter) -"""Filter messages that are replies to other messages.""" +"""Filter messages that are replies to other messages or stories.""" # endregion @@ -505,9 +505,8 @@ async def media_spoiler_filter(_, __, m: Message): # endregion # region private_filter -async def private_filter(_, __, u: Update): - m = u.message if isinstance(u, CallbackQuery) else u - return bool(m and m.chat and m.chat.type in {enums.ChatType.PRIVATE, enums.ChatType.BOT}) +async def private_filter(_, __, m: Message): + return bool(m.chat and m.chat.type in {enums.ChatType.PRIVATE, enums.ChatType.BOT}) private = create(private_filter) @@ -517,9 +516,8 @@ async def private_filter(_, __, u: Update): # endregion # region group_filter -async def group_filter(_, __, u: Update): - m = u.message if isinstance(u, CallbackQuery) else u - return bool(m and m.chat and m.chat.type in {enums.ChatType.GROUP, enums.ChatType.SUPERGROUP}) +async def group_filter(_, __, m: Message): + return bool(m.chat and m.chat.type in {enums.ChatType.GROUP, enums.ChatType.SUPERGROUP}) group = create(group_filter) @@ -529,9 +527,8 @@ async def group_filter(_, __, u: Update): # endregion # region channel_filter -async def channel_filter(_, __, u: Update): - m = u.message if isinstance(u, CallbackQuery) else u - return bool(m and m.chat and m.chat.type == enums.ChatType.CHANNEL) +async def channel_filter(_, __, m: Message): + return bool(m.chat and m.chat.type == enums.ChatType.CHANNEL) channel = create(channel_filter) diff --git a/pyrogram/handlers/story_handler.py b/pyrogram/handlers/story_handler.py index e15d8477f..b6a07d1cd 100644 --- a/pyrogram/handlers/story_handler.py +++ b/pyrogram/handlers/story_handler.py @@ -46,4 +46,4 @@ class StoryHandler(Handler): """ def __init__(self, callback: Callable, filters=None): - super().__init__(callback, filters) \ No newline at end of file + super().__init__(callback, filters) diff --git a/pyrogram/methods/auth/send_code.py b/pyrogram/methods/auth/send_code.py index 92ffc9999..124f0a008 100644 --- a/pyrogram/methods/auth/send_code.py +++ b/pyrogram/methods/auth/send_code.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import logging +from typing import List import pyrogram from pyrogram import raw @@ -30,7 +31,15 @@ class SendCode: async def send_code( self: "pyrogram.Client", - phone_number: str + phone_number: str, + current_number: bool = None, + allow_flashcall: bool = None, + allow_app_hash: bool = None, + allow_missed_call: bool = None, + allow_firebase: bool = None, + logout_tokens: List[bytes] = None, + token: str = None, + app_sandbox: bool = None, ) -> "types.SentCode": """Send the confirmation code to the given phone number. @@ -40,6 +49,38 @@ async def send_code( phone_number (``str``): Phone number in international format (includes the country prefix). + current_number (``bool``, *optional*): + Whether the phone number is the current one. + Defaults to None. + + allow_flashcall (``bool``, *optional*): + Whether to allow a flash call. + Defaults to None. + + allow_app_hash (``bool``, *optional*): + Whether to allow an app hash. + Defaults to None. + + allow_missed_call (``bool``, *optional*): + Whether to allow a missed call. + Defaults to None. + + allow_firebase (``bool``, *optional*): + Whether to allow firebase. + Defaults to None. + + logout_tokens (List of ``bytes``, *optional*): + List of logout tokens. + Defaults to None. + + token (``str``, *optional*): + Token. + Defaults to None. + + app_sandbox (``bool``, *optional*): + Whether to use the app sandbox. + Defaults to None. + Returns: :obj:`~pyrogram.types.SentCode`: On success, an object containing information on the sent confirmation code is returned. @@ -56,7 +97,16 @@ async def send_code( phone_number=phone_number, api_id=self.api_id, api_hash=self.api_hash, - settings=raw.types.CodeSettings() + settings=raw.types.CodeSettings( + allow_flashcall=allow_flashcall, + current_number=current_number, + allow_app_hash=allow_app_hash, + allow_missed_call=allow_missed_call, + allow_firebase=allow_firebase, + logout_tokens=logout_tokens, + token=token, + app_sandbox=app_sandbox + ) ) ) except (PhoneMigrate, NetworkMigrate) as e: diff --git a/pyrogram/methods/bots/send_inline_bot_result.py b/pyrogram/methods/bots/send_inline_bot_result.py index 4b7e0dbf6..18f0ef1b7 100644 --- a/pyrogram/methods/bots/send_inline_bot_result.py +++ b/pyrogram/methods/bots/send_inline_bot_result.py @@ -35,8 +35,9 @@ async def send_inline_bot_result( reply_to_chat_id: Union[int, str] = None, reply_to_story_id: int = None, quote_text: str = None, + parse_mode: Optional["enums.ParseMode"] = None, quote_entities: List["types.MessageEntity"] = None, - parse_mode: Optional["enums.ParseMode"] = None + quote_offset: int = None ) -> "raw.base.Updates": """Send an inline bot result. Bot results can be retrieved using :meth:`~pyrogram.Client.get_inline_bot_results` @@ -61,21 +62,30 @@ async def send_inline_bot_result( message_thread_id (``int``, *optional*): Unique identifier of a message thread to which the message belongs. - for supergroups only + For supergroups only. reply_to_message_id (``bool``, *optional*): If the message is a reply, ID of the original message. - quote_text (``str``): + reply_to_chat_id (``int``, *optional*): + If the message is a reply, ID of the original chat. + + reply_to_story_id (``int``, *optional*): + If the message is a reply to a story, ID of the story. + + quote_text (``str``, *optional*): Text of the quote to be sent. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. - quote_entities (List of :obj:`~pyrogram.types.MessageEntity`): + quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*): List of special entities that appear in quote text, which can be specified instead of *parse_mode*. + quote_offset (``int``, *optional*): + Offset for quote in original message. + Returns: :obj:`~pyrogram.raw.base.Updates`: Currently, on success, a raw result is returned. @@ -100,7 +110,8 @@ async def send_inline_bot_result( message_thread_id=message_thread_id, reply_to_story_id=reply_to_story_id, quote_text=quote_text, - quote_entities=quote_entities + quote_entities=quote_entities, + quote_offset=quote_offset, ) ) ) diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py index 570d8bdf6..3fc711f5e 100644 --- a/pyrogram/methods/chats/__init__.py +++ b/pyrogram/methods/chats/__init__.py @@ -64,7 +64,9 @@ from .set_chat_username import SetChatUsername from .set_send_as_chat import SetSendAsChat from .set_slow_mode import SetSlowMode +from .toggle_folder_tags import ToggleFolderTags from .toggle_forum_topics import ToggleForumTopics +from .toggle_join_to_send import ToggleJoinToSend from .unarchive_chats import UnarchiveChats from .unban_chat_member import UnbanChatMember from .unpin_all_chat_messages import UnpinAllChatMessages @@ -121,7 +123,9 @@ class Chats( GetNearbyChats, SetAdministratorTitle, SetSlowMode, + ToggleFolderTags, ToggleForumTopics, + ToggleJoinToSend, DeleteUserHistory, UnpinAllChatMessages, MarkChatUnread, @@ -132,4 +136,4 @@ class Chats( SetSendAsChat, SetChatProtectedContent ): - pass \ No newline at end of file + pass diff --git a/pyrogram/methods/chats/close_forum_topic.py b/pyrogram/methods/chats/close_forum_topic.py index 3fbb41ba1..8dde444f2 100644 --- a/pyrogram/methods/chats/close_forum_topic.py +++ b/pyrogram/methods/chats/close_forum_topic.py @@ -55,4 +55,4 @@ async def close_forum_topic( ) ) - return True \ No newline at end of file + return True diff --git a/pyrogram/methods/chats/create_forum_topic.py b/pyrogram/methods/chats/create_forum_topic.py index b5d6b140c..04f68c5d8 100644 --- a/pyrogram/methods/chats/create_forum_topic.py +++ b/pyrogram/methods/chats/create_forum_topic.py @@ -65,4 +65,4 @@ async def create_forum_topic( ) ) - return types.ForumTopicCreated._parse(r.updates[1].message) \ No newline at end of file + return types.ForumTopicCreated._parse(r.updates[1].message) diff --git a/pyrogram/methods/chats/delete_folder.py b/pyrogram/methods/chats/delete_folder.py index fada0ffb5..e90663372 100644 --- a/pyrogram/methods/chats/delete_folder.py +++ b/pyrogram/methods/chats/delete_folder.py @@ -48,4 +48,4 @@ async def delete_folder( ) ) - return r \ No newline at end of file + return r diff --git a/pyrogram/methods/chats/delete_forum_topic.py b/pyrogram/methods/chats/delete_forum_topic.py index e701e5d61..5c1b39abe 100644 --- a/pyrogram/methods/chats/delete_forum_topic.py +++ b/pyrogram/methods/chats/delete_forum_topic.py @@ -54,4 +54,4 @@ async def delete_forum_topic( ) ) - return True \ No newline at end of file + return True diff --git a/pyrogram/methods/chats/edit_forum_topic.py b/pyrogram/methods/chats/edit_forum_topic.py index e2b02ca96..659bce62d 100644 --- a/pyrogram/methods/chats/edit_forum_topic.py +++ b/pyrogram/methods/chats/edit_forum_topic.py @@ -74,4 +74,4 @@ async def edit_forum_topic( ) ) - return True \ No newline at end of file + return True diff --git a/pyrogram/methods/chats/export_folder_link.py b/pyrogram/methods/chats/export_folder_link.py index 7072b81f3..d2f47d492 100644 --- a/pyrogram/methods/chats/export_folder_link.py +++ b/pyrogram/methods/chats/export_folder_link.py @@ -66,4 +66,4 @@ async def export_folder_link( ) ) - return r.invite.url \ No newline at end of file + return r.invite.url diff --git a/pyrogram/methods/chats/get_chat_event_log.py b/pyrogram/methods/chats/get_chat_event_log.py index 06fa01c84..a71c75660 100644 --- a/pyrogram/methods/chats/get_chat_event_log.py +++ b/pyrogram/methods/chats/get_chat_event_log.py @@ -32,7 +32,7 @@ async def get_chat_event_log( limit: int = 0, filters: "types.ChatEventFilter" = None, user_ids: List[Union[int, str]] = None - ) -> Optional[AsyncGenerator["types.ChatEvent", None]]: + ) -> AsyncGenerator["types.ChatEvent", None]: """Get the actions taken by chat members and administrators in the last 48h. Only available for supergroups and channels. Requires administrator rights. diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py index 49fb0a094..e51fa8c48 100644 --- a/pyrogram/methods/chats/get_chat_members.py +++ b/pyrogram/methods/chats/get_chat_members.py @@ -64,7 +64,7 @@ async def get_chat_members( query: str = "", limit: int = 0, filter: "enums.ChatMembersFilter" = enums.ChatMembersFilter.SEARCH - ) -> Optional[AsyncGenerator["types.ChatMember", None]]: + ) -> AsyncGenerator["types.ChatMember", None]: """Get the members list of a chat. A chat can be either a basic group, a supergroup or a channel. diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py index 3509ccbcc..b40443e9f 100644 --- a/pyrogram/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import AsyncGenerator, Optional +from typing import AsyncGenerator import pyrogram from pyrogram import types, raw, utils @@ -26,7 +26,7 @@ class GetDialogs: async def get_dialogs( self: "pyrogram.Client", limit: int = 0 - ) -> Optional[AsyncGenerator["types.Dialog", None]]: + ) -> AsyncGenerator["types.Dialog", None]: """Get a user's dialogs sequentially. .. include:: /_includes/usable-by/users.rst @@ -76,7 +76,11 @@ async def get_dialogs( continue chat_id = utils.get_peer_id(message.peer_id) - messages[chat_id] = await types.Message._parse(self, message, users, chats) + + try: + messages[chat_id] = await types.Message._parse(self, message, users, chats) + except KeyError: + pass dialogs = [] @@ -84,7 +88,10 @@ async def get_dialogs( if not isinstance(dialog, raw.types.Dialog): continue - dialogs.append(types.Dialog._parse(self, dialog, messages, users, chats)) + try: + dialogs.append(types.Dialog._parse(self, dialog, messages, users, chats)) + except KeyError: + pass if not dialogs: return diff --git a/pyrogram/methods/chats/get_folders.py b/pyrogram/methods/chats/get_folders.py index 66b0ad721..5583fd050 100644 --- a/pyrogram/methods/chats/get_folders.py +++ b/pyrogram/methods/chats/get_folders.py @@ -19,13 +19,13 @@ from typing import Union, List, Iterable import pyrogram -from pyrogram import types, raw +from pyrogram import types, raw, utils class GetFolders: async def get_folders( self: "pyrogram.Client", - folder_ids: Union[int, Iterable[int]] = None, + folder_ids: Union[int, Iterable[int]] = None ) -> Union["types.Folder", List["types.Folder"]]: """Get one or more folders by using folder identifiers. @@ -54,37 +54,44 @@ async def get_folders( await app.get_folders() """ is_iterable = hasattr(folder_ids, "__iter__") - ids = list(folder_ids) if is_iterable else [folder_ids] + ids = set(folder_ids) if is_iterable else {folder_ids} - raw_folders = await self.invoke(raw.functions.messages.GetDialogFilters()) - dialog_peers = [] + dialog_filters = await self.invoke(raw.functions.messages.GetDialogFilters()) - for folder in raw_folders: - if isinstance(folder, (raw.types.DialogFilter, raw.types.DialogFilterChatlist)): - peers = folder.pinned_peers + folder.include_peers + getattr(folder, "exclude_peers", []) - input_peers = [raw.types.InputDialogPeer(peer=peer) for peer in peers] + [raw.types.InputDialogPeerFolder(folder_id=folder.id)] - - dialog_peers.extend(input_peers) - - r = await self.invoke(raw.functions.messages.GetPeerDialogs(peers=dialog_peers)) - - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - folders = types.List([]) + raw_folders = [ + folder for folder in dialog_filters.filters + if not isinstance(folder, raw.types.DialogFilterDefault) and (is_iterable and folder.id in ids or not is_iterable) + ] + raw_peers = {} for folder in raw_folders: - if isinstance(folder, (raw.types.DialogFilter, raw.types.DialogFilterChatlist)): - folders.append(types.Folder._parse(self, folder, users, chats)) + for peer in folder.pinned_peers + folder.include_peers + getattr(folder, "exclude_peers", []): + raw_peers[utils.get_peer_id(peer)] = peer + + users = {} + chats = {} + for i in range(0, len(raw_peers), 100): + chunk = list(raw_peers.values())[i:i + 100] + r = await self.invoke( + raw.functions.messages.GetPeerDialogs( + peers=[raw.types.InputDialogPeer(peer=peer) for peer in chunk] + ) + ) + users.update({i.id: i for i in r.users}) + chats.update({i.id: i for i in r.chats}) + + folders = types.List(types.Folder._parse(self, folder, users, chats) for folder in raw_folders) if not folders: return None if folder_ids: - folders = types.List([folder for folder in folders if folder.id in ids]) if is_iterable: - return folders or None + return folders else: - return folders[0] if folders else None + for folder in folders: + if folder.id == folder_ids: + return folder + return None - return folders \ No newline at end of file + return folders diff --git a/pyrogram/methods/chats/get_forum_topics.py b/pyrogram/methods/chats/get_forum_topics.py index 7f156ed76..8e24e48eb 100644 --- a/pyrogram/methods/chats/get_forum_topics.py +++ b/pyrogram/methods/chats/get_forum_topics.py @@ -16,11 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, Optional, AsyncGenerator +from typing import Union, AsyncGenerator import pyrogram -from pyrogram import raw -from pyrogram import types +from pyrogram import types, raw, utils class GetForumTopics: @@ -28,7 +27,7 @@ async def get_forum_topics( self: "pyrogram.Client", chat_id: Union[int, str], limit: int = 0 - ) -> Optional[AsyncGenerator["types.ForumTopic", None]]: + ) -> AsyncGenerator["types.ForumTopic", None]: """Get one or more topic from a chat. .. include:: /_includes/usable-by/users.rst @@ -39,26 +38,66 @@ async def get_forum_topics( limit (``int``, *optional*): Limits the number of topics to be retrieved. + By default, no limit is applied and all topics are returned. Returns: - ``Generator``: On success, a generator yielding :obj:`~pyrogram.types.ForumTopic` objects is returned. + ``Generator``: A generator yielding :obj:`~pyrogram.types.ForumTopic` objects. Example: .. code-block:: python - # get all forum topics + # Iterate through all topics async for topic in app.get_forum_topics(chat_id): print(topic) """ - r = await self.invoke( - raw.functions.channels.GetForumTopics( - channel=await self.resolve_peer(chat_id), - offset_date=0, - offset_id=0, - offset_topic=0, - limit=limit + current = 0 + total = limit or (1 << 31) - 1 + limit = min(100, total) + + offset_date = 0 + offset_id = 0 + offset_topic = 0 + + while True: + r = await self.invoke( + raw.functions.channels.GetForumTopics( + channel=await self.resolve_peer(chat_id), + offset_date=offset_date, + offset_id=offset_id, + offset_topic=offset_topic, + limit=limit + ) ) - ) - for topic in r.topics: - yield types.ForumTopic._parse(topic) \ No newline at end of file + users = {i.id: i for i in r.users} + chats = {i.id: i for i in r.chats} + + messages = {} + + for message in r.messages: + if isinstance(message, raw.types.MessageEmpty): + continue + + messages[message.id] = await types.Message._parse(self, message, users, chats) + + topics = [] + + for topic in r.topics: + topics.append(types.ForumTopic._parse(self, topic, messages, users, chats)) + + if not topics: + return + + last = topics[-1] + + offset_id = last.top_message.id + offset_date = utils.datetime_to_timestamp(last.top_message.date) + offset_topic = last.id + + for topic in topics: + yield topic + + current += 1 + + if current >= total: + return diff --git a/pyrogram/methods/chats/get_forum_topics_by_id.py b/pyrogram/methods/chats/get_forum_topics_by_id.py index 69b390498..919b1b8ad 100644 --- a/pyrogram/methods/chats/get_forum_topics_by_id.py +++ b/pyrogram/methods/chats/get_forum_topics_by_id.py @@ -22,7 +22,6 @@ import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram import utils log = logging.getLogger(__name__) @@ -77,6 +76,12 @@ async def get_forum_topics_by_id( messages = {m.id: m for m in getattr(r, "messages", [])} for current in getattr(r, "topics", []): - topics.append(types.ForumTopic._parse(self, current, messages, users, chats)) - - return topics if is_iterable else topics[0] \ No newline at end of file + topics.append(types.ForumTopic._parse( + self, + forum_topic=current, + messages=messages, + users=users, + chats=chats + )) + + return topics if is_iterable else topics[0] if topics else None diff --git a/pyrogram/methods/chats/get_send_as_chats.py b/pyrogram/methods/chats/get_send_as_chats.py index c9a358c39..7f8c8b30a 100644 --- a/pyrogram/methods/chats/get_send_as_chats.py +++ b/pyrogram/methods/chats/get_send_as_chats.py @@ -57,9 +57,9 @@ async def get_send_as_chats( send_as_chats = types.List() for p in r.peers: - if isinstance(p, raw.types.PeerUser): - send_as_chats.append(types.Chat._parse_chat(self, users[p.user_id])) + if isinstance(p.peer, raw.types.PeerUser): + send_as_chats.append(types.Chat._parse_chat(self, users[p.peer.user_id])) else: - send_as_chats.append(types.Chat._parse_chat(self, chats[p.channel_id])) + send_as_chats.append(types.Chat._parse_chat(self, chats[p.peer.channel_id])) return send_as_chats diff --git a/pyrogram/methods/chats/get_similar_channels.py b/pyrogram/methods/chats/get_similar_channels.py index 356361c77..75f74865a 100644 --- a/pyrogram/methods/chats/get_similar_channels.py +++ b/pyrogram/methods/chats/get_similar_channels.py @@ -56,4 +56,4 @@ async def get_similar_channels( return types.List([types.Chat._parse_channel_chat(self, chat) for chat in r.chats]) or None else: - raise ValueError(f'The chat_id "{chat_id}" belongs to a user or chat') \ No newline at end of file + raise ValueError(f'The chat_id "{chat_id}" belongs to a user or chat') diff --git a/pyrogram/methods/chats/join_folder.py b/pyrogram/methods/chats/join_folder.py index e004dcfea..64dde3870 100644 --- a/pyrogram/methods/chats/join_folder.py +++ b/pyrogram/methods/chats/join_folder.py @@ -72,4 +72,4 @@ async def join_folder( ) ) - return True \ No newline at end of file + return True diff --git a/pyrogram/methods/chats/leave_folder.py b/pyrogram/methods/chats/leave_folder.py index 70d93824b..1ef34cded 100644 --- a/pyrogram/methods/chats/leave_folder.py +++ b/pyrogram/methods/chats/leave_folder.py @@ -76,4 +76,4 @@ async def leave_folder( ) ) - return True \ No newline at end of file + return True diff --git a/pyrogram/methods/chats/promote_chat_member.py b/pyrogram/methods/chats/promote_chat_member.py index 39a398f96..653a31436 100644 --- a/pyrogram/methods/chats/promote_chat_member.py +++ b/pyrogram/methods/chats/promote_chat_member.py @@ -85,8 +85,11 @@ async def promote_chat_member( anonymous=privileges.is_anonymous, change_info=privileges.can_change_info, post_messages=privileges.can_post_messages, + post_stories=privileges.can_post_stories, edit_messages=privileges.can_edit_messages, + edit_stories=privileges.can_edit_stories, delete_messages=privileges.can_delete_messages, + delete_stories=privileges.can_delete_stories, ban_users=privileges.can_restrict_members, invite_users=privileges.can_invite_users, pin_messages=privileges.can_pin_messages, diff --git a/pyrogram/methods/chats/set_chat_ttl.py b/pyrogram/methods/chats/set_chat_ttl.py index ef97d3976..1abe07ced 100644 --- a/pyrogram/methods/chats/set_chat_ttl.py +++ b/pyrogram/methods/chats/set_chat_ttl.py @@ -54,11 +54,19 @@ async def set_chat_ttl( # Disable TTL for this chat app.set_chat_ttl(chat_id, 0) """ - await self.invoke( + r = await self.invoke( raw.functions.messages.SetHistoryTTL( peer=await self.resolve_peer(chat_id), period=ttl_seconds, ) ) - return True \ No newline at end of file + for i in r.updates: + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage)): + return await types.Message._parse( + self, + i.message, + {i.id: i for i in getattr(r, "users", [])}, + {i.id: i for i in getattr(r, "chats", [])}, + ) diff --git a/pyrogram/methods/chats/toggle_folder_tags.py b/pyrogram/methods/chats/toggle_folder_tags.py new file mode 100644 index 000000000..21dd2f337 --- /dev/null +++ b/pyrogram/methods/chats/toggle_folder_tags.py @@ -0,0 +1,50 @@ +# 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 ToggleFolderTags: + async def toggle_folder_tags( + self: "pyrogram.Client", + enabled: bool + ) -> bool: + """Toggle folder tags. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + enabled (``bool``): + The new status. Pass True to enable folder tags. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + await app.toggle_folder_tags(True) + """ + r = await self.invoke( + raw.functions.messages.ToggleDialogFilterTags( + enabled=enabled + ) + ) + + return r diff --git a/pyrogram/methods/chats/toggle_forum_topics.py b/pyrogram/methods/chats/toggle_forum_topics.py index 2576e0f18..ad0b0a5d2 100644 --- a/pyrogram/methods/chats/toggle_forum_topics.py +++ b/pyrogram/methods/chats/toggle_forum_topics.py @@ -62,4 +62,4 @@ async def toggle_forum_topics( return bool(r) except errors.RPCError: - return False \ No newline at end of file + return False diff --git a/pyrogram/methods/chats/toggle_join_to_send.py b/pyrogram/methods/chats/toggle_join_to_send.py new file mode 100644 index 000000000..41edb3481 --- /dev/null +++ b/pyrogram/methods/chats/toggle_join_to_send.py @@ -0,0 +1,65 @@ +# 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 + +import pyrogram +from pyrogram import raw +from pyrogram import errors + + +class ToggleJoinToSend: + async def toggle_join_to_send( + self: "pyrogram.Client", + chat_id: Union[int, str], + enabled: bool = False + ) -> bool: + """Enable or disable guest users' ability to send messages in a supergroup. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + enabled (``bool``): + The new status. Pass True to enable guest users to send message. + + Returns: + ``bool``: True on success. False otherwise. + + Example: + .. code-block:: python + + # Change status of guests sending messages to disabled + await app.toggle_join_to_send() + + # Change status of guests sending messages to enabled + await app.toggle_join_to_send(enabled=True) + """ + try: + r = await self.invoke( + raw.functions.channels.ToggleJoinToSend( + channel=await self.resolve_peer(chat_id), + enabled=enabled + ) + ) + + return bool(r) + except errors.RPCError: + return False diff --git a/pyrogram/methods/chats/update_chat_notifications.py b/pyrogram/methods/chats/update_chat_notifications.py index 1c3657892..f9e0eafdf 100644 --- a/pyrogram/methods/chats/update_chat_notifications.py +++ b/pyrogram/methods/chats/update_chat_notifications.py @@ -90,4 +90,4 @@ async def update_chat_notifications( ) ) - return r \ No newline at end of file + return r diff --git a/pyrogram/methods/chats/update_color.py b/pyrogram/methods/chats/update_color.py index 2543c3ff0..f8bae54ec 100644 --- a/pyrogram/methods/chats/update_color.py +++ b/pyrogram/methods/chats/update_color.py @@ -73,4 +73,4 @@ async def update_color( ) ) - return bool(r) \ No newline at end of file + return bool(r) diff --git a/pyrogram/methods/chats/update_folder.py b/pyrogram/methods/chats/update_folder.py index b38ace87a..ab0243d47 100644 --- a/pyrogram/methods/chats/update_folder.py +++ b/pyrogram/methods/chats/update_folder.py @@ -20,6 +20,7 @@ import pyrogram from pyrogram import raw +from pyrogram import enums class UpdateFolder: @@ -38,6 +39,7 @@ async def update_folder( exclude_muted: bool = None, exclude_read: bool = None, exclude_archived: bool = None, + color: "enums.FolderColor" = None, emoji: str = None ) -> bool: """Create or update a user's folder. @@ -91,6 +93,10 @@ async def update_folder( Folder emoji. Pass None to leave the folder icon as default. + color (:obj:`~pyrogram.enums.FolderColor`, *optional*): + Color type. + Pass :obj:`~pyrogram.enums.FolderColor` to set folder color. + Returns: ``bool``: True, on success. @@ -133,9 +139,10 @@ async def update_folder( exclude_muted=exclude_muted, exclude_read=exclude_read, exclude_archived=exclude_archived, - emoticon=emoji + emoticon=emoji, + color=color.value if color else None ) ) ) - return r \ No newline at end of file + return r diff --git a/pyrogram/methods/decorators/on_callback_query.py b/pyrogram/methods/decorators/on_callback_query.py index 07e15a3e7..2566a26a4 100644 --- a/pyrogram/methods/decorators/on_callback_query.py +++ b/pyrogram/methods/decorators/on_callback_query.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Callable +from typing import Callable, Optional, Union import pyrogram from pyrogram.filters import Filter @@ -24,9 +24,9 @@ class OnCallbackQuery: def on_callback_query( - self=None, - filters=None, - group: int = 0 + self: Union["OnCallbackQuery", Filter, None] = None, + filters: Optional[Filter] = None, + group: int = 0, ) -> Callable: """Decorator for handling callback queries. diff --git a/pyrogram/methods/decorators/on_chat_join_request.py b/pyrogram/methods/decorators/on_chat_join_request.py index 57fb709cb..5bccae039 100644 --- a/pyrogram/methods/decorators/on_chat_join_request.py +++ b/pyrogram/methods/decorators/on_chat_join_request.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Callable +from typing import Callable, Optional, Union import pyrogram from pyrogram.filters import Filter @@ -24,9 +24,9 @@ class OnChatJoinRequest: def on_chat_join_request( - self=None, - filters=None, - group: int = 0 + self: Union["OnChatJoinRequest", Filter, None] = None, + filters: Optional[Filter] = None, + group: int = 0, ) -> Callable: """Decorator for handling chat join requests. diff --git a/pyrogram/methods/decorators/on_chat_member_updated.py b/pyrogram/methods/decorators/on_chat_member_updated.py index c2f0e888a..01fad0370 100644 --- a/pyrogram/methods/decorators/on_chat_member_updated.py +++ b/pyrogram/methods/decorators/on_chat_member_updated.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Callable +from typing import Callable, Optional, Union import pyrogram from pyrogram.filters import Filter @@ -24,9 +24,9 @@ class OnChatMemberUpdated: def on_chat_member_updated( - self=None, - filters=None, - group: int = 0 + self: Union["OnChatMemberUpdated", Filter, None] = None, + filters: Optional[Filter] = None, + group: int = 0, ) -> Callable: """Decorator for handling event changes on chat members. diff --git a/pyrogram/methods/decorators/on_chosen_inline_result.py b/pyrogram/methods/decorators/on_chosen_inline_result.py index 090f6c042..9398b69ab 100644 --- a/pyrogram/methods/decorators/on_chosen_inline_result.py +++ b/pyrogram/methods/decorators/on_chosen_inline_result.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Callable +from typing import Callable, Optional, Union import pyrogram from pyrogram.filters import Filter @@ -24,9 +24,9 @@ class OnChosenInlineResult: def on_chosen_inline_result( - self=None, - filters=None, - group: int = 0 + self: Union["OnChosenInlineResult", Filter, None] = None, + filters: Optional[Filter] = None, + group: int = 0, ) -> Callable: """Decorator for handling chosen inline results. diff --git a/pyrogram/methods/decorators/on_deleted_messages.py b/pyrogram/methods/decorators/on_deleted_messages.py index 9565c1132..eb371cb09 100644 --- a/pyrogram/methods/decorators/on_deleted_messages.py +++ b/pyrogram/methods/decorators/on_deleted_messages.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Callable +from typing import Callable, Optional, Union import pyrogram from pyrogram.filters import Filter @@ -24,9 +24,9 @@ class OnDeletedMessages: def on_deleted_messages( - self=None, - filters=None, - group: int = 0 + self: Union["OnDeletedMessages", Filter, None] = None, + filters: Optional[Filter] = None, + group: int = 0, ) -> Callable: """Decorator for handling deleted messages. diff --git a/pyrogram/methods/decorators/on_disconnect.py b/pyrogram/methods/decorators/on_disconnect.py index 26aa62f8f..d8aa9e834 100644 --- a/pyrogram/methods/decorators/on_disconnect.py +++ b/pyrogram/methods/decorators/on_disconnect.py @@ -16,13 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Callable +from typing import Callable, Optional import pyrogram class OnDisconnect: - def on_disconnect(self=None) -> Callable: + def on_disconnect(self: Optional["OnDisconnect"] = None) -> Callable: """Decorator for handling disconnections. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the diff --git a/pyrogram/methods/decorators/on_edited_message.py b/pyrogram/methods/decorators/on_edited_message.py index a8c86bb6d..10f894a3b 100644 --- a/pyrogram/methods/decorators/on_edited_message.py +++ b/pyrogram/methods/decorators/on_edited_message.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Callable +from typing import Callable, Optional, Union import pyrogram from pyrogram.filters import Filter @@ -24,9 +24,9 @@ class OnEditedMessage: def on_edited_message( - self=None, - filters=None, - group: int = 0 + self: Union["OnEditedMessage", Filter, None] = None, + filters: Optional[Filter] = None, + group: int = 0, ) -> Callable: """Decorator for handling edited messages. diff --git a/pyrogram/methods/decorators/on_inline_query.py b/pyrogram/methods/decorators/on_inline_query.py index 6b53a464d..fc47ca33f 100644 --- a/pyrogram/methods/decorators/on_inline_query.py +++ b/pyrogram/methods/decorators/on_inline_query.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Callable +from typing import Callable, Optional, Union import pyrogram from pyrogram.filters import Filter @@ -24,9 +24,9 @@ class OnInlineQuery: def on_inline_query( - self=None, - filters=None, - group: int = 0 + self: Union["OnInlineQuery", Filter, None] = None, + filters: Optional[Filter] = None, + group: int = 0, ) -> Callable: """Decorator for handling inline queries. diff --git a/pyrogram/methods/decorators/on_message.py b/pyrogram/methods/decorators/on_message.py index 220c12bbc..631b61440 100644 --- a/pyrogram/methods/decorators/on_message.py +++ b/pyrogram/methods/decorators/on_message.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Callable +from typing import Callable, Optional, Union import pyrogram from pyrogram.filters import Filter @@ -24,9 +24,9 @@ class OnMessage: def on_message( - self=None, - filters=None, - group: int = 0 + self: Union["OnMessage", Filter, None] = None, + filters: Optional[Filter] = None, + group: int = 0, ) -> Callable: """Decorator for handling new messages. diff --git a/pyrogram/methods/decorators/on_poll.py b/pyrogram/methods/decorators/on_poll.py index 6990c456b..56d647b58 100644 --- a/pyrogram/methods/decorators/on_poll.py +++ b/pyrogram/methods/decorators/on_poll.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Callable +from typing import Callable, Optional, Union import pyrogram from pyrogram.filters import Filter @@ -24,9 +24,9 @@ class OnPoll: def on_poll( - self=None, - filters=None, - group: int = 0 + self: Union["OnPoll", Filter, None] = None, + filters: Optional[Filter] = None, + group: int = 0, ) -> Callable: """Decorator for handling poll updates. diff --git a/pyrogram/methods/decorators/on_raw_update.py b/pyrogram/methods/decorators/on_raw_update.py index 644bc8a5b..88311c04d 100644 --- a/pyrogram/methods/decorators/on_raw_update.py +++ b/pyrogram/methods/decorators/on_raw_update.py @@ -16,15 +16,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Callable +from typing import Callable, Optional import pyrogram class OnRawUpdate: def on_raw_update( - self=None, - group: int = 0 + self: Optional["OnRawUpdate"] = None, + group: int = 0, ) -> Callable: """Decorator for handling raw updates. diff --git a/pyrogram/methods/decorators/on_story.py b/pyrogram/methods/decorators/on_story.py index 8c3a66f4e..75dae6689 100644 --- a/pyrogram/methods/decorators/on_story.py +++ b/pyrogram/methods/decorators/on_story.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Callable +from typing import Callable, Optional, Union import pyrogram from pyrogram.filters import Filter @@ -24,9 +24,9 @@ class OnStory: def on_story( - self=None, - filters=None, - group: int = 0 + self: Union["OnStory", Filter, None] = None, + filters: Optional[Filter] = None, + group: int = 0, ) -> Callable: """Decorator for handling new stories. @@ -58,4 +58,4 @@ def decorator(func: Callable) -> Callable: return func - return decorator \ No newline at end of file + return decorator diff --git a/pyrogram/methods/decorators/on_user_status.py b/pyrogram/methods/decorators/on_user_status.py index a4328c37f..5a97a51b5 100644 --- a/pyrogram/methods/decorators/on_user_status.py +++ b/pyrogram/methods/decorators/on_user_status.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Callable +from typing import Callable, Optional, Union import pyrogram from pyrogram.filters import Filter @@ -24,9 +24,9 @@ class OnUserStatus: def on_user_status( - self=None, - filters=None, - group: int = 0 + self: Union["OnUserStatus", Filter, None] = None, + filters: Optional[Filter] = None, + group: int = 0, ) -> Callable: """Decorator for handling user status updates. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the diff --git a/pyrogram/methods/invite_links/get_chat_invite_link.py b/pyrogram/methods/invite_links/get_chat_invite_link.py index 8ad575f34..2fa03deda 100644 --- a/pyrogram/methods/invite_links/get_chat_invite_link.py +++ b/pyrogram/methods/invite_links/get_chat_invite_link.py @@ -31,7 +31,7 @@ async def get_chat_invite_link( ) -> "types.ChatInviteLink": """Get detailed information about a chat invite link. - .. include:: /_includes/usable-by/users-bots.rst + .. include:: /_includes/usable-by/users.rst Parameters: chat_id (``int`` | ``str``): diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py index ba699c0ed..5c799f3bf 100644 --- a/pyrogram/methods/messages/__init__.py +++ b/pyrogram/methods/messages/__init__.py @@ -35,8 +35,12 @@ from .get_discussion_message import GetDiscussionMessage from .get_discussion_replies import GetDiscussionReplies from .get_discussion_replies_count import GetDiscussionRepliesCount +from .get_history import GetHistory from .get_media_group import GetMediaGroup +from .get_message_by_link import GetMessageByLink from .get_messages import GetMessages +from .get_scheduled_messages import GetScheduledMessages +from .get_stickers import GetStickers from .read_chat_history import ReadChatHistory from .read_mentions import ReadMentions from .read_reactions import ReadReactions @@ -77,8 +81,12 @@ class Messages( EditMessageMedia, EditMessageText, ForwardMessages, + GetHistory, GetMediaGroup, + GetMessageByLink, GetMessages, + GetScheduledMessages, + GetStickers, SendAudio, SendChatAction, SendContact, diff --git a/pyrogram/methods/messages/copy_media_group.py b/pyrogram/methods/messages/copy_media_group.py index c4c3db010..5e0f22e78 100644 --- a/pyrogram/methods/messages/copy_media_group.py +++ b/pyrogram/methods/messages/copy_media_group.py @@ -40,7 +40,7 @@ async def copy_media_group( quote_entities: List["types.MessageEntity"] = None, quote_offset: int = None, schedule_date: datetime = None, - invert_media: bool = None, + show_above_text: bool = None, ) -> List["types.Message"]: """Copy a media group by providing one of the message ids. @@ -102,7 +102,7 @@ async def copy_media_group( schedule_date (:py:obj:`~datetime.datetime`, *optional*): Date when the message will be automatically sent. - invert_media (``bool``, *optional*): + show_above_text (``bool``, *optional*): If True, link preview will be shown above the message text. Otherwise, the link preview will be shown below the message text. @@ -165,7 +165,7 @@ async def copy_media_group( quote_offset=quote_offset, ), schedule_date=utils.datetime_to_timestamp(schedule_date), - invert_media=invert_media + invert_media=show_above_text ), sleep_threshold=60 ) diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py index 103810264..ae15ff6ce 100644 --- a/pyrogram/methods/messages/download_media.py +++ b/pyrogram/methods/messages/download_media.py @@ -122,19 +122,26 @@ async def progress(current, total): available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note", "new_chat_photo") - if isinstance(message, (types.Message, types.Story)): - story = getattr(message, "story", None) + media = None + if isinstance(message, types.Message) and message.media: for kind in available_media: - media = getattr(story or message, kind, None) + story = message.story or message.reply_to_story + if story: + media = getattr(story, kind, None) + else: + media = getattr(message, kind, None) if media is not None: break - else: - raise ValueError("This message doesn't contain any downloadable media") - else: + elif isinstance(message, types.Story): + media = getattr(message, message.media.value, None) + elif isinstance(message, str): media = message + if not media: + raise ValueError("This message doesn't contain any downloadable media") + if isinstance(media, str): file_id_str = media else: diff --git a/pyrogram/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py index ca7552264..8e12093c3 100644 --- a/pyrogram/methods/messages/edit_message_media.py +++ b/pyrogram/methods/messages/edit_message_media.py @@ -35,7 +35,7 @@ async def edit_message_media( chat_id: Union[int, str], message_id: int, media: "types.InputMedia", - invert_media: bool = None, + show_above_text: bool = None, schedule_date: datetime = None, reply_markup: "types.InlineKeyboardMarkup" = None, file_name: str = None @@ -59,7 +59,7 @@ async def edit_message_media( media (:obj:`~pyrogram.types.InputMedia`): One of the InputMedia objects describing an animation, audio, document, photo or video. - invert_media (``bool``, *optional*): + show_above_text (``bool``, *optional*): If True, link preview will be shown above the message text. Otherwise, the link preview will be shown below the message text. @@ -281,7 +281,7 @@ async def edit_message_media( raw.functions.messages.EditMessage( peer=await self.resolve_peer(chat_id), id=message_id, - invert_media=invert_media, + invert_media=show_above_text, media=media, schedule_date=utils.datetime_to_timestamp(schedule_date), reply_markup=await reply_markup.write(self) if reply_markup else None, diff --git a/pyrogram/methods/messages/edit_message_text.py b/pyrogram/methods/messages/edit_message_text.py index 66868c984..828234c5b 100644 --- a/pyrogram/methods/messages/edit_message_text.py +++ b/pyrogram/methods/messages/edit_message_text.py @@ -34,6 +34,7 @@ async def edit_message_text( parse_mode: Optional["enums.ParseMode"] = None, entities: List["types.MessageEntity"] = None, disable_web_page_preview: bool = None, + show_above_text: bool = None, schedule_date: datetime = None, reply_markup: "types.InlineKeyboardMarkup" = None ) -> "types.Message": @@ -63,6 +64,10 @@ async def edit_message_text( disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. + show_above_text (``bool``, *optional*): + If True, link preview will be shown above the message text. + Otherwise, the link preview will be shown below the message text. + schedule_date (:py:obj:`~datetime.datetime`, *optional*): Date when the message will be automatically sent. @@ -89,6 +94,7 @@ async def edit_message_text( peer=await self.resolve_peer(chat_id), id=message_id, no_webpage=disable_web_page_preview or None, + invert_media=show_above_text or None, schedule_date=utils.datetime_to_timestamp(schedule_date), reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, text, parse_mode, entities) diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py index aa017ba00..73ec6a35a 100644 --- a/pyrogram/methods/messages/forward_messages.py +++ b/pyrogram/methods/messages/forward_messages.py @@ -33,6 +33,8 @@ async def forward_messages( message_thread_id: int = None, disable_notification: bool = None, schedule_date: datetime = None, + hide_sender_name: bool = None, + hide_captions: bool = None, protect_content: bool = None ) -> Union["types.Message", List["types.Message"]]: """Forward messages of any kind. @@ -64,6 +66,12 @@ async def forward_messages( schedule_date (:py:obj:`~datetime.datetime`, *optional*): Date when the message will be automatically sent. + hide_sender_name (``bool``, *optional*): + If True, the original author of the message will not be shown. + + hide_captions (``bool``, *optional*): + If True, the original media captions will be removed. + protect_content (``bool``, *optional*): Protects the contents of the sent message from forwarding and saving. @@ -92,6 +100,8 @@ async def forward_messages( silent=disable_notification or None, random_id=[self.rnd_id() for _ in message_ids], schedule_date=utils.datetime_to_timestamp(schedule_date), + drop_author=hide_sender_name, + drop_media_captions=hide_captions, noforwards=protect_content, top_msg_id=message_thread_id ) diff --git a/pyrogram/methods/messages/get_history.py b/pyrogram/methods/messages/get_history.py new file mode 100644 index 000000000..e3c77edcc --- /dev/null +++ b/pyrogram/methods/messages/get_history.py @@ -0,0 +1,79 @@ +# 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 logging +import datetime +from typing import Union, List + +import pyrogram +from pyrogram import raw +from pyrogram import types +from pyrogram import utils + +log = logging.getLogger(__name__) + + +class GetHistory: + async def get_history( + self: 'pyrogram.Client', + chat_id: Union[int, str], + limit: int = 0, + offset: int = 0, + offset_id: int = 0, + offset_date: datetime = utils.zero_datetime() + ) -> List["types.Message"]: + """Get messages from a chat history. + + The messages are returned in reverse chronological order. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + limit (``int``, *optional*): + Limits the number of messages to be retrieved. + By default, no limit is applied and all messages are returned. + + offset (``int``, *optional*): + Sequential number of the first message to be returned.. + Negative values are also accepted and become useful in case you set offset_id or offset_date. + + offset_id (``int``, *optional*): + Identifier of the first message to be returned. + + offset_date (:py:obj:`~datetime.datetime`, *optional*): + Pass a date as offset to retrieve only older messages starting from that date. + + Returns: + ``Generator``: A generator yielding :obj:`~pyrogram.types.Message` objects. + + Example: + .. code-block:: python + + async for message in app.get_chat_history(chat_id): + print(message.text) + """ + all_messages = [] + async for current in self.get_chat_history(chat_id, limit, offset, offset_id, offset_date): + all_messages.append(current) + + return all_messages diff --git a/pyrogram/methods/messages/get_message_by_link.py b/pyrogram/methods/messages/get_message_by_link.py new file mode 100644 index 000000000..2d01719e8 --- /dev/null +++ b/pyrogram/methods/messages/get_message_by_link.py @@ -0,0 +1,118 @@ +# 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 logging +from typing import Union, List + +import pyrogram +from pyrogram import raw +from pyrogram import types +from pyrogram import utils + +log = logging.getLogger(__name__) + + +class GetMessageByLink: + async def get_message_by_link( + self: 'pyrogram.Client', + link: str, + continue_til_found: bool = False, + chunk_amount: int = 10, + ) -> types.Message: + """Gets a single message from a certain link. + If necessary, continues message iteration till it finds a valid link. + + .. include:: /_includes/usable-by/users-bots.rst + + Parameters: + link (``str``): + The message link. + + Returns: + :obj:`~pyrogram.types.Message`: a single message is returned. + + Example: + .. code-block:: python + + # Get message via a link + await app.get_message_by_link(chat_id) + + Raises: + ValueError: In case of invalid arguments. + """ + link = link.replace('telegram.me', 't.me') + link = link.replace('telegram.dog', 't.me') + link = link.replace('https://', '') + link = link.replace('http://', '') + if link.find('t.me') == -1: + return None + + chat_id = None + message_id: int = 0 + # the format can be either like t.me/c/1627169341/1099 or + # t.me/username/123 + if link.find('/c/') != -1: + my_strs = link.split('/c/') + if len(my_strs) < 2: + return None + my_strs = my_strs[1].split('/') + if len(my_strs) < 2: + return None + chat_id = utils.get_channel_id(int(my_strs[0])) + message_id = int(my_strs[1]) + else: + my_strs = link.split('/') + if len(my_strs) < 3: + return None + chat_id = my_strs[1] + message_id = int(my_strs[2]) + + if not chat_id: + return None + + if not continue_til_found: + return await self.get_messages(chat_id, message_id) + + messages = await self.get_history( + chat_id=chat_id, + limit=1, + ) + if messages: + to_id = messages[0].id + + while message_id <= to_id: + the_messages: List[types.Message] = \ + await self.get_messages(chat_id, [i for i in range(message_id, message_id + chunk_amount)]) + for msg in the_messages: + if msg and not msg.empty: + return msg + + message_id += chunk_amount + + return None + + async def get_scheduled_messages( + self: "pyrogram.Client", + chat_id: Union[int, str] + ) -> List["types.Message"]: + + r = await self.invoke( + raw.functions.GetScheduledHistory(peer=await self.resolve_peer(chat_id), hash=0) + ) + + return await utils.parse_messages(self, r, replies=0) diff --git a/pyrogram/methods/messages/get_scheduled_messages.py b/pyrogram/methods/messages/get_scheduled_messages.py new file mode 100644 index 000000000..6b4bb8c64 --- /dev/null +++ b/pyrogram/methods/messages/get_scheduled_messages.py @@ -0,0 +1,61 @@ +# 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 logging +from typing import Union, List + +import pyrogram +from pyrogram import raw +from pyrogram import types +from pyrogram import utils + +log = logging.getLogger(__name__) + + +class GetScheduledMessages: + async def get_scheduled_messages( + self: "pyrogram.Client", + chat_id: Union[int, str] + ) -> List["types.Message"]: + """Get one or more scheduled messages from a chat. + + .. include:: /_includes/usable-by/users-bots.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + Returns: + :List of :obj:`~pyrogram.types.Message`: a list of messages is returned. + + Example: + .. code-block:: python + + # Get scheduled messages + await app.get_scheduled_messages(chat_id) + + Raises: + ValueError: In case of invalid arguments. + """ + r = await self.invoke( + raw.functions.GetScheduledHistory(peer=await self.resolve_peer(chat_id), hash=0) + ) + + return await utils.parse_messages(self, r, replies=0) diff --git a/pyrogram/methods/messages/get_stickers.py b/pyrogram/methods/messages/get_stickers.py new file mode 100644 index 000000000..6f012cfdb --- /dev/null +++ b/pyrogram/methods/messages/get_stickers.py @@ -0,0 +1,64 @@ +# 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 logging +from typing import List + +import pyrogram +from pyrogram import raw +from pyrogram import types + +log = logging.getLogger(__name__) + + +class GetStickers: + async def get_stickers( + self: "pyrogram.Client", + short_name: str + ) -> List["types.Sticker"]: + """Get all stickers from set by short name. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + short_name (``str``): + Short name of the sticker set, serves as the unique identifier for the sticker set. + + Returns: + List of :obj:`~pyrogram.types.Sticker`: A list of stickers is returned. + + Example: + .. code-block:: python + + # Get all stickers by short name + await app.get_stickers("short_name") + + Raises: + ValueError: In case of invalid arguments. + """ + sticker_set = await self.invoke( + raw.functions.messages.GetStickerSet( + stickerset=raw.types.InputStickerSetShortName(short_name=short_name), + hash=0 + ) + ) + + return [ + await types.Sticker._parse(self, doc, {type(a): a for a in doc.attributes}) + for doc in sticker_set.documents + ] diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py index 95a20fc0d..5f85eb797 100644 --- a/pyrogram/methods/messages/send_audio.py +++ b/pyrogram/methods/messages/send_audio.py @@ -197,10 +197,13 @@ async def progress(current, total): try: if isinstance(audio, str): if os.path.isfile(audio): + mime_type = self.guess_mime_type(audio) or "audio/mpeg" + if mime_type == "audio/ogg": + mime_type = "audio/opus" thumb = await self.save_file(thumb) file = await self.save_file(audio, progress=progress, progress_args=progress_args) media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(audio) or "audio/mpeg", + mime_type=mime_type, file=file, thumb=thumb, attributes=[ @@ -219,10 +222,13 @@ async def progress(current, total): else: media = utils.get_input_media_from_file_id(audio, FileType.AUDIO) else: + mime_type = self.guess_mime_type(file_name or audio.name) or "audio/mpeg" + if mime_type == "audio/ogg": + mime_type = "audio/opus" thumb = await self.save_file(thumb) file = await self.save_file(audio, progress=progress, progress_args=progress_args) media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(file_name or audio.name) or "audio/mpeg", + mime_type=mime_type, file=file, thumb=thumb, attributes=[ diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py index d1461dc2b..d837e51fc 100644 --- a/pyrogram/methods/messages/send_cached_media.py +++ b/pyrogram/methods/messages/send_cached_media.py @@ -43,7 +43,7 @@ async def send_cached_media( schedule_date: datetime = None, protect_content: bool = None, has_spoiler: bool = None, - invert_media: bool = None, + show_above_text: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -111,7 +111,7 @@ async def send_cached_media( has_spoiler (``bool``, *optional*): True, if the message media is covered by a spoiler animation. - invert_media (``bool``, *optional*): + show_above_text (``bool``, *optional*): If True, link preview will be shown above the message text. Otherwise, the link preview will be shown below the message text. @@ -135,7 +135,7 @@ async def send_cached_media( peer=peer, media=utils.get_input_media_from_file_id(file_id, has_spoiler=has_spoiler), silent=disable_notification or None, - invert_media=invert_media, + invert_media=show_above_text, reply_to=utils.get_reply_to( reply_to_message_id=reply_to_message_id, message_thread_id=message_thread_id, diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index f0df2783b..eac7b80aa 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -54,7 +54,7 @@ async def send_media_group( quote_offset: int = None, schedule_date: datetime = None, protect_content: bool = None, - invert_media: bool = None, + show_above_text: bool = None, ) -> List["types.Message"]: """Send a group of photos or videos as an album. @@ -105,7 +105,7 @@ async def send_media_group( protect_content (``bool``, *optional*): Protects the contents of the sent message from forwarding and saving. - invert_media (``bool``, *optional*): + show_above_text (``bool``, *optional*): If True, link preview will be shown above the message text. Otherwise, the link preview will be shown below the message text. @@ -201,6 +201,7 @@ async def send_media_group( thumb=await self.save_file(i.thumb), spoiler=i.has_spoiler, mime_type=self.guess_mime_type(i.media) or "video/mp4", + nosound_video=i.no_sound, attributes=[ raw.types.DocumentAttributeVideo( supports_streaming=i.supports_streaming or None, @@ -252,6 +253,7 @@ async def send_media_group( thumb=await self.save_file(i.thumb), spoiler=i.has_spoiler, mime_type=self.guess_mime_type(getattr(i.media, "name", "video.mp4")) or "video/mp4", + nosound_video=i.no_sound, attributes=[ raw.types.DocumentAttributeVideo( supports_streaming=i.supports_streaming or None, @@ -445,7 +447,7 @@ async def send_media_group( ), schedule_date=utils.datetime_to_timestamp(schedule_date), noforwards=protect_content, - invert_media=invert_media + invert_media=show_above_text ), sleep_threshold=60 ) diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py index 0fa073335..1602be4b4 100644 --- a/pyrogram/methods/messages/send_message.py +++ b/pyrogram/methods/messages/send_message.py @@ -34,7 +34,7 @@ async def send_message( disable_web_page_preview: bool = None, disable_notification: bool = None, message_thread_id: int = None, - invert_media: bool = None, + show_above_text: bool = None, reply_to_message_id: int = None, reply_to_chat_id: Union[int, str] = None, reply_to_story_id: int = None, @@ -81,7 +81,7 @@ async def send_message( Unique identifier for the target message thread (topic) of the forum. For supergroups only. - invert_media (``bool``, *optional*): + show_above_text (``bool``, *optional*): If True, link preview will be shown above the message text. Otherwise, the link preview will be shown below the message text. @@ -161,7 +161,7 @@ async def send_message( peer=peer, no_webpage=disable_web_page_preview or None, silent=disable_notification or None, - invert_media=invert_media or None, + invert_media=show_above_text or None, reply_to=utils.get_reply_to( reply_to_message_id=reply_to_message_id, message_thread_id=message_thread_id, diff --git a/pyrogram/methods/messages/send_reaction.py b/pyrogram/methods/messages/send_reaction.py index 626878331..454989c03 100644 --- a/pyrogram/methods/messages/send_reaction.py +++ b/pyrogram/methods/messages/send_reaction.py @@ -27,8 +27,8 @@ async def send_reaction( self: "pyrogram.Client", chat_id: Union[int, str], message_id: int = None, - story_id: int = None, emoji: Union[int, str, List[Union[int, str]]] = None, + story_id: int = None, big: bool = False ) -> bool: """Send a reaction to a message or story. @@ -42,14 +42,14 @@ async def send_reaction( message_id (``int``, *optional*): Identifier of the message. - story_id (``int``, *optional*): - Identifier of the story. - emoji (``int`` | ``str`` | List of ``int`` | ``str``, *optional*): Reaction emoji. Pass None as emoji (default) to retract the reaction. Pass list of int or str to react multiple emojis. + story_id (``int``, *optional*): + Identifier of the story. + big (``bool``, *optional*): Pass True to show a bigger and longer reaction. Defaults to False. diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index 51809c4dc..203fc4098 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -56,6 +56,7 @@ async def send_video( quote_offset: int = None, schedule_date: datetime = None, protect_content: bool = None, + no_sound: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -155,6 +156,10 @@ async def send_video( protect_content (``bool``, *optional*): Protects the contents of the sent message from forwarding and saving. + no_sound (``bool``, *optional*): + Pass True, if the uploaded video is a video message with no sound. + Doesn't work for external links. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -216,6 +221,7 @@ async def progress(current, total): ttl_seconds=ttl_seconds, spoiler=has_spoiler, thumb=thumb, + nosound_video=no_sound, attributes=[ raw.types.DocumentAttributeVideo( supports_streaming=supports_streaming or None, @@ -243,6 +249,7 @@ async def progress(current, total): ttl_seconds=ttl_seconds, spoiler=has_spoiler, thumb=thumb, + nosound_video=no_sound, attributes=[ raw.types.DocumentAttributeVideo( supports_streaming=supports_streaming or None, diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py index a9b9aa6fd..596ce3d53 100644 --- a/pyrogram/methods/messages/send_video_note.py +++ b/pyrogram/methods/messages/send_video_note.py @@ -49,6 +49,7 @@ async def send_video_note( quote_offset: int = None, schedule_date: datetime = None, protect_content: bool = None, + view_once: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -123,6 +124,10 @@ async def send_video_note( protect_content (``bool``, *optional*): Protects the contents of the sent message from forwarding and saving. + view_once (``bool``, *optional*): + Self-Destruct Timer. + If True, the video note will self-destruct after it was viewed. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -162,6 +167,9 @@ async def send_video_note( # Set video note length await app.send_video_note("me", "video_note.mp4", length=25) + + # Send self-destructing video note + await app.send_video_note("me", "video_note.mp4", view_once=True) """ file = None @@ -181,7 +189,8 @@ async def send_video_note( w=length, h=length ) - ] + ], + ttl_seconds=(1 << 31) - 1 if view_once else None ) else: media = utils.get_input_media_from_file_id(video_note, FileType.VIDEO_NOTE) @@ -199,7 +208,8 @@ async def send_video_note( w=length, h=length ) - ] + ], + ttl_seconds=(1 << 31) - 1 if view_once else None ) quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values() diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py index b0c249b46..8e59c2b9d 100644 --- a/pyrogram/methods/messages/send_voice.py +++ b/pyrogram/methods/messages/send_voice.py @@ -49,7 +49,7 @@ async def send_voice( quote_offset: int = None, schedule_date: datetime = None, protect_content: bool = None, - ttl_seconds: int = None, + view_once: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -121,10 +121,9 @@ async def send_voice( protect_content (``bool``, *optional*): Protects the contents of the sent message from forwarding and saving. - ttl_seconds (``int``, *optional*): + view_once (``bool``, *optional*): Self-Destruct Timer. - If you set a timer, the voice note will self-destruct in *ttl_seconds* - seconds after it was listened. + If True, the voice note will self-destruct after it was listened. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, @@ -176,9 +175,12 @@ async def send_voice( try: if isinstance(voice, str): if os.path.isfile(voice): + mime_type = self.guess_mime_type(voice) or "audio/ogg" + if mime_type == "audio/mpeg": + mime_type = "audio/ogg" file = await self.save_file(voice, progress=progress, progress_args=progress_args) media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(voice) or "audio/mpeg", + mime_type=mime_type, file=file, attributes=[ raw.types.DocumentAttributeAudio( @@ -186,7 +188,7 @@ async def send_voice( duration=duration ) ], - ttl_seconds=ttl_seconds + ttl_seconds=(1 << 31) - 1 if view_once else None ) elif re.match("^https?://", voice): media = raw.types.InputMediaDocumentExternal( @@ -195,9 +197,12 @@ async def send_voice( else: media = utils.get_input_media_from_file_id(voice, FileType.VOICE) else: + mime_type = self.guess_mime_type(voice.name) or "audio/ogg" + if mime_type == "audio/mpeg": + mime_type = "audio/ogg" file = await self.save_file(voice, progress=progress, progress_args=progress_args) media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(voice.name) or "audio/mpeg", + mime_type=mime_type, file=file, attributes=[ raw.types.DocumentAttributeAudio( @@ -205,7 +210,7 @@ async def send_voice( duration=duration ) ], - ttl_seconds=ttl_seconds + ttl_seconds=(1 << 31) - 1 if view_once else None ) quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values() diff --git a/pyrogram/methods/messages/send_web_page.py b/pyrogram/methods/messages/send_web_page.py index 7727f6c2b..9738fd720 100644 --- a/pyrogram/methods/messages/send_web_page.py +++ b/pyrogram/methods/messages/send_web_page.py @@ -27,15 +27,15 @@ class SendWebPage: async def send_web_page( self: "pyrogram.Client", chat_id: Union[int, str], - url: str, text: str = None, - force_large_media: bool = None, - force_small_media: bool = None, + url: str = None, + prefer_large_media: bool = None, + prefer_small_media: bool = None, parse_mode: Optional["enums.ParseMode"] = None, entities: List["types.MessageEntity"] = None, disable_notification: bool = None, message_thread_id: int = None, - invert_media: bool = None, + show_above_text: bool = None, reply_to_message_id: int = None, reply_to_chat_id: Union[int, str] = None, reply_to_story_id: int = None, @@ -51,7 +51,7 @@ async def send_web_page( "types.ForceReply" ] = None ) -> "types.Message": - """Send text Web Page Preview. + """Send Web Page Preview. .. include:: /_includes/usable-by/users-bots.rst @@ -61,12 +61,13 @@ async def send_web_page( 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). - url (``str``): - Link that will be previewed. - text (``str``, *optional*): Text of the message to be sent. + url (``str``, *optional*): + Link that will be previewed. + If url not specified, the first URL found in the text will be used. + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. You can combine both syntaxes together. @@ -74,15 +75,15 @@ async def send_web_page( entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in message text, which can be specified instead of *parse_mode*. - force_large_media (``bool``, *optional*): + prefer_large_media (``bool``, *optional*): If True, media in the link preview will be larger. Ignored if the URL isn't explicitly specified or media size change isn't supported for the preview. - force_small_media (``bool``, *optional*): + prefer_small_media (``bool``, *optional*): If True, media in the link preview will be smaller. Ignored if the URL isn't explicitly specified or media size change isn't supported for the preview. - invert_media (``bool``, *optional*): + show_above_text (``bool``, *optional*): If True, link preview will be shown above the message text. Otherwise, the link preview will be shown below the message text. @@ -123,7 +124,7 @@ async def send_web_page( instructions to remove reply keyboard or to force a reply from the user. Returns: - :obj:`~pyrogram.types.Message`: On success, the sent text message is returned. + :obj:`~pyrogram.types.Message`: On success, the sent message is returned. Example: .. code-block:: python @@ -132,7 +133,7 @@ async def send_web_page( await app.send_web_page("me", "https://docs.pyrogram.org") # Make web preview image larger - await app.send_web_page("me", "https://docs.pyrogram.org", force_large_media=True) + await app.send_web_page("me", "https://docs.pyrogram.org", prefer_large_media=True) """ @@ -140,6 +141,19 @@ async def send_web_page( quote_text, quote_entities = (await utils.parse_text_entities(self, quote_text, parse_mode, quote_entities)).values() + if not url: + if entities: + for entity in entities: + if isinstance(entity, enums.MessageEntityType.URL): + url = entity.url + break + + if not url: + url = utils.get_first_url(message) + + if not url: + raise ValueError("URL not specified") + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), @@ -159,10 +173,10 @@ async def send_web_page( message=message, media=raw.types.InputMediaWebPage( url=url, - force_large_media=force_large_media, - force_small_media=force_small_media + force_large_media=prefer_large_media, + force_small_media=prefer_small_media ), - invert_media=invert_media, + invert_media=show_above_text, entities=entities, noforwards=protect_content ) diff --git a/pyrogram/methods/messages/vote_poll.py b/pyrogram/methods/messages/vote_poll.py index 620fac5a1..ad8ed8b0c 100644 --- a/pyrogram/methods/messages/vote_poll.py +++ b/pyrogram/methods/messages/vote_poll.py @@ -43,7 +43,7 @@ async def vote_poll( message_id (``int``): Identifier of the original message with the poll. - options (``Int`` | List of ``int``): + options (``int`` | List of ``int``): Index or list of indexes (for multiple answers) of the poll option(s) you want to vote for (0 to 9). Returns: diff --git a/pyrogram/methods/stories/__init__.py b/pyrogram/methods/stories/__init__.py index 870889c59..a77b8e614 100644 --- a/pyrogram/methods/stories/__init__.py +++ b/pyrogram/methods/stories/__init__.py @@ -22,7 +22,6 @@ from .edit_story_caption import EditStoryCaption from .edit_story_media import EditStoryMedia from .edit_story_privacy import EditStoryPrivacy -from .export_story_link import ExportStoryLink from .forward_story import ForwardStory from .get_all_stories import GetAllStories from .get_chat_stories import GetChatStories @@ -42,7 +41,6 @@ class Stories( EditStoryCaption, EditStoryMedia, EditStoryPrivacy, - ExportStoryLink, ForwardStory, GetAllStories, GetChatStories, diff --git a/pyrogram/methods/stories/increment_story_views.py b/pyrogram/methods/stories/increment_story_views.py index c0fa937c7..3ae6281b1 100644 --- a/pyrogram/methods/stories/increment_story_views.py +++ b/pyrogram/methods/stories/increment_story_views.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Union, List import pyrogram from pyrogram import raw @@ -26,7 +26,7 @@ class IncrementStoryViews: async def increment_story_views( self: "pyrogram.Client", chat_id: Union[int, str], - story_id: int, + story_id: Union[int, List[int]], ) -> bool: """Increment story views. @@ -37,8 +37,8 @@ async def increment_story_views( Unique identifier (int) or username (str) of the target chat. For a contact that exists in your Telegram address book you can use his phone number (str). - story_id (``int``): - Unique identifier of the target story. + story_id (``int`` | List of ``int``): + Identifier or list of story identifiers of the target story. Returns: ``bool``: On success, True is returned. @@ -49,10 +49,12 @@ async def increment_story_views( # Increment story views await app.increment_story_views(chat_id, 1) """ + ids = [story_id] if not isinstance(story_id, list) else story_id + r = await self.invoke( raw.functions.stories.IncrementStoryViews( peer=await self.resolve_peer(chat_id), - id=story_id + id=ids ) ) diff --git a/pyrogram/methods/stories/send_story.py b/pyrogram/methods/stories/send_story.py index f82440428..d55856217 100644 --- a/pyrogram/methods/stories/send_story.py +++ b/pyrogram/methods/stories/send_story.py @@ -218,8 +218,13 @@ async def send_story( privacy_rules = [raw.types.InputPrivacyValueAllowCloseFriends()] elif privacy == "contacts": privacy_rules = [raw.types.InputPrivacyValueAllowContacts()] - elif privacy == "all": + elif privacy == "all" or privacy == "everyone": privacy_rules = [raw.types.InputPrivacyValueAllowAll()] + else: + # It's just better to raise an exception here than to risk user-privacy... + # maybe the user have made a typo and wrote "friends" wrongly? that's why + # we will be raising an exception here so they can fix it on next try. + raise ValueError(f"invalid privacy (str) parameter passed: {privacy}") elif privacy == enums.StoriesPrivacyRules.PUBLIC: privacy_rules.append(raw.types.InputPrivacyValueAllowAll()) if disallowed_users: diff --git a/pyrogram/methods/users/__init__.py b/pyrogram/methods/users/__init__.py index b1b8bc71a..5f1ddf7ea 100644 --- a/pyrogram/methods/users/__init__.py +++ b/pyrogram/methods/users/__init__.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . from .block_user import BlockUser +from .check_username import CheckUsername from .delete_profile_photos import DeleteProfilePhotos from .get_chat_photos import GetChatPhotos from .get_chat_photos_count import GetChatPhotosCount @@ -34,6 +35,7 @@ class Users( BlockUser, + CheckUsername, GetCommonChats, GetChatPhotos, SetProfilePhoto, diff --git a/pyrogram/methods/users/block_user.py b/pyrogram/methods/users/block_user.py index 0e7ff7da1..25cd0ce84 100644 --- a/pyrogram/methods/users/block_user.py +++ b/pyrogram/methods/users/block_user.py @@ -25,8 +25,7 @@ class BlockUser: async def block_user( self: "pyrogram.Client", - user_id: Union[int, str], - my_stories_from: Union[bool, None] = None + user_id: Union[int, str] ) -> bool: """Block a user. @@ -49,8 +48,7 @@ async def block_user( return bool( await self.invoke( raw.functions.contacts.Block( - id=await self.resolve_peer(user_id), - my_stories_from=my_stories_from + id=await self.resolve_peer(user_id) ) ) ) diff --git a/pyrogram/methods/users/check_username.py b/pyrogram/methods/users/check_username.py new file mode 100644 index 000000000..a78566a89 --- /dev/null +++ b/pyrogram/methods/users/check_username.py @@ -0,0 +1,66 @@ +# 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 + +import pyrogram +from pyrogram import raw + + +class CheckUsername: + async def check_username( + self: "pyrogram.Client", + chat_id: Union[int, str], + username: str + ) -> bool: + """Check if a username is available. + + .. include:: /_includes/usable-by/users.rst + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + username (``str``): + Username to check. + + Returns: + ``bool``: True on success. + + Example: + .. code-block:: python + + await app.check_username("me", "username") + """ + peer = await self.resolve_peer(chat_id) + + if isinstance(peer, raw.types.InputPeerChannel): + r = await self.invoke( + raw.functions.channels.CheckUsername( + channel=peer, + username=username + ) + ) + else: + r = await self.invoke( + raw.functions.account.CheckUsername( + username=username + ) + ) + + return bool(r) diff --git a/pyrogram/methods/users/unblock_user.py b/pyrogram/methods/users/unblock_user.py index e6e1ebb67..db4fdddcf 100644 --- a/pyrogram/methods/users/unblock_user.py +++ b/pyrogram/methods/users/unblock_user.py @@ -25,8 +25,7 @@ class UnblockUser: async def unblock_user( self: "pyrogram.Client", - user_id: Union[int, str], - my_stories_from: Union[bool, None] = None + user_id: Union[int, str] ) -> bool: """Unblock a user. @@ -49,8 +48,7 @@ async def unblock_user( return bool( await self.invoke( raw.functions.contacts.Unblock( - id=await self.resolve_peer(user_id), - my_stories_from=my_stories_from + id=await self.resolve_peer(user_id) ) ) ) diff --git a/pyrogram/methods/utilities/stop_transmission.py b/pyrogram/methods/utilities/stop_transmission.py index 03b62ba3e..da6456952 100644 --- a/pyrogram/methods/utilities/stop_transmission.py +++ b/pyrogram/methods/utilities/stop_transmission.py @@ -20,8 +20,7 @@ class StopTransmission: - @staticmethod - def stop_transmission(): + def stop_transmission(self): """Stop downloading or uploading a file. This method must be called inside a progress callback function in order to stop the transmission at the diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index 46722a8c4..0753fc379 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -116,7 +116,7 @@ class HTML: def __init__(self, client: Optional["pyrogram.Client"]): self.client = client - async def parse(self, text: str): + async def parse(self, text: str) -> dict: # Strip whitespaces from the beginning and the end, but preserve closing tags text = re.sub(r"^\s*(<[\w<>=\s\"]*>)\s*", r"\1", text) text = re.sub(r"\s*(]*>)\s*$", r"\1", text) @@ -154,7 +154,7 @@ async def parse(self, text: str): } @staticmethod - def unparse(text: str, entities: list): + def unparse(text: str, entities: list) -> str: def parse_one(entity): """ Parses a single entity and returns (start_tag, start), (end_tag, end) diff --git a/pyrogram/parser/markdown.py b/pyrogram/parser/markdown.py index 6219d9583..93ca67c60 100644 --- a/pyrogram/parser/markdown.py +++ b/pyrogram/parser/markdown.py @@ -32,8 +32,9 @@ SPOILER_DELIM = "||" CODE_DELIM = "`" PRE_DELIM = "```" +BLOCKQUOTE_DELIM = ">" -MARKDOWN_RE = re.compile(r"({d})|\[(.+?)\]\((.+?)\)".format( +MARKDOWN_RE = re.compile(r"({d})|(!?)\[(.+?)\]\((.+?)\)".format( d="|".join( ["".join(i) for i in [ [rf"\{j}" for j in i] @@ -52,6 +53,7 @@ OPENING_TAG = "<{}>" CLOSING_TAG = "" URL_MARKUP = '{}' +EMOJI_MARKUP = '{}' FIXED_WIDTH_DELIMS = [CODE_DELIM, PRE_DELIM] @@ -59,16 +61,41 @@ class Markdown: def __init__(self, client: Optional["pyrogram.Client"]): self.html = HTML(client) + def _parse_blockquotes(self, text: str): + text = html.unescape(text) + lines = text.split('\n') + result = [] + in_blockquote = False + current_blockquote = [] + + for line in lines: + if line.startswith(BLOCKQUOTE_DELIM): + in_blockquote = True + current_blockquote.append(line[1:].strip()) + else: + if in_blockquote: + in_blockquote = False + result.append(OPENING_TAG.format("blockquote") + '\n'.join(current_blockquote) + CLOSING_TAG.format("blockquote")) + current_blockquote = [] + result.append(line) + + if in_blockquote: + result.append(OPENING_TAG.format("blockquote") + '\n'.join(current_blockquote) + CLOSING_TAG.format("blockquote")) + + return '\n'.join(result) + async def parse(self, text: str, strict: bool = False): if strict: text = html.escape(text) + text = self._parse_blockquotes(text) + delims = set() is_fixed_width = False for i, match in enumerate(re.finditer(MARKDOWN_RE, text)): start, _ = match.span() - delim, text_url, url = match.groups() + delim, is_emoji, text_url, url = match.groups() full = match.group(0) if delim in FIXED_WIDTH_DELIMS: @@ -77,10 +104,16 @@ async def parse(self, text: str, strict: bool = False): if is_fixed_width and delim not in FIXED_WIDTH_DELIMS: continue - if text_url: + if not is_emoji and text_url: text = utils.replace_once(text, full, URL_MARKUP.format(url, text_url), start) continue + if is_emoji: + emoji = text_url + emoji_id = url.lstrip("tg://emoji?id=") + text = utils.replace_once(text, full, EMOJI_MARKUP.format(emoji_id, emoji), start) + continue + if delim == BOLD_DELIM: tag = "b" elif delim == ITALIC_DELIM: @@ -141,7 +174,21 @@ def unparse(text: str, entities: list): start_tag = f"{PRE_DELIM}{language}\n" end_tag = f"\n{PRE_DELIM}" elif entity_type == MessageEntityType.BLOCKQUOTE: - start_tag = end_tag = PRE_DELIM + start_tag = BLOCKQUOTE_DELIM + " " + end_tag = "" + blockquote_text = text[start:end] + lines = blockquote_text.split("\n") + last_length = 0 + for line in lines: + if len(line) == 0 and last_length == end: + continue + start_offset = start+last_length + last_length = last_length+len(line) + end_offset = start_offset+last_length + entities_offsets.append((start_tag, start_offset,)) + entities_offsets.append((end_tag, end_offset,)) + last_length = last_length+1 + continue elif entity_type == MessageEntityType.SPOILER: start_tag = end_tag = SPOILER_DELIM elif entity_type == MessageEntityType.TEXT_LINK: @@ -152,6 +199,10 @@ def unparse(text: str, entities: list): user = entity.user start_tag = "[" end_tag = f"](tg://user?id={user.id})" + elif entity_type == MessageEntityType.CUSTOM_EMOJI: + emoji_id = entity.custom_emoji_id + start_tag = "![" + end_tag = f"](tg://emoji?id={emoji_id})" else: continue diff --git a/pyrogram/parser/parser.py b/pyrogram/parser/parser.py index 0ce2b2375..bd4005871 100644 --- a/pyrogram/parser/parser.py +++ b/pyrogram/parser/parser.py @@ -30,8 +30,8 @@ def __init__(self, client: Optional["pyrogram.Client"]): self.html = HTML(client) self.markdown = Markdown(client) - async def parse(self, text: str, mode: Optional[enums.ParseMode] = None): - text = str(text if text else "").strip() + async def parse(self, text: str, mode: Optional[enums.ParseMode] = None) -> dict: + text = str(text or "").strip() if mode is None: if self.client: @@ -54,7 +54,7 @@ async def parse(self, text: str, mode: Optional[enums.ParseMode] = None): raise ValueError(f'Invalid parse mode "{mode}"') @staticmethod - def unparse(text: str, entities: list, is_html: bool): + def unparse(text: str, entities: list, is_html: bool) -> str: if is_html: return HTML.unparse(text, entities) else: diff --git a/pyrogram/parser/utils.py b/pyrogram/parser/utils.py index 0d6040289..68890a310 100644 --- a/pyrogram/parser/utils.py +++ b/pyrogram/parser/utils.py @@ -23,7 +23,7 @@ SMP_RE = re.compile(r"[\U00010000-\U0010FFFF]") -def add_surrogates(text): +def add_surrogates(text: str) -> str: # Replace each SMP code point with a surrogate pair return SMP_RE.sub( lambda match: # Split SMP in two surrogates @@ -32,7 +32,7 @@ def add_surrogates(text): ) -def remove_surrogates(text): +def remove_surrogates(text: str) -> str: # Replace each surrogate pair with a SMP code point return text.encode("utf-16", "surrogatepass").decode("utf-16") diff --git a/pyrogram/py.typed b/pyrogram/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/pyrogram/raw/core/primitives/vector.py b/pyrogram/raw/core/primitives/vector.py index c6c6e8e5d..873a54d09 100644 --- a/pyrogram/raw/core/primitives/vector.py +++ b/pyrogram/raw/core/primitives/vector.py @@ -19,6 +19,7 @@ from io import BytesIO from typing import cast, Union, Any +from .bool import BoolFalse, BoolTrue, Bool from .int import Int, Long from ..list import List from ..tl_object import TLObject @@ -32,6 +33,12 @@ class Vector(bytes, TLObject): @staticmethod def read_bare(b: BytesIO, size: int) -> Union[int, Any]: if size == 4: + e = int.from_bytes(b.read(4), "little") + b.seek(-4, 1) + + if e in {BoolFalse.ID, BoolTrue.ID}: + return Bool.read(b) + return Int.read(b) if size == 8: diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index c140fa8b3..efc7581b0 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -62,6 +62,7 @@ class Session: # last time used date of this session. last_used_time: datetime.datetime = None + auto_restart_on_fail: bool = False def __init__( self, @@ -70,7 +71,8 @@ def __init__( auth_key: bytes, test_mode: bool, is_media: bool = False, - is_cdn: bool = False + is_cdn: bool = False, + auto_restart_on_fail: bool = False ): self.client = client self.dc_id = dc_id @@ -78,6 +80,7 @@ def __init__( self.test_mode = test_mode self.is_media = is_media self.is_cdn = is_cdn + self.auto_restart_on_fail = auto_restart_on_fail self.connection = None @@ -129,10 +132,11 @@ async def start(self): app_version=self.client.app_version, device_model=self.client.device_model, system_version=self.client.system_version, - system_lang_code=self.client.lang_code, + system_lang_code=self.client.system_lang_code, + lang_pack=self.client.lang_pack, lang_code=self.client.lang_code, - lang_pack="", query=raw.functions.help.GetConfig(), + params=self.client.init_connection_params, ) ), timeout=self.START_TIMEOUT @@ -289,7 +293,12 @@ async def ping_worker(self): ping_id=0, disconnect_delay=self.WAIT_TIMEOUT + 10 ), False ) - except (OSError, RPCError): + except OSError: + if self.auto_restart_on_fail: + self.loop.create_task(self.restart()) + break + pass + except RPCError: pass log.info("PingTask stopped") @@ -304,6 +313,12 @@ async def recv_worker(self): if packet: error_code = -Int.read(BytesIO(packet)) + if error_code == 404: + raise Exception( + "Auth key not found in the system. You must delete your session file" + "and log in again with your phone number or bot token" + ) + log.warning( "Server sent transport error: %s (%s)", error_code, Session.TRANSPORT_ERRORS.get(error_code, "unknown error") diff --git a/pyrogram/storage/file_storage.py b/pyrogram/storage/file_storage.py index 1c77688c5..6dbb3cb66 100644 --- a/pyrogram/storage/file_storage.py +++ b/pyrogram/storage/file_storage.py @@ -25,7 +25,7 @@ log = logging.getLogger(__name__) -SCHEMA = """ +USERNAMES_SCHEMA = """ CREATE TABLE usernames ( id INTEGER, @@ -36,6 +36,17 @@ CREATE INDEX idx_usernames_username ON usernames (username); """ +UPDATE_STATE_SCHEMA = """ +CREATE TABLE update_state +( + id INTEGER PRIMARY KEY, + pts INTEGER, + qts INTEGER, + date INTEGER, + seq INTEGER +); +""" + class FileStorage(SQLiteStorage): FILE_EXTENSION = ".session" @@ -62,7 +73,13 @@ def update(self): if version == 3: with self.conn: - self.conn.executescript(SCHEMA) + self.conn.executescript(USERNAMES_SCHEMA) + + version += 1 + + if version == 4: + with self.conn: + self.conn.executescript(UPDATE_STATE_SCHEMA) version += 1 diff --git a/pyrogram/storage/sqlite_storage.py b/pyrogram/storage/sqlite_storage.py index d249d765a..cba2e74c1 100644 --- a/pyrogram/storage/sqlite_storage.py +++ b/pyrogram/storage/sqlite_storage.py @@ -54,6 +54,15 @@ FOREIGN KEY (id) REFERENCES peers(id) ); +CREATE TABLE update_state +( + id INTEGER PRIMARY KEY, + pts INTEGER, + qts INTEGER, + date INTEGER, + seq INTEGER +); + CREATE TABLE version ( number INTEGER PRIMARY KEY @@ -96,7 +105,7 @@ def get_input_peer(peer_id: int, access_hash: int, peer_type: str): class SQLiteStorage(Storage): - VERSION = 4 + VERSION = 5 USERNAME_TTL = 8 * 60 * 60 def __init__(self, name: str): @@ -132,24 +141,49 @@ async def delete(self): raise NotImplementedError async def update_peers(self, peers: List[Tuple[int, int, str, List[str], str]]): - for peer_data in peers: - id, access_hash, type, usernames, phone_number = peer_data + peers_data = [] + usernames_data = [] + ids_to_delete = [] - self.conn.execute( - "REPLACE INTO peers (id, access_hash, type, phone_number)" - "VALUES (?, ?, ?, ?)", - (id, access_hash, type, phone_number) - ) + for id, access_hash, type, usernames, phone_number in peers: + ids_to_delete.append((id,)) + peers_data.append((id, access_hash, type, phone_number)) - self.conn.execute( - "DELETE FROM usernames WHERE id = ?", - (id,) - ) + if usernames: + usernames_data.extend([(id, username) for username in usernames]) + + self.conn.executemany( + "REPLACE INTO peers (id, access_hash, type, phone_number) VALUES (?, ?, ?, ?)", + peers_data) + self.conn.executemany( + "DELETE FROM usernames WHERE id = ?", + ids_to_delete) + + if usernames_data: self.conn.executemany( "REPLACE INTO usernames (id, username) VALUES (?, ?)", - [(id, username) for username in usernames] if usernames else [(id, None)] - ) + usernames_data) + + async def update_state(self, value: Tuple[int, int, int, int, int] = object): + if value == object: + return self.conn.execute( + "SELECT id, pts, qts, date, seq FROM update_state " + "ORDER BY date ASC" + ).fetchall() + else: + with self.conn: + if isinstance(value, int): + self.conn.execute( + "DELETE FROM update_state WHERE id = ?", + (value,) + ) + else: + self.conn.execute( + "REPLACE INTO update_state (id, pts, qts, date, seq)" + "VALUES (?, ?, ?, ?, ?)", + value + ) async def get_peer_by_id(self, peer_id: int): r = self.conn.execute( diff --git a/pyrogram/storage/storage.py b/pyrogram/storage/storage.py index 6af890d02..fca7a1709 100644 --- a/pyrogram/storage/storage.py +++ b/pyrogram/storage/storage.py @@ -16,12 +16,20 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from abc import ABC, abstractmethod import base64 import struct from typing import List, Tuple -class Storage: +class Storage(ABC): + """ + Abstract class for storage engines. + + Parameters: + name (``str``): + The name of the session. + """ OLD_SESSION_STRING_FORMAT = ">B?256sI?" OLD_SESSION_STRING_FORMAT_64 = ">B?256sQ?" SESSION_STRING_SIZE = 351 @@ -32,22 +40,27 @@ class Storage: def __init__(self, name: str): self.name = name + @abstractmethod async def open(self): """Opens the storage engine.""" raise NotImplementedError + @abstractmethod async def save(self): """Saves the current state of the storage engine.""" raise NotImplementedError + @abstractmethod async def close(self): """Closes the storage engine.""" raise NotImplementedError + @abstractmethod async def delete(self): """Deletes the storage.""" raise NotImplementedError + @abstractmethod async def update_peers(self, peers: List[Tuple[int, int, str, List[str], str]]): """ Update the peers table with the provided information. @@ -64,6 +77,22 @@ async def update_peers(self, peers: List[Tuple[int, int, str, List[str], str]]): """ raise NotImplementedError + @abstractmethod + async def update_state(self, update_state: Tuple[int, int, int, int, int] = object): + """Get or set the update state of the current session. + + Parameters: + update_state (``Tuple[int, int, int, int, int]``): A tuple containing the update state to set. + Tuple must contain the following information: + - ``int``: The id of the entity. + - ``int``: The pts. + - ``int``: The qts. + - ``int``: The date. + - ``int``: The seq. + """ + raise NotImplementedError + + @abstractmethod async def get_peer_by_id(self, peer_id: int): """Retrieve a peer by its ID. @@ -73,6 +102,7 @@ async def get_peer_by_id(self, peer_id: int): """ raise NotImplementedError + @abstractmethod async def get_peer_by_username(self, username: str): """Retrieve a peer by its username. @@ -82,6 +112,7 @@ async def get_peer_by_username(self, username: str): """ raise NotImplementedError + @abstractmethod async def get_peer_by_phone_number(self, phone_number: str): """Retrieve a peer by its phone number. @@ -91,6 +122,7 @@ async def get_peer_by_phone_number(self, phone_number: str): """ raise NotImplementedError + @abstractmethod async def dc_id(self, value: int = object): """Get or set the DC ID of the current session. @@ -100,6 +132,7 @@ async def dc_id(self, value: int = object): """ raise NotImplementedError + @abstractmethod async def api_id(self, value: int = object): """Get or set the API ID of the current session. @@ -109,6 +142,7 @@ async def api_id(self, value: int = object): """ raise NotImplementedError + @abstractmethod async def test_mode(self, value: bool = object): """Get or set the test mode of the current session. @@ -118,6 +152,7 @@ async def test_mode(self, value: bool = object): """ raise NotImplementedError + @abstractmethod async def auth_key(self, value: bytes = object): """Get or set the authorization key of the current session. @@ -127,6 +162,7 @@ async def auth_key(self, value: bytes = object): """ raise NotImplementedError + @abstractmethod async def date(self, value: int = object): """Get or set the date of the current session. @@ -136,6 +172,7 @@ async def date(self, value: int = object): """ raise NotImplementedError + @abstractmethod async def user_id(self, value: int = object): """Get or set the user ID of the current session. @@ -145,6 +182,7 @@ async def user_id(self, value: int = object): """ raise NotImplementedError + @abstractmethod async def is_bot(self, value: bool = object): """Get or set the bot flag of the current session. @@ -156,7 +194,7 @@ async def is_bot(self, value: bool = object): async def export_session_string(self): """Exports the session string for the current session. - + Returns: ``str``: The session string for the current session. """ diff --git a/pyrogram/types/bots_and_keyboards/__init__.py b/pyrogram/types/bots_and_keyboards/__init__.py index a9dc8ea6b..66dbd339a 100644 --- a/pyrogram/types/bots_and_keyboards/__init__.py +++ b/pyrogram/types/bots_and_keyboards/__init__.py @@ -43,6 +43,7 @@ from .request_chat_info import RequestChatInfo from .request_user_info import RequestUserInfo from .request_poll_info import RequestPollInfo +from .requested_chats import RequestedChats from .sent_web_app_message import SentWebAppMessage from .web_app_info import WebAppInfo @@ -60,6 +61,7 @@ "RequestChatInfo", "RequestUserInfo", "RequestPollInfo", + "RequestedChats", "LoginUrl", "BotCommand", "BotCommandScope", diff --git a/pyrogram/types/bots_and_keyboards/keyboard_button.py b/pyrogram/types/bots_and_keyboards/keyboard_button.py index 5c8d4b733..7bb4d377b 100644 --- a/pyrogram/types/bots_and_keyboards/keyboard_button.py +++ b/pyrogram/types/bots_and_keyboards/keyboard_button.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + from pyrogram import raw, types from ..object import Object @@ -38,6 +40,12 @@ class KeyboardButton(Object): If True, the user's current location will be sent when the button is pressed. Available in private chats only. + request_poll (:obj:`~pyrogram.types.RequestPollInfo`, *optional*): + If specified, the poll be sent when the button is pressed. + + request_peer (:obj:`~pyrogram.types.RequestPeerTypeChannelInfo` | :obj:`~pyrogram.types.RequestPeerTypeChatInfo` | :obj:`~pyrogram.types.RequestPeerTypeUserInfo`, *optional*): + If specified, the requested peer will be sent when the button is pressed. + web_app (:obj:`~pyrogram.types.WebAppInfo`, *optional*): If specified, the described `Web App `_ will be launched when the button is pressed. The Web App will be able to send a “web_app_data” service message. Available in private @@ -50,13 +58,17 @@ def __init__( text: str, request_contact: bool = None, request_location: bool = None, - web_app: "types.WebAppInfo" = None + request_poll: "types.RequestPollInfo" = None, + request_peer: Union["types.RequestChannelInfo", "types.RequestChatInfo", "types.RequestUserInfo"] = None, + web_app: "types.WebAppInfo" = None, ): super().__init__() self.text = str(text) self.request_contact = request_contact self.request_location = request_location + self.request_poll = request_poll + self.request_peer = request_peer self.web_app = web_app @staticmethod @@ -76,6 +88,56 @@ def read(b): request_location=True ) + if isinstance(b, raw.types.KeyboardButtonRequestPoll): + return KeyboardButton( + text=b.text, + request_poll=types.RequestPollInfo(is_quiz=b.quiz) + ) + + 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_peer=types.RequestChannelInfo( + button_id=b.button_id, + is_creator=getattr(b.peer_type, "creator", None), + has_username=getattr(b.peer_type, "has_username", None), + user_privileges=types.ChatPrivileges._parse(user_privileges) if user_privileges else None, + bot_privileges=types.ChatPrivileges._parse(bot_privileges) if bot_privileges else None + ) + ) + + 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_peer=types.RequestChatInfo( + button_id=b.button_id, + is_creator=getattr(b.peer_type, "creator", None), + is_bot_participant=getattr(b.peer_type, "bot_participant", None), + has_username=getattr(b.peer_type, "has_username", None), + has_forum=getattr(b.peer_type, "forum", None), + user_privileges=types.ChatPrivileges._parse(user_privileges) if user_privileges else None, + bot_privileges=types.ChatPrivileges._parse(bot_privileges) if bot_privileges else None + ) + ) + + if isinstance(b.peer_type, raw.types.RequestPeerTypeUser): + return KeyboardButton( + text=b.text, + request_peer=types.RequestUserInfo( + button_id=b.button_id, + is_bot=getattr(b.peer_type, "bot", None), + is_premium=getattr(b.peer_type, "premium", None), + max_quantity=getattr(b, "max_quantity", None) + ) + ) + if isinstance(b, raw.types.KeyboardButtonSimpleWebView): return KeyboardButton( text=b.text, @@ -89,6 +151,125 @@ def write(self): return raw.types.KeyboardButtonRequestPhone(text=self.text) elif self.request_location: return raw.types.KeyboardButtonRequestGeoLocation(text=self.text) + elif self.request_poll: + return raw.types.KeyboardButtonRequestPoll( + text=self.text, + quiz=self.request_poll.is_quiz + ) + elif self.request_peer: + if isinstance(self.request_peer, types.RequestChannelInfo): + user_privileges = self.request_peer.user_privileges + bot_privileges = self.request_peer.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 + + return raw.types.KeyboardButtonRequestPeer( + text=self.text, + button_id=self.request_peer.button_id, + peer_type=raw.types.RequestPeerTypeBroadcast( + creator=self.request_peer.is_creator, + has_username=self.request_peer.has_username, + user_admin_rights=user_admin_rights, + bot_admin_rights=bot_admin_rights + ), + max_quantity=1 + ) + + if isinstance(self.request_peer, types.RequestChatInfo): + user_privileges = self.request_peer.user_privileges + bot_privileges = self.request_peer.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 + + return raw.types.KeyboardButtonRequestPeer( + text=self.text, + button_id=self.request_peer.button_id, + peer_type=raw.types.RequestPeerTypeChat( + creator=self.request_peer.is_creator, + bot_participant=self.request_peer.is_bot_participant, + has_username=self.request_peer.has_username, + forum=self.request_peer.has_forum, + user_admin_rights=user_admin_rights, + bot_admin_rights=bot_admin_rights + ), + max_quantity=1 + ) + + if isinstance(self.request_peer, types.RequestUserInfo): + return raw.types.KeyboardButtonRequestPeer( + text=self.text, + button_id=self.request_peer.button_id, + peer_type=raw.types.RequestPeerTypeUser( + bot=self.request_peer.is_bot, + premium=self.request_peer.is_premium + ), + max_quantity=self.request_peer.max_quantity + ) + elif self.web_app: return raw.types.KeyboardButtonSimpleWebView(text=self.text, url=self.web_app.url) else: diff --git a/pyrogram/types/bots_and_keyboards/request_channel_info.py b/pyrogram/types/bots_and_keyboards/request_channel_info.py index e639c4a26..56fb456fd 100644 --- a/pyrogram/types/bots_and_keyboards/request_channel_info.py +++ b/pyrogram/types/bots_and_keyboards/request_channel_info.py @@ -58,4 +58,4 @@ def __init__( self.is_creator = is_creator self.has_username = has_username self.user_privileges = user_privileges - self.bot_privileges = bot_privileges \ No newline at end of file + self.bot_privileges = bot_privileges diff --git a/pyrogram/types/bots_and_keyboards/request_chat_info.py b/pyrogram/types/bots_and_keyboards/request_chat_info.py index 62f3412f6..22704dda4 100644 --- a/pyrogram/types/bots_and_keyboards/request_chat_info.py +++ b/pyrogram/types/bots_and_keyboards/request_chat_info.py @@ -71,4 +71,4 @@ def __init__( self.has_username = has_username self.has_forum = has_forum self.user_privileges = user_privileges - self.bot_privileges = bot_privileges \ No newline at end of file + self.bot_privileges = bot_privileges diff --git a/pyrogram/types/bots_and_keyboards/request_poll_info.py b/pyrogram/types/bots_and_keyboards/request_poll_info.py index e800ac2dd..35c749c76 100644 --- a/pyrogram/types/bots_and_keyboards/request_poll_info.py +++ b/pyrogram/types/bots_and_keyboards/request_poll_info.py @@ -33,4 +33,4 @@ def __init__( ): super().__init__() - self.is_quiz = is_quiz \ No newline at end of file + self.is_quiz = is_quiz diff --git a/pyrogram/types/bots_and_keyboards/request_user_info.py b/pyrogram/types/bots_and_keyboards/request_user_info.py index 99a46f5a2..6f96d923e 100644 --- a/pyrogram/types/bots_and_keyboards/request_user_info.py +++ b/pyrogram/types/bots_and_keyboards/request_user_info.py @@ -55,4 +55,4 @@ def __init__( self.button_id = button_id self.is_bot = is_bot self.is_premium = is_premium - self.max_quantity = max_quantity or 1 \ No newline at end of file + self.max_quantity = max_quantity or 1 diff --git a/pyrogram/types/bots_and_keyboards/requested_chats.py b/pyrogram/types/bots_and_keyboards/requested_chats.py new file mode 100644 index 000000000..e132cf9d9 --- /dev/null +++ b/pyrogram/types/bots_and_keyboards/requested_chats.py @@ -0,0 +1,77 @@ +# 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 List + +import pyrogram +from pyrogram import enums +from pyrogram import raw, utils, types + +from ..object import Object + + +class RequestedChats(Object): + """Contains information about requested chats. + + Parameters: + button_id (``int``): + Identifier of button. + + chats (List of :obj:`~pyrogram.types.Chat`): + List of requested chats. + """ + + def __init__( + self, *, + client: "pyrogram.Client" = None, + button_id: int, + chats: List["types.Chat"], + ): + super().__init__(client) + + self.button_id = button_id + self.chats = chats + + @staticmethod + def _parse(client, action: "raw.types.MessageActionRequestedPeer") -> "RequestedChats": + _requested_chats = [] + + for requested_peer in action.peers: + chat_id = utils.get_peer_id(requested_peer) + peer_type = utils.get_peer_type(chat_id) + + if peer_type == "user": + chat_type = enums.ChatType.PRIVATE + elif peer_type == "chat": + chat_type = enums.ChatType.GROUP + else: + chat_type = enums.ChatType.CHANNEL + + _requested_chats.append( + types.Chat( + id=chat_id, + type=chat_type, + client=client + ) + ) + + return RequestedChats( + button_id=action.button_id, + chats=types.List(_requested_chats), + client=client + ) diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py index ab1823d33..6ab7a1a6e 100644 --- a/pyrogram/types/input_media/input_media_video.py +++ b/pyrogram/types/input_media/input_media_video.py @@ -66,6 +66,10 @@ class InputMediaVideo(InputMedia): has_spoiler (``bool``, *optional*): Pass True if the photo needs to be covered with a spoiler animation. + + no_sound (``bool``, *optional*): + Pass True, if the uploaded video is a video message with no sound. + Doesn't work for external links. """ def __init__( @@ -80,6 +84,7 @@ def __init__( duration: int = 0, supports_streaming: bool = True, has_spoiler: bool = None, + no_sound: bool = None, ): super().__init__(media, caption, parse_mode, caption_entities) @@ -89,3 +94,4 @@ def __init__( self.duration = duration self.supports_streaming = supports_streaming self.has_spoiler = has_spoiler + self.no_sound = no_sound diff --git a/pyrogram/types/input_message_content/__init__.py b/pyrogram/types/input_message_content/__init__.py index ec2153286..b445f3ba6 100644 --- a/pyrogram/types/input_message_content/__init__.py +++ b/pyrogram/types/input_message_content/__init__.py @@ -17,10 +17,8 @@ # along with Pyrogram. If not, see . from .input_message_content import InputMessageContent -from .input_reply_to_message import InputReplyToMessage -from .input_reply_to_story import InputReplyToStory from .input_text_message_content import InputTextMessageContent __all__ = [ - "InputMessageContent", "InputReplyToMessage", "InputReplyToStory", "InputTextMessageContent" -] \ No newline at end of file + "InputMessageContent", "InputTextMessageContent" +] diff --git a/pyrogram/types/messages_and_media/__init__.py b/pyrogram/types/messages_and_media/__init__.py index 0b908cc40..73cd07dc3 100644 --- a/pyrogram/types/messages_and_media/__init__.py +++ b/pyrogram/types/messages_and_media/__init__.py @@ -60,4 +60,4 @@ "Message", "MessageEntity", "Photo", "Thumbnail", "StrippedThumbnail", "Story", "Poll", "PollOption", "Sticker", "Venue", "Video", "VideoNote", "Voice", "WebPage", "Dice", "Reaction", "WebAppData", "MessageReactions", "MyBoost" -] \ No newline at end of file +] diff --git a/pyrogram/types/messages_and_media/forum_topic.py b/pyrogram/types/messages_and_media/forum_topic.py index a2525c697..cf1d516dc 100644 --- a/pyrogram/types/messages_and_media/forum_topic.py +++ b/pyrogram/types/messages_and_media/forum_topic.py @@ -69,6 +69,9 @@ class ForumTopic(Object): is_hidden (``bool``, *optional*): True, if the topic is hidden. + + deleted (``bool``, *optional*): + The forum topic is deleted. """ def __init__( @@ -88,7 +91,8 @@ def __init__( is_closed: bool = None, is_pinned: bool = None, is_short: bool = None, - is_hidden: bool = None + is_hidden: bool = None, + deleted: bool = None ): super().__init__() @@ -107,9 +111,13 @@ def __init__( self.is_pinned = is_pinned self.is_short = is_short self.is_hidden = is_hidden + self.deleted = deleted @staticmethod def _parse(client: "pyrogram.Client", forum_topic: "raw.types.ForumTopic", messages: dict = {}, users: dict = {}, chats: dict = {}) -> "ForumTopic": + if isinstance(forum_topic, raw.types.ForumTopicDeleted): + return ForumTopic(id=forum_topic.id, deleted=True) + creator = None peer = getattr(forum_topic, "from_id", None) diff --git a/pyrogram/types/messages_and_media/gift_code.py b/pyrogram/types/messages_and_media/gift_code.py index 70b5571dc..2d32ced39 100644 --- a/pyrogram/types/messages_and_media/gift_code.py +++ b/pyrogram/types/messages_and_media/gift_code.py @@ -76,4 +76,4 @@ def _parse(client, giftcode: "raw.types.MessageActionGiftCode", chats): @property def link(self) -> str: - return f"https://t.me/giftcode/{self.slug}" \ No newline at end of file + return f"https://t.me/giftcode/{self.slug}" diff --git a/pyrogram/types/messages_and_media/giveaway.py b/pyrogram/types/messages_and_media/giveaway.py index 04c18c00a..415e9b941 100644 --- a/pyrogram/types/messages_and_media/giveaway.py +++ b/pyrogram/types/messages_and_media/giveaway.py @@ -97,4 +97,4 @@ def _parse( only_for_countries=types.List(getattr(giveaway, "countries_iso2", [])) or None, winners_are_visible=getattr(giveaway, "winners_are_visible", None), client=client - ) \ No newline at end of file + ) diff --git a/pyrogram/types/messages_and_media/giveaway_result.py b/pyrogram/types/messages_and_media/giveaway_result.py index 842ad1470..950f0448d 100644 --- a/pyrogram/types/messages_and_media/giveaway_result.py +++ b/pyrogram/types/messages_and_media/giveaway_result.py @@ -131,4 +131,4 @@ async def _parse( launch_message=launch_message, description=getattr(giveaway_result, "prize_description", None) or None, client=client - ) \ No newline at end of file + ) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 0591f5ba0..2a5c7bfc2 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -25,7 +25,7 @@ from pyrogram import raw, enums from pyrogram import types from pyrogram import utils -from pyrogram.errors import MessageIdsEmpty, PeerIdInvalid, ChannelPrivate, BotMethodInvalid +from pyrogram.errors import ChannelPrivate, MessageIdsEmpty, PeerIdInvalid, ChannelPrivate, BotMethodInvalid, ChannelForumMissing from pyrogram.parser import utils as parser_utils, Parser from ..object import Object from ..update import Update @@ -37,22 +37,22 @@ class Str(str): def __init__(self, *args): super().__init__() - self.entities = None + self.entities: list = None - def init(self, entities): + def init(self, entities: list): self.entities = entities return self @property - def markdown(self): + def markdown(self) -> str: return Parser.unparse(self, self.entities, False) @property - def html(self): + def html(self) -> str: return Parser.unparse(self, self.entities, True) - def __getitem__(self, item): + def __getitem__(self, item) -> str: return parser_utils.remove_surrogates(parser_utils.add_surrogates(self)[item]) @@ -139,7 +139,7 @@ class Message(Object, Update): This field will contain the enumeration type of the media message. You can use ``media = getattr(message, message.media.value)`` to access the media message. - invert_media (``bool``, *optional*): + show_above_text (``bool``, *optional*): If True, link preview will be shown above the message text. Otherwise, the link preview will be shown below the message text. @@ -300,6 +300,9 @@ class Message(Object, Update): forwards (``int``, *optional*): Channel post forwards. + sender_boost_count (``int``, *optional*): + The number of boosts applied by the sender. + via_bot (:obj:`~pyrogram.types.User`): The information of the bot that generated the message from an inline query of a user. @@ -357,9 +360,18 @@ class Message(Object, Update): gift_code (:obj:`~pyrogram.types.GiftCode`, *optional*): Service message: gift code information. - requested_chats (List of :obj:`~pyrogram.types.Chat`, *optional*): + requested_chats (:obj:`~pyrogram.types.RequestedChats`, *optional*): Service message: requested chats information. + giveaway_launched (``bool``, *optional*): + Service message: giveaway launched. + + chat_ttl_period (``int``, *optional*): + Service message: chat TTL period changed. + + boosts_applied (``int``, *optional*): + Service message: how many boosts were applied. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -367,6 +379,9 @@ class Message(Object, Update): reactions (List of :obj:`~pyrogram.types.Reaction`): List of the reactions to this message. + raw (``pyrogram.raw.types.Message``, *optional*): + The raw message object, as received from the Telegram API. + link (``str``, *property*): Generate a link to this message, only for groups and channels. """ @@ -402,7 +417,7 @@ def __init__( scheduled: bool = None, from_scheduled: bool = None, media: "enums.MessageMediaType" = None, - invert_media: bool = None, + show_above_text: bool = None, edit_date: datetime = None, edit_hidden: bool = None, media_group_id: int = None, @@ -447,6 +462,7 @@ def __init__( game_high_score: int = None, views: int = None, forwards: int = None, + sender_boost_count: int = None, via_bot: "types.User" = None, outgoing: bool = None, quote: bool = None, @@ -464,15 +480,18 @@ def __init__( video_chat_members_invited: "types.VideoChatMembersInvited" = None, web_app_data: "types.WebAppData" = None, gift_code: "types.GiftCode" = None, - requested_chats: List["types.Chat"] = None, + requested_chats: "types.RequestedChats" = None, giveaway_launched: bool = None, + chat_ttl_period: int = None, + boosts_applied: int = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", "types.ReplyKeyboardRemove", "types.ForceReply" ] = None, - reactions: List["types.Reaction"] = None + reactions: List["types.Reaction"] = None, + raw: "raw.types.Message" = None ): super().__init__(client) @@ -501,7 +520,7 @@ def __init__( self.scheduled = scheduled self.from_scheduled = from_scheduled self.media = media - self.invert_media = invert_media + self.show_above_text = show_above_text self.edit_date = edit_date self.edit_hidden = edit_hidden self.media_group_id = media_group_id @@ -546,6 +565,7 @@ def __init__( self.game_high_score = game_high_score self.views = views self.forwards = forwards + self.sender_boost_count = sender_boost_count self.via_bot = via_bot self.outgoing = outgoing self.quote = quote @@ -566,7 +586,10 @@ def __init__( self.gift_code = gift_code self.requested_chats = requested_chats self.giveaway_launched = giveaway_launched + self.chat_ttl_period = chat_ttl_period + self.boosts_applied = boosts_applied self.reactions = reactions + self.raw = raw @staticmethod async def _parse( @@ -579,7 +602,7 @@ async def _parse( replies: int = 1 ): if isinstance(message, raw.types.MessageEmpty): - return Message(id=message.id, empty=True, client=client) + return Message(id=message.id, empty=True, client=client, raw=message) from_id = utils.get_raw_peer_id(message.from_id) peer_id = utils.get_raw_peer_id(message.peer_id) @@ -629,6 +652,8 @@ async def _parse( gift_code = None giveaway_launched = None requested_chats = None + chat_ttl_period = None + boosts_applied = None service_type = None @@ -708,30 +733,14 @@ async def _parse( gift_code = types.GiftCode._parse(client, action, chats) service_type = enums.MessageServiceType.GIFT_CODE elif isinstance(action, raw.types.MessageActionRequestedPeer): - _requested_chats = [] - - for requested_peer in action.peers: - chat_id = utils.get_peer_id(requested_peer) - peer_type = utils.get_peer_type(chat_id) - - if peer_type == "user": - chat_type = enums.ChatType.PRIVATE - elif peer_type == "chat": - chat_type = enums.ChatType.GROUP - else: - chat_type = enums.ChatType.CHANNEL - - _requested_chats.append( - types.Chat( - id=chat_id, - type=chat_type, - client=client - ) - ) - - requested_chats = types.List(_requested_chats) or None - + requested_chats = types.RequestedChats._parse(client, action) service_type = enums.MessageServiceType.REQUESTED_CHAT + elif isinstance(action, raw.types.MessageActionSetMessagesTTL): + chat_ttl_period = action.period + service_type = enums.MessageServiceType.CHAT_TTL_CHANGED + elif isinstance(action, raw.types.MessageActionBoostApply): + boosts_applied = action.boosts + service_type = enums.MessageServiceType.BOOST_APPLY from_user = types.User._parse(client, users.get(user_id, None)) sender_chat = types.Chat._parse(client, message, users, chats, is_chat=False) if not from_user else None @@ -768,6 +777,9 @@ async def _parse( giveaway_launched=giveaway_launched, gift_code=gift_code, requested_chats=requested_chats, + chat_ttl_period=chat_ttl_period, + boosts_applied=boosts_applied, + raw=message, client=client # TODO: supergroup_chat_created ) @@ -891,7 +903,7 @@ async def _parse( else: try: story = await client.get_stories(utils.get_peer_id(media.peer), media.id) - except BotMethodInvalid: + except (BotMethodInvalid, ChannelPrivate): story = await types.Story._parse(client, media, users, chats, media.peer) media_type = enums.MessageMediaType.STORY @@ -919,7 +931,7 @@ async def _parse( video_attributes = attributes[raw.types.DocumentAttributeVideo] if video_attributes.round_message: - video_note = types.VideoNote._parse(client, doc, video_attributes) + video_note = types.VideoNote._parse(client, doc, video_attributes, media.ttl_seconds) media_type = enums.MessageMediaType.VIDEO_NOTE else: video = types.Video._parse(client, doc, video_attributes, file_name, media.ttl_seconds) @@ -929,7 +941,7 @@ async def _parse( audio_attributes = attributes[raw.types.DocumentAttributeAudio] if audio_attributes.voice: - voice = types.Voice._parse(client, doc, audio_attributes) + voice = types.Voice._parse(client, doc, audio_attributes, media.ttl_seconds) media_type = enums.MessageMediaType.VOICE else: audio = types.Audio._parse(client, doc, audio_attributes, file_name) @@ -1018,7 +1030,7 @@ async def _parse( scheduled=is_scheduled, from_scheduled=message.from_scheduled, media=media_type, - invert_media=getattr(message, "invert_media", None), + show_above_text=getattr(message, "invert_media", None), edit_date=utils.timestamp_to_datetime(message.edit_date), edit_hidden=message.edit_hide, media_group_id=message.grouped_id, @@ -1042,10 +1054,12 @@ async def _parse( dice=dice, views=message.views, forwards=message.forwards, + sender_boost_count=getattr(message, "from_boosts_applied", None), via_bot=types.User._parse(client, users.get(message.via_bot_id, None)), outgoing=message.out, reply_markup=reply_markup, reactions=reactions, + raw=message, client=client ) @@ -1084,9 +1098,9 @@ async def _parse( parsed_message.reply_to_message_id = message.reply_to.reply_to_msg_id parsed_message.reply_to_top_message_id = message.reply_to.reply_to_top_id - else: + elif isinstance(message.reply_to, raw.types.MessageReplyStoryHeader): parsed_message.reply_to_story_id = message.reply_to.story_id - parsed_message.reply_to_story_user_id = message.reply_to.user_id + parsed_message.reply_to_story_user_id = utils.get_peer_id(message.reply_to.peer) if replies: if parsed_message.reply_to_message_id: @@ -1112,6 +1126,8 @@ async def _parse( pass if reply_to_message and not reply_to_message.forum_topic_created: parsed_message.reply_to_message = reply_to_message + except ChannelPrivate: + pass except MessageIdsEmpty: pass elif parsed_message.reply_to_story_id: @@ -1131,7 +1147,7 @@ async def _parse( chat_id=parsed_message.chat.id, topic_ids=parsed_message.message_thread_id or 1 ) - except BotMethodInvalid: + except (BotMethodInvalid, ChannelForumMissing): pass if not parsed_message.poll: # Do not cache poll messages @@ -1195,7 +1211,7 @@ async def reply_text( disable_web_page_preview: bool = None, disable_notification: bool = None, message_thread_id: int = None, - invert_media: bool = None, + show_above_text: bool = None, reply_to_message_id: int = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, @@ -1249,7 +1265,7 @@ async def reply_text( Unique identifier of a message thread to which the message belongs. For supergroups only. - invert_media (``bool``, *optional*): + show_above_text (``bool``, *optional*): If True, link preview will be shown above the message text. Otherwise, the link preview will be shown below the message text. @@ -1295,7 +1311,7 @@ async def reply_text( disable_web_page_preview=disable_web_page_preview, disable_notification=disable_notification, message_thread_id=message_thread_id, - invert_media=invert_media, + show_above_text=show_above_text, reply_to_message_id=reply_to_message_id, quote_text=quote_text, quote_entities=quote_entities, @@ -1895,6 +1911,7 @@ async def reply_document( quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, schedule_date: datetime = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -1981,6 +1998,9 @@ async def reply_document( 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. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -2039,6 +2059,7 @@ async def reply_document( quote_text=quote_text, quote_entities=quote_entities, schedule_date=schedule_date, + protect_content=protect_content, reply_markup=reply_markup, progress=progress, progress_args=progress_args @@ -2975,6 +2996,7 @@ async def reply_video( reply_to_message_id: int = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, + no_sound: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -3065,6 +3087,10 @@ async def reply_video( quote_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in quote text, which can be specified instead of *parse_mode*. + no_sound (``bool``, *optional*): + Pass True, if the uploaded video is a video message with no sound. + Doesn't work for external links. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -3126,6 +3152,7 @@ async def reply_video( reply_to_message_id=reply_to_message_id, quote_text=quote_text, quote_entities=quote_entities, + no_sound=no_sound, reply_markup=reply_markup, progress=progress, progress_args=progress_args @@ -3144,6 +3171,8 @@ async def reply_video_note( quote_text: str = None, parse_mode: Optional["enums.ParseMode"] = None, quote_entities: List["types.MessageEntity"] = None, + protect_content: bool = None, + view_once: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -3214,6 +3243,13 @@ async def reply_video_note( quote_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in quote text, which can be specified instead of *parse_mode*. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + + view_once (``bool``, *optional*): + Self-Destruct Timer. + If True, the video note will self-destruct after it was viewed. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -3269,6 +3305,8 @@ async def reply_video_note( quote_text=quote_text, parse_mode=parse_mode, quote_entities=quote_entities, + protect_content=protect_content, + view_once=view_once, reply_markup=reply_markup, progress=progress, progress_args=progress_args @@ -3287,7 +3325,7 @@ async def reply_voice( reply_to_message_id: int = None, quote_text: str = None, quote_entities: List["types.MessageEntity"] = None, - ttl_seconds: int = None, + view_once: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -3355,10 +3393,9 @@ async def reply_voice( quote_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in quote text, which can be specified instead of *parse_mode*. - ttl_seconds (``int``, *optional*): + view_once (``bool``, *optional*): Self-Destruct Timer. - If you set a timer, the voice note will self-destruct in *ttl_seconds* - seconds after it was listened. + If True, the voice note will self-destruct after it was listened. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, @@ -3415,18 +3452,149 @@ async def reply_voice( reply_to_message_id=reply_to_message_id, quote_text=quote_text, quote_entities=quote_entities, - ttl_seconds=ttl_seconds, + view_once=view_once, reply_markup=reply_markup, progress=progress, progress_args=progress_args ) + async def reply_web_page( + self, + text: str = None, + url: str = None, + prefer_large_media: bool = None, + prefer_small_media: bool = None, + parse_mode: Optional["enums.ParseMode"] = None, + entities: List["types.MessageEntity"] = None, + disable_notification: bool = None, + message_thread_id: int = None, + show_above_text: bool = None, + reply_to_message_id: int = None, + reply_to_chat_id: Union[int, str] = None, + reply_to_story_id: int = None, + quote_text: str = None, + quote_entities: List["types.MessageEntity"] = None, + quote_offset: int = None, + schedule_date: datetime = None, + protect_content: bool = None, + reply_markup: Union[ + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" + ] = None + ) -> "types.Message": + """Bound method *reply_web_page* of :obj:`~pyrogram.types.Message`. + + Use as a shortcut for: + + .. code-block:: python + + await client.send_web_page( + chat_id=message.chat.id, + url="https://docs.pyrogram.org" + ) + + Example: + .. code-block:: python + + await message.reply_web_page("https://docs.pyrogram.org") + + Parameters: + text (``str``, *optional*): + Text of the message to be sent. + + url (``str``, *optional*): + Link that will be previewed. + If url not specified, the first URL found in the text will be used. + + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + + entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in message text, which can be specified instead of *parse_mode*. + + prefer_large_media (``bool``, *optional*): + If True, media in the link preview will be larger. + Ignored if the URL isn't explicitly specified or media size change isn't supported for the preview. + + prefer_small_media (``bool``, *optional*): + If True, media in the link preview will be smaller. + Ignored if the URL isn't explicitly specified or media size change isn't supported for the preview. + + show_above_text (``bool``, *optional*): + If True, link preview will be shown above the message text. + Otherwise, the link preview will be shown below the message text. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + message_thread_id (``int``, *optional*): + Unique identifier for the target message thread (topic) of the forum. + for forum supergroups only. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_to_chat_id (``int`` | ``str``, *optional*): + If the message is a reply, ID of the original chat. + + reply_to_story_id (``int``, *optional*): + Unique identifier for the target story. + + quote_text (``str``, *optional*): + Text of the quote to be sent. + + quote_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*): + List of special entities that appear in quote text, which can be specified instead of *parse_mode*. + + quote_offset (``int``, *optional*): + Offset for quote in original message. + + 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. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + :obj:`~pyrogram.types.Message`: On success, the sent message is returned. + """ + return await self._client.send_web_page( + chat_id=self.chat.id, + text=text, + url=url, + prefer_large_media=prefer_large_media, + prefer_small_media=prefer_small_media, + parse_mode=parse_mode, + entities=entities, + disable_notification=disable_notification, + message_thread_id=message_thread_id, + show_above_text=show_above_text, + reply_to_message_id=reply_to_message_id, + reply_to_chat_id=reply_to_chat_id, + reply_to_story_id=reply_to_story_id, + quote_text=quote_text, + quote_entities=quote_entities, + quote_offset=quote_offset, + schedule_date=schedule_date, + protect_content=protect_content, + reply_markup=reply_markup + ) + async def edit_text( self, text: str, parse_mode: Optional["enums.ParseMode"] = None, entities: List["types.MessageEntity"] = None, disable_web_page_preview: bool = None, + show_above_text: bool = None, reply_markup: "types.InlineKeyboardMarkup" = None ) -> "Message": """Bound method *edit_text* of :obj:`~pyrogram.types.Message`. @@ -3462,6 +3630,10 @@ async def edit_text( disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. + show_above_text (``bool``, *optional*): + If True, link preview will be shown above the message text. + Otherwise, the link preview will be shown below the message text. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. @@ -3478,6 +3650,7 @@ async def edit_text( parse_mode=parse_mode, entities=entities, disable_web_page_preview=disable_web_page_preview, + show_above_text=show_above_text, reply_markup=reply_markup ) @@ -3618,6 +3791,8 @@ async def forward( chat_id: Union[int, str], message_thread_id: int = None, disable_notification: bool = None, + hide_sender_name: bool = None, + hide_captions: bool = None, schedule_date: datetime = None ) -> Union["types.Message", List["types.Message"]]: """Bound method *forward* of :obj:`~pyrogram.types.Message`. @@ -3654,6 +3829,12 @@ async def forward( schedule_date (:py:obj:`~datetime.datetime`, *optional*): Date when the message will be automatically sent. + hide_sender_name (``bool``, *optional*): + If True, the original author of the message will not be shown. + + hide_captions (``bool``, *optional*): + If True, the original media captions will be removed. + Returns: On success, the forwarded Message is returned. @@ -3666,7 +3847,9 @@ async def forward( message_ids=self.id, message_thread_id=message_thread_id, disable_notification=disable_notification, - schedule_date=schedule_date + schedule_date=schedule_date, + hide_sender_name=hide_sender_name, + hide_captions=hide_captions ) async def copy( diff --git a/pyrogram/types/messages_and_media/my_boost.py b/pyrogram/types/messages_and_media/my_boost.py index 12ed0cc32..d0b50cdd8 100644 --- a/pyrogram/types/messages_and_media/my_boost.py +++ b/pyrogram/types/messages_and_media/my_boost.py @@ -28,7 +28,7 @@ class MyBoost(Object): Parameters: slot (``int``): - Unique user identifier of story sender. + Boost slot. date (:py:obj:`~datetime.datetime`): Date the boost was sent. @@ -74,6 +74,6 @@ def _parse(client: "pyrogram.Client", my_boost: "raw.types.MyBoost", users, chat slot=my_boost.slot, chat=chat, date=utils.timestamp_to_datetime(my_boost.date), - expire_date=utils.timestamp_to_datetime(my_boost.expire_date), + expire_date=utils.timestamp_to_datetime(my_boost.expires), cooldown_until_date=utils.timestamp_to_datetime(my_boost.cooldown_until_date), - ) \ No newline at end of file + ) diff --git a/pyrogram/types/messages_and_media/poll.py b/pyrogram/types/messages_and_media/poll.py index 10a10e444..cdb97ea10 100644 --- a/pyrogram/types/messages_and_media/poll.py +++ b/pyrogram/types/messages_and_media/poll.py @@ -201,40 +201,3 @@ def _parse_update(client, update: "raw.types.UpdateMessagePoll"): correct_option_id=correct_option_id, client=client ) - - async def stop( - self, - reply_markup: "types.InlineKeyboardMarkup" = None - ) -> "types.Poll": - """Bound method *stop* of :obj:`~pyrogram.types.Poll`. - - Use as a shortcut for: - - .. code-block:: python - - client.stop_poll( - chat_id=message.chat.id, - message_id=message.id, - ) - - Parameters: - reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): - An InlineKeyboardMarkup object. - - Example: - .. code-block:: python - - message.poll.stop() - - Returns: - :obj:`~pyrogram.types.Poll`: On success, the stopped poll with the final results is returned. - - Raises: - RPCError: In case of a Telegram RPC error. - """ - - return await self._client.stop_poll( - chat_id=self.chat.id, - message_id=self.id, - reply_markup=reply_markup - ) diff --git a/pyrogram/types/messages_and_media/story.py b/pyrogram/types/messages_and_media/story.py index 0260cd399..ea7312678 100644 --- a/pyrogram/types/messages_and_media/story.py +++ b/pyrogram/types/messages_and_media/story.py @@ -123,6 +123,9 @@ class Story(Object, Update): deleted (``bool``, *optional*): The story is deleted. A story can be deleted in case it was deleted or you tried to retrieve a story that doesn't exist yet. + + raw (``pyrogram.raw.types.StoryItem``, *optional*): + The raw story object, as received from the Telegram API. """ # TODO: Add Media Areas @@ -160,7 +163,8 @@ def __init__( disallowed_users: List[Union[int, str]] = None, reactions: List["types.Reaction"] = None, skipped: bool = None, - deleted: bool = None + deleted: bool = None, + raw: "raw.types.StoryItem" = None ): super().__init__(client) @@ -194,6 +198,7 @@ def __init__( self.reactions = reactions self.skipped = skipped self.deleted = deleted + self.raw = raw @staticmethod async def _parse( @@ -340,9 +345,17 @@ async def _parse( allowed_users=allowed_users, disallowed_users=disallowed_users, reactions=reactions, + raw=story, client=client ) + @property + def link(self) -> str: + if not self.chat.username: + return None + + return f"https://t.me/{self.chat.username}/s/{self.id}" + async def reply_text( self, text: str, @@ -1023,6 +1036,7 @@ async def reply_video( file_name: str = None, supports_streaming: bool = True, disable_notification: bool = None, + no_sound: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -1100,6 +1114,10 @@ async def reply_video( Sends the message silently. Users will receive a notification with no sound. + no_sound (``bool``, *optional*): + Pass True, if the uploaded video is a video message with no sound. + Doesn't work for external links. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -1149,6 +1167,7 @@ async def reply_video( file_name=file_name, supports_streaming=supports_streaming, disable_notification=disable_notification, + no_sound=no_sound, reply_to_story_id=self.id, reply_markup=reply_markup, progress=progress, @@ -1162,6 +1181,7 @@ async def reply_video_note( length: int = 1, thumb: Union[str, BinaryIO] = None, disable_notification: bool = None, + view_once: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -1211,6 +1231,10 @@ async def reply_video_note( Sends the message silently. Users will receive a notification with no sound. + view_once (``bool``, *optional*): + Self-Destruct Timer. + If True, the video note will self-destruct after it was viewed. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -1253,6 +1277,7 @@ async def reply_video_note( thumb=thumb, disable_notification=disable_notification, reply_to_story_id=self.id, + view_once=view_once, reply_markup=reply_markup, progress=progress, progress_args=progress_args @@ -1266,6 +1291,7 @@ async def reply_voice( caption_entities: List["types.MessageEntity"] = None, duration: int = 0, disable_notification: bool = None, + view_once: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -1316,6 +1342,10 @@ async def reply_voice( Sends the message silently. Users will receive a notification with no sound. + view_once (``bool``, *optional*): + Self-Destruct Timer. + If True, the voice note will self-destruct after it was listened. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -1359,6 +1389,7 @@ async def reply_voice( duration=duration, disable_notification=disable_notification, reply_to_story_id=self.id, + view_once=view_once, reply_markup=reply_markup, progress=progress, progress_args=progress_args @@ -1617,31 +1648,6 @@ async def edit_privacy( disallowed_users=disallowed_users, ) - async def export_link(self) -> "types.ExportedStoryLink": - """Bound method *export_link* of :obj:`~pyrogram.types.Story`. - - Use as a shortcut for: - - .. code-block:: python - - await client.export_story_link( - chat_id=self.chat.id, - story_id=story.id - ) - - Example: - .. code-block:: python - - link = await story.export_link() - - Returns: - ``str``: On success, a link to the story as string is returned. - - Raises: - RPCError: In case of a Telegram RPC error. - """ - return await self._client.export_story_link(chat_id=self.chat.id, story_id=self.id) - async def react(self, emoji: Union[int, str] = None) -> bool: """Bound method *react* of :obj:`~pyrogram.types.Story`. @@ -1800,7 +1806,7 @@ async def download( ``ValueError``: If the message doesn't contain any downloadable media """ return await self._client.download_media( - message=getattr(self, self.media.value), + message=self, file_name=file_name, in_memory=in_memory, block=block, @@ -1828,4 +1834,4 @@ async def read(self) -> List[int]: return await self._client.read_stories( chat_id=self.chat.id, max_id=self.id - ) \ No newline at end of file + ) diff --git a/pyrogram/types/messages_and_media/video_note.py b/pyrogram/types/messages_and_media/video_note.py index 3e6b40d0c..9aa70449f 100644 --- a/pyrogram/types/messages_and_media/video_note.py +++ b/pyrogram/types/messages_and_media/video_note.py @@ -52,6 +52,9 @@ class VideoNote(Object): date (:py:obj:`~datetime.datetime`, *optional*): Date the video note was sent. + ttl_seconds (``int``, *optional*): + Time-to-live seconds, for one-time media. + thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*): Video thumbnails. """ @@ -67,7 +70,8 @@ def __init__( thumbs: List["types.Thumbnail"] = None, mime_type: str = None, file_size: int = None, - date: datetime = None + date: datetime = None, + ttl_seconds: int = None ): super().__init__(client) @@ -76,6 +80,7 @@ def __init__( self.mime_type = mime_type self.file_size = file_size self.date = date + self.ttl_seconds = ttl_seconds self.length = length self.duration = duration self.thumbs = thumbs @@ -84,7 +89,8 @@ def __init__( def _parse( client, video_note: "raw.types.Document", - video_attributes: "raw.types.DocumentAttributeVideo" + video_attributes: "raw.types.DocumentAttributeVideo", + ttl_seconds: int = None ) -> "VideoNote": return VideoNote( file_id=FileId( @@ -103,6 +109,7 @@ def _parse( file_size=video_note.size, mime_type=video_note.mime_type, date=utils.timestamp_to_datetime(video_note.date), + ttl_seconds=ttl_seconds, thumbs=types.Thumbnail._parse(client, video_note), client=client ) diff --git a/pyrogram/types/messages_and_media/voice.py b/pyrogram/types/messages_and_media/voice.py index 8d1c15f65..adceafb7f 100644 --- a/pyrogram/types/messages_and_media/voice.py +++ b/pyrogram/types/messages_and_media/voice.py @@ -49,6 +49,9 @@ class Voice(Object): date (:py:obj:`~datetime.datetime`, *optional*): Date the voice was sent. + + ttl_seconds (``int``, *optional*): + Time-to-live seconds, for one-time media. """ def __init__( @@ -61,7 +64,8 @@ def __init__( waveform: bytes = None, mime_type: str = None, file_size: int = None, - date: datetime = None + date: datetime = None, + ttl_seconds: int = None ): super().__init__(client) @@ -72,9 +76,10 @@ def __init__( self.mime_type = mime_type self.file_size = file_size self.date = date + self.ttl_seconds = ttl_seconds @staticmethod - def _parse(client, voice: "raw.types.Document", attributes: "raw.types.DocumentAttributeAudio") -> "Voice": + def _parse(client, voice: "raw.types.Document", attributes: "raw.types.DocumentAttributeAudio", ttl_seconds: int = None) -> "Voice": return Voice( file_id=FileId( file_type=FileType.VOICE, @@ -92,5 +97,6 @@ def _parse(client, voice: "raw.types.Document", attributes: "raw.types.DocumentA file_size=voice.size, waveform=attributes.waveform, date=utils.timestamp_to_datetime(voice.date), + ttl_seconds=ttl_seconds, client=client ) diff --git a/pyrogram/types/messages_and_media/web_page.py b/pyrogram/types/messages_and_media/web_page.py index 9cf55478d..ef513f017 100644 --- a/pyrogram/types/messages_and_media/web_page.py +++ b/pyrogram/types/messages_and_media/web_page.py @@ -80,11 +80,11 @@ class WebPage(Object): has_large_media (``bool``, *optional*): Whether the webpage preview is large. - force_large_media (``bool``, *optional*): - Whether the webpage preview is forced large. + prefer_large_media (``bool``, *optional*): + Whether the webpage preview is large. - force_small_media (``bool``, *optional*): - Whether the webpage preview is forced small. + prefer_small_media (``bool``, *optional*): + Whether the webpage preview is small. manual (``bool``, *optional*): Whether the webpage preview was changed by the user. @@ -120,8 +120,8 @@ def __init__( embed_width: int = None, embed_height: int = None, has_large_media: bool = None, - force_large_media: bool = None, - force_small_media: bool = None, + prefer_large_media: bool = None, + prefer_small_media: bool = None, manual: bool = None, safe: bool = None, duration: int = None, @@ -146,8 +146,8 @@ def __init__( self.embed_width = embed_width self.embed_height = embed_height self.has_large_media = has_large_media - self.force_large_media = force_large_media - self.force_small_media = force_small_media + self.prefer_large_media = prefer_large_media + self.prefer_small_media = prefer_small_media self.manual = manual self.safe = safe self.duration = duration @@ -157,8 +157,8 @@ def __init__( def _parse( client, webpage: "raw.types.WebPage", - force_large_media: bool = None, - force_small_media: bool = None, + prefer_large_media: bool = None, + prefer_small_media: bool = None, manual: bool = None, safe: bool = None ) -> "WebPage": @@ -215,8 +215,8 @@ def _parse( embed_width=webpage.embed_width, embed_height=webpage.embed_height, has_large_media=webpage.has_large_media, - force_large_media=force_large_media, - force_small_media=force_small_media, + prefer_large_media=prefer_large_media, + prefer_small_media=prefer_small_media, manual=manual, safe=safe, duration=webpage.duration, diff --git a/pyrogram/types/object.py b/pyrogram/types/object.py index 3253c8ecf..aa898e6ad 100644 --- a/pyrogram/types/object.py +++ b/pyrogram/types/object.py @@ -60,16 +60,22 @@ def default(obj: "Object"): if isinstance(obj, datetime): return str(obj) + attributes_to_hide = [ + "raw" + ] + + filtered_attributes = { + attr: ("*" * 9 if attr == "phone_number" else getattr(obj, attr)) + for attr in filter( + lambda x: not x.startswith("_") and x not in attributes_to_hide, + obj.__dict__, + ) + if getattr(obj, attr) is not None + } + return { "_": obj.__class__.__name__, - **{ - attr: ( - "*" * 9 if attr == "phone_number" else - getattr(obj, attr) - ) - for attr in filter(lambda x: not x.startswith("_"), obj.__dict__) - if getattr(obj, attr) is not None - } + **filtered_attributes } def __str__(self) -> str: diff --git a/pyrogram/types/update.py b/pyrogram/types/update.py index a048b0dc7..d3e45b4ab 100644 --- a/pyrogram/types/update.py +++ b/pyrogram/types/update.py @@ -20,10 +20,8 @@ class Update: - @staticmethod - def stop_propagation(): + def stop_propagation(self): raise pyrogram.StopPropagation - @staticmethod - def continue_propagation(): + def continue_propagation(self): raise pyrogram.ContinuePropagation diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py index cfc2f5eab..e64034a42 100644 --- a/pyrogram/types/user_and_chats/__init__.py +++ b/pyrogram/types/user_and_chats/__init__.py @@ -16,6 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from .business_info import BusinessInfo +from .business_message import BusinessMessage +from .business_recipients import BusinessRecipients +from .business_weekly_open import BusinessWeeklyOpen +from .business_working_hours import BusinessWorkingHours from .chat import Chat from .chat_admin_with_invite_links import ChatAdminWithInviteLinks from .chat_color import ChatColor @@ -44,6 +49,11 @@ from .video_chat_started import VideoChatStarted __all__ = [ + "BusinessInfo", + "BusinessMessage", + "BusinessRecipients", + "BusinessWeeklyOpen", + "BusinessWorkingHours", "Chat", "ChatMember", "ChatPermissions", @@ -70,4 +80,4 @@ "EmojiStatus", "Folder", "ChatReactions" -] \ No newline at end of file +] diff --git a/pyrogram/types/user_and_chats/business_info.py b/pyrogram/types/user_and_chats/business_info.py new file mode 100644 index 000000000..1c5117ad9 --- /dev/null +++ b/pyrogram/types/user_and_chats/business_info.py @@ -0,0 +1,81 @@ +# 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 Optional + +from pyrogram import types, raw +from ..object import Object + + +class BusinessInfo(Object): + """Business information of a user. + + Parameters: + address (``str``, *optional*): + Address of the business. + + location (:obj:`~pyrogram.types.Location`, *optional*): + Location of the business on the map. + + greeting_message (:obj:`~pyrogram.types.BusinessMessage`, *optional*): + Greeting message of the business. + + away_message (:obj:`~pyrogram.types.BusinessMessage`, *optional*): + Away message of the business. + + working_hours (:obj:`~pyrogram.types.BusinessWorkingHours`, *optional*): + Working hours of the business. + """ + + def __init__( + self, + *, + address: str = None, + location: "types.Location" = None, + greeting_message: "types.BusinessMessage" = None, + away_message: "types.BusinessMessage" = None, + working_hours: "types.BusinessWorkingHours" = None, + + ): + self.address = address + self.location = location + self.greeting_message = greeting_message + self.away_message = away_message + self.working_hours = working_hours + + @staticmethod + def _parse( + client, + user: "raw.types.UserFull" = None, + users: dict = None + ) -> Optional["BusinessInfo"]: + working_hours = getattr(user, "business_work_hours", None) + location = getattr(user, "business_location", None) + greeting_message = getattr(user, "business_greeting_message", None) + away_message = getattr(user, "business_away_message", None) + + if not any((working_hours, location, greeting_message, away_message)): + return None + + return BusinessInfo( + address=getattr(location, "address", None), + location=types.Location._parse(client, getattr(location, "geo_point", None)), + greeting_message=types.BusinessMessage._parse(client, greeting_message, users), + away_message=types.BusinessMessage._parse(client, away_message, users), + working_hours=types.BusinessWorkingHours._parse(working_hours), + ) diff --git a/pyrogram/types/user_and_chats/business_message.py b/pyrogram/types/user_and_chats/business_message.py new file mode 100644 index 000000000..f9d75a921 --- /dev/null +++ b/pyrogram/types/user_and_chats/business_message.py @@ -0,0 +1,111 @@ +# 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 Optional, Union, List + +from pyrogram import types, enums, raw, utils +from ..object import Object + + +class BusinessMessage(Object): + """Business working hours. + + Parameters: + shortcut_id (``int``): + ID of the shortcut. + + is_greeting (``bool``, *optional*): + True, if the message is a greeting message. + + is_away (``bool``, *optional*): + True, if the message is an away message. + + no_activity_days (``int``, *optional*): + Period of inactivity after which the greeting message should be sent again. + + offline_only (``bool``, *optional*): + Dont send the away message if you've recently been online. + + recipients (List of :obj:`~pyrogram.types.User`, *optional*): + Recipients of the message. + + schedule (:obj:`~pyrogram.enums.BusinessSchedule`, *optional*): + Schedule of the away message to be sent. + + start_date (``datetime``, *optional*): + Start date of the away message. + + end_date (``datetime``, *optional*): + End date of the away message. + """ + + def __init__( + self, + *, + shortcut_id: int, + is_greeting: bool = None, + is_away: bool = None, + no_activity_days: int = None, + offline_only: bool = None, + recipients: List["types.User"] = None, + schedule: "enums.BusinessSchedule" = None, + start_date: datetime = None, + end_date: datetime = None, + + ): + self.shortcut_id = shortcut_id + self.is_greeting = is_greeting + self.is_away = is_away + self.no_activity_days = no_activity_days + self.offline_only = offline_only + self.recipients = recipients + self.schedule = schedule + self.start_date = start_date + self.end_date = end_date + + @staticmethod + def _parse( + client, + message: Union["raw.types.BusinessGreetingMessage", "raw.types.BusinessAwayMessage"] = None, + users: dict = None + ) -> Optional["BusinessMessage"]: + if not message: + return None + + schedule = None + + if isinstance(message, raw.types.BusinessAwayMessage): + if isinstance(message.schedule, raw.types.BusinessAwayMessageScheduleAlways): + schedule = enums.BusinessSchedule.ALWAYS + elif isinstance(message.schedule, raw.types.BusinessAwayMessageScheduleOutsideWorkHours): + schedule = enums.BusinessSchedule.OUTSIDE_WORK_HOURS + elif isinstance(message.schedule, raw.types.BusinessAwayMessageScheduleCustom): + schedule = enums.BusinessSchedule.CUSTOM + + return BusinessMessage( + shortcut_id=message.shortcut_id, + is_greeting=isinstance(message, raw.types.BusinessGreetingMessage), + is_away=isinstance(message, raw.types.BusinessAwayMessage), + no_activity_days=getattr(message, "no_activity_days", None), + offline_only=getattr(message, "offline_only", None), + recipients=types.BusinessRecipients._parse(client, message.recipients, users), + schedule=schedule, + start_date=utils.timestamp_to_datetime(message.schedule.start_date) if schedule == enums.BusinessSchedule.CUSTOM else None, + end_date=utils.timestamp_to_datetime(message.schedule.end_date) if schedule == enums.BusinessSchedule.CUSTOM else None + ) diff --git a/pyrogram/types/user_and_chats/business_recipients.py b/pyrogram/types/user_and_chats/business_recipients.py new file mode 100644 index 000000000..eaf6bd420 --- /dev/null +++ b/pyrogram/types/user_and_chats/business_recipients.py @@ -0,0 +1,78 @@ +# 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 List + +from pyrogram import types, raw +from ..object import Object + + +class BusinessRecipients(Object): + """Business recipients. + + Parameters: + existing_chats (``bool``, *optional*): + True, if the message should be sent to existing chats. + + new_chats (``bool``, *optional*): + True, if the message should be sent to new chats. + + contacts (``bool``, *optional*): + True, if the message should be sent to contacts. + + non_contacts (``bool``, *optional*): + True, if the message should be sent to non-contacts. + + exclude_selected (``bool``, *optional*): + True, if the message should be sent to non-selected contacts. + + users (List of :obj:`~pyrogram.types.User`, *optional*): + Recipients of the message. + """ + + def __init__( + self, + *, + existing_chats: bool = None, + new_chats: bool = None, + contacts: bool = None, + non_contacts: bool = None, + exclude_selected: bool = None, + users: List[int] = None + ): + self.existing_chats = existing_chats + self.new_chats = new_chats + self.contacts = contacts + self.non_contacts = non_contacts + self.exclude_selected = exclude_selected + self.users = users + + @staticmethod + def _parse( + client, + recipients: "raw.types.BusinessRecipients", + users: dict = None + ) -> "BusinessRecipients": + return BusinessRecipients( + existing_chats=getattr(recipients, "existing_chats", None), + new_chats=getattr(recipients, "new_chats", None), + contacts=getattr(recipients, "contacts", None), + non_contacts=getattr(recipients, "non_contacts", None), + exclude_selected=getattr(recipients, "exclude_selected", None), + users=types.List(types.User._parse(client, users[i]) for i in recipients.users) or None if getattr(recipients, "users", None) else None + ) diff --git a/pyrogram/types/user_and_chats/business_weekly_open.py b/pyrogram/types/user_and_chats/business_weekly_open.py new file mode 100644 index 000000000..aa63a9375 --- /dev/null +++ b/pyrogram/types/user_and_chats/business_weekly_open.py @@ -0,0 +1,49 @@ +# 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 pyrogram import raw +from ..object import Object + + +class BusinessWeeklyOpen(Object): + """Business weekly open hours. + + Parameters: + start_minute (``int``): + Start minute of the working day. + + end_minute (``int``): + End minute of the working day. + """ + + def __init__( + self, + *, + start_minute: int, + end_minute: int, + + ): + self.start_minute = start_minute + self.end_minute = end_minute + + @staticmethod + def _parse(weekly_open: "raw.types.BusinessWeeklyOpen" = None) -> "BusinessWeeklyOpen": + return BusinessWeeklyOpen( + start_minute=weekly_open.start_minute, + end_minute=weekly_open.end_minute, + ) diff --git a/pyrogram/types/user_and_chats/business_working_hours.py b/pyrogram/types/user_and_chats/business_working_hours.py new file mode 100644 index 000000000..112ce3bf8 --- /dev/null +++ b/pyrogram/types/user_and_chats/business_working_hours.py @@ -0,0 +1,62 @@ +# 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 Optional, List + +from pyrogram import types, raw +from ..object import Object + + +class BusinessWorkingHours(Object): + """Business working hours. + + Parameters: + timezone (``str``): + Timezone of the business. + + working_hours (List of :obj:`~pyrogram.types.BusinessWeeklyOpen`): + Working hours of the business. + + is_open_now (``bool``, *optional*): + True, if the business is open now. + """ + + def __init__( + self, + *, + timezone: str, + working_hours: List["types.BusinessWeeklyOpen"], + is_open_now: bool = None + + ): + self.timezone = timezone + self.is_open_now = is_open_now + self.working_hours = working_hours + + @staticmethod + def _parse(work_hours: "raw.types.BusinessWorkHours" = None) -> Optional["BusinessWorkingHours"]: + if not work_hours: + return None + + return BusinessWorkingHours( + timezone=work_hours.timezone_id, + working_hours=types.List( + types.BusinessWeeklyOpen._parse(i) for i in work_hours.weekly_open + ), + is_open_now=getattr(work_hours, "open_now", None), + ) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 001f570e0..4f1e13b89 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -171,6 +171,9 @@ class Chat(Object): profile_color (:obj:`~pyrogram.types.ChatColor`, *optional*): Chat profile color. + + business_info (:obj:`~pyrogram.types.BusinessInfo`, *optional*): + Business information of a chat. """ def __init__( @@ -217,7 +220,8 @@ def __init__( available_reactions: Optional["types.ChatReactions"] = None, level: int = None, reply_color: "types.ChatColor" = None, - profile_color: "types.ChatColor" = None + profile_color: "types.ChatColor" = None, + business_info: "types.BusinessInfo" = None ): super().__init__(client) @@ -262,6 +266,7 @@ def __init__( self.level = level self.reply_color = reply_color self.profile_color = profile_color + self.business_info = business_info @staticmethod def _parse_user_chat(client, user: raw.types.User) -> "Chat": @@ -384,6 +389,7 @@ async def _parse_full(client, chat_full: Union[raw.types.messages.ChatFull, raw. parsed_chat = Chat._parse_user_chat(client, users[full_user.id]) parsed_chat.bio = full_user.about parsed_chat.folder_id = getattr(full_user, "folder_id", None) + parsed_chat.business_info = types.BusinessInfo._parse(client, full_user, users) if full_user.pinned_msg_id: parsed_chat.pinned_message = await client.get_messages( @@ -661,7 +667,7 @@ async def set_photo( video_start_ts=video_start_ts ) - async def set_ttl(self, ttl_seconds: int) -> bool: + async def set_ttl(self, ttl_seconds: int) -> "types.Message": """Bound method *set_ttl* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -679,7 +685,7 @@ async def set_ttl(self, ttl_seconds: int) -> bool: await chat.set_ttl(86400) Returns: - ``bool``: True on success. + :obj:`~pyrogram.types.Message`: On success, the generated service message is returned. """ return await self._client.set_chat_ttl( chat_id=self.id, diff --git a/pyrogram/types/user_and_chats/chat_event.py b/pyrogram/types/user_and_chats/chat_event.py index 4f9da9cb7..9faea6a0c 100644 --- a/pyrogram/types/user_and_chats/chat_event.py +++ b/pyrogram/types/user_and_chats/chat_event.py @@ -526,7 +526,7 @@ async def _parse( new_invite_link=new_invite_link, revoked_invite_link=revoked_invite_link, deleted_invite_link=deleted_invite_link, - + created_forum_topic=created_forum_topic, old_forum_topic=old_forum_topic, new_forum_topic=new_forum_topic, diff --git a/pyrogram/types/user_and_chats/folder.py b/pyrogram/types/user_and_chats/folder.py index 60f2b0e2c..9469b3ffe 100644 --- a/pyrogram/types/user_and_chats/folder.py +++ b/pyrogram/types/user_and_chats/folder.py @@ -19,6 +19,7 @@ from typing import List, Union import pyrogram +from pyrogram import enums from pyrogram import raw from pyrogram import types from pyrogram import utils @@ -70,6 +71,9 @@ class Folder(Object): emoji (``str``, *optional*): Folder emoji. + + color (:obj:`~pyrogram.enums.FolderColor`, *optional*) + Chat reply color. """ def __init__( @@ -90,6 +94,7 @@ def __init__( exclude_read: bool = None, exclude_archived: bool = None, emoji: str = None, + color: "enums.FolderColor" = None, has_my_invites: bool = None ): super().__init__(client) @@ -108,6 +113,7 @@ def __init__( self.exclude_read = exclude_read self.exclude_archived = exclude_archived self.emoji = emoji + self.color = color self.has_my_invites = has_my_invites @staticmethod @@ -150,6 +156,7 @@ def _parse(client, folder: "raw.types.DialogFilter", users, chats) -> "Folder": exclude_read=getattr(folder, "exclude_read", None), exclude_archived=getattr(folder, "exclude_archived", None), emoji=folder.emoticon or None, + color=enums.FolderColor(getattr(folder, "color", None)), has_my_invites=getattr(folder, "has_my_invites", None), client=client ) @@ -188,7 +195,8 @@ async def update( exclude_muted: bool = None, exclude_read: bool = None, exclude_archived: bool = None, - emoji: str = None + emoji: str = None, + color: "enums.FolderColor" = None ): """Bound method *update_peers* of :obj:`~pyrogram.types.Folder`. @@ -251,6 +259,10 @@ async def update( Folder emoji. Pass None to leave the folder icon as default. + color (:obj:`~pyrogram.enums.FolderColor`, *optional*): + Color type. + Pass :obj:`~pyrogram.enums.FolderColor` to set folder color. + Returns: True on success. """ @@ -277,7 +289,8 @@ async def update( exclude_muted=exclude_muted or self.exclude_muted, exclude_read=exclude_read or self.exclude_read, exclude_archived=exclude_archived or self.exclude_archived, - emoji=emoji or self.emoji + emoji=emoji or self.emoji, + color=color or self.color ) async def include_chat(self, chat_id: Union[int, str]): @@ -348,6 +361,39 @@ async def exclude_chat(self, chat_id: Union[int, str]): pinned_chats=[i.id for i in self.pinned_chats or []], ) + async def update_color(self, color: "enums.FolderColor"): + """Bound method *update_color* of :obj:`~pyrogram.types.Folder`. + + Use as a shortcut for: + + .. code-block:: python + + await client.update_folder( + folder_id=123456789, + included_chats=[chat_id], + excluded_chats=[chat_id], + pinned_chats=[...], + color=color + ) + + Example: + .. code-block:: python + + await folder.update_color(enums.FolderColor.RED) + + Parameters: + color (:obj:`~pyrogram.enums.FolderColor`, *optional*): + Color type. + Pass :obj:`~pyrogram.enums.FolderColor` to set folder color. + + Returns: + True on success. + """ + + return await self.update( + color=color + ) + async def pin_chat(self, chat_id: Union[int, str]): """Bound method *pin_chat* of :obj:`~pyrogram.types.Folder`. diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py index a705fd849..8b1a20a57 100644 --- a/pyrogram/types/user_and_chats/user.py +++ b/pyrogram/types/user_and_chats/user.py @@ -100,6 +100,9 @@ class User(Object, Update): is_premium (``bool``, *optional*): True, if this user is a premium user. + is_contact_require_premium (``bool``, *optional*): + True, if this user requires premium to send messages to him. + is_close_friend (``bool``, *optional*): True, if this user is a close friend. @@ -155,9 +158,6 @@ class User(Object, Update): The list of reasons why this bot might be unavailable to some users. This field is available only in case *is_restricted* is True. - full_name (``str``, *optional*): - User's or bot's full name. - mention (``str``, *property*): Generate a text mention for this user. You can use ``user.mention()`` to mention the user using their first name (styled using html), or @@ -187,6 +187,7 @@ def __init__( is_fake: bool = None, is_support: bool = None, is_premium: bool = None, + is_contact_require_premium: bool = None, is_close_friend: bool = None, is_stories_hidden: bool = None, is_stories_unavailable: bool = None, @@ -220,6 +221,7 @@ def __init__( self.is_fake = is_fake self.is_support = is_support self.is_premium = is_premium + self.is_contact_require_premium = is_contact_require_premium self.is_close_friend = is_close_friend self.is_stories_hidden = is_stories_hidden self.is_stories_unavailable = is_stories_unavailable @@ -269,6 +271,7 @@ def _parse(client, user: "raw.base.User") -> Optional["User"]: is_fake=user.fake, is_support=user.support, is_premium=user.premium, + is_contact_require_premium=user.contact_require_premium, is_close_friend=user.close_friend, is_stories_hidden=user.stories_hidden, is_stories_unavailable=user.stories_unavailable, diff --git a/pyrogram/utils.py b/pyrogram/utils.py index 0bc9474b4..d42e0a9bf 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -25,6 +25,7 @@ from concurrent.futures.thread import ThreadPoolExecutor from datetime import datetime, timezone from getpass import getpass +import re from typing import Union, List, Dict, Optional import pyrogram @@ -331,7 +332,7 @@ def get_reply_to( ) -> Optional[Union[raw.types.InputReplyToMessage, raw.types.InputReplyToStory]]: """Get InputReply for reply_to argument""" if all((reply_to_peer, reply_to_story_id)): - return raw.types.InputReplyToStory(user_id=reply_to_peer, story_id=reply_to_story_id) # type: ignore[arg-type] + return raw.types.InputReplyToStory(peer=reply_to_peer, story_id=reply_to_story_id) # type: ignore[arg-type] if any((reply_to_message_id, message_thread_id)): return raw.types.InputReplyToMessage( @@ -472,3 +473,12 @@ def timestamp_to_datetime(ts: Optional[int]) -> Optional[datetime]: def datetime_to_timestamp(dt: Optional[datetime]) -> Optional[int]: return int(dt.timestamp()) if dt else None + + +def get_first_url(text): + text = re.sub(r"^\s*(<[\w<>=\s\"]*>)\s*", r"\1", text) + text = re.sub(r"\s*(]*>)\s*$", r"\1", text) + + matches = re.findall(r"(https?):\/\/([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])", text) + + return f"{matches[0][0]}://{matches[0][1]}{matches[0][2]}" if matches else None diff --git a/setup.py b/setup.py index 668704a2d..55c383a0f 100644 --- a/setup.py +++ b/setup.py @@ -56,11 +56,11 @@ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Programming Language :: Python :: Implementation", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", @@ -78,7 +78,7 @@ "Source": "https://github.com/ALiwoto/WPyrogram", "Documentation": "https://docs.pyrogram.org", }, - python_requires="~=3.7", + python_requires="~=3.8", package_data={ "pyrogram": ["py.typed"], }, diff --git a/tests/parser/test_markdown.py b/tests/parser/test_markdown.py index 81aaacb33..584479d88 100644 --- a/tests/parser/test_markdown.py +++ b/tests/parser/test_markdown.py @@ -69,6 +69,15 @@ def test_markdown_unparse_url(): assert Markdown.unparse(text=text, entities=entities) == expected +def test_markdown_unparse_emoji(): + expected = '![🥲](tg://emoji?id=5195264424893488796) im crying' + text = "🥲 im crying" + entities = pyrogram.types.List([pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.CUSTOM_EMOJI, + offset=0, length=2, custom_emoji_id=5195264424893488796)]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + def test_markdown_unparse_code(): expected = '`code`' text = "code" @@ -93,6 +102,21 @@ def test_markdown_unparse_pre(): assert Markdown.unparse(text=text, entities=entities) == expected +def test_markdown_unparse_blockquote(): + expected = """> Hello +> from + +> pyrogram!""" + + text = """Hello\nfrom\n\npyrogram!""" + + entities = pyrogram.types.List( + [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BLOCKQUOTE, offset=0, length=10), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BLOCKQUOTE, offset=12, length=9)]) + + assert Markdown.unparse(text=text, entities=entities) == expected + + def test_markdown_unparse_mixed(): expected = "**aaaaaaa__aaabbb**__~~dddddddd||ddeee~~||||eeeeeeefff||ffff`fffggggggg`ggghhhhhhhhhh" text = "aaaaaaaaaabbbddddddddddeeeeeeeeeeffffffffffgggggggggghhhhhhhhhh"