diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index f34f615a..3437aeae 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ -github: delivrance +# github: delivrance custom: https://docs.pyrogram.org/support-pyrogram diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 88d91ecd..05f342bc 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -10,6 +10,6 @@ labels: "question" # Important This place is for issues about Pyrogram, it's **not a forum**. -If you'd like to post a question, please move to https://stackoverflow.com or join the Telegram community at https://t.me/pyrogram. +If you'd like to post a question, please move to https://stackoverflow.com or join the Telegram community at https://t.me/pyrogram. Useful information on how to ask good questions can be found here: https://stackoverflow.com/help/how-to-ask. -Thanks. \ No newline at end of file +Thanks. diff --git a/.gitignore b/.gitignore index 0b1a0699..ce3407dd 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,9 @@ pyrogram/api/all.py # PyCharm stuff .idea/ +# VS Code +.vscode/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -78,6 +81,7 @@ instance/ # Sphinx documentation docs/_build/ +docs/source/_build # PyBuilder target/ diff --git a/MANIFEST.in b/MANIFEST.in index 97d04588..9f4d328c 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,10 +1,10 @@ ## Include include README.md COPYING COPYING.lesser NOTICE requirements.txt recursive-include compiler *.py *.tl *.tsv *.txt -recursive-include pyrogram mime.types +recursive-include pyrogram mime.types schema.sql ## Exclude -prune pyrogram/api/errors/exceptions +prune pyrogram/errors/exceptions prune pyrogram/api/functions prune pyrogram/api/types exclude pyrogram/api/all.py \ No newline at end of file diff --git a/README.md b/README.md index 4e758b5a..a4294175 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ app = Client("my_account") @app.on_message(Filters.private) def hello(client, message): - message.reply("Hello {}".format(message.from_user.first_name)) + message.reply_text("Hello {}".format(message.from_user.first_name)) app.run() diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index 3995fd5f..255884db 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -478,7 +478,6 @@ def start(): f.write("\n 0xbc799737: \"pyrogram.api.core.BoolFalse\",") f.write("\n 0x997275b5: \"pyrogram.api.core.BoolTrue\",") - f.write("\n 0x56730bcc: \"pyrogram.api.core.Null\",") f.write("\n 0x1cb5c415: \"pyrogram.api.core.Vector\",") f.write("\n 0x73f1f8dc: \"pyrogram.api.core.MsgContainer\",") f.write("\n 0xae500895: \"pyrogram.api.core.FutureSalts\",") diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 16a93420..fa2c7af8 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -101,11 +101,11 @@ userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#9ba2d800 id:int = Chat; chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#7328bdb id:int title:string = Chat; -channel#4df30834 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; +channel#4df30834 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?string admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; chatFull#1b7c9db3 flags:# can_set_username:flags.7?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull; -channelFull#9882e516 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_view_stats:flags.12?true id:int 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:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int 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.13?int pts:int = ChatFull; +channelFull#10916653 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_view_stats:flags.12?true can_set_location:flags.16?true id:int 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:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int 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?int location:flags.15?ChannelLocation pts:int = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -189,7 +189,7 @@ inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings; -peerSettings#818426cd flags:# report_spam:flags.0?true = PeerSettings; +peerSettings#818426cd flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true = PeerSettings; wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper; @@ -199,8 +199,9 @@ inputReportReasonPornography#2e59d922 = ReportReason; inputReportReasonChildAbuse#adf44ee3 = ReportReason; inputReportReasonOther#e1746d0a text:string = ReportReason; inputReportReasonCopyright#9b89f93a = ReportReason; +inputReportReasonGeoIrrelevant#dbd4feed = ReportReason; -userFull#745559cc flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true user:User about:flags.1?string link:contacts.Link profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull; +userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull; contact#f911c994 user_id:int mutual:Bool = Contact; @@ -210,8 +211,6 @@ contactBlocked#561bc879 user_id:int date:int = ContactBlocked; contactStatus#d3680c61 user_id:int status:UserStatus = ContactStatus; -contacts.link#3ace484c my_link:ContactLink foreign_link:ContactLink user:User = contacts.Link; - contacts.contactsNotModified#b74ba9d2 = contacts.Contacts; contacts.contacts#eae87e42 contacts:Vector saved_count:int users:Vector = contacts.Contacts; @@ -262,7 +261,6 @@ updateChatParticipants#7761198 participants:ChatParticipants = Update; updateUserStatus#1bfbd823 user_id:int status:UserStatus = Update; updateUserName#a7332b73 user_id:int first_name:string last_name:string username:string = Update; updateUserPhoto#95313b0c user_id:int date:int photo:UserProfilePhoto previous:Bool = Update; -updateContactLink#9d2e67c5 user_id:int my_link:ContactLink foreign_link:ContactLink = Update; updateNewEncryptedMessage#12bcbd9a message:EncryptedMessage qts:int = Update; updateEncryptedChatTyping#1710f156 chat_id:int = Update; updateEncryption#b4a2e88d chat:EncryptedChat date:int = Update; @@ -323,6 +321,8 @@ updateChatPinnedMessage#e10db349 chat_id:int id:int version:int = Update; updateMessagePoll#aca1657b flags:# poll_id:long poll:flags.0?Poll results:PollResults = Update; updateChatDefaultBannedRights#54c01850 peer:Peer default_banned_rights:ChatBannedRights version:int = Update; updateFolderPeers#19360dc0 folder_peers:Vector pts:int pts_count:int = Update; +updatePeerSettings#6a7e7366 peer:Peer settings:PeerSettings = Update; +updatePeerLocated#b4afcfb0 peers:Vector = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -468,10 +468,6 @@ messages.allStickers#edfd405f hash:int sets:Vector = messages.AllSti messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMessages; -contactLinkUnknown#5f4f9247 = ContactLink; -contactLinkNone#feedd3ad = ContactLink; -contactLinkContact#d502c2d0 = ContactLink; - webPageEmpty#eb1477e8 id:long = WebPage; webPagePending#c586da1c id:long date:int = WebPage; webPage#5f07b4bc flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page = WebPage; @@ -501,7 +497,7 @@ inputStickerSetEmpty#ffb62b95 = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; -stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet; +stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet; messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents:Vector = messages.StickerSet; @@ -542,6 +538,9 @@ messageEntityMentionName#352dca58 offset:int length:int user_id:int = MessageEnt inputMessageEntityMentionName#208e68c9 offset:int length:int user_id:InputUser = MessageEntity; messageEntityPhone#9b69e34b offset:int length:int = MessageEntity; messageEntityCashtag#4c4e743f offset:int length:int = MessageEntity; +messageEntityUnderline#9c4e7e8b offset:int length:int = MessageEntity; +messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity; +messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; inputChannelEmpty#ee8c1e86 = InputChannel; inputChannel#afeb712e channel_id:int access_hash:long = InputChannel; @@ -828,6 +827,7 @@ channelAdminLogEventActionTogglePreHistoryHidden#5f5c95f1 new_value:Bool = Chann channelAdminLogEventActionDefaultBannedRights#2df5fc0a prev_banned_rights:ChatBannedRights new_banned_rights:ChatBannedRights = ChannelAdminLogEventAction; channelAdminLogEventActionStopPoll#8f079643 message:Message = ChannelAdminLogEventAction; channelAdminLogEventActionChangeLinkedChat#a26f881b prev_value:int new_value:int = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeLocation#e6b76ae prev_value:ChannelLocation new_value:ChannelLocation = ChannelAdminLogEventAction; channelAdminLogEvent#3b5a3e40 id:long date:int user_id:int action:ChannelAdminLogEventAction = ChannelAdminLogEvent; @@ -1034,6 +1034,11 @@ urlAuthResultRequest#92d33a0e flags:# request_write_access:flags.0?true bot:User urlAuthResultAccepted#8f8c0e4e url:string = UrlAuthResult; urlAuthResultDefault#a9d6db1f = UrlAuthResult; +channelLocationEmpty#bfb5ad8b = ChannelLocation; +channelLocation#209b82db geo_point:GeoPoint address:string = ChannelLocation; + +peerLocated#ca461b5d peer:Peer expires:int distance:int = PeerLocated; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1060,7 +1065,7 @@ auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentC auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool; auth.dropTempAuthKeys#8e48a188 except_auth_keys:Vector = Bool; -account.registerDevice#5cbea590 token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector = Bool; +account.registerDevice#68976c6f flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector = Bool; account.unregisterDevice#3076c4bf token_type:int token:string other_uids:Vector = Bool; account.updateNotifySettings#84be5b93 peer:InputNotifyPeer settings:InputPeerNotifySettings = Bool; account.getNotifySettings#12b3ad31 peer:InputNotifyPeer = PeerNotifySettings; @@ -1124,8 +1129,7 @@ contacts.getContactIDs#2caa4a42 hash:int = Vector; contacts.getStatuses#c4a353ee = Vector; contacts.getContacts#c023849f hash:int = contacts.Contacts; contacts.importContacts#2c800be5 contacts:Vector = contacts.ImportedContacts; -contacts.deleteContact#8e953744 id:InputUser = contacts.Link; -contacts.deleteContacts#59ab389e id:Vector = Bool; +contacts.deleteContacts#96a0e00 id:Vector = Updates; contacts.deleteByPhones#1013fd9e phones:Vector = Bool; contacts.block#332b49fc id:InputUser = Bool; contacts.unblock#e54100bd id:InputUser = Bool; @@ -1137,6 +1141,9 @@ contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = B contacts.resetSaved#879537f1 = Bool; contacts.getSaved#82f1e39f = Vector; contacts.toggleTopPeers#8514bdda enabled:Bool = Bool; +contacts.addContact#e8f463d0 flags:# add_phone_privacy_exception:flags.0?true id:InputUser first_name:string last_name:string phone:string = Updates; +contacts.acceptContact#f831a20f id:InputUser = Updates; +contacts.getLocated#a356056 geo_point:InputGeoPoint = Updates; messages.getMessages#63c66506 id:Vector = messages.Messages; messages.getDialogs#a0ee3b73 flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:int = messages.Dialogs; @@ -1151,7 +1158,6 @@ messages.sendMessage#fa88427a flags:# no_webpage:flags.1?true silent:flags.5?tru messages.sendMedia#b8d1262b flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Updates; messages.forwardMessages#708e0195 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true grouped:flags.9?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; -messages.hideReportSpam#a8f1709b peer:InputPeer = Bool; messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings; messages.report#bd82b658 peer:InputPeer id:Vector reason:ReportReason = Bool; messages.getChats#3c6aa187 id:Vector = messages.Chats; @@ -1186,7 +1192,7 @@ messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_par messages.getMessagesViews#c4c8a55d peer:InputPeer id:Vector increment:Bool = Vector; messages.editChatAdmin#a9e69f2e chat_id:int user_id:InputUser is_admin:Bool = Bool; messages.migrateChat#15a3b8e3 chat_id:int = Updates; -messages.searchGlobal#f79c611 q:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; +messages.searchGlobal#bf7225a4 flags:# folder_id:flags.0?int q:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector = Bool; messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; messages.searchGifs#bf9a776b q:string offset:int = messages.FoundGifs; @@ -1251,6 +1257,7 @@ messages.getEmojiURL#d5b10c26 lang_code:string = EmojiURL; messages.getSearchCounters#732eef00 peer:InputPeer filters:Vector = Vector; messages.requestUrlAuth#e33f5613 peer:InputPeer msg_id:int button_id:int = UrlAuthResult; messages.acceptUrlAuth#f729ea98 flags:# write_allowed:flags.0?true peer:InputPeer msg_id:int button_id:int = UrlAuthResult; +messages.hidePeerSettingsBar#4facb138 peer:InputPeer = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1299,7 +1306,7 @@ channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipant channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant; channels.getChannels#a7f6bbb id:Vector = messages.Chats; channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull; -channels.createChannel#f4893d7f flags:# broadcast:flags.0?true megagroup:flags.1?true title:string about:string = Updates; +channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates; channels.editAdmin#70f893ba channel:InputChannel user_id:InputUser admin_rights:ChatAdminRights = Updates; channels.editTitle#566decd0 channel:InputChannel title:string = Updates; channels.editPhoto#f12e57c9 channel:InputChannel photo:InputChatPhoto = Updates; @@ -1311,7 +1318,7 @@ channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector = channels.deleteChannel#c0111fe3 channel:InputChannel = Updates; channels.exportMessageLink#ceb77163 channel:InputChannel id:int grouped:Bool = ExportedMessageLink; channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; -channels.getAdminedPublicChannels#8d8d82d7 = messages.Chats; +channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats; channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates; channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector max_id:long min_id:long limit:int = channels.AdminLogResults; channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool; @@ -1320,8 +1327,9 @@ channels.deleteHistory#af369d42 channel:InputChannel max_id:int = Bool; channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates; channels.getLeftChannels#8341ecc0 offset:int = messages.Chats; channels.getGroupsForDiscussion#f5dad378 = messages.Chats; -channels.getBroadcastsForDiscussion#1a87f304 = messages.Chats; channels.setDiscussionGroup#40582bb2 broadcast:InputChannel group:InputChannel = Bool; +channels.editCreator#8f38cd1f channel:InputChannel user_id:InputUser password:InputCheckPasswordSRP = Updates; +channels.editLocation#58e63f6d channel:InputChannel geo_point:InputGeoPoint address:string = Bool; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -1356,7 +1364,4 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; folders.deleteFolder#1c295881 folder_id:int = Updates; -// LAYER 100 - -// Ports -channels.exportInvite#c7560885 channel:InputChannel = ExportedChatInvite; \ No newline at end of file +// LAYER 103 \ No newline at end of file diff --git a/compiler/api/source/sys_msgs.tl b/compiler/api/source/sys_msgs.tl index 067ab91e..6a3f6325 100644 --- a/compiler/api/source/sys_msgs.tl +++ b/compiler/api/source/sys_msgs.tl @@ -53,6 +53,15 @@ ipPortSecret#37982646 ipv4:int port:int secret:bytes = IpPort; accessPointRule#4679b65f phone_prefix_rules:string dc_id:int ips:vector = AccessPointRule; help.configSimple#5a592a6c date:int expires:int rules:vector = help.ConfigSimple; +// tlsClientHello blocks:vector = TlsClientHello; +// +// tlsBlockString data:string = TlsBlock; +// tlsBlockRandom length:int = TlsBlock; +// tlsBlockZero length:int = TlsBlock; +// tlsBlockDomain = TlsBlock; +// tlsBlockGrease seed:int = TlsBlock; +// tlsBlockScope entries:Vector = TlsBlock; + ---functions--- rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer; diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index b167fa57..e515dc77 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -23,6 +23,7 @@ import shutil HOME = "compiler/docs" DESTINATION = "docs/source/telegram" +PYROGRAM_API_DEST = "docs/source/api" FUNCTIONS_PATH = "pyrogram/api/functions" TYPES_PATH = "pyrogram/api/types" @@ -117,6 +118,362 @@ def generate(source_path, base): f.write("\n") +def pyrogram_api(): + def get_title_list(s: str) -> list: + return [i.strip() for i in [j.strip() for j in s.split("\n") if j] if i] + + # Methods + + categories = dict( + utilities=""" + Utilities + start + stop + restart + idle + run + add_handler + remove_handler + stop_transmission + export_session_string + set_parse_mode + """, + messages=""" + Messages + send_message + forward_messages + send_photo + send_audio + send_document + send_sticker + send_video + send_animation + send_voice + send_video_note + send_media_group + send_location + send_venue + send_contact + send_cached_media + edit_message_text + edit_message_caption + edit_message_media + edit_message_reply_markup + edit_inline_text + edit_inline_caption + edit_inline_media + edit_inline_reply_markup + send_chat_action + delete_messages + get_messages + get_history + get_history_count + read_history + iter_history + send_poll + vote_poll + stop_poll + retract_vote + download_media + """, + chats=""" + Chats + join_chat + leave_chat + kick_chat_member + unban_chat_member + restrict_chat_member + promote_chat_member + export_chat_invite_link + set_chat_photo + delete_chat_photo + set_chat_title + set_chat_description + set_chat_permissions + pin_chat_message + unpin_chat_message + get_chat + get_chat_member + get_chat_members + get_chat_members_count + iter_chat_members + get_dialogs + iter_dialogs + get_dialogs_count + update_chat_username + archive_chats + unarchive_chats + add_chat_members + create_channel + create_group + create_supergroup + delete_channel + delete_supergroup + """, + users=""" + Users + get_me + get_users + get_profile_photos + get_profile_photos_count + iter_profile_photos + set_profile_photo + delete_profile_photos + update_username + block_user + unblock_user + """, + contacts=""" + Contacts + add_contacts + get_contacts + get_contacts_count + delete_contacts + """, + password=""" + Pssword + enable_cloud_password + change_cloud_password + remove_cloud_password + """, + bots=""" + Bots + get_inline_bot_results + send_inline_bot_result + answer_callback_query + answer_inline_query + request_callback_answer + send_game + set_game_score + get_game_high_scores + """, + advanced=""" + Advanced + send + resolve_peer + save_file + """ + ) + + root = PYROGRAM_API_DEST + "/methods" + + shutil.rmtree(root, ignore_errors=True) + os.mkdir(root) + + with open(HOME + "/template/methods.rst") as f: + template = f.read() + + with open(root + "/index.rst", "w") as f: + fmt_keys = {} + + for k, v in categories.items(): + name, *methods = get_title_list(v) + fmt_keys.update({k: "\n ".join("{0} <{0}>".format(m) for m in methods)}) + + for method in methods: + with open(root + "/{}.rst".format(method), "w") as f2: + title = "{}()".format(method) + + f2.write(title + "\n" + "=" * len(title) + "\n\n") + f2.write(".. automethod:: pyrogram.Client.{}()".format(method)) + + f.write(template.format(**fmt_keys)) + + # Types + + categories = dict( + users_chats=""" + Users & Chats + User + Chat + ChatPreview + ChatPhoto + ChatMember + ChatPermissions + Dialog + """, + messages_media=""" + Messages & Media + Message + MessageEntity + Photo + Thumbnail + Audio + Document + Animation + Video + Voice + VideoNote + Contact + Location + Venue + Sticker + Game + WebPage + Poll + PollOption + """, + bots_keyboard=""" + Bots & Keyboards + ReplyKeyboardMarkup + KeyboardButton + ReplyKeyboardRemove + InlineKeyboardMarkup + InlineKeyboardButton + ForceReply + CallbackQuery + GameHighScore + CallbackGame + """, + input_media=""" + Input Media + InputMedia + InputMediaPhoto + InputMediaVideo + InputMediaAudio + InputMediaAnimation + InputMediaDocument + InputPhoneContact + """, + inline_mode=""" + Inline Mode + InlineQuery + InlineQueryResult + InlineQueryResultArticle + InlineQueryResultPhoto + InlineQueryResultAnimation + """, + input_message_content=""" + InputMessageContent + InputMessageContent + InputTextMessageContent + """ + ) + + root = PYROGRAM_API_DEST + "/types" + + shutil.rmtree(root, ignore_errors=True) + os.mkdir(root) + + with open(HOME + "/template/types.rst") as f: + template = f.read() + + with open(root + "/index.rst", "w") as f: + fmt_keys = {} + + for k, v in categories.items(): + name, *types = get_title_list(v) + + fmt_keys.update({k: "\n ".join(types)}) + + # noinspection PyShadowingBuiltins + for type in types: + with open(root + "/{}.rst".format(type), "w") as f2: + title = "{}".format(type) + + f2.write(title + "\n" + "=" * len(title) + "\n\n") + f2.write(".. autoclass:: pyrogram.{}()".format(type)) + + f.write(template.format(**fmt_keys)) + + # Bound Methods + + categories = dict( + message=""" + Message + Message.click + Message.delete + Message.download + Message.forward + Message.pin + Message.edit_text + Message.edit_caption + Message.edit_media + Message.edit_reply_markup + Message.reply_text + Message.reply_animation + Message.reply_audio + Message.reply_cached_media + Message.reply_chat_action + Message.reply_contact + Message.reply_document + Message.reply_game + Message.reply_inline_bot_result + Message.reply_location + Message.reply_media_group + Message.reply_photo + Message.reply_poll + Message.reply_sticker + Message.reply_venue + Message.reply_video + Message.reply_video_note + Message.reply_voice + """, + chat=""" + Chat + Chat.archive + Chat.unarchive + Chat.set_title + Chat.set_description + Chat.set_photo + Chat.kick_member + Chat.unban_member + Chat.restrict_member + Chat.promote_member + Chat.join + Chat.leave + """, + user=""" + User + User.archive + User.unarchive + User.block + User.unblock + """, + callback_query=""" + Callback Query + CallbackQuery.answer + CallbackQuery.edit_message_text + CallbackQuery.edit_message_caption + CallbackQuery.edit_message_media + CallbackQuery.edit_message_reply_markup + """, + inline_query=""" + InlineQuery + InlineQuery.answer + """ + ) + + root = PYROGRAM_API_DEST + "/bound-methods" + + shutil.rmtree(root, ignore_errors=True) + os.mkdir(root) + + with open(HOME + "/template/bound-methods.rst") as f: + template = f.read() + + with open(root + "/index.rst", "w") as f: + fmt_keys = {} + + for k, v in categories.items(): + name, *bound_methods = get_title_list(v) + + fmt_keys.update({"{}_hlist".format(k): "\n ".join("- :meth:`~{}`".format(bm) for bm in bound_methods)}) + + fmt_keys.update( + {"{}_toctree".format(k): "\n ".join("{} <{}>".format(bm.split(".")[1], bm) for bm in bound_methods)}) + + # noinspection PyShadowingBuiltins + for bm in bound_methods: + with open(root + "/{}.rst".format(bm), "w") as f2: + title = "{}()".format(bm) + + f2.write(title + "\n" + "=" * len(title) + "\n\n") + f2.write(".. automethod:: pyrogram.{}()".format(bm)) + + f.write(template.format(**fmt_keys)) + + def start(): global page_template global toctree @@ -131,6 +488,7 @@ def start(): generate(TYPES_PATH, TYPES_BASE) generate(FUNCTIONS_PATH, FUNCTIONS_BASE) + pyrogram_api() if "__main__" == __name__: @@ -138,5 +496,6 @@ if "__main__" == __name__: TYPES_PATH = "../../pyrogram/api/types" HOME = "." DESTINATION = "../../docs/source/telegram" + PYROGRAM_API_DEST = "../../docs/source/api" start() diff --git a/compiler/docs/template/bound-methods.rst b/compiler/docs/template/bound-methods.rst new file mode 100644 index 00000000..0057e071 --- /dev/null +++ b/compiler/docs/template/bound-methods.rst @@ -0,0 +1,88 @@ +Bound Methods +============= + +Some Pyrogram types define what are called bound methods. Bound methods are functions attached to a class which are +accessed via an instance of that class. They make it even easier to call specific methods by automatically inferring +some of the required arguments. + +.. code-block:: python + :emphasize-lines: 8 + + from pyrogram import Client + + app = Client("my_account") + + + @app.on_message() + def hello(client, message) + message.reply("hi") + + + app.run() + +.. currentmodule:: pyrogram + +Message +------- + +.. hlist:: + :columns: 3 + + {message_hlist} + +.. toctree:: + :hidden: + + {message_toctree} + +Chat +---- + +.. hlist:: + :columns: 4 + + {chat_hlist} + +.. toctree:: + :hidden: + + {chat_toctree} + +User +---- + +.. hlist:: + :columns: 2 + + {user_hlist} + +.. toctree:: + :hidden: + + {user_toctree} + +CallbackQuery +------------- + +.. hlist:: + :columns: 3 + + {callback_query_hlist} + +.. toctree:: + :hidden: + + {callback_query_toctree} + +InlineQuery +----------- + +.. hlist:: + :columns: 2 + + {inline_query_hlist} + +.. toctree:: + :hidden: + + {inline_query_toctree} diff --git a/compiler/docs/template/methods.rst b/compiler/docs/template/methods.rst new file mode 100644 index 00000000..0de7ee87 --- /dev/null +++ b/compiler/docs/template/methods.rst @@ -0,0 +1,122 @@ +Available Methods +================= + +This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance. + +.. code-block:: python + :emphasize-lines: 6 + + from pyrogram import Client + + app = Client("my_account") + + with app: + app.send_message("haskell", "hi") + +.. currentmodule:: pyrogram.Client + +Utilities +--------- + +.. autosummary:: + :nosignatures: + + {utilities} + +.. toctree:: + :hidden: + + {utilities} + +Messages +-------- + +.. autosummary:: + :nosignatures: + + {messages} + +.. toctree:: + :hidden: + + {messages} + +Chats +----- + +.. autosummary:: + :nosignatures: + + {chats} + +.. toctree:: + :hidden: + + {chats} + +Users +----- + +.. autosummary:: + :nosignatures: + + {users} + +.. toctree:: + :hidden: + + {users} + +Contacts +-------- + +.. autosummary:: + :nosignatures: + + {contacts} + +.. toctree:: + :hidden: + + {contacts} + +Password +-------- + +.. autosummary:: + :nosignatures: + + {password} + +.. toctree:: + :hidden: + + {password} + +Bots +---- + +.. autosummary:: + :nosignatures: + + {bots} + +.. toctree:: + :hidden: + + {bots} + +Advanced +-------- + +Learn more about these methods at :doc:`Advanced Usage <../../topics/advanced-usage>`. + +.. autosummary:: + :nosignatures: + + {advanced} + +.. toctree:: + :hidden: + + {advanced} \ No newline at end of file diff --git a/compiler/docs/template/types.rst b/compiler/docs/template/types.rst new file mode 100644 index 00000000..635a81d3 --- /dev/null +++ b/compiler/docs/template/types.rst @@ -0,0 +1,95 @@ +Available Types +=============== + +This page is about Pyrogram types. All types listed here are accessible through the main package directly. + +.. code-block:: python + :emphasize-lines: 1 + + from pyrogram import User, Message, ... + +.. note:: + + **Optional** fields may not exist when irrelevant -- i.e.: they will contain the value of ``None`` and aren't shown + when, for example, using ``print()``. + +.. currentmodule:: pyrogram + + +Users & Chats +------------- + +.. autosummary:: + :nosignatures: + + {users_chats} + +.. toctree:: + :hidden: + + {users_chats} + +Messages & Media +---------------- + +.. autosummary:: + :nosignatures: + + {messages_media} + +.. toctree:: + :hidden: + + {messages_media} + +Bots & Keyboards +---------------- + +.. autosummary:: + :nosignatures: + + {bots_keyboard} + +.. toctree:: + :hidden: + + {bots_keyboard} + +Input Media +----------- + +.. autosummary:: + :nosignatures: + + {input_media} + +.. toctree:: + :hidden: + + {input_media} + +Inline Mode +----------- + +.. autosummary:: + :nosignatures: + + {inline_mode} + +.. toctree:: + :hidden: + + {inline_mode} + +InputMessageContent +------------------- + +.. autosummary:: + :nosignatures: + + {input_message_content} + +.. toctree:: + :hidden: + + {input_message_content} \ No newline at end of file diff --git a/compiler/error/compiler.py b/compiler/error/compiler.py index 996c4981..80826396 100644 --- a/compiler/error/compiler.py +++ b/compiler/error/compiler.py @@ -81,6 +81,8 @@ def start(): sub_classes = [] + f_all.write(" \"_\": \"{}\",\n".format(super_class)) + for j, row in enumerate(reader): if j == 0: continue @@ -90,13 +92,13 @@ def start(): if not row: # Row is empty (blank line) continue - id, message = row + error_id, error_message = row - sub_class = caml(re.sub(r"_X", "_", id)) + sub_class = caml(re.sub(r"_X", "_", error_id)) - f_all.write(" \"{}\": \"{}\",\n".format(id, sub_class)) + f_all.write(" \"{}\": \"{}\",\n".format(error_id, sub_class)) - sub_classes.append((sub_class, id, message)) + sub_classes.append((sub_class, error_id, error_message)) with open("{}/template/class.txt".format(HOME), "r", encoding="utf-8") as f_class_template: class_template = f_class_template.read() diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 83d4b753..a2915689 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -12,10 +12,10 @@ PHONE_NUMBER_UNOCCUPIED The phone number is not yet being used USERS_TOO_FEW Not enough users (to create a chat, for example) USERS_TOO_MUCH The maximum number of users has been exceeded (to create a chat, for example) TYPE_CONSTRUCTOR_INVALID The type constructor is invalid -FILE_PART_INVALID The file part number is invalid -FILE_PARTS_INVALID The number of file parts is invalid +FILE_PART_INVALID The file part number is invalid. The value is not between 0 and 2999 +FILE_PARTS_INVALID Invalid number of parts. The value is not between 1 and 3000 FILE_PART_X_MISSING Part {x} of the file is missing from storage -MD5_CHECKSUM_INVALID The MD5 checksums do not match +MD5_CHECKSUM_INVALID The file's checksum did not match the md5_checksum parameter PHOTO_INVALID_DIMENSIONS The photo dimensions are invalid FIELD_NAME_INVALID The field with the name FIELD_NAME is invalid FIELD_NAME_EMPTY The field with the name FIELD_NAME is missing @@ -41,7 +41,7 @@ PERSISTENT_TIMESTAMP_EMPTY The pts is empty CDN_METHOD_INVALID The method can't be used on CDN DCs VOLUME_LOC_NOT_FOUND The volume location can't be found FILE_ID_INVALID The file id is invalid -LOCATION_INVALID The file location is invalid +LOCATION_INVALID The file address is invalid CHAT_ADMIN_REQUIRED The method requires chat admin privileges PHONE_NUMBER_BANNED The phone number is banned ABOUT_TOO_LONG The about text is too long @@ -106,4 +106,21 @@ CHAT_LINK_EXISTS The action failed because the supergroup is linked to a channel LINK_NOT_MODIFIED The chat link was not modified because you tried to link to the same target BROADCAST_ID_INVALID The channel is invalid MEGAGROUP_ID_INVALID The supergroup is invalid +BUTTON_DATA_INVALID The button callback data contains invalid data or exceeds 64 bytes +START_PARAM_INVALID The start parameter is invalid +ARTICLE_TITLE_EMPTY The article title is empty +FILE_PART_TOO_BIG The size limit (512 KB) for the content of the file part has been exceeded +FILE_PART_EMPTY The file part sent is empty +FILE_PART_SIZE_INVALID 512 KB cannot be evenly divided by part_size +FILE_PART_SIZE_CHANGED The part size is different from the size of one of the previous parts in the same file +FILE_MIGRATE_X The file is in Data Center No. {x} +RESULT_TYPE_INVALID The result type is invalid +PHOTO_THUMB_URL_EMPTY The photo thumb URL is empty +PHOTO_THUMB_URL_INVALID The photo thumb URL is invalid +PHOTO_CONTENT_URL_EMPTY The photo content URL is empty +PHOTO_CONTENT_TYPE_INVALID The photo content type is invalid +WEBDOCUMENT_INVALID The web document is invalid +WEBDOCUMENT_URL_EMPTY The web document URL is empty +WEBDOCUMENT_URL_INVALID The web document URL is invalid +WEBDOCUMENT_MIME_INVALID The web document mime type is invalid BUTTON_URL_INVALID The button url is invalid \ No newline at end of file diff --git a/compiler/error/source/406_NOT_ACCEPTABLE.tsv b/compiler/error/source/406_NOT_ACCEPTABLE.tsv index e94706ed..1c8e5647 100644 --- a/compiler/error/source/406_NOT_ACCEPTABLE.tsv +++ b/compiler/error/source/406_NOT_ACCEPTABLE.tsv @@ -1,3 +1,4 @@ id message AUTH_KEY_DUPLICATED Authorization error - you must delete your session file and log in again with your phone number -FILEREF_UPGRADE_NEEDED The file reference has expired - you must obtain the original media message \ No newline at end of file +FILEREF_UPGRADE_NEEDED The file reference has expired - you must obtain the original media message +STICKERSET_INVALID The sticker set is invalid \ No newline at end of file diff --git a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv index 446fe908..4bbea8ea 100644 --- a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv +++ b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv @@ -9,4 +9,5 @@ RANDOM_ID_DUPLICATE Telegram is having internal problems. Please try again later WORKER_BUSY_TOO_LONG_RETRY Telegram is having internal problems. Please try again later INTERDC_X_CALL_ERROR Telegram is having internal problems at DC{x}. Please try again later INTERDC_X_CALL_RICH_ERROR Telegram is having internal problems at DC{x}. Please try again later -FOLDER_DEAC_AUTOFIX_ALL Telegram is having internal problems. Please try again later \ No newline at end of file +FOLDER_DEAC_AUTOFIX_ALL Telegram is having internal problems. Please try again later +MSGID_DECREASE_RETRY Telegram is having internal problems. Please try again later \ No newline at end of file diff --git a/docs/releases.py b/docs/releases.py index 0c284f0b..0b566ca7 100644 --- a/docs/releases.py +++ b/docs/releases.py @@ -35,8 +35,7 @@ backwards-incompatible changes made in that version. When upgrading to a new version of Pyrogram, you will need to check all the breaking changes in order to find incompatible code in your application, but also to take advantage of new features and improvements. -Releases --------- +**Contents** """.lstrip("\n") diff --git a/docs/robots.txt b/docs/robots.txt index 1b9e8da6..e7799fdd 100644 --- a/docs/robots.txt +++ b/docs/robots.txt @@ -2,7 +2,7 @@ User-agent: * Allow: / -Disallow: /dev/* -Disallow: /old/* +Disallow: /dev* +Disallow: /v0* Sitemap: https://docs.pyrogram.org/sitemap.xml \ No newline at end of file diff --git a/docs/source/api/bound-methods.rst b/docs/source/api/bound-methods.rst deleted file mode 100644 index d01a4383..00000000 --- a/docs/source/api/bound-methods.rst +++ /dev/null @@ -1,142 +0,0 @@ -Bound Methods -============= - -Some Pyrogram types define what are called bound methods. Bound methods are functions attached to a class which are -accessed via an instance of that class. They make it even easier to call specific methods by automatically inferring -some of the required arguments. - -.. code-block:: python - :emphasize-lines: 8 - - from pyrogram import Client - - app = Client("my_account") - - - @app.on_message() - def hello(client, message) - message.reply("hi") - - - app.run() - -.. currentmodule:: pyrogram - -Index ------ - -Message -^^^^^^^ - -.. hlist:: - :columns: 3 - - - :meth:`~Message.click` - - :meth:`~Message.delete` - - :meth:`~Message.download` - - :meth:`~Message.edit` - - :meth:`~Message.edit_caption` - - :meth:`~Message.edit_media` - - :meth:`~Message.edit_reply_markup` - - :meth:`~Message.forward` - - :meth:`~Message.pin` - - :meth:`~Message.reply` - - :meth:`~Message.reply_animation` - - :meth:`~Message.reply_audio` - - :meth:`~Message.reply_cached_media` - - :meth:`~Message.reply_chat_action` - - :meth:`~Message.reply_contact` - - :meth:`~Message.reply_document` - - :meth:`~Message.reply_game` - - :meth:`~Message.reply_inline_bot_result` - - :meth:`~Message.reply_location` - - :meth:`~Message.reply_media_group` - - :meth:`~Message.reply_photo` - - :meth:`~Message.reply_poll` - - :meth:`~Message.reply_sticker` - - :meth:`~Message.reply_venue` - - :meth:`~Message.reply_video` - - :meth:`~Message.reply_video_note` - - :meth:`~Message.reply_voice` - -Chat -^^^^ - -.. hlist:: - :columns: 2 - - - :meth:`~Chat.archive` - - :meth:`~Chat.unarchive` - -User -^^^^ - -.. hlist:: - :columns: 2 - - - :meth:`~User.archive` - - :meth:`~User.unarchive` - -CallbackQuery -^^^^^^^^^^^^^ - -.. hlist:: - :columns: 2 - - - :meth:`~CallbackQuery.answer` - -InlineQuery -^^^^^^^^^^^ - -.. hlist:: - :columns: 2 - - - :meth:`~InlineQuery.answer` - ------ - -Details -------- - -.. Message -.. automethod:: Message.click() -.. automethod:: Message.delete() -.. automethod:: Message.download() -.. automethod:: Message.edit() -.. automethod:: Message.edit_caption() -.. automethod:: Message.edit_media() -.. automethod:: Message.edit_reply_markup() -.. automethod:: Message.forward() -.. automethod:: Message.pin() -.. automethod:: Message.reply() -.. automethod:: Message.reply_animation() -.. automethod:: Message.reply_audio() -.. automethod:: Message.reply_cached_media() -.. automethod:: Message.reply_chat_action() -.. automethod:: Message.reply_contact() -.. automethod:: Message.reply_document() -.. automethod:: Message.reply_game() -.. automethod:: Message.reply_inline_bot_result() -.. automethod:: Message.reply_location() -.. automethod:: Message.reply_media_group() -.. automethod:: Message.reply_photo() -.. automethod:: Message.reply_poll() -.. automethod:: Message.reply_sticker() -.. automethod:: Message.reply_venue() -.. automethod:: Message.reply_video() -.. automethod:: Message.reply_video_note() -.. automethod:: Message.reply_voice() - -.. Chat -.. automethod:: Chat.archive() -.. automethod:: Chat.unarchive() - -.. User -.. automethod:: User.archive() -.. automethod:: User.unarchive() - -.. CallbackQuery -.. automethod:: CallbackQuery.answer() - -.. InlineQuery -.. automethod:: InlineQuery.answer() diff --git a/docs/source/api/client.rst b/docs/source/api/client.rst index d1b8c4b0..d28b7f61 100644 --- a/docs/source/api/client.rst +++ b/docs/source/api/client.rst @@ -1,7 +1,11 @@ Pyrogram Client =============== -This is the Client class. It exposes high-level methods for an easy access to the API. +You have entered the API Reference section where you can find detailed information about Pyrogram's API. The main Client +class, all available methods and types, filters, handlers, decorators and bound-methods detailed descriptions can be +found starting from this page. + +This page is about the Client class, which exposes high-level methods for an easy access to the API. .. code-block:: python :emphasize-lines: 1-3 diff --git a/docs/source/api/decorators.rst b/docs/source/api/decorators.rst index ff31cb27..fd397cc4 100644 --- a/docs/source/api/decorators.rst +++ b/docs/source/api/decorators.rst @@ -6,7 +6,7 @@ deserve a dedicated page. Decorators are able to register callback functions for handling updates in a much easier and cleaner way compared to :doc:`Handlers `; they do so by instantiating the correct handler and calling -:meth:`~pyrogram.Client.add_handler`, automatically. All you need to do is adding the decorators on top of your +:meth:`~pyrogram.Client.add_handler` automatically. All you need to do is adding the decorators on top of your functions. .. code-block:: python diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst index f91dd3d5..1ae0961b 100644 --- a/docs/source/api/handlers.rst +++ b/docs/source/api/handlers.rst @@ -2,10 +2,7 @@ Update Handlers =============== Handlers are used to instruct Pyrogram about which kind of updates you'd like to handle with your callback functions. - For a much more convenient way of registering callback functions have a look at :doc:`Decorators ` instead. -In case you decided to manually create a handler, use :class:`~pyrogram.Client.add_handler` to register -it. .. code-block:: python :emphasize-lines: 1, 10 diff --git a/docs/source/api/methods.rst b/docs/source/api/methods.rst deleted file mode 100644 index ac515f6e..00000000 --- a/docs/source/api/methods.rst +++ /dev/null @@ -1,282 +0,0 @@ -Available Methods -================= - -All Pyrogram methods listed here are bound to a :class:`~pyrogram.Client` instance. - -.. code-block:: python - :emphasize-lines: 6 - - from pyrogram import Client - - app = Client("my_account") - - with app: - app.send_message("haskell", "hi") - -.. currentmodule:: pyrogram - -Index ------ - -Utilities -^^^^^^^^^ - -.. hlist:: - :columns: 4 - - - :meth:`~Client.start` - - :meth:`~Client.stop` - - :meth:`~Client.restart` - - :meth:`~Client.idle` - - :meth:`~Client.run` - - :meth:`~Client.add_handler` - - :meth:`~Client.remove_handler` - - :meth:`~Client.stop_transmission` - -Messages -^^^^^^^^ - -.. hlist:: - :columns: 3 - - - :meth:`~Client.send_message` - - :meth:`~Client.forward_messages` - - :meth:`~Client.send_photo` - - :meth:`~Client.send_audio` - - :meth:`~Client.send_document` - - :meth:`~Client.send_sticker` - - :meth:`~Client.send_animated_sticker` - - :meth:`~Client.send_video` - - :meth:`~Client.send_animation` - - :meth:`~Client.send_voice` - - :meth:`~Client.send_video_note` - - :meth:`~Client.send_media_group` - - :meth:`~Client.send_location` - - :meth:`~Client.send_venue` - - :meth:`~Client.send_contact` - - :meth:`~Client.send_cached_media` - - :meth:`~Client.send_chat_action` - - :meth:`~Client.edit_message_text` - - :meth:`~Client.edit_message_caption` - - :meth:`~Client.edit_message_reply_markup` - - :meth:`~Client.edit_message_media` - - :meth:`~Client.delete_messages` - - :meth:`~Client.get_messages` - - :meth:`~Client.get_history` - - :meth:`~Client.get_history_count` - - :meth:`~Client.read_history` - - :meth:`~Client.iter_history` - - :meth:`~Client.send_poll` - - :meth:`~Client.vote_poll` - - :meth:`~Client.stop_poll` - - :meth:`~Client.retract_vote` - - :meth:`~Client.download_media` - -Chats -^^^^^ - -.. hlist:: - :columns: 3 - - - :meth:`~Client.join_chat` - - :meth:`~Client.leave_chat` - - :meth:`~Client.kick_chat_member` - - :meth:`~Client.unban_chat_member` - - :meth:`~Client.restrict_chat_member` - - :meth:`~Client.promote_chat_member` - - :meth:`~Client.export_chat_invite_link` - - :meth:`~Client.set_chat_photo` - - :meth:`~Client.delete_chat_photo` - - :meth:`~Client.set_chat_title` - - :meth:`~Client.set_chat_description` - - :meth:`~Client.pin_chat_message` - - :meth:`~Client.unpin_chat_message` - - :meth:`~Client.get_chat` - - :meth:`~Client.get_chat_member` - - :meth:`~Client.get_chat_members` - - :meth:`~Client.get_chat_members_count` - - :meth:`~Client.iter_chat_members` - - :meth:`~Client.get_dialogs` - - :meth:`~Client.iter_dialogs` - - :meth:`~Client.get_dialogs_count` - - :meth:`~Client.restrict_chat` - - :meth:`~Client.update_chat_username` - - :meth:`~Client.archive_chats` - - :meth:`~Client.unarchive_chats` - -Users -^^^^^ - -.. hlist:: - :columns: 3 - - - :meth:`~Client.get_me` - - :meth:`~Client.get_users` - - :meth:`~Client.get_profile_photos` - - :meth:`~Client.get_profile_photos_count` - - :meth:`~Client.iter_profile_photos` - - :meth:`~Client.set_profile_photo` - - :meth:`~Client.delete_profile_photos` - - :meth:`~Client.update_username` - - :meth:`~Client.get_user_dc` - -Contacts -^^^^^^^^ - -.. hlist:: - :columns: 3 - - - :meth:`~Client.add_contacts` - - :meth:`~Client.get_contacts` - - :meth:`~Client.get_contacts_count` - - :meth:`~Client.delete_contacts` - -Password -^^^^^^^^ - -.. hlist:: - :columns: 3 - - - :meth:`~Client.enable_cloud_password` - - :meth:`~Client.change_cloud_password` - - :meth:`~Client.remove_cloud_password` - -Bots -^^^^ - -.. hlist:: - :columns: 3 - - - :meth:`~Client.get_inline_bot_results` - - :meth:`~Client.send_inline_bot_result` - - :meth:`~Client.answer_callback_query` - - :meth:`~Client.answer_inline_query` - - :meth:`~Client.request_callback_answer` - - :meth:`~Client.send_game` - - :meth:`~Client.set_game_score` - - :meth:`~Client.get_game_high_scores` - -Advanced Usage (Raw API) -^^^^^^^^^^^^^^^^^^^^^^^^ - -Learn more about these methods at :doc:`Advanced Usage <../topics/advanced-usage>`. - -.. hlist:: - :columns: 4 - - - :meth:`~Client.send` - - :meth:`~Client.resolve_peer` - - :meth:`~Client.save_file` - ------ - -Details -------- - -.. Utilities -.. automethod:: Client.start() -.. automethod:: Client.stop() -.. automethod:: Client.restart() -.. automethod:: Client.idle() -.. automethod:: Client.run() -.. automethod:: Client.add_handler() -.. automethod:: Client.remove_handler() -.. automethod:: Client.stop_transmission() - -.. Messages -.. automethod:: Client.send_message() -.. automethod:: Client.forward_messages() -.. automethod:: Client.send_photo() -.. automethod:: Client.send_audio() -.. automethod:: Client.send_document() -.. automethod:: Client.send_sticker() -.. automethod:: Client.send_animated_sticker() -.. automethod:: Client.send_video() -.. automethod:: Client.send_animation() -.. automethod:: Client.send_voice() -.. automethod:: Client.send_video_note() -.. automethod:: Client.send_media_group() -.. automethod:: Client.send_location() -.. automethod:: Client.send_venue() -.. automethod:: Client.send_contact() -.. automethod:: Client.send_cached_media() -.. automethod:: Client.send_chat_action() -.. automethod:: Client.edit_message_text() -.. automethod:: Client.edit_message_caption() -.. automethod:: Client.edit_message_reply_markup() -.. automethod:: Client.edit_message_media() -.. automethod:: Client.delete_messages() -.. automethod:: Client.get_messages() -.. automethod:: Client.get_history() -.. automethod:: Client.get_history_count() -.. automethod:: Client.read_history() -.. automethod:: Client.iter_history() -.. automethod:: Client.send_poll() -.. automethod:: Client.vote_poll() -.. automethod:: Client.stop_poll() -.. automethod:: Client.retract_vote() -.. automethod:: Client.download_media() - -.. Chats -.. automethod:: Client.join_chat() -.. automethod:: Client.leave_chat() -.. automethod:: Client.kick_chat_member() -.. automethod:: Client.unban_chat_member() -.. automethod:: Client.restrict_chat_member() -.. automethod:: Client.promote_chat_member() -.. automethod:: Client.export_chat_invite_link() -.. automethod:: Client.set_chat_photo() -.. automethod:: Client.delete_chat_photo() -.. automethod:: Client.set_chat_title() -.. automethod:: Client.set_chat_description() -.. automethod:: Client.pin_chat_message() -.. automethod:: Client.unpin_chat_message() -.. automethod:: Client.get_chat() -.. automethod:: Client.get_chat_member() -.. automethod:: Client.get_chat_members() -.. automethod:: Client.get_chat_members_count() -.. automethod:: Client.iter_chat_members() -.. automethod:: Client.get_dialogs() -.. automethod:: Client.iter_dialogs() -.. automethod:: Client.get_dialogs_count() -.. automethod:: Client.restrict_chat() -.. automethod:: Client.update_chat_username() -.. automethod:: Client.archive_chats() -.. automethod:: Client.unarchive_chats() - -.. Users -.. automethod:: Client.get_me() -.. automethod:: Client.get_users() -.. automethod:: Client.get_profile_photos() -.. automethod:: Client.get_profile_photos_count() -.. automethod:: Client.iter_profile_photos() -.. automethod:: Client.set_profile_photo() -.. automethod:: Client.delete_profile_photos() -.. automethod:: Client.update_username() -.. automethod:: Client.get_user_dc() - -.. Contacts -.. automethod:: Client.add_contacts() -.. automethod:: Client.get_contacts() -.. automethod:: Client.get_contacts_count() -.. automethod:: Client.delete_contacts() - -.. Password -.. automethod:: Client.enable_cloud_password() -.. automethod:: Client.change_cloud_password() -.. automethod:: Client.remove_cloud_password() - -.. Bots -.. automethod:: Client.get_inline_bot_results() -.. automethod:: Client.send_inline_bot_result() -.. automethod:: Client.answer_callback_query() -.. automethod:: Client.answer_inline_query() -.. automethod:: Client.request_callback_answer() -.. automethod:: Client.send_game() -.. automethod:: Client.set_game_score() -.. automethod:: Client.get_game_high_scores() - -.. Advanced Usage -.. automethod:: Client.send() -.. automethod:: Client.resolve_peer() -.. automethod:: Client.save_file() \ No newline at end of file diff --git a/docs/source/api/types.rst b/docs/source/api/types.rst deleted file mode 100644 index 644f8bb2..00000000 --- a/docs/source/api/types.rst +++ /dev/null @@ -1,170 +0,0 @@ -Available Types -=============== - -All Pyrogram types listed here are accessible through the main package directly. - -.. code-block:: python - :emphasize-lines: 1 - - from pyrogram import User, Message, ... - -.. note:: - - **Optional** fields may not exist when irrelevant -- i.e.: they will contain the value of ``None`` and aren't shown - when, for example, using ``print()``. - -.. currentmodule:: pyrogram - -Index ------ - -Users & Chats -^^^^^^^^^^^^^ - -.. hlist:: - :columns: 5 - - - :class:`User` - - :class:`UserStatus` - - :class:`Chat` - - :class:`ChatPreview` - - :class:`ChatPhoto` - - :class:`ChatMember` - - :class:`ChatPermissions` - - :class:`Dialog` - -Messages & Media -^^^^^^^^^^^^^^^^ - -.. hlist:: - :columns: 5 - - - :class:`Message` - - :class:`MessageEntity` - - :class:`Photo` - - :class:`Thumbnail` - - :class:`Audio` - - :class:`Document` - - :class:`Animation` - - :class:`Video` - - :class:`Voice` - - :class:`VideoNote` - - :class:`Contact` - - :class:`Location` - - :class:`Venue` - - :class:`Sticker` - - :class:`Game` - - :class:`Poll` - - :class:`PollOption` - -Bots & Keyboards -^^^^^^^^^^^^^^^^ - -.. hlist:: - :columns: 4 - - - :class:`ReplyKeyboardMarkup` - - :class:`KeyboardButton` - - :class:`ReplyKeyboardRemove` - - :class:`InlineKeyboardMarkup` - - :class:`InlineKeyboardButton` - - :class:`ForceReply` - - :class:`CallbackQuery` - - :class:`GameHighScore` - - :class:`CallbackGame` - -Input Media -^^^^^^^^^^^ - -.. hlist:: - :columns: 4 - - - :class:`InputMedia` - - :class:`InputMediaPhoto` - - :class:`InputMediaVideo` - - :class:`InputMediaAudio` - - :class:`InputMediaAnimation` - - :class:`InputMediaDocument` - - :class:`InputPhoneContact` - -Inline Mode -^^^^^^^^^^^ - -.. hlist:: - :columns: 3 - - - :class:`InlineQuery` - - :class:`InlineQueryResult` - - :class:`InlineQueryResultArticle` - -InputMessageContent -^^^^^^^^^^^^^^^^^^^ - -.. hlist:: - :columns: 3 - - - :class:`InputMessageContent` - - :class:`InputTextMessageContent` - ------ - -Details -------- - -.. User & Chats -.. autoclass:: User() -.. autoclass:: UserStatus() -.. autoclass:: Chat() -.. autoclass:: ChatPreview() -.. autoclass:: ChatPhoto() -.. autoclass:: ChatMember() -.. autoclass:: ChatPermissions() -.. autoclass:: Dialog() - -.. Messages & Media -.. autoclass:: Message() -.. autoclass:: MessageEntity() -.. autoclass:: Photo() -.. autoclass:: Thumbnail() -.. autoclass:: Audio() -.. autoclass:: Document() -.. autoclass:: Animation() -.. autoclass:: Video() -.. autoclass:: Voice() -.. autoclass:: VideoNote() -.. autoclass:: Contact() -.. autoclass:: Location() -.. autoclass:: Venue() -.. autoclass:: Sticker() -.. autoclass:: Game() -.. autoclass:: Poll() -.. autoclass:: PollOption() - -.. Bots & Keyboards -.. autoclass:: ReplyKeyboardMarkup() -.. autoclass:: KeyboardButton() -.. autoclass:: ReplyKeyboardRemove() -.. autoclass:: InlineKeyboardMarkup() -.. autoclass:: InlineKeyboardButton() -.. autoclass:: ForceReply() -.. autoclass:: CallbackQuery() -.. autoclass:: GameHighScore() -.. autoclass:: CallbackGame() - -.. Input Media -.. autoclass:: InputMedia() -.. autoclass:: InputMediaPhoto() -.. autoclass:: InputMediaVideo() -.. autoclass:: InputMediaAudio() -.. autoclass:: InputMediaAnimation() -.. autoclass:: InputMediaDocument() -.. autoclass:: InputPhoneContact() - -.. Inline Mode -.. autoclass:: InlineQuery() -.. autoclass:: InlineQueryResult() -.. autoclass:: InlineQueryResultArticle() - -.. InputMessageContent -.. autoclass:: InputMessageContent() -.. autoclass:: InputTextMessageContent() diff --git a/docs/source/conf.py b/docs/source/conf.py index 01fbe6de..b0313901 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -66,3 +66,15 @@ html_theme_options = { html_logo = "_images/pyrogram.png" html_favicon = "_images/favicon.ico" + +latex_engine = "xelatex" +latex_logo = "_images/pyrogram.png" + +latex_elements = { + "pointsize": "12pt", + "fontpkg": r""" + \setmainfont{Noto Sans} + \setsansfont{Roboto Slab} + \setmonofont{Ubuntu Mono} + """ +} diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 449076af..5d4823c8 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -1,6 +1,9 @@ Pyrogram FAQ ============ +.. role:: strike + :class: strike + This FAQ page provides answers to common questions about Pyrogram and, to some extent, Telegram in general. .. tip:: @@ -65,7 +68,7 @@ To challenge the framework, the creator is constantly keeping a public `welcome bot `_ online 24/7 on his own, relatively-busy account for well over a year now. -In addition to that, about six months ago, one of the most popular Telegram bot has been rewritten +In addition to that, about six months ago, one of the most popular Telegram bot has been rewritten from scratch :doc:`using Pyrogram ` and is serving more than 200,000 Monthly Active Users since then, uninterruptedly and without any need for restarting it. @@ -102,9 +105,11 @@ one: ``CAADBAADyg4AAvLQYAEYD4F7vcZ43AI``. Can I use Bot API's file_ids in Pyrogram? ----------------------------------------- -Definitely! All file ids you might have taken from the Bot API are 100% compatible and re-usable in Pyrogram... +:strike:`Definitely! All file ids you might have taken from the Bot API are 100% compatible and re-usable in Pyrogram.` -...at least for now. +Starting from :doc:`Pyrogram v0.14.1 (Layer 100) `, the file_id format of all photo-like objects has +changed. Types affected are: :obj:`~pyrogram.Thumbnail`, :obj:`~pyrogram.ChatPhoto` and :obj:`~pyrogram.Photo`. Any +other file id remains compatible with the Bot API. Telegram is slowly changing some server's internals and it's doing it in such a way that file ids are going to break inevitably. Not only this, but it seems that the new, hypothetical, file ids could also possibly expire at anytime, thus @@ -129,8 +134,8 @@ If you -- even accidentally -- fail to do so, all the previous session copies wi and eventually the server will start throwing the error ``[406 AUTH_KEY_DUPLICATED]``, inviting you to login again. Why is that so? Because the server has recognized two identical sessions are running in two different locations, and -concludes it could possibly be due to a cloned/stolen device. Having the session ended in such occasions will protect -the user's privacy. +concludes it could possibly be due to a cloned/stolen device. Having the session terminated in such occasions will +protect the user's privacy. So, the only correct way to run multiple clients on the same account is authorizing your account (either user or bot) from the beginning every time, and use one separate session for each parallel client you are going to use. @@ -139,7 +144,8 @@ I started a client and nothing happens! --------------------------------------- If you are connecting from Russia, China or Iran :doc:`you need a proxy `, because Telegram could be -partially or totally blocked in those countries. +partially or totally blocked in those countries. More information about this block can be found at +`Wikipedia `_. Another possible cause might be network issues, either yours or Telegram's. To confirm this, add the following code on the top of your script and run it again. You should see some error mentioning a socket timeout or an unreachable network @@ -156,20 +162,20 @@ fails or not. What are the IP addresses of Telegram Data Centers? --------------------------------------------------- -The Telegram cloud is currently composed by a decentralized, multi-DC infrastructure (each of which can work -independently) spread in 5 different locations. However, some of the less busy DCs have been lately dismissed and their -IP addresses are now kept as aliases. +The Telegram cloud is currently composed by a decentralized, multi-DC infrastructure (currently 5 DCs, each of which can +work independently) spread in different locations worldwide. However, some of the less busy DCs have been lately +dismissed and their IP addresses are now kept as aliases to the nearest one. .. csv-table:: Production Environment :header: ID, Location, IPv4, IPv6 :widths: auto :align: center - DC1, "MIA, Miami FL, USA", ``149.154.175.50``, ``2001:b28:f23d:f001::a`` + DC1, "MIA, Miami FL, USA", ``149.154.175.53``, ``2001:b28:f23d:f001::a`` DC2, "AMS, Amsterdam, NL", ``149.154.167.51``, ``2001:67c:4e8:f002::a`` DC3*, "MIA, Miami FL, USA", ``149.154.175.100``, ``2001:b28:f23d:f003::a`` DC4, "AMS, Amsterdam, NL", ``149.154.167.91``, ``2001:67c:4e8:f004::a`` - DC5, "SIN, Singapore, SG", ``91.108.56.149``, ``2001:b28:f23f:f005::a`` + DC5, "SIN, Singapore, SG", ``91.108.56.130``, ``2001:b28:f23f:f005::a`` .. csv-table:: Test Environment :header: ID, Location, IPv4, IPv6 @@ -180,9 +186,11 @@ IP addresses are now kept as aliases. DC2, "AMS, Amsterdam, NL", ``149.154.167.40``, ``2001:67c:4e8:f002::e`` DC3*, "MIA, Miami FL, USA", ``149.154.175.117``, ``2001:b28:f23d:f003::e`` +.. centered:: More info about the Test Environment can be found :doc:`here `. + ***** Alias DC -More info about the Test Environment can be found :doc:`here `. +Thanks to `@FrayxRulez `_ for telling about alias DCs. I want to migrate my account from DCX to DCY. --------------------------------------------- @@ -200,6 +208,36 @@ mechanism is also `confirmed `_ for confirming the feature was not implemented yet. + +Why is my client reacting slowly in supergroups? +------------------------------------------------ + +This issue affects only some supergroups or only some members within the same supergroup. Mostly, it affects supergroups +whose creator's account (and thus the supergroup itself) lives inside a **different DC**, far away from yours, but could +also depend on where a member is connecting from. + +Because of how Telegram works internally, every single message you receive from and send to other members must pass +through the creator's DC, and in the worst case where you, the creator and another member all belong to three different +DCs, the other member messages have to go through from its DC to the creator's DC and finally to your DC. This process +will inevitably take its time. + + To confirm this theory and see it by yourself, you can test in a supergroup where you are sure all parties live + inside the same DC. In this case the responses will be faster. + +Another reason that makes responses come slowly is that messages are **dispatched by priority**. Depending on the kind +of member, some users receive messages faster than others and for big and busy supergroups the delay might become +noticeable, especially if you are among the lower end of the priority list: + +1. Creator. +2. Administrators. +3. Bots. +4. Mentioned users. +5. Recent online users. +6. Everyone else. + +Thanks to `@Manuel15 `_ for the priority list. + I keep getting PEER_ID_INVALID error! ------------------------------------- @@ -207,9 +245,13 @@ The error in question is ``[400 PEER_ID_INVALID]``, and could mean several thing - The chat id you tried to use is simply wrong, double check it. - The chat id refers to a group or channel you are not a member of. -- The chat id refers to a user you have't seen yet (from contacts, groups in common, forwarded messages or private - chats). - The chat id argument you passed is in form of a string; you have to convert it into an integer with ``int(chat_id)``. +- The chat id refers to a user your current session haven't met yet. + +About the last point: in order for you to meet a user and thus communicate with them, you should ask yourself how to +contact people using official apps. The answer is the same for Pyrogram too and involves normal usages such as searching +for usernames, meeting them in a common group, have their phone contacts saved or getting a message mentioning them, +either a forward or a mention in the message text. UnicodeEncodeError: '' codec can't encode … ----------------------------------------------------- @@ -217,7 +259,15 @@ UnicodeEncodeError: '' codec can't encode … Where ```` might be *ascii*, *cp932*, *charmap* or anything else other than **utf-8**. This error usually shows up when you try to print something and has very little to do with Pyrogram itself as it is strictly related to your own terminal. To fix it, either find a way to change the encoding settings of your terminal to UTF-8 or switch to a -better one. +better terminal altogether. + +Uploading with URLs gives error WEBPAGE_CURL_FAILED +--------------------------------------------------- + +When uploading media files using an URL, the server automatically tries to download the media and uploads it to the +Telegram cloud. This error usually happens in case the provided URL is not publicly accessible by Telegram itself or the +media exceeds 20 MB in size. In such cases, your only option is to download the media yourself and upload from your +local machine. My verification code expires immediately! ----------------------------------------- @@ -243,7 +293,7 @@ Having said that, here's a list of what Telegram definitely doesn't like: - Spam, sending unsolicited messages or adding people to unwanted groups and channels. - Virtual/VoIP and cheap real numbers, because they are relatively easy to get and likely used for spam/flood. -And here's a good explanation of how, probably, the system works: +And thanks to `@koteeq `_, here's a good explanation of how, probably, the system works: .. raw:: html @@ -252,8 +302,7 @@ And here's a good explanation of how, probably, the system works: data-telegram-post="PyrogramChat/69424" data-width="100%"> - -.. centered:: Join the discussion at `@Pyrogram `_ +

However, you might be right, and your account was deactivated/limited without any good reason. This could happen because of mistakes by either the automatic systems or a moderator. In such cases you can kindly email Telegram at diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index bcb1193c..80ae1ecc 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -29,7 +29,7 @@ Terms achieve high quality and availability for services. RPC - Acronym for Remote Procedure call, that is, a function which gets executed at some remote place (i.e. Telegram + Acronym for Remote Procedure Call, that is, a function which gets executed at some remote place (i.e. Telegram server) and not in your local machine. RPCError @@ -58,7 +58,7 @@ Terms Pyrogram --- to automate some behaviours, like sending messages or reacting to text commands or any other event. Session - Also known as *login session*, is a strictly personal piece of information created and held by both parties + Also known as *login session*, is a strictly personal piece of data created and held by both parties (client and server) which is used to grant permission into a single account without having to start a new authorization process from scratch. diff --git a/docs/source/index.rst b/docs/source/index.rst index 0bc175ee..f6961bc6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,6 +1,78 @@ Welcome to Pyrogram =================== +.. toctree:: + :hidden: + :caption: Introduction + + intro/quickstart + intro/install + intro/setup + +.. toctree:: + :hidden: + :caption: Getting Started + + start/auth + start/invoking + start/updates + start/errors + +.. toctree:: + :hidden: + :caption: API Reference + + api/client + api/methods/index + api/types/index + api/bound-methods/index + api/handlers + api/decorators + api/errors + api/filters + +.. toctree:: + :hidden: + :caption: Topic Guides + + topics/use-filters + topics/create-filters + topics/more-on-updates + topics/config-file + topics/smart-plugins + topics/auto-auth + topics/session-settings + topics/tgcrypto + topics/storage-engines + topics/text-formatting + topics/serializing + topics/proxy + topics/scheduling + topics/bots-interaction + topics/mtproto-vs-botapi + topics/debugging + topics/test-servers + topics/advanced-usage + topics/voice-calls + +.. toctree:: + :hidden: + :caption: Meta + + faq + glossary + powered-by + support-pyrogram + license + releases/index + +.. toctree:: + :hidden: + :caption: Telegram API + + telegram/functions/index + telegram/types/index + .. raw:: html
@@ -35,7 +107,7 @@ Welcome to Pyrogram @app.on_message(Filters.private) def hello(client, message): - message.reply("Hello {}".format(message.from_user.first_name)) + message.reply_text("Hello {}".format(message.from_user.first_name)) app.run() @@ -54,7 +126,7 @@ order using the :guilabel:`Next` button at the end of each page. Here below you relevant pages for a quick access. First Steps ------------ +^^^^^^^^^^^ .. hlist:: :columns: 2 @@ -65,18 +137,18 @@ First Steps - :doc:`Error Handling `: How to handle API errors correctly. API Reference -------------- +^^^^^^^^^^^^^ .. hlist:: :columns: 2 - :doc:`Pyrogram Client `: Reference details about the Client class. - - :doc:`Available Methods `: List of available high-level methods. - - :doc:`Available Types `: List of available high-level types. - - :doc:`Bound Methods `: List of convenient bound methods. + - :doc:`Available Methods `: List of available high-level methods. + - :doc:`Available Types `: List of available high-level types. + - :doc:`Bound Methods `: List of convenient bound methods. Meta ----- +^^^^ .. hlist:: :columns: 2 @@ -88,74 +160,4 @@ Meta - :doc:`About the License `: Information about the Project license. - :doc:`Release Notes `: Release notes for Pyrogram releases. -.. toctree:: - :hidden: - :caption: Introduction - - intro/quickstart - intro/install - intro/setup - -.. toctree:: - :hidden: - :caption: Getting Started - - start/auth - start/invoking - start/updates - start/errors - -.. toctree:: - :hidden: - :caption: API Reference - - api/client - api/methods - api/types - api/bound-methods - api/handlers - api/decorators - api/filters - api/errors - -.. toctree:: - :hidden: - :caption: Topic Guides - - topics/use-filters - topics/create-filters - topics/more-on-updates - topics/config-file - topics/smart-plugins - topics/auto-auth - topics/session-settings - topics/tgcrypto - topics/text-formatting - topics/serialize - topics/proxy - topics/bots-interaction - topics/mtproto-vs-botapi - topics/debugging - topics/test-servers - topics/advanced-usage - topics/voice-calls - -.. toctree:: - :hidden: - :caption: Meta - - faq - glossary - powered-by - support-pyrogram - license - releases/index - -.. toctree:: - :hidden: - :caption: Telegram API - - telegram/functions/index - telegram/types/index - Last updated on |today| \ No newline at end of file diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst index 82ab4c0b..b60671f0 100644 --- a/docs/source/intro/install.rst +++ b/docs/source/intro/install.rst @@ -47,7 +47,7 @@ Pyrogram heavily depends on IO-bound network code (it's a cloud-based messaging where asyncio shines the most by providing extra performance and efficiency while running on a single OS-level thread only. -**A fully asynchronous variant of Pyrogram is therefore available** (Python 3.5.3+ required). +**A fully asynchronous variant of Pyrogram is therefore available** (Python 3.5.3 or higher is required). Use this command to install (note "asyncio.zip" in the link): .. code-block:: text diff --git a/docs/source/intro/quickstart.rst b/docs/source/intro/quickstart.rst index a7a7e377..13f646d1 100644 --- a/docs/source/intro/quickstart.rst +++ b/docs/source/intro/quickstart.rst @@ -46,4 +46,4 @@ In the next few pages of the introduction, we'll take a much more in-depth look Feeling eager to continue? You can take a shortcut to :doc:`Calling Methods <../start/invoking>` and come back later to learn some more details. -.. _community: //t.me/Pyrogram +.. _community: https://t.me/Pyrogram diff --git a/docs/source/intro/setup.rst b/docs/source/intro/setup.rst index 6273b2b2..b3aa1836 100644 --- a/docs/source/intro/setup.rst +++ b/docs/source/intro/setup.rst @@ -29,9 +29,9 @@ Configuration Having the API key from the previous step in handy, we can now begin to configure a Pyrogram project. There are two ways to do so, and you can choose what fits better for you: -- First option (recommended): create a new ``config.ini`` file at the root of your working directory, copy-paste the - following and replace the **api_id** and **api_hash** values with your own. This is the preferred method because - allows you to keep your credentials out of your code without having to deal with how to load them: +- First option (recommended): create a new ``config.ini`` file next to your main script, copy-paste the following and + replace the **api_id** and **api_hash** values with your own. This is the preferred method because allows you to + keep your credentials out of your code without having to deal with how to load them: .. code-block:: ini diff --git a/docs/source/license.rst b/docs/source/license.rst index 38302bdc..43f59d73 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -2,7 +2,7 @@ About the License ================= .. image:: https://www.gnu.org/graphics/lgplv3-with-text-154x68.png - :align: right + :align: left Pyrogram is free software and is currently licensed under the terms of the `GNU Lesser General Public License v3 or later (LGPLv3+)`_. In short: you may use, redistribute and/or modify it diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst index cf329947..bd82bf73 100644 --- a/docs/source/start/errors.rst +++ b/docs/source/start/errors.rst @@ -28,7 +28,7 @@ Error Categories ---------------- The ``RPCError`` packs together all the possible errors Telegram could raise, but to make things tidier, Pyrogram -provides categories of errors, which are named after the common HTTP errors and subclass-ed from the RPCError: +provides categories of errors, which are named after the common HTTP errors and are subclass-ed from the RPCError: .. code-block:: python @@ -71,14 +71,22 @@ RPCError, thus building a class of error hierarchy such as this: Unknown Errors -------------- -In case Pyrogram does not know anything yet about a specific error, it raises a special ``520 - UnknownError`` exception -and logs it in the ``unknown_errors.txt`` file. Users are invited to report these unknown errors. +In case Pyrogram does not know anything about a specific error yet, it raises a generic error from its known category, +for example, an unknown error with error code ``400``, will be raised as a ``BadRequest``. This way you can catch the +whole category of errors and be sure to also handle these unknown errors. + +In case a whole class of errors is unknown (that is, an error code that is unknown), Pyrogram will raise a special +``520 UnknownError`` exception. + +In both cases, Pyrogram will log them in the ``unknown_errors.txt`` file. Users are invited to report +these unknown errors in the `discussion group `_. Errors with Values ------------------ Exception objects may also contain some informative values. For example, ``FloodWait`` holds the amount of seconds you -have to wait before you can try again. The value is always stored in the ``x`` field of the returned exception object: +have to wait before you can try again, some other errors contain the DC number on which the request must be repeated on. +The value is stored in the ``x`` attribute of the exception object: .. code-block:: python @@ -88,4 +96,4 @@ have to wait before you can try again. The value is always stored in the ``x`` f try: ... except FloodWait as e: - time.sleep(e.x) # Wait before trying again + time.sleep(e.x) # Wait "x" seconds before continuing diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst index 9ac428b3..056fcb3d 100644 --- a/docs/source/start/updates.rst +++ b/docs/source/start/updates.rst @@ -45,7 +45,9 @@ arrives: app.run() -#. Let's examine these four new pieces. First one: a callback function we defined which accepts two arguments - +Let's examine these four new pieces. + +#. A callback function we defined which accepts two arguments - *(client, message)*. This will be the function that gets executed every time a new message arrives and Pyrogram will call that function by passing the client instance and the new message instance as argument. @@ -54,14 +56,14 @@ arrives: def my_function(client, message): print(message) -#. Second one: the :class:`~pyrogram.MessageHandler`. This object tells Pyrogram the function we defined above must - only handle updates that are in form of a :class:`~pyrogram.Message`: +#. The :class:`~pyrogram.MessageHandler`. This object tells Pyrogram the function we defined above must only handle + updates that are in form of a :class:`~pyrogram.Message`: .. code-block:: python my_handler = MessageHandler(my_function) -#. Third: the method :meth:`~pyrogram.Client.add_handler`. This method is used to actually register the handler and let +#. The method :meth:`~pyrogram.Client.add_handler`. This method is used to actually register the handler and let Pyrogram know it needs to be taken into consideration when new updates arrive and the internal dispatching phase begins. @@ -69,7 +71,7 @@ arrives: app.add_handler(my_handler) -#. Last one, the :meth:`~pyrogram.Client.run` method. What this does is simply call :meth:`~pyrogram.Client.start` and +#. The :meth:`~pyrogram.Client.run` method. What this does is simply call :meth:`~pyrogram.Client.start` and a special method :meth:`~pyrogram.Client.idle` that keeps your main scripts alive until you press ``CTRL+C``; the client will be automatically stopped after that. @@ -96,14 +98,3 @@ to do so is by decorating your callback function with the :meth:`~pyrogram.Clien app.run() - - -.. note:: - - Due to how these decorators work in Pyrogram, they will wrap your defined callback function in a tuple consisting of - ``(handler, group)``; this will be the value held by your function identifier (e.g.: *my_function* from the example - above). - - In case, for some reason, you want to get your own function back after it has been decorated, you need to access - ``my_function[0].callback``, that is, the *callback* field of the *handler* object which is the first element in the - tuple, accessed by bracket notation *[0]*. diff --git a/docs/source/topics/advanced-usage.rst b/docs/source/topics/advanced-usage.rst index 9c794be0..1460a3d8 100644 --- a/docs/source/topics/advanced-usage.rst +++ b/docs/source/topics/advanced-usage.rst @@ -1,9 +1,9 @@ Advanced Usage ============== -Pyrogram's API, which consists of well documented convenience :doc:`methods <../api/methods>` and facade -:doc:`types <../api/types>`, exists to provide a much easier interface to the undocumented and often confusing Telegram -API. +Pyrogram's API, which consists of well documented convenience :doc:`methods <../api/methods/index>` and facade +:doc:`types <../api/types/index>`, exists to provide a much easier interface to the undocumented and often confusing +Telegram API. In this section, you'll be shown the alternative way of communicating with Telegram using Pyrogram: the main "raw" Telegram API with its functions and types. @@ -23,21 +23,21 @@ some pitfalls to take into consideration when working with the raw API. Every available high-level methods in Pyrogram is built on top of these raw functions. Nothing stops you from using the raw functions only, but they are rather complex and - :doc:`plenty of them <../api/methods>` are already re-implemented by providing a much simpler and cleaner interface - which is very similar to the Bot API (yet much more powerful). + :doc:`plenty of them <../api/methods/index>` are already re-implemented by providing a much simpler and cleaner + interface which is very similar to the Bot API (yet much more powerful). If you think a raw function should be wrapped and added as a high-level method, feel free to ask in our Community_! Invoking Functions ^^^^^^^^^^^^^^^^^^ -Unlike the :doc:`methods <../api/methods>` found in Pyrogram's API, which can be called in the usual simple way, +Unlike the :doc:`methods <../api/methods/index>` found in Pyrogram's API, which can be called in the usual simple way, functions to be invoked from the raw Telegram API have a different way of usage and are more complex. -First of all, both :doc:`raw functions <../telegram/functions/index>` and :doc:`raw types <../telegram/types/index>` live in their -respective packages (and sub-packages): ``pyrogram.api.functions``, ``pyrogram.api.types``. They all exist as Python -classes, meaning you need to create an instance of each every time you need them and fill them in with the correct -values using named arguments. +First of all, both :doc:`raw functions <../telegram/functions/index>` and :doc:`raw types <../telegram/types/index>` +live in their respective packages (and sub-packages): ``pyrogram.api.functions``, ``pyrogram.api.types``. They all exist +as Python classes, meaning you need to create an instance of each every time you need them and fill them in with the +correct values using named arguments. Next, to actually invoke the raw function you have to use the :meth:`~pyrogram.Client.send` method provided by the Client class and pass the function object you created. diff --git a/docs/source/topics/create-filters.rst b/docs/source/topics/create-filters.rst index 6cb33a50..6ae6e98c 100644 --- a/docs/source/topics/create-filters.rst +++ b/docs/source/topics/create-filters.rst @@ -24,7 +24,7 @@ button: app.send_message( "username", # Change this to your username or id - "Pyrogram's custom filter test", + "Pyrogram custom filter test", reply_markup=InlineKeyboardMarkup( [[InlineKeyboardButton("Press me", "pyrogram")]] ) @@ -33,61 +33,54 @@ button: Basic Filters ------------- -For this basic filter we will be using only the first two parameters of :meth:`~pyrogram.Filters.create`. +For this basic filter we will be using only the first parameter of :meth:`~pyrogram.Filters.create`. The code below creates a simple filter for hardcoded, static callback data. This filter will only allow callback queries -containing "Pyrogram" as data, that is, the function *func* you pass returns True in case the callback query data -equals to ``"Pyrogram"``. +containing "pyrogram" as data, that is, the function *func* you pass returns True in case the callback query data +equals to ``"pyrogram"``. .. code-block:: python - static_data = Filters.create( - name="StaticdData", - func=lambda flt, query: query.data == "Pyrogram" - ) + static_data_filter = Filters.create(lambda _, query: query.data == "pyrogram") The ``lambda`` operator in python is used to create small anonymous functions and is perfect for this example, the same -could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter's scope: +could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter scope: .. code-block:: python - def func(flt, query): - return query.data == "Pyrogram" + def func(_, query): + return query.data == "pyrogram" - static_data = Filters.create( - name="StaticData", - func=func - ) + static_data_filter = Filters.create(func) The filter usage remains the same: .. code-block:: python - @app.on_callback_query(static_data) + @app.on_callback_query(static_data_filter) def pyrogram_data(_, query): query.answer("it works!") Filters with Arguments ---------------------- -A much cooler filter would be one that accepts "Pyrogram" or any other data as argument at usage time. -A dynamic filter like this will make use of the third parameter of :meth:`~pyrogram.Filters.create`. +A much cooler filter would be one that accepts "pyrogram" or any other data as argument at usage time. +A dynamic filter like this will make use of named arguments for the :meth:`~pyrogram.Filters.create` method. This is how a dynamic custom filter looks like: .. code-block:: python - def dynamic_data(data): + def dynamic_data_filter(data): return Filters.create( - name="DynamicData", - func=lambda flt, query: flt.data == query.data, - data=data # "data" kwarg is accessed with "flt.data" + lambda flt, query: flt.data == query.data, + data=data # "data" kwarg is accessed with "flt.data" above ) And its usage: .. code-block:: python - @app.on_callback_query(dynamic_data("Pyrogram")) + @app.on_callback_query(dynamic_data_filter("pyrogram")) def pyrogram_data(_, query): query.answer("it works!") diff --git a/docs/source/topics/scheduling.rst b/docs/source/topics/scheduling.rst new file mode 100644 index 00000000..3cb95ec7 --- /dev/null +++ b/docs/source/topics/scheduling.rst @@ -0,0 +1,87 @@ +Scheduling Tasks +================ + +Scheduling tasks means executing one or more functions periodically at pre-defined intervals or after a delay. This is +useful, for example, to send recurring messages to specific chats or users. + +Since there's no built-in task scheduler in Pyrogram, this page will only show examples on how to integrate Pyrogram +with the main Python schedule libraries such as ``schedule`` and ``apscheduler``. For more detailed information, you can +visit and learn from each library documentation. + +Using ``schedule`` +------------------ + +- Install with ``pip3 install schedule`` +- Documentation: https://schedule.readthedocs.io + +.. code-block:: python + + import time + + import schedule + + from pyrogram import Client + + app = Client("my_account") + + + def job(): + app.send_message("me", "Hi!") + + + schedule.every(3).seconds.do(job) + + with app: + while True: + schedule.run_pending() + time.sleep(1) + + + +Using ``apscheduler`` +--------------------- + +- Install with ``pip3 install apscheduler`` +- Documentation: https://apscheduler.readthedocs.io + +.. code-block:: python + + from apscheduler.schedulers.background import BackgroundScheduler + + from pyrogram import Client + + app = Client("my_account") + + + def job(): + app.send_message("me", "Hi!") + + + scheduler = BackgroundScheduler() + scheduler.add_job(job, "interval", seconds=3) + + scheduler.start() + app.run() + +``apscheduler`` does also support async code, here's an example with +`Pyrogram Asyncio `_: + +.. code-block:: python + + from apscheduler.schedulers.asyncio import AsyncIOScheduler + + from pyrogram import Client + + app = Client("my_account") + + + async def job(): + await app.send_message("me", "Hi!") + + + scheduler = AsyncIOScheduler() + scheduler.add_job(job, "interval", seconds=3) + + scheduler.start() + app.run() + diff --git a/docs/source/topics/serialize.rst b/docs/source/topics/serializing.rst similarity index 93% rename from docs/source/topics/serialize.rst rename to docs/source/topics/serializing.rst index a238f8dc..4c0b2327 100644 --- a/docs/source/topics/serialize.rst +++ b/docs/source/topics/serializing.rst @@ -9,7 +9,7 @@ For Humans - str(obj) --------------------- If you want a nicely formatted, human readable JSON representation of any object in the API -- namely, any object from -:doc:`Pyrogram types <../api/types>`, :doc:`raw functions <../telegram/functions/index>` and +:doc:`Pyrogram types <../api/types/index>`, :doc:`raw functions <../telegram/functions/index>` and :doc:`raw types <../telegram/types/index>` -- you can use use ``str(obj)``. .. code-block:: python diff --git a/docs/source/topics/smart-plugins.rst b/docs/source/topics/smart-plugins.rst index 8e59b971..5131f27b 100644 --- a/docs/source/topics/smart-plugins.rst +++ b/docs/source/topics/smart-plugins.rst @@ -316,9 +316,9 @@ attribute. Here's an example: Unloading ^^^^^^^^^ -In order to unload a plugin, or any other handler, all you need to do is obtain a reference to it by importing the -relevant module and call :meth:`~pyrogram.Client.remove_handler` Client's method with your function -name preceded by the star ``*`` operator as argument. Example: +In order to unload a plugin, all you need to do is obtain a reference to it by importing the relevant module and call +:meth:`~pyrogram.Client.remove_handler` Client's method with your function's *handler* special attribute preceded by the +star ``*`` operator as argument. Example: - ``main.py`` @@ -328,14 +328,14 @@ name preceded by the star ``*`` operator as argument. Example: ... - app.remove_handler(*echo) + app.remove_handler(*echo.handler) The star ``*`` operator is used to unpack the tuple into positional arguments so that *remove_handler* will receive exactly what is needed. The same could have been achieved with: .. code-block:: python - handler, group = echo + handler, group = echo.handler app.remove_handler(handler, group) Loading @@ -352,4 +352,4 @@ using :meth:`~pyrogram.Client.add_handler` instead. Example: ... - app.add_handler(*echo) \ No newline at end of file + app.add_handler(*echo.handler) \ No newline at end of file diff --git a/docs/source/topics/storage-engines.rst b/docs/source/topics/storage-engines.rst new file mode 100644 index 00000000..44b4afa6 --- /dev/null +++ b/docs/source/topics/storage-engines.rst @@ -0,0 +1,99 @@ +Storage Engines +=============== + +Every time you login to Telegram, some personal piece of data are created and held by both parties (the client, Pyrogram +and the server, Telegram). This session data is uniquely bound to your own account, indefinitely (until you logout or +decide to manually terminate it) and is used to authorize a client to execute API calls on behalf of your identity. + +Persisting Sessions +------------------- + +In order to make a client reconnect successfully between restarts, that is, without having to start a new +authorization process from scratch each time, Pyrogram needs to store the generated session data somewhere. + +Other useful data being stored is peers' cache. In short, peers are all those entities you can chat with, such as users +or bots, basic groups, but also channels and supergroups. Because of how Telegram works, a unique pair of **id** and +**access_hash** is needed to contact a peer. This, plus other useful info such as the peer type, is what is stored +inside a session storage. + +So, if you ever wondered how is Pyrogram able to contact peers just by asking for their ids, it's because of this very +reason: the peer *id* is looked up in the internal database and the available *access_hash* is retrieved, which is then +used to correctly invoke API methods. + +Different Storage Engines +------------------------- + +Let's now talk about how Pyrogram actually stores all the relevant data. Pyrogram offers two different types of storage +engines: a **File Storage** and a **Memory Storage**. These engines are well integrated in the library and require a +minimal effort to set up. Here's how they work: + +File Storage +^^^^^^^^^^^^ + +This is the most common storage engine. It is implemented by using **SQLite**, which will store the session and peers +details. The database will be saved to disk as a single portable file and is designed to efficiently store and retrieve +peers whenever they are needed. + +To use this type of engine, simply pass any name of your choice to the ``session_name`` parameter of the +:obj:`~pyrogram.Client` constructor, as usual: + +.. code-block:: python + + from pyrogram import Client + + with Client("my_account") as app: + print(app.get_me()) + +Once you successfully log in (either with a user or a bot identity), a session file will be created and saved to disk as +``my_account.session``. Any subsequent client restart will make Pyrogram search for a file named that way and the +session database will be automatically loaded. + +Memory Storage +^^^^^^^^^^^^^^ + +In case you don't want to have any session file saved to disk, you can use an in-memory storage by passing the special +session name "**:memory:**" to the ``session_name`` parameter of the :obj:`~pyrogram.Client` constructor: + +.. code-block:: python + + from pyrogram import Client + + with Client(":memory:") as app: + print(app.get_me()) + +This storage engine is still backed by SQLite, but the database exists purely in memory. This means that, once you stop a +client, the entire database is discarded and the session details used for logging in again will be lost forever. + +Session Strings +--------------- + +In case you want to use an in-memory storage, but also want to keep access to the session you created, call +:meth:`~pyrogram.Client.export_session_string` anytime before stopping the client... + +.. code-block:: python + + from pyrogram import Client + + with Client(":memory:") as app: + print(app.export_session_string()) + +...and save the resulting (quite long) string somewhere. You can use this string as session name the next time you want +to login using the same session; the storage used will still be completely in-memory: + +.. code-block:: python + + from pyrogram import Client + + session_string = "...ZnUIFD8jsjXTb8g_vpxx48k1zkov9sapD-tzjz-S4WZv70M..." + + with Client(session_string) as app: + print(app.get_me()) + +Session strings are useful when you want to run authorized Pyrogram clients on platforms like +`Heroku `_, where their ephemeral filesystems makes it much harder for a file-based storage +engine to properly work as intended. + +But, why is the session string so long? Can't it be shorter? No, it can't. The session string already packs the bare +minimum data Pyrogram needs to successfully reconnect to an authorized session, and the 2048-bits auth key is the major +contributor to the overall length. Needless to say that this string, as well as any other session storage, represent +strictly personal data. Keep them safe. diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst index bc74d562..0194dc58 100644 --- a/docs/source/topics/text-formatting.rst +++ b/docs/source/topics/text-formatting.rst @@ -1,40 +1,99 @@ Text Formatting =============== -Pyrogram, just like the `Telegram Bot API`_, natively supports basic Markdown and HTML formatting styles for text -messages and media captions. +.. role:: strike + :class: strike -Markdown style uses the same syntax as Telegram Desktop's and is enabled by default. +.. role:: underline + :class: underline -Beside bold, italic, and pre-formatted code, **Pyrogram does also support inline URLs and inline mentions of users**. +.. role:: bold-underline + :class: bold-underline + +.. role:: strike-italic + :class: strike-italic + +Pyrogram uses a custom Markdown dialect for text formatting which adds some unique features that make writing styled +texts easier in both Markdown and HTML. You can send sophisticated text messages and media captions using a great +variety of decorations that can also be nested in order to combine multiple styles together. + +Basic Styles +------------ + +When formatting your messages, you can choose between Markdown-style, HTML-style or both (default). The following is a +list of the basic styles currently supported by Pyrogram. + +- **bold** +- *italic* +- :strike:`strike` +- :underline:`underline` +- `text URL `_ +- `user text mention `_ +- ``inline fixed-width code`` +- .. code-block:: text + + pre-formatted + fixed-width + code block + +.. note:: + + User text mentions are only guaranteed to work if you have already met the user (in groups or private chats). Markdown Style -------------- -To use this mode, pass "markdown" in the *parse_mode* field when using +To strictly use this mode, pass "markdown" to the *parse_mode* parameter when using :meth:`~pyrogram.Client.send_message`. Use the following syntax in your message: .. code-block:: text - **bold text** + **bold** - __italic text__ + __italic__ - [inline URL](https://docs.pyrogram.org/) + --underline-- - [inline mention of a user](tg://user?id=23122162) + ~~strike~~ + + [text URL](https://docs.pyrogram.org/) + + [text user mention](tg://user?id=23122162) `inline fixed-width code` - ```block_language - pre-formatted fixed-width code block + ``` + pre-formatted + fixed-width + code block ``` +**Example**: + +.. code-block:: python + + app.send_message( + "haskell", + ( + "**bold**, " + "__italic__, " + "--underline--, " + "~~strike~~, " + "[mention](tg://user?id=23122162), " + "[URL](https://pyrogram.org), " + "`code`, " + "```" + "for i in range(10):\n" + " print(i)" + "```" + ), + parse_mode="markdown" + ) HTML Style ---------- -To use this mode, pass "html" in the *parse_mode* field when using :meth:`~pyrogram.Client.send_message`. +To strictly use this mode, pass "html" to the *parse_mode* parameter when using :meth:`~pyrogram.Client.send_message`. The following tags are currently supported: .. code-block:: text @@ -43,55 +102,124 @@ The following tags are currently supported: italic, italic - inline URL + underline - inline mention of a user + strike, strike, strike + + text URL + + inline mention inline fixed-width code -
pre-formatted fixed-width code block
+
+    pre-formatted
+      fixed-width
+        code block
+    
-.. note:: Mentions are only guaranteed to work if you have already met the user (in groups or private chats). +**Example**: -Examples --------- +.. code-block:: python -- Markdown: + app.send_message( + "haskell", + ( + "bold, " + "italic, " + "underline, " + "strike, " + "mention, " + "URL, " + "code\n\n" + "
"
+            "for i in range(10):\n"
+            "    print(i)"
+            "
" + ), + parse_mode="html" + ) + +.. note:: + + All ``<``, ``>`` and ``&`` symbols that are not a part of a tag or an HTML entity must be replaced with the + corresponding HTML entities (``<`` with ``<``, ``>`` with ``>`` and ``&`` with ``&``). You can use this + snippet to quickly escape those characters: .. code-block:: python - app.send_message( - chat_id="haskell", - text=( - "**bold**, " - "__italic__, " - "[mention](tg://user?id=23122162), " - "[URL](https://docs.pyrogram.org), " - "`code`, " - "```" - "for i in range(10):\n" - " print(i)```" - ) - ) + import html -- HTML: + text = "" + text = html.escape(text) - .. code-block:: python + print(text) - app.send_message( - chat_id="haskell", - text=( - "bold, " - "italic, " - "mention, " - "URL, " - "code, " - "
"
-                "for i in range(10):\n"
-                "    print(i)"
-                "
" - ), - parse_mode="html" - ) + .. code-block:: text -.. _Telegram Bot API: https://core.telegram.org/bots/api#formatting-options \ No newline at end of file + <my text> + +Different Styles +---------------- + +By default, when ignoring the *parse_mode* parameter, both Markdown and HTML styles are enabled together. +This means you can combine together both syntaxes in the same text: + +.. code-block:: python + + app.send_message("haskell", "**bold**, italic") + +Result: + + **bold**, *italic* + +If you don't like this behaviour you can always choose to only enable either Markdown or HTML in strict mode by passing +"markdown" or "html" as argument to the *parse_mode* parameter. + +.. code-block:: + + app.send_message("haskell", "**bold**, italic", parse_mode="markdown") + app.send_message("haskell", "**bold**, italic", parse_mode="html") + +Result: + + **bold**, italic + + \*\*bold**, *italic* + +In case you want to completely turn off the style parser, simply pass ``None`` to *parse_mode*. The text will be sent +as-is. + +.. code-block:: python + + app.send_message("haskell", "**bold**, italic", parse_mode=None) + +Result: + + \*\*bold**, italic + +Nested and Overlapping Entities +------------------------------- + +You can also style texts with more than one decoration at once by nesting entities together. For example, you can send +a text message with both :bold-underline:`bold and underline` styles, or a text that has both :strike-italic:`italic and +strike` styles, and you can still combine both Markdown and HTML together. + +Here there are some example texts you can try sending: + +**Markdown**: + +- ``**bold, --underline--**`` +- ``**bold __italic --underline ~~strike~~--__**`` +- ``**bold __and** italic__`` + +**HTML**: + +- ``bold, underline`` +- ``bold italic underline strike`` +- ``bold and italic`` + +**Combined**: + +- ``--you can combine HTML with **Markdown**--`` +- ``**and also overlap** --entities this way--`` diff --git a/docs/source/topics/use-filters.rst b/docs/source/topics/use-filters.rst index d481b393..de7a35a8 100644 --- a/docs/source/topics/use-filters.rst +++ b/docs/source/topics/use-filters.rst @@ -1,8 +1,8 @@ Using Filters ============= -So far we've seen how to register a callback function that executes every time a specific update comes from the server, -but there's much more than that to come. +So far we've seen :doc:`how to register a callback function <../start/updates>` that executes every time a specific update +comes from the server, but there's much more than that to come. Here we'll discuss about :class:`~pyrogram.Filters`. Filters enable a fine-grain control over what kind of updates are allowed or not to be passed in your callback functions, based on their inner details. diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index ac184844..bc4a0545 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -24,7 +24,7 @@ if sys.version_info[:3] in [(3, 5, 0), (3, 5, 1), (3, 5, 2)]: # Monkey patch the standard "typing" module because Python versions from 3.5.0 to 3.5.2 have a broken one. sys.modules["typing"] = typing -__version__ = "0.15.0-develop" +__version__ = "0.16.0.dev" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2019 Dan " diff --git a/pyrogram/api/__init__.py b/pyrogram/api/__init__.py index 8d7831ff..78f1a579 100644 --- a/pyrogram/api/__init__.py +++ b/pyrogram/api/__init__.py @@ -19,8 +19,7 @@ from importlib import import_module from .all import objects -from .core.tl_object import TLObject for k, v in objects.items(): path, name = v.rsplit(".", 1) - TLObject.all[k] = getattr(import_module(path), name) + objects[k] = getattr(import_module(path), name) diff --git a/pyrogram/api/core/__init__.py b/pyrogram/api/core/__init__.py index aaf5a324..ff4fc9c5 100644 --- a/pyrogram/api/core/__init__.py +++ b/pyrogram/api/core/__init__.py @@ -22,8 +22,5 @@ from .gzip_packed import GzipPacked from .list import List from .message import Message from .msg_container import MsgContainer -from .primitives import ( - Bool, BoolTrue, BoolFalse, Bytes, Double, - Int, Long, Int128, Int256, Null, String, Vector -) +from .primitives import * from .tl_object import TLObject diff --git a/pyrogram/api/core/primitives/__init__.py b/pyrogram/api/core/primitives/__init__.py index 8885878b..f86e3cab 100644 --- a/pyrogram/api/core/primitives/__init__.py +++ b/pyrogram/api/core/primitives/__init__.py @@ -16,10 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .bool import Bool, BoolTrue, BoolFalse +from .bool import Bool, BoolFalse, BoolTrue from .bytes import Bytes from .double import Double from .int import Int, Long, Int128, Int256 -from .null import Null from .string import String from .vector import Vector + +__all__ = ["Bool", "BoolFalse", "BoolTrue", "Bytes", "Double", "Int", "Long", "Int128", "Int256", "String", "Vector"] diff --git a/pyrogram/api/core/tl_object.py b/pyrogram/api/core/tl_object.py index 4b951404..d39a8ae2 100644 --- a/pyrogram/api/core/tl_object.py +++ b/pyrogram/api/core/tl_object.py @@ -20,17 +20,17 @@ from collections import OrderedDict from io import BytesIO from json import dumps +from ..all import objects + class TLObject: - all = {} - __slots__ = [] QUALNAME = "Base" @staticmethod def read(b: BytesIO, *args): # TODO: Rename b -> data - return TLObject.all[int.from_bytes(b.read(4), "little")].read(b, *args) + return objects[int.from_bytes(b.read(4), "little")].read(b, *args) def write(self, *args) -> bytes: pass diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 1106a416..7ac155c2 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -16,8 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import base64 -import json import logging import math import mimetypes @@ -28,7 +26,6 @@ import tempfile import threading import time from configparser import ConfigParser -from datetime import datetime from hashlib import sha256, md5 from importlib import import_module from pathlib import Path @@ -53,6 +50,7 @@ from pyrogram.errors import ( from pyrogram.session import Auth, Session from .ext import utils, Syncer, BaseClient, Dispatcher from .methods import Methods +from .storage import Storage, FileStorage, MemoryStorage log = logging.getLogger(__name__) @@ -62,8 +60,13 @@ class Client(Methods, BaseClient): Parameters: session_name (``str``): - Name to uniquely identify a session of either a User or a Bot, e.g.: "my_account". This name will be used - to save a file to disk that stores details needed for reconnecting without asking again for credentials. + Pass a string of your choice to give a name to the client session, e.g.: "*my_account*". This name will be + used to save a file on disk that stores details needed to reconnect without asking again for credentials. + Alternatively, if you don't want a file to be saved on disk, pass the special name "**:memory:**" to start + an in-memory session that will be discarded as soon as you stop the Client. In order to reconnect again + using a memory storage without having to login again, you can use + :meth:`~pyrogram.Client.export_session_string` before stopping the client to get a session string you can + pass here as argument. api_id (``int``, *optional*): The *api_id* part of your Telegram API Key, as integer. E.g.: 12345 @@ -150,7 +153,7 @@ class Client(Methods, BaseClient): workdir (``str``, *optional*): Define a custom working directory. The working directory is the location in your filesystem - where Pyrogram will store your session files. Defaults to "." (current directory). + where Pyrogram will store your session files. Defaults to the parent directory of the main script. config_file (``str``, *optional*): Path of the configuration file. Defaults to ./config.ini @@ -171,13 +174,24 @@ class Client(Methods, BaseClient): download_media, ...) are less prone to throw FloodWait exceptions. Only available for users, bots will ignore this parameter. Defaults to False (normal session). + + Example: + .. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + + with app: + app.send_message("me", "Hi!") + """ terms_of_service_displayed = False def __init__( self, - session_name: str, + session_name: Union[str, Storage], api_id: Union[int, str] = None, api_hash: str = None, app_version: str = None, @@ -224,12 +238,23 @@ class Client(Methods, BaseClient): self.first_name = first_name self.last_name = last_name self.workers = workers - self.workdir = workdir - self.config_file = config_file + self.workdir = Path(workdir) + self.config_file = Path(config_file) self.plugins = plugins self.no_updates = no_updates self.takeout = takeout + if isinstance(session_name, str): + if session_name == ":memory:" or len(session_name) >= MemoryStorage.SESSION_STRING_SIZE: + session_name = re.sub(r"[\n\s]+", "", session_name) + self.storage = MemoryStorage(session_name) + else: + self.storage = FileStorage(session_name, self.workdir) + elif isinstance(session_name, Storage): + self.storage = session_name + else: + raise ValueError("Unknown storage engine") + self.dispatcher = Dispatcher(self, workers) def __enter__(self): @@ -255,59 +280,58 @@ class Client(Methods, BaseClient): self._proxy.update(value) def start(self): - """Start the Client. + """Start the client. + + This method connects the client to Telegram and, in case of new sessions, automatically manages the full login + process using an interactive prompt (by default). + + Has no parameters. Raises: - RPCError: In case of a Telegram RPC error. - ConnectionError: In case you try to start an already started Client. + ConnectionError: In case you try to start an already started client. + + Example: + .. code-block:: python + :emphasize-lines: 4 + + from pyrogram import Client + + app = Client("my_account") + app.start() + + ... # Call API methods + + app.stop() """ if self.is_started: raise ConnectionError("Client has already been started") - if self.BOT_TOKEN_RE.match(self.session_name): - self.is_bot = True - self.bot_token = self.session_name - self.session_name = self.session_name.split(":")[0] - log.warning('\nWARNING: You are using a bot token as session name!\n' - 'This usage will be deprecated soon. Please use a session file name to load ' - 'an existing session and the bot_token argument to create new sessions.\n' - 'More info: https://docs.pyrogram.org/intro/auth#bot-authorization\n') - self.load_config() self.load_session() self.load_plugins() - self.session = Session( - self, - self.dc_id, - self.auth_key - ) + self.session = Session(self, self.storage.dc_id, self.storage.auth_key) self.session.start() self.is_started = True try: - if self.user_id is None: + if self.storage.user_id is None: if self.bot_token is None: - self.is_bot = False + self.storage.is_bot = False self.authorize_user() else: - self.is_bot = True + self.storage.is_bot = True self.authorize_bot() - self.save_session() - - if not self.is_bot: + if not self.storage.is_bot: if self.takeout: self.takeout_id = self.send(functions.account.InitTakeoutSession()).id log.warning("Takeout session {} initiated".format(self.takeout_id)) now = time.time() - if abs(now - self.date) > Client.OFFLINE_SLEEP: - self.peers_by_username = {} - self.peers_by_phone = {} - + if abs(now - self.storage.date) > Client.OFFLINE_SLEEP: self.get_initial_dialogs() self.get_contacts() else: @@ -350,8 +374,25 @@ class Client(Methods, BaseClient): def stop(self): """Stop the Client. + This method disconnects the client from Telegram and stops the underlying tasks. + + Has no parameters. + Raises: - ConnectionError: In case you try to stop an already stopped Client. + ConnectionError: In case you try to stop an already stopped client. + + Example: + .. code-block:: python + :emphasize-lines: 8 + + from pyrogram import Client + + app = Client("my_account") + app.start() + + ... # Call API methods + + app.stop() """ if not self.is_started: raise ConnectionError("Client is already stopped") @@ -392,64 +433,125 @@ class Client(Methods, BaseClient): def restart(self): """Restart the Client. + This method will first call :meth:`~Client.stop` and then :meth:`~Client.start` in a row in order to restart + a client using a single method. + + Has no parameters. + Raises: ConnectionError: In case you try to restart a stopped Client. + + Example: + .. code-block:: python + :emphasize-lines: 8 + + from pyrogram import Client + + app = Client("my_account") + app.start() + + ... # Call API methods + + app.restart() + + ... # Call other API methods + + app.stop() """ self.stop() self.start() - def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): - """Block the main script execution until a signal (e.g.: from CTRL+C) is received. - Once the signal is received, the client will automatically stop and the main script will continue its execution. + @staticmethod + def idle(stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): + """Block the main script execution until a signal is received. - This is used after starting one or more clients and is useful for event-driven applications only, that are, - applications which react upon incoming Telegram updates through handlers, rather than executing a set of methods - sequentially. + This static method will run an infinite loop in order to block the main script execution and prevent it from + exiting while having client(s) that are still running in the background. - The way Pyrogram works, will keep your handlers in a pool of workers, which are executed concurrently outside - the main script; calling idle() will ensure the client(s) will be kept alive by not letting the main script to - end, until you decide to quit. + It is useful for event-driven application only, that are, applications which react upon incoming Telegram + updates through handlers, rather than executing a set of methods sequentially. + + The way Pyrogram works, it will keep your handlers in a pool of worker threads, which are executed concurrently + outside the main thread; calling idle() will ensure the client(s) will be kept alive by not letting the main + script to end, until you decide to quit. + + Once a signal is received (e.g.: from CTRL+C) the inner infinite loop will break and your main script will + continue. Don't forget to call :meth:`~Client.stop` for each running client before the script ends. Parameters: stop_signals (``tuple``, *optional*): Iterable containing signals the signal handler will listen to. - Defaults to (SIGINT, SIGTERM, SIGABRT). + Defaults to *(SIGINT, SIGTERM, SIGABRT)*. + + Example: + .. code-block:: python + :emphasize-lines: 13 + + from pyrogram import Client + + app1 = Client("account1") + app2 = Client("account2") + app3 = Client("account3") + + ... # Set handlers up + + app1.start() + app2.start() + app3.start() + + Client.idle() + + app1.stop() + app2.stop() + app3.stop() """ - # TODO: Maybe make this method static and don't automatically stop - def signal_handler(*args): - self.is_idle = False + Client.is_idling = False for s in stop_signals: signal(s, signal_handler) - self.is_idle = True + Client.is_idling = True - while self.is_idle: + while Client.is_idling: time.sleep(1) - self.stop() - def run(self): - """Start the Client and automatically idle the main script. + """Start the client, idle the main script and finally stop the client. - This is a convenience method that literally just calls :meth:`~Client.start` and :meth:`~Client.idle`. It makes - running a client less verbose, but is not suitable in case you want to run more than one client in a single main - script, since :meth:`~Client.idle` will block. + This is a convenience method that calls :meth:`~Client.start`, :meth:`~Client.idle` and :meth:`~Client.stop` in + sequence. It makes running a client less verbose, but is not suitable in case you want to run more than one + client in a single main script, since idle() will block after starting the own client. + + Has no parameters. Raises: - RPCError: In case of a Telegram RPC error. + ConnectionError: In case you try to run an already started client. + + Example: + .. code-block:: python + :emphasize-lines: 7 + + from pyrogram import Client + + app = Client("my_account") + + ... # Set handlers up + + app.run() """ self.start() - self.idle() + Client.idle() + self.stop() def add_handler(self, handler: Handler, group: int = 0): """Register an update handler. - You can register multiple handlers, but at most one handler within a group - will be used for a single update. To handle the same update more than once, register - your handler using a different group id (lower group id == higher priority). + You can register multiple handlers, but at most one handler within a group will be used for a single update. + To handle the same update more than once, register your handler using a different group id (lower group id + == higher priority). This mechanism is explained in greater details at + :doc:`More on Updates <../../topics/more-on-updates>`. Parameters: handler (``Handler``): @@ -459,7 +561,22 @@ class Client(Methods, BaseClient): The group identifier, defaults to 0. Returns: - ``tuple``: A tuple consisting of (handler, group). + ``tuple``: A tuple consisting of *(handler, group)*. + + Example: + .. code-block:: python + :emphasize-lines: 8 + + from pyrogram import Client, MessageHandler + + def dump(client, message): + print(message) + + app = Client("my_account") + + app.add_handler(MessageHandler(dump)) + + app.run() """ if isinstance(handler, DisconnectHandler): self.disconnect_handler = handler.callback @@ -471,9 +588,8 @@ class Client(Methods, BaseClient): def remove_handler(self, handler: Handler, group: int = 0): """Remove a previously-registered update handler. - Make sure to provide the right group that the handler was added in. You can use - the return value of the :meth:`~Client.add_handler` method, a tuple of (handler, group), and - pass it directly. + Make sure to provide the right group where the handler was added in. You can use the return value of the + :meth:`~Client.add_handler` method, a tuple of *(handler, group)*, and pass it directly. Parameters: handler (``Handler``): @@ -481,6 +597,24 @@ class Client(Methods, BaseClient): group (``int``, *optional*): The group identifier, defaults to 0. + + Example: + .. code-block:: python + :emphasize-lines: 11 + + from pyrogram import Client, MessageHandler + + def dump(client, message): + print(message) + + app = Client("my_account") + + handler = app.add_handler(MessageHandler(dump)) + + # Starred expression to unpack (handler, group) + app.remove_handler(*handler) + + app.run() """ if isinstance(handler, DisconnectHandler): self.disconnect_handler = None @@ -489,10 +623,109 @@ class Client(Methods, BaseClient): def stop_transmission(self): """Stop downloading or uploading a file. - Must be called inside a progress callback function. + + This method must be called inside a progress callback function in order to stop the transmission at the + desired time. The progress callback is called every time a file chunk is uploaded/downloaded. + + Has no parameters. + + Example: + .. code-block:: python + :emphasize-lines: 9 + + from pyrogram import Client + + app = Client("my_account") + + # Example to stop transmission once the upload progress reaches 50% + # Useless in practice, but shows how to stop on command + def progress(client, current, total): + if (current * 100 / total) > 50: + client.stop_transmission() + + with app: + app.send_document("me", "files.zip", progress=progress) """ raise Client.StopTransmission + def export_session_string(self): + """Export the current authorized session as a serialized string. + + Session strings are useful for storing in-memory authorized sessions in a portable, serialized string. + More detailed information about session strings can be found at the dedicated page of + :doc:`Storage Engines <../../topics/storage-engines>`. + + Has no parameters. + + Returns: + ``str``: The session serialized into a printable, url-safe string. + + Example: + .. code-block:: python + :emphasize-lines: 6 + + from pyrogram import Client + + app = Client("my_account") + + with app: + print(app.export_session_string()) + """ + return self.storage.export_session_string() + + def set_parse_mode(self, parse_mode: Union[str, None] = "combined"): + """Set the parse mode to be used globally by the client. + + When setting the parse mode with this method, all other methods having a *parse_mode* parameter will follow the + global value by default. The default value *"combined"* enables both Markdown and HTML styles to be used and + combined together. + + Parameters: + parse_mode (``str``): + The new parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"* + to force Markdown-only styles. *"html"* to force HTML-only styles. *None* to disable the parser + completely. + + Raises: + ValueError: In case the provided *parse_mode* is not a valid parse mode. + + Example: + .. code-block:: python + :emphasize-lines: 10,14,18,22 + + from pyrogram import Client + + app = Client("my_account") + + with app: + # Default combined mode: Markdown + HTML + app.send_message("haskell", "1. **markdown** and html") + + # Force Markdown-only, HTML is disabled + app.set_parse_mode("markdown") + app.send_message("haskell", "2. **markdown** and html") + + # Force HTML-only, Markdown is disabled + app.set_parse_mode("html") + app.send_message("haskell", "3. **markdown** and html") + + # Disable the parser completely + app.set_parse_mode(None) + app.send_message("haskell", "4. **markdown** and html") + + # Bring back the default combined mode + app.set_parse_mode() + app.send_message("haskell", "5. **markdown** and html") + """ + + if parse_mode not in self.PARSE_MODES: + raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( + ", ".join('"{}"'.format(m) for m in self.PARSE_MODES[:-1]), + parse_mode + )) + + self.parse_mode = parse_mode + def authorize_bot(self): try: r = self.send( @@ -506,19 +739,15 @@ class Client(Methods, BaseClient): except UserMigrate as e: self.session.stop() - self.dc_id = e.x - self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() - - self.session = Session( - self, - self.dc_id, - self.auth_key - ) + self.storage.dc_id = e.x + self.storage.auth_key = Auth(self, self.storage.dc_id).create() + self.session = Session(self, self.storage.dc_id, self.storage.auth_key) self.session.start() + self.authorize_bot() else: - self.user_id = r.user.id + self.storage.user_id = r.user.id print("Logged in successfully as @{}".format(r.user.username)) @@ -559,20 +788,10 @@ class Client(Methods, BaseClient): except (PhoneMigrate, NetworkMigrate) as e: self.session.stop() - self.dc_id = e.x + self.storage.dc_id = e.x + self.storage.auth_key = Auth(self, self.storage.dc_id).create() - self.auth_key = Auth( - self.dc_id, - self.test_mode, - self.ipv6, - self._proxy - ).create() - - self.session = Session( - self, - self.dc_id, - self.auth_key - ) + self.session = Session(self, self.storage.dc_id, self.storage.auth_key) self.session.start() except (PhoneNumberInvalid, PhoneNumberBanned) as e: @@ -752,13 +971,13 @@ class Client(Methods, BaseClient): ) self.password = None - self.user_id = r.user.id + self.storage.user_id = r.user.id print("Logged in successfully as {}".format(r.user.first_name)) def fetch_peers( self, - entities: List[ + peers: List[ Union[ types.User, types.Chat, types.ChatForbidden, @@ -767,64 +986,57 @@ class Client(Methods, BaseClient): ] ) -> bool: is_min = False + parsed_peers = [] - for entity in entities: - if isinstance(entity, types.User): - user_id = entity.id + for peer in peers: + username = None + phone_number = None - access_hash = entity.access_hash + if isinstance(peer, types.User): + peer_id = peer.id + access_hash = peer.access_hash + + username = peer.username + phone_number = peer.phone + + if peer.bot: + peer_type = "bot" + else: + peer_type = "user" if access_hash is None: is_min = True continue - username = entity.username - phone = entity.phone - - input_peer = types.InputPeerUser( - user_id=user_id, - access_hash=access_hash - ) - - self.peers_by_id[user_id] = input_peer - if username is not None: - self.peers_by_username[username.lower()] = input_peer + username = username.lower() + elif isinstance(peer, (types.Chat, types.ChatForbidden)): + peer_id = -peer.id + access_hash = 0 + peer_type = "group" + elif isinstance(peer, (types.Channel, types.ChannelForbidden)): + peer_id = utils.get_channel_id(peer.id) + access_hash = peer.access_hash - if phone is not None: - self.peers_by_phone[phone] = input_peer + username = getattr(peer, "username", None) - if isinstance(entity, (types.Chat, types.ChatForbidden)): - chat_id = entity.id - peer_id = -chat_id - - input_peer = types.InputPeerChat( - chat_id=chat_id - ) - - self.peers_by_id[peer_id] = input_peer - - if isinstance(entity, (types.Channel, types.ChannelForbidden)): - channel_id = entity.id - peer_id = int("-100" + str(channel_id)) - - access_hash = entity.access_hash + if peer.broadcast: + peer_type = "channel" + else: + peer_type = "supergroup" if access_hash is None: is_min = True continue - username = getattr(entity, "username", None) - - input_peer = types.InputPeerChannel( - channel_id=channel_id, - access_hash=access_hash - ) - - self.peers_by_id[peer_id] = input_peer - if username is not None: - self.peers_by_username[username.lower()] = input_peer + username = username.lower() + else: + continue + + parsed_peers.append((peer_id, access_hash, peer_type, username, phone_number)) + + self.storage.update_peers(parsed_peers) return is_min @@ -842,39 +1054,7 @@ class Client(Methods, BaseClient): final_file_path = "" try: - data, file_name, done, progress, progress_args, path = packet - - directory, file_name = os.path.split(file_name) - directory = directory or "downloads" - - media_type_str = Client.MEDIA_TYPE_ID[data.media_type] - - file_name = file_name or data.file_name - - if not file_name: - guessed_extension = self.guess_extension(data.mime_type) - - if data.media_type in (0, 1, 2, 14): - extension = ".jpg" - elif data.media_type == 3: - extension = guessed_extension or ".ogg" - elif data.media_type in (4, 10, 13): - extension = guessed_extension or ".mp4" - elif data.media_type == 5: - extension = guessed_extension or ".zip" - elif data.media_type == 8: - extension = guessed_extension or ".webp" - elif data.media_type == 9: - extension = guessed_extension or ".mp3" - else: - continue - - file_name = "{}_{}_{}{}".format( - media_type_str, - datetime.fromtimestamp(data.date or time.time()).strftime("%Y-%m-%d_%H-%M-%S"), - self.rnd_id(), - extension - ) + data, directory, file_name, done, progress, progress_args, path = packet temp_file_path = self.get_file( media_type=data.media_type, @@ -951,7 +1131,7 @@ class Client(Methods, BaseClient): try: diff = self.send( functions.updates.GetChannelDifference( - channel=self.resolve_peer(int("-100" + str(channel_id))), + channel=self.resolve_peer(utils.get_channel_id(channel_id)), filter=types.ChannelMessagesFilter( ranges=[types.MessageRange( min_id=update.message.id, @@ -1047,7 +1227,7 @@ class Client(Methods, BaseClient): def load_config(self): parser = ConfigParser() - parser.read(self.config_file) + parser.read(str(self.config_file)) if self.api_id and self.api_hash: pass @@ -1117,36 +1297,23 @@ class Client(Methods, BaseClient): self.plugins = None def load_session(self): - try: - with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), encoding="utf-8") as f: - s = json.load(f) - except FileNotFoundError: - self.dc_id = 1 - self.date = 0 - self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create() - else: - self.dc_id = s["dc_id"] - self.test_mode = s["test_mode"] - self.auth_key = base64.b64decode("".join(s["auth_key"])) - self.user_id = s["user_id"] - self.date = s.get("date", 0) - # TODO: replace default with False once token session name will be deprecated - self.is_bot = s.get("is_bot", self.is_bot) + self.storage.open() - for k, v in s.get("peers_by_id", {}).items(): - self.peers_by_id[int(k)] = utils.get_input_peer(int(k), v) + session_empty = any([ + self.storage.test_mode is None, + self.storage.auth_key is None, + self.storage.user_id is None, + self.storage.is_bot is None + ]) - for k, v in s.get("peers_by_username", {}).items(): - peer = self.peers_by_id.get(v, None) + if session_empty: + self.storage.dc_id = 4 + self.storage.date = 0 - if peer: - self.peers_by_username[k] = peer - - for k, v in s.get("peers_by_phone", {}).items(): - peer = self.peers_by_id.get(v, None) - - if peer: - self.peers_by_phone[k] = peer + self.storage.test_mode = self.test_mode + self.storage.auth_key = Auth(self, self.storage.dc_id).create() + self.storage.user_id = None + self.storage.is_bot = None def load_plugins(self): if self.plugins: @@ -1176,7 +1343,7 @@ class Client(Methods, BaseClient): for name in vars(module).keys(): # noinspection PyBroadException try: - handler, group = getattr(module, name) + handler, group = getattr(module, name).handler if isinstance(handler, Handler) and isinstance(group, int): self.add_handler(handler, group) @@ -1211,7 +1378,7 @@ class Client(Methods, BaseClient): for name in handlers: # noinspection PyBroadException try: - handler, group = getattr(module, name) + handler, group = getattr(module, name).handler if isinstance(handler, Handler) and isinstance(group, int): self.add_handler(handler, group) @@ -1249,7 +1416,7 @@ class Client(Methods, BaseClient): for name in handlers: # noinspection PyBroadException try: - handler, group = getattr(module, name) + handler, group = getattr(module, name).handler if isinstance(handler, Handler) and isinstance(group, int): self.remove_handler(handler, group) @@ -1270,26 +1437,6 @@ class Client(Methods, BaseClient): log.warning('[{}] No plugin loaded from "{}"'.format( self.session_name, root)) - def save_session(self): - auth_key = base64.b64encode(self.auth_key).decode() - auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] - - os.makedirs(self.workdir, exist_ok=True) - - with open(os.path.join(self.workdir, "{}.session".format(self.session_name)), "w", encoding="utf-8") as f: - json.dump( - dict( - dc_id=self.dc_id, - test_mode=self.test_mode, - auth_key=auth_key, - user_id=self.user_id, - date=self.date, - is_bot=self.is_bot, - ), - f, - indent=4 - ) - def get_initial_dialogs_chunk(self, offset_date: int = 0): while True: try: @@ -1307,7 +1454,7 @@ class Client(Methods, BaseClient): log.warning("get_dialogs flood: waiting {} seconds".format(e.x)) time.sleep(e.x) else: - log.info("Total peers: {}".format(len(self.peers_by_id))) + log.info("Total peers: {}".format(self.storage.peers_count)) return r def get_initial_dialogs(self): @@ -1345,7 +1492,7 @@ class Client(Methods, BaseClient): KeyError: In case the peer doesn't exist in the internal database. """ try: - return self.peers_by_id[peer_id] + return self.storage.get_peer_by_id(peer_id) except KeyError: if type(peer_id) is str: if peer_id in ("self", "me"): @@ -1356,44 +1503,57 @@ class Client(Methods, BaseClient): try: int(peer_id) except ValueError: - if peer_id not in self.peers_by_username: + try: + return self.storage.get_peer_by_username(peer_id) + except KeyError: self.send( functions.contacts.ResolveUsername( username=peer_id ) ) - return self.peers_by_username[peer_id] + return self.storage.get_peer_by_username(peer_id) else: try: - return self.peers_by_phone[peer_id] + return self.storage.get_peer_by_phone_number(peer_id) except KeyError: raise PeerIdInvalid - if peer_id > 0: + peer_type = utils.get_type(peer_id) + + if peer_type == "user": self.fetch_peers( self.send( functions.users.GetUsers( - id=[types.InputUser(user_id=peer_id, access_hash=0)] + id=[ + types.InputUser( + user_id=peer_id, + access_hash=0 + ) + ] ) ) ) + elif peer_type == "chat": + self.send( + functions.messages.GetChats( + id=[-peer_id] + ) + ) else: - if str(peer_id).startswith("-100"): - self.send( - functions.channels.GetChannels( - id=[types.InputChannel(channel_id=int(str(peer_id)[4:]), access_hash=0)] - ) - ) - else: - self.send( - functions.messages.GetChats( - id=[-peer_id] - ) + self.send( + functions.channels.GetChannels( + id=[ + types.InputChannel( + channel_id=utils.get_channel_id(peer_id), + access_hash=0 + ) + ] ) + ) try: - return self.peers_by_id[peer_id] + return self.storage.get_peer_by_id(peer_id) except KeyError: raise PeerIdInvalid @@ -1425,23 +1585,22 @@ class Client(Methods, BaseClient): In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -1468,7 +1627,7 @@ class Client(Methods, BaseClient): file_id = file_id or self.rnd_id() md5_sum = md5() if not is_big and not is_missing_part else None - session = Session(self, self.dc_id, self.auth_key, is_media=True) + session = Session(self, self.storage.dc_id, self.storage.auth_key, is_media=True) session.start() try: @@ -1554,19 +1713,14 @@ class Client(Methods, BaseClient): session = self.media_sessions.get(dc_id, None) if session is None: - if dc_id != self.dc_id: + if dc_id != self.storage.dc_id: exported_auth = self.send( functions.auth.ExportAuthorization( dc_id=dc_id ) ) - session = Session( - self, - dc_id, - Auth(dc_id, self.test_mode, self.ipv6, self._proxy).create(), - is_media=True - ) + session = Session(self, dc_id, Auth(self, dc_id).create(), is_media=True) session.start() @@ -1579,12 +1733,7 @@ class Client(Methods, BaseClient): ) ) else: - session = Session( - self, - dc_id, - self.auth_key, - is_media=True - ) + session = Session(self, dc_id, self.storage.auth_key, is_media=True) session.start() @@ -1669,13 +1818,7 @@ class Client(Methods, BaseClient): cdn_session = self.media_sessions.get(r.dc_id, None) if cdn_session is None: - cdn_session = Session( - self, - r.dc_id, - Auth(r.dc_id, self.test_mode, self.ipv6, self._proxy).create(), - is_media=True, - is_cdn=True - ) + cdn_session = Session(self, r.dc_id, Auth(self, r.dc_id).create(), is_media=True, is_cdn=True) cdn_session.start() diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index b4c16666..ce736e87 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -19,11 +19,13 @@ import os import platform import re +import sys +from pathlib import Path from queue import Queue from threading import Lock from pyrogram import __version__ -from ..style import Markdown, HTML +from ..parser import Parser from ...session.internals import MsgId @@ -45,6 +47,8 @@ class BaseClient: LANG_CODE = "en" + PARENT_DIR = Path(sys.argv[0]).parent + INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/joinchat/)([\w-]+)$") BOT_TOKEN_RE = re.compile(r"^\d+:[\w-]+$") DIALOGS_AT_ONCE = 100 @@ -52,8 +56,10 @@ class BaseClient: DOWNLOAD_WORKERS = 1 OFFLINE_SLEEP = 900 WORKERS = 4 - WORKDIR = "." - CONFIG_FILE = "./config.ini" + WORKDIR = PARENT_DIR + CONFIG_FILE = PARENT_DIR / "config.ini" + + PARSE_MODES = ["combined", "markdown", "md", "html", None] MEDIA_TYPE_ID = { 0: "photo_thumbnail", @@ -83,28 +89,21 @@ class BaseClient: mime_types_to_extensions[mime_type] = " ".join(extensions) + is_idling = False + def __init__(self): - self.is_bot = None - self.dc_id = None - self.auth_key = None - self.user_id = None - self.date = None + self.storage = None self.rnd_id = MsgId - self.peers_by_id = {} - self.peers_by_username = {} - self.peers_by_phone = {} - - self.markdown = Markdown(self) - self.html = HTML(self) + self.parser = Parser(self) + self.parse_mode = "combined" self.session = None self.media_sessions = {} self.media_sessions_lock = Lock() self.is_started = None - self.is_idle = None self.takeout_id = None @@ -159,3 +158,18 @@ class BaseClient: def edit_message_text(self, *args, **kwargs): pass + + def edit_inline_text(self, *args, **kwargs): + pass + + def edit_message_media(self, *args, **kwargs): + pass + + def edit_inline_media(self, *args, **kwargs): + pass + + def edit_message_reply_markup(self, *args, **kwargs): + pass + + def edit_inline_reply_markup(self, *args, **kwargs): + pass diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 56cdead6..5b6bccd2 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -20,7 +20,7 @@ import logging import threading from collections import OrderedDict from queue import Queue -from threading import Thread +from threading import Thread, Lock import pyrogram from pyrogram.api import types @@ -61,6 +61,8 @@ class Dispatcher: self.workers = workers self.workers_list = [] + self.locks_list = [] + self.updates_queue = Queue() self.groups = OrderedDict() @@ -75,9 +77,7 @@ class Dispatcher: lambda upd, usr, cht: (pyrogram.CallbackQuery._parse(self.client, upd, usr), CallbackQueryHandler), (types.UpdateUserStatus,): - lambda upd, usr, cht: ( - pyrogram.UserStatus._parse(self.client, upd.status, upd.user_id), UserStatusHandler - ), + lambda upd, usr, cht: (pyrogram.User._parse_user_status(self.client, upd), UserStatusHandler), (types.UpdateBotInlineQuery,): lambda upd, usr, cht: (pyrogram.InlineQuery._parse(self.client, upd, usr), InlineQueryHandler), @@ -90,10 +90,13 @@ class Dispatcher: def start(self): for i in range(self.workers): + self.locks_list.append(Lock()) + self.workers_list.append( Thread( target=self.update_worker, - name="UpdateWorker#{}".format(i + 1) + name="UpdateWorker#{}".format(i + 1), + args=(self.locks_list[-1],) ) ) @@ -107,22 +110,37 @@ class Dispatcher: worker.join() self.workers_list.clear() + self.locks_list.clear() self.groups.clear() def add_handler(self, handler, group: int): - if group not in self.groups: - self.groups[group] = [] - self.groups = OrderedDict(sorted(self.groups.items())) + for lock in self.locks_list: + lock.acquire() - self.groups[group].append(handler) + try: + if group not in self.groups: + self.groups[group] = [] + self.groups = OrderedDict(sorted(self.groups.items())) + + self.groups[group].append(handler) + finally: + for lock in self.locks_list: + lock.release() def remove_handler(self, handler, group: int): - if group not in self.groups: - raise ValueError("Group {} does not exist. Handler was not removed.".format(group)) + for lock in self.locks_list: + lock.acquire() - self.groups[group].remove(handler) + try: + if group not in self.groups: + raise ValueError("Group {} does not exist. Handler was not removed.".format(group)) - def update_worker(self): + self.groups[group].remove(handler) + finally: + for lock in self.locks_list: + lock.release() + + def update_worker(self, lock): name = threading.current_thread().name log.debug("{} started".format(name)) @@ -142,29 +160,35 @@ class Dispatcher: else (None, type(None)) ) - for group in self.groups.values(): - for handler in group: - args = None + with lock: + for group in self.groups.values(): + for handler in group: + args = None - if isinstance(handler, handler_type): - if handler.check(parsed_update): - args = (parsed_update,) - elif isinstance(handler, RawUpdateHandler): - args = (update, users, chats) + if isinstance(handler, handler_type): + try: + if handler.check(parsed_update): + args = (parsed_update,) + except Exception as e: + log.error(e, exc_info=True) + continue - if args is None: - continue + elif isinstance(handler, RawUpdateHandler): + args = (update, users, chats) - try: - handler.callback(self.client, *args) - except pyrogram.StopPropagation: - raise - except pyrogram.ContinuePropagation: - continue - except Exception as e: - log.error(e, exc_info=True) + if args is None: + continue - break + try: + handler.callback(self.client, *args) + except pyrogram.StopPropagation: + raise + except pyrogram.ContinuePropagation: + continue + except Exception as e: + log.error(e, exc_info=True) + + break except pyrogram.StopPropagation: pass except Exception as e: diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index c3921205..42e1f95a 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -16,16 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import base64 -import json import logging -import os -import shutil import time from threading import Thread, Event, Lock -from . import utils - log = logging.getLogger(__name__) @@ -81,48 +75,13 @@ class Syncer: @classmethod def sync(cls, client): - temporary = os.path.join(client.workdir, "{}.sync".format(client.session_name)) - persistent = os.path.join(client.workdir, "{}.session".format(client.session_name)) - try: - auth_key = base64.b64encode(client.auth_key).decode() - auth_key = [auth_key[i: i + 43] for i in range(0, len(auth_key), 43)] - - data = dict( - dc_id=client.dc_id, - test_mode=client.test_mode, - auth_key=auth_key, - user_id=client.user_id, - date=int(time.time()), - is_bot=bool(client.is_bot), - peers_by_id={ - k: getattr(v, "access_hash", None) - for k, v in client.peers_by_id.copy().items() - }, - peers_by_username={ - k: utils.get_peer_id(v) - for k, v in client.peers_by_username.copy().items() - }, - peers_by_phone={ - k: utils.get_peer_id(v) - for k, v in client.peers_by_phone.copy().items() - } - ) - - os.makedirs(client.workdir, exist_ok=True) - - with open(temporary, "w", encoding="utf-8") as f: - json.dump(data, f, indent=4) - - f.flush() - os.fsync(f.fileno()) + start = time.time() + client.storage.save() except Exception as e: log.critical(e, exc_info=True) else: - shutil.move(temporary, persistent) - log.info("Synced {}".format(client.session_name)) - finally: - try: - os.remove(temporary) - except OSError: - pass + log.info('Synced "{}" in {:.6} ms'.format( + client.storage.name, + (time.time() - start) * 1000 + )) diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index fa107fab..cdc0684c 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -18,22 +18,30 @@ import base64 import struct -from base64 import b64decode, b64encode -from typing import Union, List +from typing import List +from typing import Union import pyrogram +from pyrogram.api.types import PeerUser, PeerChat, PeerChannel from . import BaseClient from ...api import types def decode(s: str) -> bytes: - s = b64decode(s + "=" * (-len(s) % 4), "-_") + s = base64.urlsafe_b64decode(s + "=" * (-len(s) % 4)) r = b"" - assert s[-1] == 2 + try: + assert s[-1] == 2 + skip = 1 + except AssertionError: + assert s[-2] == 22 + assert s[-1] == 4 + skip = 2 i = 0 - while i < len(s) - 1: + + while i < len(s) - skip: if s[i] != 0: r += bytes([s[i]]) else: @@ -49,7 +57,7 @@ def encode(s: bytes) -> str: r = b"" n = 0 - for i in s + bytes([2]): + for i in s + bytes([22]) + bytes([4]): if i == 0: n += 1 else: @@ -59,24 +67,7 @@ def encode(s: bytes) -> str: r += bytes([i]) - return b64encode(r, b"-_").decode().rstrip("=") - - -def get_peer_id(input_peer) -> int: - return ( - input_peer.user_id if isinstance(input_peer, types.InputPeerUser) - else -input_peer.chat_id if isinstance(input_peer, types.InputPeerChat) - else int("-100" + str(input_peer.channel_id)) - ) - - -def get_input_peer(peer_id: int, access_hash: int): - return ( - types.InputPeerUser(user_id=peer_id, access_hash=access_hash) if peer_id > 0 - else types.InputPeerChannel(channel_id=int(str(peer_id)[4:]), access_hash=access_hash) - if (str(peer_id).startswith("-100") and access_hash) - else types.InputPeerChat(chat_id=-peer_id) - ) + return base64.urlsafe_b64encode(r).decode().rstrip("=") def get_offset_date(dialogs): @@ -183,7 +174,7 @@ def parse_deleted_messages(client, update) -> List["pyrogram.Message"]: pyrogram.Message( message_id=message, chat=pyrogram.Chat( - id=int("-100" + str(channel_id)), + id=get_channel_id(channel_id), type="channel", client=client ) if channel_id is not None else None, @@ -203,3 +194,39 @@ def unpack_inline_message_id(inline_message_id: str) -> types.InputBotInlineMess id=r[1], access_hash=r[2] ) + + +MIN_CHANNEL_ID = -1002147483647 +MAX_CHANNEL_ID = -1000000000000 +MIN_CHAT_ID = -2147483647 +MAX_USER_ID = 2147483647 + + +def get_peer_id(peer: Union[PeerUser, PeerChat, PeerChannel]) -> int: + if isinstance(peer, PeerUser): + return peer.user_id + + if isinstance(peer, PeerChat): + return -peer.chat_id + + if isinstance(peer, PeerChannel): + return MAX_CHANNEL_ID - peer.channel_id + + raise ValueError("Peer type invalid: {}".format(peer)) + + +def get_type(peer_id: int) -> str: + if peer_id < 0: + if MIN_CHAT_ID <= peer_id: + return "chat" + + if MIN_CHANNEL_ID <= peer_id < MAX_CHANNEL_ID: + return "channel" + elif 0 < peer_id <= MAX_USER_ID: + return "user" + + raise ValueError("Peer id invalid: {}".format(peer_id)) + + +def get_channel_id(peer_id: int) -> int: + return MAX_CHANNEL_ID - peer_id diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index fb0a3615..f80127c2 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -17,190 +17,193 @@ # along with Pyrogram. If not, see . import re +from typing import Callable from .filter import Filter from ..types.bots_and_keyboards import InlineKeyboardMarkup, ReplyKeyboardMarkup +CUSTOM_FILTER_NAME = "CustomFilter" -def create(name: str, func: callable, **kwargs) -> type: - """Create a Filter. + +def create(func: Callable, name: str = None, **kwargs) -> Filter: + """Easily create a custom filter. Custom filters give you extra control over which updates are allowed or not to be processed by your handlers. Parameters: - name (``str``): - Your filter's name. Can be anything you like. - func (``callable``): - A function that accepts two arguments *(filter, update)* and returns a Boolean: True if the update should be - handled, False otherwise. - The "update" argument type will vary depending on which `Handler `_ is coming from. - For example, in a :obj:`MessageHandler` the update type will be - a :obj:`Message`; in a :obj:`CallbackQueryHandler` the - update type will be a :obj:`CallbackQuery`. Your function body can then access the - incoming update and decide whether to allow it or not. + A function that accepts two positional arguments *(filter, update)* and returns a boolean: True if the + update should be handled, False otherwise. The *filter* argument refers to the filter itself and can be used + to access keyword arguments (read below). The *update* argument type will vary depending on which + `Handler `_ is coming from. For example, in a :obj:`MessageHandler` the *update* argument will be + a :obj:`Message`; in a :obj:`CallbackQueryHandler` the *update* will be a :obj:`CallbackQuery`. Your + function body can then access the incoming update attributes and decide whether to allow it or not. + + name (``str``, *optional*): + Your filter's name. Can be anything you like. + Defaults to "CustomFilter". **kwargs (``any``, *optional*): - Any keyword argument you would like to pass. Useful for custom filters that accept parameters (e.g.: - :meth:`~Filters.command`, :meth:`~Filters.regex`). + Any keyword argument you would like to pass. Useful when creating parameterized custom filters, such as + :meth:`~Filters.command` or :meth:`~Filters.regex`. """ # TODO: unpack kwargs using **kwargs into the dict itself. For Python 3.5+ only d = {"__call__": func} d.update(kwargs) - return type(name, (Filter,), d)() + return type(name or CUSTOM_FILTER_NAME, (Filter,), d)() class Filters: """This class provides access to all library-defined Filters available in Pyrogram. - The Filters listed here are intended to be used with the :obj:`MessageHandler` only. + The Filters listed here are currently intended to be used with the :obj:`MessageHandler` only. At the moment, if you want to filter updates coming from different `Handlers `_ you have to create your own filters with :meth:`~Filters.create` and use them in the same way. """ create = create - me = create("Me", lambda _, m: bool(m.from_user and m.from_user.is_self)) + me = create(lambda _, m: bool(m.from_user and m.from_user.is_self), "MeFilter") """Filter messages generated by you yourself.""" - bot = create("Bot", lambda _, m: bool(m.from_user and m.from_user.is_bot)) + bot = create(lambda _, m: bool(m.from_user and m.from_user.is_bot), "BotFilter") """Filter messages coming from bots.""" - incoming = create("Incoming", lambda _, m: not m.outgoing) + incoming = create(lambda _, m: not m.outgoing, "IncomingFilter") """Filter incoming messages. Messages sent to your own chat (Saved Messages) are also recognised as incoming.""" - outgoing = create("Outgoing", lambda _, m: m.outgoing) + outgoing = create(lambda _, m: m.outgoing, "OutgoingFilter") """Filter outgoing messages. Messages sent to your own chat (Saved Messages) are not recognized as outgoing.""" - text = create("Text", lambda _, m: bool(m.text)) + text = create(lambda _, m: bool(m.text), "TextFilter") """Filter text messages.""" - reply = create("Reply", lambda _, m: bool(m.reply_to_message)) + reply = create(lambda _, m: bool(m.reply_to_message), "ReplyFilter") """Filter messages that are replies to other messages.""" - forwarded = create("Forwarded", lambda _, m: bool(m.forward_date)) + forwarded = create(lambda _, m: bool(m.forward_date), "ForwardedFilter") """Filter messages that are forwarded.""" - caption = create("Caption", lambda _, m: bool(m.caption)) + caption = create(lambda _, m: bool(m.caption), "CaptionFilter") """Filter media messages that contain captions.""" - edited = create("Edited", lambda _, m: bool(m.edit_date)) + edited = create(lambda _, m: bool(m.edit_date), "EditedFilter") """Filter edited messages.""" - audio = create("Audio", lambda _, m: bool(m.audio)) + audio = create(lambda _, m: bool(m.audio), "AudioFilter") """Filter messages that contain :obj:`Audio` objects.""" - document = create("Document", lambda _, m: bool(m.document)) + document = create(lambda _, m: bool(m.document), "DocumentFilter") """Filter messages that contain :obj:`Document` objects.""" - photo = create("Photo", lambda _, m: bool(m.photo)) + photo = create(lambda _, m: bool(m.photo), "PhotoFilter") """Filter messages that contain :obj:`Photo` objects.""" - sticker = create("Sticker", lambda _, m: bool(m.sticker)) + sticker = create(lambda _, m: bool(m.sticker), "StickerFilter") """Filter messages that contain :obj:`Sticker` objects.""" - animation = create("Animation", lambda _, m: bool(m.animation)) + animation = create(lambda _, m: bool(m.animation), "AnimationFilter") """Filter messages that contain :obj:`Animation` objects.""" - game = create("Game", lambda _, m: bool(m.game)) + game = create(lambda _, m: bool(m.game), "GameFilter") """Filter messages that contain :obj:`Game` objects.""" - video = create("Video", lambda _, m: bool(m.video)) + video = create(lambda _, m: bool(m.video), "VideoFilter") """Filter messages that contain :obj:`Video` objects.""" - media_group = create("MediaGroup", lambda _, m: bool(m.media_group_id)) + media_group = create(lambda _, m: bool(m.media_group_id), "MediaGroupFilter") """Filter messages containing photos or videos being part of an album.""" - voice = create("Voice", lambda _, m: bool(m.voice)) + voice = create(lambda _, m: bool(m.voice), "VoiceFilter") """Filter messages that contain :obj:`Voice` note objects.""" - video_note = create("VideoNote", lambda _, m: bool(m.video_note)) + video_note = create(lambda _, m: bool(m.video_note), "VideoNoteFilter") """Filter messages that contain :obj:`VideoNote` objects.""" - contact = create("Contact", lambda _, m: bool(m.contact)) + contact = create(lambda _, m: bool(m.contact), "ContactFilter") """Filter messages that contain :obj:`Contact` objects.""" - location = create("Location", lambda _, m: bool(m.location)) + location = create(lambda _, m: bool(m.location), "LocationFilter") """Filter messages that contain :obj:`Location` objects.""" - venue = create("Venue", lambda _, m: bool(m.venue)) + venue = create(lambda _, m: bool(m.venue), "VenueFilter") """Filter messages that contain :obj:`Venue` objects.""" - web_page = create("WebPage", lambda _, m: m.web_page) + web_page = create(lambda _, m: m.web_page, "WebPageFilter") """Filter messages sent with a webpage preview.""" - poll = create("Poll", lambda _, m: m.poll) + poll = create(lambda _, m: m.poll, "PollFilter") """Filter messages that contain :obj:`Poll` objects.""" - private = create("Private", lambda _, m: bool(m.chat and m.chat.type == "private")) + private = create(lambda _, m: bool(m.chat and m.chat.type in {"private", "bot"}), "PrivateFilter") """Filter messages sent in private chats.""" - group = create("Group", lambda _, m: bool(m.chat and m.chat.type in {"group", "supergroup"})) + group = create(lambda _, m: bool(m.chat and m.chat.type in {"group", "supergroup"}), "GroupFilter") """Filter messages sent in group or supergroup chats.""" - channel = create("Channel", lambda _, m: bool(m.chat and m.chat.type == "channel")) + channel = create(lambda _, m: bool(m.chat and m.chat.type == "channel"), "ChannelFilter") """Filter messages sent in channels.""" - new_chat_members = create("NewChatMembers", lambda _, m: bool(m.new_chat_members)) + new_chat_members = create(lambda _, m: bool(m.new_chat_members), "NewChatMembersFilter") """Filter service messages for new chat members.""" - left_chat_member = create("LeftChatMember", lambda _, m: bool(m.left_chat_member)) + left_chat_member = create(lambda _, m: bool(m.left_chat_member), "LeftChatMemberFilter") """Filter service messages for members that left the chat.""" - new_chat_title = create("NewChatTitle", lambda _, m: bool(m.new_chat_title)) + new_chat_title = create(lambda _, m: bool(m.new_chat_title), "NewChatTitleFilter") """Filter service messages for new chat titles.""" - new_chat_photo = create("NewChatPhoto", lambda _, m: bool(m.new_chat_photo)) + new_chat_photo = create(lambda _, m: bool(m.new_chat_photo), "NewChatPhotoFilter") """Filter service messages for new chat photos.""" - delete_chat_photo = create("DeleteChatPhoto", lambda _, m: bool(m.delete_chat_photo)) + delete_chat_photo = create(lambda _, m: bool(m.delete_chat_photo), "DeleteChatPhotoFilter") """Filter service messages for deleted photos.""" - group_chat_created = create("GroupChatCreated", lambda _, m: bool(m.group_chat_created)) + group_chat_created = create(lambda _, m: bool(m.group_chat_created), "GroupChatCreatedFilter") """Filter service messages for group chat creations.""" - supergroup_chat_created = create("SupergroupChatCreated", lambda _, m: bool(m.supergroup_chat_created)) + supergroup_chat_created = create(lambda _, m: bool(m.supergroup_chat_created), "SupergroupChatCreatedFilter") """Filter service messages for supergroup chat creations.""" - channel_chat_created = create("ChannelChatCreated", lambda _, m: bool(m.channel_chat_created)) + channel_chat_created = create(lambda _, m: bool(m.channel_chat_created), "ChannelChatCreatedFilter") """Filter service messages for channel chat creations.""" - migrate_to_chat_id = create("MigrateToChatId", lambda _, m: bool(m.migrate_to_chat_id)) + migrate_to_chat_id = create(lambda _, m: bool(m.migrate_to_chat_id), "MigrateToChatIdFilter") """Filter service messages that contain migrate_to_chat_id.""" - migrate_from_chat_id = create("MigrateFromChatId", lambda _, m: bool(m.migrate_from_chat_id)) + migrate_from_chat_id = create(lambda _, m: bool(m.migrate_from_chat_id), "MigrateFromChatIdFilter") """Filter service messages that contain migrate_from_chat_id.""" - pinned_message = create("PinnedMessage", lambda _, m: bool(m.pinned_message)) + pinned_message = create(lambda _, m: bool(m.pinned_message), "PinnedMessageFilter") """Filter service messages for pinned messages.""" - game_high_score = create("GameHighScore", lambda _, m: bool(m.game_high_score)) + game_high_score = create(lambda _, m: bool(m.game_high_score), "GameHighScoreFilter") """Filter service messages for game high scores.""" - reply_keyboard = create("ReplyKeyboard", lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup)) + reply_keyboard = create(lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup), "ReplyKeyboardFilter") """Filter messages containing reply keyboard markups""" - inline_keyboard = create("InlineKeyboard", lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup)) + inline_keyboard = create(lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup), "InlineKeyboardFilter") """Filter messages containing inline keyboard markups""" - mentioned = create("Mentioned", lambda _, m: bool(m.mentioned)) + mentioned = create(lambda _, m: bool(m.mentioned), "MentionedFilter") """Filter messages containing mentions""" - via_bot = create("ViaBot", lambda _, m: bool(m.via_bot)) + via_bot = create(lambda _, m: bool(m.via_bot), "ViaBotFilter") """Filter messages sent via inline bots""" - service = create("Service", lambda _, m: bool(m.service)) + service = create(lambda _, m: bool(m.service), "ServiceFilter") """Filter service messages. - + A service message contains any of the following fields set: *left_chat_member*, *new_chat_title*, *new_chat_photo*, *delete_chat_photo*, *group_chat_created*, *supergroup_chat_created*, *channel_chat_created*, *migrate_to_chat_id*, *migrate_from_chat_id*, *pinned_message*, *game_score*. """ - media = create("Media", lambda _, m: bool(m.media)) + media = create(lambda _, m: bool(m.media), "MediaFilter") """Filter media messages. - + A media message contains any of the following fields set: *audio*, *document*, *photo*, *sticker*, *video*, *animation*, *voice*, *video_note*, *contact*, *location*, *venue*, *poll*. """ @@ -237,6 +240,7 @@ class Filters: def func(flt, message): text = message.text or message.caption + message.command = None if text: for p in flt.p: @@ -253,27 +257,31 @@ class Filters: commands = {c if case_sensitive else c.lower() for c in commands} prefixes = set(prefix) if prefix else {""} - return create("Command", func=func, c=commands, p=prefixes, s=separator, cs=case_sensitive) + return create(func, "CommandFilter", c=commands, p=prefixes, s=separator, cs=case_sensitive) @staticmethod def regex(pattern, flags: int = 0): - """Filter messages that match a given RegEx pattern. + """Filter message texts or captions that match a given regular expression pattern. Parameters: pattern (``str``): - The RegEx pattern as string, it will be applied to the text of a message. When a pattern matches, - all the `Match Objects `_ - are stored in the *matches* field of the :obj:`Message` itself. + The RegEx pattern as string, it will be applied to the text or the caption of a message. When a pattern + matches, all the `Match Objects `_ are stored + in the *matches* field of the :obj:`Message` itself. flags (``int``, *optional*): RegEx flags. """ - def f(_, m): - m.matches = [i for i in _.p.finditer(m.text or m.caption or "")] - return bool(m.matches) + def func(flt, message): + text = message.text or message.caption - return create("Regex", f, p=re.compile(pattern, flags)) + if text: + message.matches = list(flt.p.finditer(text)) or None + + return bool(message.matches) + + return create(func, "RegexFilter", p=re.compile(pattern, flags)) # noinspection PyPep8Naming class user(Filter, set): @@ -285,7 +293,7 @@ class Filters: Parameters: users (``int`` | ``str`` | ``list``): Pass one or more user ids/usernames to filter users. - For you yourself, "me" or "self" can be used as well. + For you yourself, "me" or "self" can be used as well. Defaults to None (no users). """ @@ -342,12 +350,12 @@ class Filters: @staticmethod def callback_data(data: str or bytes): """Filter callback queries for their data. - + Parameters: data (``str`` | ``bytes``): Pass the data you want to filter for. """ - return create("CallbackData", lambda flt, cb: cb.data == flt.data, data=data) + return create(lambda flt, cb: cb.data == flt.data, "CallbackDataFilter", data=data) - dan = create("Dan", lambda _, m: bool(m.from_user and m.from_user.id == 23122162)) + dan = create(lambda _, m: bool(m.from_user and m.from_user.id == 23122162), "DanFilter") diff --git a/pyrogram/client/handlers/user_status_handler.py b/pyrogram/client/handlers/user_status_handler.py index 9b39aab6..1f84d77f 100644 --- a/pyrogram/client/handlers/user_status_handler.py +++ b/pyrogram/client/handlers/user_status_handler.py @@ -21,26 +21,24 @@ from .handler import Handler class UserStatusHandler(Handler): """The UserStatus handler class. Used to handle user status updates (user going online or offline). - It is intended to be used with :meth:`~Client.add_handler` + It is intended to be used with :meth:`~Client.add_handler`. - For a nicer way to register this handler, have a look at the - :meth:`~Client.on_user_status` decorator. + For a nicer way to register this handler, have a look at the :meth:`~Client.on_user_status` decorator. Parameters: callback (``callable``): - Pass a function that will be called when a new UserStatus update arrives. It takes *(client, user_status)* + Pass a function that will be called when a new user status update arrives. It takes *(client, user)* as positional arguments (look at the section below for a detailed description). filters (:obj:`Filters`): - Pass one or more filters to allow only a subset of messages to be passed - in your callback function. + Pass one or more filters to allow only a subset of users to be passed in your callback function. Other parameters: client (:obj:`Client`): The Client itself, useful when you want to call other API methods inside the user status handler. - user_status (:obj:`UserStatus`): - The received UserStatus update. + user (:obj:`User`): + The user containing the updated status. """ def __init__(self, callback: callable, filters=None): diff --git a/pyrogram/client/methods/bots/answer_callback_query.py b/pyrogram/client/methods/bots/answer_callback_query.py index 010c29ea..dec3bef0 100644 --- a/pyrogram/client/methods/bots/answer_callback_query.py +++ b/pyrogram/client/methods/bots/answer_callback_query.py @@ -56,8 +56,14 @@ class AnswerCallbackQuery(BaseClient): Returns: ``bool``: True, on success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Answer without alert + app.answer_callback_query(query_id, text=text) + + # Answer with alert + app.answer_callback_query(query_id, text=text, show_alert=True) """ return self.send( functions.messages.SetBotCallbackAnswer( diff --git a/pyrogram/client/methods/bots/answer_inline_query.py b/pyrogram/client/methods/bots/answer_inline_query.py index 38ed99c3..da801c62 100644 --- a/pyrogram/client/methods/bots/answer_inline_query.py +++ b/pyrogram/client/methods/bots/answer_inline_query.py @@ -29,28 +29,34 @@ class AnswerInlineQuery(BaseClient): inline_query_id: str, results: List[InlineQueryResult], cache_time: int = 300, - is_personal: bool = None, + is_gallery: bool = False, + is_personal: bool = False, next_offset: str = "", switch_pm_text: str = "", switch_pm_parameter: str = "" ): """Send answers to an inline query. - No more than 50 results per query are allowed. + + A maximum of 50 results per query is allowed. Parameters: inline_query_id (``str``): Unique identifier for the answered query. - results (List of :obj:`InlineQueryResult `): + results (List of :obj:`InlineQueryResult`): A list of results for the inline query. cache_time (``int``, *optional*): The maximum amount of time in seconds that the result of the inline query may be cached on the server. Defaults to 300. + is_gallery (``bool``, *optional*): + Pass True, if results should be displayed in gallery mode instead of list mode. + Defaults to False. + is_personal (``bool``, *optional*): Pass True, if results may be cached on the server side only for the user that sent the query. - By default, results may be returned to any user who sends the same query. + By default (False), results may be returned to any user who sends the same query. next_offset (``str``, *optional*): Pass the offset that a client should send in the next query with the same text to receive more results. @@ -75,15 +81,24 @@ class AnswerInlineQuery(BaseClient): Returns: ``bool``: True, on success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from pyrogram import InlineQueryResultArticle, InputTextMessageContent + + app.answer_inline_query( + inline_query_id, + results=[ + InlineQueryResultArticle( + "Title", + InputTextMessageContent("Message content"))]) """ return self.send( functions.messages.SetInlineBotResults( query_id=int(inline_query_id), results=[r.write() for r in results], cache_time=cache_time, - gallery=None, + gallery=is_gallery or None, private=is_personal or None, next_offset=next_offset or None, switch_pm=types.InlineBotSwitchPM( diff --git a/pyrogram/client/methods/bots/get_game_high_scores.py b/pyrogram/client/methods/bots/get_game_high_scores.py index e6459bac..595e4e1a 100644 --- a/pyrogram/client/methods/bots/get_game_high_scores.py +++ b/pyrogram/client/methods/bots/get_game_high_scores.py @@ -51,8 +51,11 @@ class GetGameHighScores(BaseClient): Returns: List of :obj:`GameHighScore`: On success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + scores = app.get_game_high_scores(user_id, chat_id, message_id) + print(scores) """ # TODO: inline_message_id diff --git a/pyrogram/client/methods/bots/get_inline_bot_results.py b/pyrogram/client/methods/bots/get_inline_bot_results.py index cc0fc1b1..99f05c95 100644 --- a/pyrogram/client/methods/bots/get_inline_bot_results.py +++ b/pyrogram/client/methods/bots/get_inline_bot_results.py @@ -27,7 +27,7 @@ class GetInlineBotResults(BaseClient): def get_inline_bot_results( self, bot: Union[int, str], - query: str, + query: str = "", offset: str = "", latitude: float = None, longitude: float = None @@ -40,8 +40,9 @@ class GetInlineBotResults(BaseClient): Unique identifier of the inline bot you want to get results from. You can specify a @username (str) or a bot ID (int). - query (``str``): + query (``str``, *optional*): Text of the query (up to 512 characters). + Defaults to "" (empty string). offset (``str``, *optional*): Offset of the results to be returned. @@ -58,8 +59,13 @@ class GetInlineBotResults(BaseClient): :obj:`BotResults `: On Success. Raises: - RPCError: In case of a Telegram RPC error. TimeoutError: In case the bot fails to answer within 10 seconds. + + Example: + .. code-block:: python + + results = app.get_inline_bot_results("pyrogrambot") + print(results) """ # TODO: Don't return the raw type diff --git a/pyrogram/client/methods/bots/request_callback_answer.py b/pyrogram/client/methods/bots/request_callback_answer.py index 97d8d42b..01879bbb 100644 --- a/pyrogram/client/methods/bots/request_callback_answer.py +++ b/pyrogram/client/methods/bots/request_callback_answer.py @@ -53,8 +53,12 @@ class RequestCallbackAnswer(BaseClient): or as an alert. Raises: - RPCError: In case of a Telegram RPC error. TimeoutError: In case the bot fails to answer within 10 seconds. + + Example: + .. code-block:: python + + app.request_callback_answer(chat_id, message_id, "callback_data") """ # Telegram only wants bytes, but we are allowed to pass strings too. diff --git a/pyrogram/client/methods/bots/send_game.py b/pyrogram/client/methods/bots/send_game.py index c10d328a..1a6a772a 100644 --- a/pyrogram/client/methods/bots/send_game.py +++ b/pyrogram/client/methods/bots/send_game.py @@ -62,8 +62,10 @@ class SendGame(BaseClient): Returns: :obj:`Message`: On success, the sent game message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.send_game(chat_id, "gamename") """ r = self.send( functions.messages.SendMedia( diff --git a/pyrogram/client/methods/bots/send_inline_bot_result.py b/pyrogram/client/methods/bots/send_inline_bot_result.py index 411ab462..059185db 100644 --- a/pyrogram/client/methods/bots/send_inline_bot_result.py +++ b/pyrogram/client/methods/bots/send_inline_bot_result.py @@ -60,8 +60,10 @@ class SendInlineBotResult(BaseClient): Returns: :obj:`Message`: On success, the sent inline result message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.send_inline_bot_result(chat_id, query_id, result_id) """ return self.send( functions.messages.SendInlineBotResult( diff --git a/pyrogram/client/methods/bots/set_game_score.py b/pyrogram/client/methods/bots/set_game_score.py index f9115b74..ba2e74fa 100644 --- a/pyrogram/client/methods/bots/set_game_score.py +++ b/pyrogram/client/methods/bots/set_game_score.py @@ -66,8 +66,14 @@ class SetGameScore(BaseClient): :obj:`Message` | ``bool``: On success, if the message was sent by the bot, the edited message is returned, True otherwise. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Set new score + app.set_game_score(user_id, 1000) + + # Force set new score + app.set_game_score(user_id, 25, force=True) """ r = self.send( functions.messages.SetGameScore( diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index 969628ee..fddb48ce 100644 --- a/pyrogram/client/methods/chats/__init__.py +++ b/pyrogram/client/methods/chats/__init__.py @@ -16,8 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from .add_chat_members import AddChatMembers from .archive_chats import ArchiveChats +from .create_channel import CreateChannel +from .create_group import CreateGroup +from .create_supergroup import CreateSupergroup +from .delete_channel import DeleteChannel from .delete_chat_photo import DeleteChatPhoto +from .delete_supergroup import DeleteSupergroup from .export_chat_invite_link import ExportChatInviteLink from .get_chat import GetChat from .get_chat_member import GetChatMember @@ -32,9 +38,9 @@ from .kick_chat_member import KickChatMember from .leave_chat import LeaveChat from .pin_chat_message import PinChatMessage from .promote_chat_member import PromoteChatMember -from .restrict_chat import RestrictChat from .restrict_chat_member import RestrictChatMember from .set_chat_description import SetChatDescription +from .set_chat_permissions import SetChatPermissions from .set_chat_photo import SetChatPhoto from .set_chat_title import SetChatTitle from .unarchive_chats import UnarchiveChats @@ -65,9 +71,15 @@ class Chats( IterDialogs, IterChatMembers, UpdateChatUsername, - RestrictChat, + SetChatPermissions, GetDialogsCount, ArchiveChats, - UnarchiveChats + UnarchiveChats, + CreateGroup, + CreateSupergroup, + CreateChannel, + AddChatMembers, + DeleteChannel, + DeleteSupergroup ): pass diff --git a/pyrogram/client/methods/chats/add_chat_members.py b/pyrogram/client/methods/chats/add_chat_members.py new file mode 100644 index 00000000..8dbad1a3 --- /dev/null +++ b/pyrogram/client/methods/chats/add_chat_members.py @@ -0,0 +1,88 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union, List + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class AddChatMembers(BaseClient): + def add_chat_members( + self, + chat_id: Union[int, str], + user_ids: Union[Union[int, str], List[Union[int, str]]], + forward_limit: int = 100 + ) -> bool: + """Add new chat members to a group, supergroup or channel + + Parameters: + chat_id (``int`` | ``str``): + The group, supergroup or channel id + + user_ids (``int`` | ``str`` | List of ``int`` or ``str``): + Users to add in the chat + You can pass an ID (int), username (str) or phone number (str). + Multiple users can be added by passing a list of IDs, usernames or phone numbers. + + forward_limit (``int``, *optional*): + How many of the latest messages you want to forward to the new members. Pass 0 to forward none of them. + Only applicable to basic groups (the argument is ignored for supergroups or channels). + Defaults to 100 (max amount). + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + # Add one member to a group or channel + app.add_chat_members(chat_id, user_id) + + # Add multiple members to a group or channel + app.add_chat_members(chat_id, [user_id1, user_id2, user_id3]) + + # Change forward_limit (for basic groups only) + app.add_chat_members(chat_id, user_id, forward_limit=25) + """ + peer = self.resolve_peer(chat_id) + + if not isinstance(user_ids, list): + user_ids = [user_ids] + + if isinstance(peer, types.InputPeerChat): + for user_id in user_ids: + self.send( + functions.messages.AddChatUser( + chat_id=peer.chat_id, + user_id=self.resolve_peer(user_id), + fwd_limit=forward_limit + ) + ) + else: + self.send( + functions.channels.InviteToChannel( + channel=peer, + users=[ + self.resolve_peer(user_id) + for user_id in user_ids + ] + ) + ) + + return True diff --git a/pyrogram/client/methods/chats/archive_chats.py b/pyrogram/client/methods/chats/archive_chats.py index 3c929983..14375a92 100644 --- a/pyrogram/client/methods/chats/archive_chats.py +++ b/pyrogram/client/methods/chats/archive_chats.py @@ -37,8 +37,14 @@ class ArchiveChats(BaseClient): Returns: ``bool``: On success, True is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Archive chat + app.archive_chats(chat_id) + + # Archive multiple chats at once + app.archive_chats([chat_id1, chat_id2, chat_id3]) """ if not isinstance(chat_ids, list): diff --git a/pyrogram/client/methods/chats/create_channel.py b/pyrogram/client/methods/chats/create_channel.py new file mode 100644 index 00000000..9520ceef --- /dev/null +++ b/pyrogram/client/methods/chats/create_channel.py @@ -0,0 +1,55 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram +from pyrogram.api import functions +from ...ext import BaseClient + + +class CreateChannel(BaseClient): + def create_channel( + self, + title: str, + description: str = "" + ) -> "pyrogram.Chat": + """Create a new broadcast channel. + + Parameters: + title (``title``): + The channel title. + + description (``str``, *optional*): + The channel description. + + Returns: + :obj:`Chat`: On success, a chat object is returned. + + Example: + .. code-block:: python + + app.create_channel("Channel Title", "Channel Description") + """ + r = self.send( + functions.channels.CreateChannel( + title=title, + about=description, + broadcast=True + ) + ) + + return pyrogram.Chat._parse_chat(self, r.chats[0]) diff --git a/pyrogram/client/methods/chats/create_group.py b/pyrogram/client/methods/chats/create_group.py new file mode 100644 index 00000000..4e1d63bd --- /dev/null +++ b/pyrogram/client/methods/chats/create_group.py @@ -0,0 +1,65 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union, List + +import pyrogram +from pyrogram.api import functions +from ...ext import BaseClient + + +class CreateGroup(BaseClient): + def create_group( + self, + title: str, + users: Union[Union[int, str], List[Union[int, str]]] + ) -> "pyrogram.Chat": + """Create a new basic group. + + .. note:: + + If you want to create a new supergroup, use :meth:`~pyrogram.Client.create_supergroup` instead. + + Parameters: + title (``title``): + The group title. + + users (``int`` | ``str`` | List of ``int`` or ``str``): + Users to create a chat with. + You must pass at least one user using their IDs (int), usernames (str) or phone numbers (str). + Multiple users can be invited by passing a list of IDs, usernames or phone numbers. + + Returns: + :obj:`Chat`: On success, a chat object is returned. + + Example: + .. code-block:: python + + app.create_group("Group Title", user_id) + """ + if not isinstance(users, list): + users = [users] + + r = self.send( + functions.messages.CreateChat( + title=title, + users=[self.resolve_peer(u) for u in users] + ) + ) + + return pyrogram.Chat._parse_chat(self, r.chats[0]) diff --git a/pyrogram/client/methods/chats/create_supergroup.py b/pyrogram/client/methods/chats/create_supergroup.py new file mode 100644 index 00000000..0ad14d06 --- /dev/null +++ b/pyrogram/client/methods/chats/create_supergroup.py @@ -0,0 +1,59 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram +from pyrogram.api import functions +from ...ext import BaseClient + + +class CreateSupergroup(BaseClient): + def create_supergroup( + self, + title: str, + description: str = "" + ) -> "pyrogram.Chat": + """Create a new supergroup. + + .. note:: + + If you want to create a new basic group, use :meth:`~pyrogram.Client.create_group` instead. + + Parameters: + title (``title``): + The supergroup title. + + description (``str``, *optional*): + The supergroup description. + + Returns: + :obj:`Chat`: On success, a chat object is returned. + + Example: + .. code-block:: python + + app.create_supergroup("Supergroup Title", "Supergroup Description") + """ + r = self.send( + functions.channels.CreateChannel( + title=title, + about=description, + megagroup=True + ) + ) + + return pyrogram.Chat._parse_chat(self, r.chats[0]) diff --git a/pyrogram/client/methods/chats/delete_channel.py b/pyrogram/client/methods/chats/delete_channel.py new file mode 100644 index 00000000..74fbea13 --- /dev/null +++ b/pyrogram/client/methods/chats/delete_channel.py @@ -0,0 +1,48 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + + +from typing import Union + +from pyrogram.api import functions +from ...ext import BaseClient + + +class DeleteChannel(BaseClient): + def delete_channel(self, chat_id: Union[int, str]) -> bool: + """Delete a channel. + + Parameters: + chat_id (``int`` | ``str``): + The id of the channel to be deleted. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + app.delete_channel(channel_id) + """ + self.send( + functions.channels.DeleteChannel( + channel=self.resolve_peer(chat_id) + ) + ) + + return True diff --git a/pyrogram/client/methods/chats/delete_chat_photo.py b/pyrogram/client/methods/chats/delete_chat_photo.py index 88d97506..89f869bf 100644 --- a/pyrogram/client/methods/chats/delete_chat_photo.py +++ b/pyrogram/client/methods/chats/delete_chat_photo.py @@ -28,12 +28,8 @@ class DeleteChatPhoto(BaseClient): chat_id: Union[int, str] ) -> bool: """Delete a chat photo. - Photos can't be changed for private chats. - You must be an administrator in the chat for this to work and must have the appropriate admin rights. - Note: - In regular groups (non-supergroups), this method will only work if the "All Members Are Admins" - setting is off. + You must be an administrator in the chat for this to work and must have the appropriate admin rights. Parameters: chat_id (``int`` | ``str``): @@ -43,8 +39,12 @@ class DeleteChatPhoto(BaseClient): ``bool``: True on success. Raises: - RPCError: In case of a Telegram RPC error. - ``ValueError`` if a chat_id belongs to user. + ValueError: if a chat_id belongs to user. + + Example: + .. code-block:: python + + app.delete_chat_photo(chat_id) """ peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/chats/delete_supergroup.py b/pyrogram/client/methods/chats/delete_supergroup.py new file mode 100644 index 00000000..a1eb198d --- /dev/null +++ b/pyrogram/client/methods/chats/delete_supergroup.py @@ -0,0 +1,48 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + + +from typing import Union + +from pyrogram.api import functions +from ...ext import BaseClient + + +class DeleteSupergroup(BaseClient): + def delete_supergroup(self, chat_id: Union[int, str]) -> bool: + """Delete a supergroup. + + Parameters: + chat_id (``int`` | ``str``): + The id of the supergroup to be deleted. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + app.delete_supergroup(supergroup_id) + """ + self.send( + functions.channels.DeleteChannel( + channel=self.resolve_peer(chat_id) + ) + ) + + return True diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py index 9266183d..46886469 100644 --- a/pyrogram/client/methods/chats/export_chat_invite_link.py +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -47,19 +47,21 @@ class ExportChatInviteLink(BaseClient): ``str``: On success, the exported invite link is returned. Raises: - RPCError: In case of a Telegram RPC error. + ValueError: In case the chat_id belongs to a user. + + Example: + .. code-block:: python + + link = app.export_chat_invite_link(chat_id) + print(link) """ peer = self.resolve_peer(chat_id) - if isinstance(peer, types.InputPeerChat): + if isinstance(peer, (types.InputPeerChat, types.InputPeerChannel)): return self.send( functions.messages.ExportChatInvite( peer=peer ) ).link - elif isinstance(peer, types.InputPeerChannel): - return self.send( - functions.channels.ExportInvite( - channel=peer - ) - ).link + else: + raise ValueError('The chat_id "{}" belongs to a user'.format(chat_id)) diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index 4f71c3b3..0773ce6c 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -20,7 +20,7 @@ from typing import Union import pyrogram from pyrogram.api import functions, types -from ...ext import BaseClient +from ...ext import BaseClient, utils class GetChat(BaseClient): @@ -44,8 +44,13 @@ class GetChat(BaseClient): otherwise, a chat preview object is returned. Raises: - RPCError: In case of a Telegram RPC error. ValueError: In case the chat invite link points to a chat you haven't joined yet. + + Example: + .. code-block:: python + + chat = app.get_chat("pyrogram") + print(chat) """ match = self.INVITE_LINK_RE.match(str(chat_id)) @@ -65,7 +70,7 @@ class GetChat(BaseClient): chat_id = -r.chat.id if isinstance(r.chat, types.Channel): - chat_id = int("-100" + str(r.chat.id)) + chat_id = utils.get_channel_id(r.chat.id) peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/client/methods/chats/get_chat_member.py index b0d0641a..20d9c624 100644 --- a/pyrogram/client/methods/chats/get_chat_member.py +++ b/pyrogram/client/methods/chats/get_chat_member.py @@ -44,8 +44,11 @@ class GetChatMember(BaseClient): Returns: :obj:`ChatMember`: On success, a chat member is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + dan = app.get_chat_member("pyrogramchat", "haskell") + print(dan) """ chat = self.resolve_peer(chat_id) user = self.resolve_peer(user_id) diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index 0b4613d8..19b5971e 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -91,8 +91,19 @@ class GetChatMembers(BaseClient): List of :obj:`ChatMember`: On success, a list of chat members is returned. Raises: - RPCError: In case of a Telegram RPC error. ValueError: In case you used an invalid filter or a chat id that belongs to a user. + + Example: + .. code-block:: python + + # Get first 200 recent members + app.get_chat_members("pyrogramchat") + + # Get all administrators + app.get_chat_members("pyrogramchat", filter="administrators") + + # Get all bots + app.get_chat_members("pyrogramchat", filter="bots") """ peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/chats/get_chat_members_count.py b/pyrogram/client/methods/chats/get_chat_members_count.py index 4c7ab747..74b6cda2 100644 --- a/pyrogram/client/methods/chats/get_chat_members_count.py +++ b/pyrogram/client/methods/chats/get_chat_members_count.py @@ -37,8 +37,13 @@ class GetChatMembersCount(BaseClient): ``int``: On success, the chat members count is returned. Raises: - RPCError: In case of a Telegram RPC error. ValueError: In case a chat id belongs to user. + + Example: + .. code-block:: python + + count = app.get_chat_members_count("pyrogramchat") + print(count) """ peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index 8c374a44..30078d57 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -23,7 +23,7 @@ from typing import List import pyrogram from pyrogram.api import functions, types from pyrogram.errors import FloodWait -from ...ext import BaseClient +from ...ext import BaseClient, utils log = logging.getLogger(__name__) @@ -56,8 +56,14 @@ class GetDialogs(BaseClient): Returns: List of :obj:`Dialog`: On success, a list of dialogs is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Get first 100 dialogs + app.get_dialogs() + + # Get pinned dialogs + app.get_dialogs(pinned_only=True) """ while True: @@ -94,10 +100,8 @@ class GetDialogs(BaseClient): chat_id = to_id.user_id else: chat_id = message.from_id - elif isinstance(to_id, types.PeerChat): - chat_id = -to_id.chat_id else: - chat_id = int("-100" + str(to_id.channel_id)) + chat_id = utils.get_peer_id(to_id) messages[chat_id] = pyrogram.Message._parse(self, message, users, chats) diff --git a/pyrogram/client/methods/chats/get_dialogs_count.py b/pyrogram/client/methods/chats/get_dialogs_count.py index c804709d..128b4364 100644 --- a/pyrogram/client/methods/chats/get_dialogs_count.py +++ b/pyrogram/client/methods/chats/get_dialogs_count.py @@ -31,8 +31,11 @@ class GetDialogsCount(BaseClient): Returns: ``int``: On success, the dialogs count is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + count = app.get_dialogs_count() + print(count) """ if pinned_only: diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index fe117694..297b8ff3 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -77,8 +77,20 @@ class IterChatMembers(BaseClient): Returns: ``Generator``: A generator yielding :obj:`ChatMember` objects. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Iterate though all chat members + for member in app.iter_chat_members("pyrogramchat"): + print(member.user.first_name) + + # Iterate though all administrators + for member in app.iter_chat_members("pyrogramchat", filter="administrators"): + print(member.user.first_name) + + # Iterate though all bots + for member in app.iter_chat_members("pyrogramchat", filter="bots"): + print(member.user.first_name) """ current = 0 yielded = set() diff --git a/pyrogram/client/methods/chats/iter_dialogs.py b/pyrogram/client/methods/chats/iter_dialogs.py index fce9fb99..55de2a74 100644 --- a/pyrogram/client/methods/chats/iter_dialogs.py +++ b/pyrogram/client/methods/chats/iter_dialogs.py @@ -46,8 +46,12 @@ class IterDialogs(BaseClient): Returns: ``Generator``: A generator yielding :obj:`Dialog` objects. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Iterate through all dialogs + for dialog in app.iter_dialogs(): + print(dialog.chat.first_name or dialog.chat.title) """ current = 0 total = limit or (1 << 31) - 1 diff --git a/pyrogram/client/methods/chats/join_chat.py b/pyrogram/client/methods/chats/join_chat.py index ed6c69ce..c1dd923a 100644 --- a/pyrogram/client/methods/chats/join_chat.py +++ b/pyrogram/client/methods/chats/join_chat.py @@ -36,8 +36,14 @@ class JoinChat(BaseClient): Returns: :obj:`Chat`: On success, a chat object is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Join chat via username + app.join_chat("pyrogram") + + # Join chat via invite link + app.join_chat("https://t.me/joinchat/AAAAAE0QmSW3IUmm3UFR7A") """ match = self.INVITE_LINK_RE.match(chat_id) diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/client/methods/chats/kick_chat_member.py index 9686e754..20f26c50 100644 --- a/pyrogram/client/methods/chats/kick_chat_member.py +++ b/pyrogram/client/methods/chats/kick_chat_member.py @@ -57,8 +57,16 @@ class KickChatMember(BaseClient): :obj:`Message` | ``bool``: On success, a service message will be returned (when applicable), otherwise, in case a message object couldn't be returned, True is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from time import time + + # Ban chat member forever + app.kick_chat_member(chat_id, user_id) + + # Kick chat member and automatically unban after 24h + app.kick_chat_member(chat_id, user_id, int(time.time() + 86400)) """ chat_peer = self.resolve_peer(chat_id) user_peer = self.resolve_peer(user_id) diff --git a/pyrogram/client/methods/chats/leave_chat.py b/pyrogram/client/methods/chats/leave_chat.py index 3ed6f10f..0a8aec0e 100644 --- a/pyrogram/client/methods/chats/leave_chat.py +++ b/pyrogram/client/methods/chats/leave_chat.py @@ -37,9 +37,16 @@ class LeaveChat(BaseClient): delete (``bool``, *optional*): Deletes the group chat dialog after leaving (for simple group chats, not supergroups). + Defaults to False. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Leave chat or channel + app.leave_chat(chat_id) + + # Leave basic chat and also delete the dialog + app.leave_chat(chat_id, delete=True) """ peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/chats/pin_chat_message.py b/pyrogram/client/methods/chats/pin_chat_message.py index efb41e67..fcdb31fd 100644 --- a/pyrogram/client/methods/chats/pin_chat_message.py +++ b/pyrogram/client/methods/chats/pin_chat_message.py @@ -47,8 +47,14 @@ class PinChatMessage(BaseClient): Returns: ``bool``: True on success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Pin with notification + app.pin_chat_message(chat_id, message_id) + + # Pin without notification + app.pin_chat_message(chat_id, message_id, disable_notification=True) """ self.send( functions.messages.UpdatePinnedMessage( diff --git a/pyrogram/client/methods/chats/promote_chat_member.py b/pyrogram/client/methods/chats/promote_chat_member.py index 700b3a68..9394841b 100644 --- a/pyrogram/client/methods/chats/promote_chat_member.py +++ b/pyrogram/client/methods/chats/promote_chat_member.py @@ -78,8 +78,11 @@ class PromoteChatMember(BaseClient): Returns: ``bool``: True on success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Promote chat member to supergroup admin + app.promote_chat_member(chat_id, user_id) """ self.send( functions.channels.EditAdmin( diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index 96e07d18..f20eb348 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -20,7 +20,7 @@ from typing import Union from pyrogram.api import functions, types from ...ext import BaseClient -from ...types.user_and_chats import Chat +from ...types.user_and_chats import Chat, ChatPermissions class RestrictChatMember(BaseClient): @@ -28,20 +28,13 @@ class RestrictChatMember(BaseClient): self, chat_id: Union[int, str], user_id: Union[int, str], - until_date: int = 0, - can_send_messages: bool = False, - can_send_media_messages: bool = False, - can_send_other_messages: bool = False, - can_add_web_page_previews: bool = False, - can_send_polls: bool = False, - can_change_info: bool = False, - can_invite_users: bool = False, - can_pin_messages: bool = False + permissions: ChatPermissions, + until_date: int = 0 ) -> Chat: """Restrict a user in a supergroup. - The bot must be an administrator in the supergroup for this to work and must have the appropriate admin rights. - Pass True for all boolean parameters to lift restrictions from a user. + You must be an administrator in the supergroup for this to work and must have the appropriate admin rights. + Pass True for all permissions to lift restrictions from a user. Parameters: chat_id (``int`` | ``str``): @@ -51,42 +44,32 @@ class RestrictChatMember(BaseClient): Unique identifier (int) or username (str) of the target user. For a contact that exists in your Telegram address book you can use his phone number (str). + permissions (:obj:`ChatPermissions`): + New user permissions. + until_date (``int``, *optional*): Date when the user will be unbanned, unix time. If user is banned for more than 366 days or less than 30 seconds from the current time they are considered to be banned forever. Defaults to 0 (ban forever). - can_send_messages (``bool``, *optional*): - Pass True, if the user can send text messages, contacts, locations and venues. - - can_send_media_messages (``bool``, *optional*): - Pass True, if the user can send audios, documents, photos, videos, video notes and voice notes, - implies can_send_messages. - - can_send_other_messages (``bool``, *optional*): - Pass True, if the user can send animations, games, stickers and use inline bots, - implies can_send_media_messages. - - can_add_web_page_previews (``bool``, *optional*): - Pass True, if the user may add web page previews to their messages, implies can_send_media_messages. - - can_send_polls (``bool``, *optional*): - Pass True, if the user can send polls, implies can_send_media_messages. - - can_change_info (``bool``, *optional*): - Pass True, if the user can change the chat title, photo and other settings. - - can_invite_users (``bool``, *optional*): - Pass True, if the user can invite new users to the chat. - - can_pin_messages (``bool``, *optional*): - Pass True, if the user can pin messages. - Returns: :obj:`Chat`: On success, a chat object is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from time import time + + from pyrogram import ChatPermissions + + # Completely restrict chat member (mute) forever + app.restrict_chat_member(chat_id, user_id, ChatPermissions()) + + # Chat member muted for 24h + app.restrict_chat_member(chat_id, user_id, ChatPermissions(), int(time.time() + 86400)) + + # Chat member can only send text messages + app.restrict_chat_member(chat_id, user_id, ChatPermissions(can_send_messages=True)) """ send_messages = True send_media = True @@ -100,37 +83,35 @@ class RestrictChatMember(BaseClient): invite_users = True pin_messages = True - if can_send_messages: + if permissions.can_send_messages: send_messages = None - if can_send_media_messages: + if permissions.can_send_media_messages: send_messages = None send_media = None - if can_send_other_messages: + if permissions.can_send_other_messages: send_messages = None - send_media = None send_stickers = None send_gifs = None send_games = None send_inline = None - if can_add_web_page_previews: + if permissions.can_add_web_page_previews: send_messages = None - send_media = None embed_links = None - if can_send_polls: + if permissions.can_send_polls: send_messages = None send_polls = None - if can_change_info: + if permissions.can_change_info: change_info = None - if can_invite_users: + if permissions.can_invite_users: invite_users = None - if can_pin_messages: + if permissions.can_pin_messages: pin_messages = None r = self.send( diff --git a/pyrogram/client/methods/chats/set_chat_description.py b/pyrogram/client/methods/chats/set_chat_description.py index 68bf9fa2..8d0f0669 100644 --- a/pyrogram/client/methods/chats/set_chat_description.py +++ b/pyrogram/client/methods/chats/set_chat_description.py @@ -42,8 +42,12 @@ class SetChatDescription(BaseClient): ``bool``: True on success. Raises: - RPCError: In case of a Telegram RPC error. - ``ValueError`` if a chat_id doesn't belong to a supergroup or a channel. + ValueError: if a chat_id doesn't belong to a supergroup or a channel. + + Example: + .. code-block:: python + + app.set_chat_description(chat_id, "New Description") """ peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/chats/restrict_chat.py b/pyrogram/client/methods/chats/set_chat_permissions.py similarity index 56% rename from pyrogram/client/methods/chats/restrict_chat.py rename to pyrogram/client/methods/chats/set_chat_permissions.py index 8e63a9b2..f1ea61c7 100644 --- a/pyrogram/client/methods/chats/restrict_chat.py +++ b/pyrogram/client/methods/chats/set_chat_permissions.py @@ -20,60 +20,47 @@ from typing import Union from pyrogram.api import functions, types from ...ext import BaseClient -from ...types.user_and_chats import Chat +from ...types.user_and_chats import Chat, ChatPermissions -class RestrictChat(BaseClient): - def restrict_chat( +class SetChatPermissions(BaseClient): + def set_chat_permissions( self, chat_id: Union[int, str], - can_send_messages: bool = False, - can_send_media_messages: bool = False, - can_send_other_messages: bool = False, - can_add_web_page_previews: bool = False, - can_send_polls: bool = False, - can_change_info: bool = False, - can_invite_users: bool = False, - can_pin_messages: bool = False + permissions: ChatPermissions, ) -> Chat: - """Restrict a chat. - Pass True for all boolean parameters to lift restrictions from a chat. + """Set default chat permissions for all members. + + You must be an administrator in the group or a supergroup for this to work and must have the + *can_restrict_members* admin rights. Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. - can_send_messages (``bool``, *optional*): - Pass True, if the user can send text messages, contacts, locations and venues. - - can_send_media_messages (``bool``, *optional*): - Pass True, if the user can send audios, documents, photos, videos, video notes and voice notes, - implies can_send_messages. - - can_send_other_messages (``bool``, *optional*): - Pass True, if the user can send animations, games, stickers and use inline bots, - implies can_send_media_messages. - - can_add_web_page_previews (``bool``, *optional*): - Pass True, if the user may add web page previews to their messages, implies can_send_media_messages. - - can_send_polls (``bool``, *optional*): - Pass True, if the user can send polls, implies can_send_media_messages. - - can_change_info (``bool``, *optional*): - Pass True, if the user can change the chat title, photo and other settings. - - can_invite_users (``bool``, *optional*): - Pass True, if the user can invite new users to the chat. - - can_pin_messages (``bool``, *optional*): - Pass True, if the user can pin messages. + permissions (:obj:`ChatPermissions`): + New default chat permissions. Returns: :obj:`Chat`: On success, a chat object is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from pyrogram import ChatPermissions + + # Completely restrict chat + app.set_chat_permissions(chat_id, ChatPermissions()) + + # Chat members can only send text messages, media, stickers and GIFs + app.set_chat_permissions( + chat_id, + ChatPermissions( + can_send_messages=True, + can_send_media_messages=True, + can_send_other_messages=True + ) + ) """ send_messages = True send_media = True @@ -87,37 +74,35 @@ class RestrictChat(BaseClient): invite_users = True pin_messages = True - if can_send_messages: + if permissions.can_send_messages: send_messages = None - if can_send_media_messages: + if permissions.can_send_media_messages: send_messages = None send_media = None - if can_send_other_messages: + if permissions.can_send_other_messages: send_messages = None - send_media = None send_stickers = None send_gifs = None send_games = None send_inline = None - if can_add_web_page_previews: + if permissions.can_add_web_page_previews: send_messages = None - send_media = None embed_links = None - if can_send_polls: + if permissions.can_send_polls: send_messages = None send_polls = None - if can_change_info: + if permissions.can_change_info: change_info = None - if can_invite_users: + if permissions.can_invite_users: invite_users = None - if can_pin_messages: + if permissions.can_pin_messages: pin_messages = None r = self.send( diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index 2baa29fe..71cd6590 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -17,12 +17,11 @@ # along with Pyrogram. If not, see . import os -from base64 import b64decode from struct import unpack from typing import Union from pyrogram.api import functions, types -from ...ext import BaseClient +from ...ext import BaseClient, utils class SetChatPhoto(BaseClient): @@ -32,38 +31,43 @@ class SetChatPhoto(BaseClient): photo: str ) -> bool: """Set a new profile photo for the chat. - Photos can't be changed for private chats. - You must be an administrator in the chat for this to work and must have the appropriate admin rights. - Note: - In regular groups (non-supergroups), this method will only work if the "All Members Are Admins" - setting is off. + You must be an administrator in the chat for this to work and must have the appropriate admin rights. Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. photo (``str``): - New chat photo. You can pass a :obj:`Photo` id or a file path to upload a new photo. + New chat photo. You can pass a :obj:`Photo` file_id or a file path to upload a new photo from your local + machine. Returns: ``bool``: True on success. Raises: - RPCError: In case of a Telegram RPC error. ValueError: if a chat_id belongs to user. + + Example: + .. code-block:: python + + # Set chat photo using a local file + app.set_chat_photo(chat_id, "photo.jpg") + + # Set chat photo using an exiting Photo file_id + app.set_chat_photo(chat_id, photo.file_id) """ peer = self.resolve_peer(chat_id) if os.path.exists(photo): photo = types.InputChatUploadedPhoto(file=self.save_file(photo)) else: - s = unpack(" List["pyrogram.User"]: - # TODO: Create a Users object and return that """Get contacts from your Telegram address book. Returns: List of :obj:`User`: On success, a list of users is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + contacts = app.get_contacts() + print(contacts) """ while True: try: @@ -46,5 +48,4 @@ class GetContacts(BaseClient): log.warning("get_contacts flood: waiting {} seconds".format(e.x)) time.sleep(e.x) else: - log.info("Total contacts: {}".format(len(self.peers_by_phone))) return pyrogram.List(pyrogram.User._parse(self, user) for user in contacts.users) diff --git a/pyrogram/client/methods/contacts/get_contacts_count.py b/pyrogram/client/methods/contacts/get_contacts_count.py index dddfe8c4..8e23d698 100644 --- a/pyrogram/client/methods/contacts/get_contacts_count.py +++ b/pyrogram/client/methods/contacts/get_contacts_count.py @@ -27,8 +27,11 @@ class GetContactsCount(BaseClient): Returns: ``int``: On success, the contacts count is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + count = app.get_contacts_count() + print(count) """ return len(self.send(functions.contacts.GetContacts(hash=0)).contacts) diff --git a/pyrogram/client/methods/decorators/on_callback_query.py b/pyrogram/client/methods/decorators/on_callback_query.py index 1552bae7..1b7e2bcb 100644 --- a/pyrogram/client/methods/decorators/on_callback_query.py +++ b/pyrogram/client/methods/decorators/on_callback_query.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 Tuple +from typing import Callable import pyrogram from pyrogram.client.filters.filter import Filter -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -44,18 +43,15 @@ class OnCallbackQuery(BaseClient): The group identifier, defaults to 0. """ - def decorator(func: callable) -> Tuple[Handler, int]: - if isinstance(func, tuple): - func = func[0].callback + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.CallbackQueryHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + func.handler = ( + pyrogram.CallbackQueryHandler(func, self), + group if filters is None else filters + ) - handler = pyrogram.CallbackQueryHandler(func, filters) - - if isinstance(self, Filter): - return pyrogram.CallbackQueryHandler(func, self), group if filters is None else filters - - if self is not None: - self.add_handler(handler, group) - - return handler, group + return func return decorator diff --git a/pyrogram/client/methods/decorators/on_deleted_messages.py b/pyrogram/client/methods/decorators/on_deleted_messages.py index 0d87ba5a..cf31ac87 100644 --- a/pyrogram/client/methods/decorators/on_deleted_messages.py +++ b/pyrogram/client/methods/decorators/on_deleted_messages.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 Tuple +from typing import Callable import pyrogram from pyrogram.client.filters.filter import Filter -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -44,18 +43,15 @@ class OnDeletedMessages(BaseClient): The group identifier, defaults to 0. """ - def decorator(func: callable) -> Tuple[Handler, int]: - if isinstance(func, tuple): - func = func[0].callback + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.DeletedMessagesHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + func.handler = ( + pyrogram.DeletedMessagesHandler(func, self), + group if filters is None else filters + ) - handler = pyrogram.DeletedMessagesHandler(func, filters) - - if isinstance(self, Filter): - return pyrogram.DeletedMessagesHandler(func, self), group if filters is None else filters - - if self is not None: - self.add_handler(handler, group) - - return handler, group + return func return decorator diff --git a/pyrogram/client/methods/decorators/on_disconnect.py b/pyrogram/client/methods/decorators/on_disconnect.py index 4a514a41..012abd38 100644 --- a/pyrogram/client/methods/decorators/on_disconnect.py +++ b/pyrogram/client/methods/decorators/on_disconnect.py @@ -16,8 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Callable + import pyrogram -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -28,12 +29,10 @@ class OnDisconnect(BaseClient): This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.DisconnectHandler`. """ - def decorator(func: callable) -> Handler: - handler = pyrogram.DisconnectHandler(func) + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.DisconnectHandler(func)) - if self is not None: - self.add_handler(handler) - - return handler + return func return decorator diff --git a/pyrogram/client/methods/decorators/on_inline_query.py b/pyrogram/client/methods/decorators/on_inline_query.py index adc65d25..a84b7ca9 100644 --- a/pyrogram/client/methods/decorators/on_inline_query.py +++ b/pyrogram/client/methods/decorators/on_inline_query.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 Tuple +from typing import Callable import pyrogram from pyrogram.client.filters.filter import Filter -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -43,18 +42,15 @@ class OnInlineQuery(BaseClient): The group identifier, defaults to 0. """ - def decorator(func: callable) -> Tuple[Handler, int]: - if isinstance(func, tuple): - func = func[0].callback + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.InlineQueryHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + func.handler = ( + pyrogram.InlineQueryHandler(func, self), + group if filters is None else filters + ) - handler = pyrogram.InlineQueryHandler(func, filters) - - if isinstance(self, Filter): - return pyrogram.InlineQueryHandler(func, self), group if filters is None else filters - - if self is not None: - self.add_handler(handler, group) - - return handler, group + return func return decorator diff --git a/pyrogram/client/methods/decorators/on_message.py b/pyrogram/client/methods/decorators/on_message.py index 758a6831..0166541c 100644 --- a/pyrogram/client/methods/decorators/on_message.py +++ b/pyrogram/client/methods/decorators/on_message.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 Tuple +from typing import Callable import pyrogram from pyrogram.client.filters.filter import Filter -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -43,18 +42,15 @@ class OnMessage(BaseClient): The group identifier, defaults to 0. """ - def decorator(func: callable) -> Tuple[Handler, int]: - if isinstance(func, tuple): - func = func[0].callback + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.MessageHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + func.handler = ( + pyrogram.MessageHandler(func, self), + group if filters is None else filters + ) - handler = pyrogram.MessageHandler(func, filters) - - if isinstance(self, Filter): - return pyrogram.MessageHandler(func, self), group if filters is None else filters - - if self is not None: - self.add_handler(handler, group) - - return handler, group + return func return decorator diff --git a/pyrogram/client/methods/decorators/on_poll.py b/pyrogram/client/methods/decorators/on_poll.py index 0ade42c0..c797c8c6 100644 --- a/pyrogram/client/methods/decorators/on_poll.py +++ b/pyrogram/client/methods/decorators/on_poll.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 Tuple +from typing import Callable import pyrogram from pyrogram.client.filters.filter import Filter -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -43,18 +42,15 @@ class OnPoll(BaseClient): The group identifier, defaults to 0. """ - def decorator(func: callable) -> Tuple[Handler, int]: - if isinstance(func, tuple): - func = func[0].callback + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.PollHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + func.handler = ( + pyrogram.PollHandler(func, self), + group if filters is None else filters + ) - handler = pyrogram.PollHandler(func, filters) - - if isinstance(self, Filter): - return pyrogram.PollHandler(func, self), group if filters is None else filters - - if self is not None: - self.add_handler(handler, group) - - return handler, group + return func return decorator diff --git a/pyrogram/client/methods/decorators/on_raw_update.py b/pyrogram/client/methods/decorators/on_raw_update.py index 7dff75fa..f56de6f9 100644 --- a/pyrogram/client/methods/decorators/on_raw_update.py +++ b/pyrogram/client/methods/decorators/on_raw_update.py @@ -16,10 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Tuple +from typing import Callable import pyrogram -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -37,18 +36,15 @@ class OnRawUpdate(BaseClient): The group identifier, defaults to 0. """ - def decorator(func: callable) -> Tuple[Handler, int]: - if isinstance(func, tuple): - func = func[0].callback + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.RawUpdateHandler(func), group) + else: + func.handler = ( + pyrogram.RawUpdateHandler(func), + group if self is None else group + ) - handler = pyrogram.RawUpdateHandler(func) - - if isinstance(self, int): - return handler, group if self is None else group - - if self is not None: - self.add_handler(handler, group) - - return handler, group + return func return decorator diff --git a/pyrogram/client/methods/decorators/on_user_status.py b/pyrogram/client/methods/decorators/on_user_status.py index 09e037f7..02ed9e7b 100644 --- a/pyrogram/client/methods/decorators/on_user_status.py +++ b/pyrogram/client/methods/decorators/on_user_status.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 Tuple +from typing import Callable import pyrogram from pyrogram.client.filters.filter import Filter -from pyrogram.client.handlers.handler import Handler from ...ext import BaseClient @@ -41,18 +40,15 @@ class OnUserStatus(BaseClient): The group identifier, defaults to 0. """ - def decorator(func: callable) -> Tuple[Handler, int]: - if isinstance(func, tuple): - func = func[0].callback + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.UserStatusHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + func.handler = ( + pyrogram.UserStatusHandler(func, self), + group if filters is None else filters + ) - handler = pyrogram.UserStatusHandler(func, filters) - - if isinstance(self, Filter): - return pyrogram.UserStatusHandler(func, self), group if filters is None else filters - - if self is not None: - self.add_handler(handler, group) - - return handler, group + return func return decorator diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index 07df7a64..6237b47c 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -18,6 +18,10 @@ from .delete_messages import DeleteMessages from .download_media import DownloadMedia +from .edit_inline_caption import EditInlineCaption +from .edit_inline_media import EditInlineMedia +from .edit_inline_reply_markup import EditInlineReplyMarkup +from .edit_inline_text import EditInlineText from .edit_message_caption import EditMessageCaption from .edit_message_media import EditMessageMedia from .edit_message_reply_markup import EditMessageReplyMarkup @@ -29,7 +33,6 @@ from .get_messages import GetMessages from .iter_history import IterHistory from .read_history import ReadHistory from .retract_vote import RetractVote -from .send_animated_sticker import SendAnimatedSticker from .send_animation import SendAnimation from .send_audio import SendAudio from .send_cached_media import SendCachedMedia @@ -81,7 +84,10 @@ class Messages( IterHistory, SendCachedMedia, GetHistoryCount, - SendAnimatedSticker, - ReadHistory + ReadHistory, + EditInlineText, + EditInlineCaption, + EditInlineMedia, + EditInlineReplyMarkup ): pass diff --git a/pyrogram/client/methods/messages/delete_messages.py b/pyrogram/client/methods/messages/delete_messages.py index 3667c8ee..f0c4d991 100644 --- a/pyrogram/client/methods/messages/delete_messages.py +++ b/pyrogram/client/methods/messages/delete_messages.py @@ -50,8 +50,17 @@ class DeleteMessages(BaseClient): Returns: ``bool``: True on success, False otherwise. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Delete one message + app.delete_messages(chat_id, message_id) + + # Delete multiple messages at once + app.delete_messages(chat_id, list_of_message_ids) + + # Delete messages only on your side (without revoking) + app.delete_messages(chat_id, message_id, revoke=False) """ peer = self.resolve_peer(chat_id) message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids] diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index bd8de2d6..b00b7c72 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -17,7 +17,10 @@ # along with Pyrogram. If not, see . import binascii +import os import struct +import time +from datetime import datetime from threading import Event from typing import Union @@ -25,12 +28,14 @@ import pyrogram from pyrogram.client.ext import BaseClient, FileData, utils from pyrogram.errors import FileIdInvalid +DEFAULT_DOWNLOAD_DIR = "downloads/" + class DownloadMedia(BaseClient): def download_media( self, message: Union["pyrogram.Message", str], - file_name: str = "", + file_name: str = DEFAULT_DOWNLOAD_DIR, block: bool = True, progress: callable = None, progress_args: tuple = () @@ -52,24 +57,23 @@ class DownloadMedia(BaseClient): Blocks the code execution until the file has been downloaded. Defaults to True. - progress (``callable``): - Pass a callback function to view the download progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + progress (``callable``, *optional*): + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. - progress_args (``tuple``): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes downloaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -80,8 +84,16 @@ class DownloadMedia(BaseClient): the download failed or was deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. Raises: - RPCError: In case of a Telegram RPC error. - ``ValueError`` if the message doesn't contain any downloadable media + ValueError: if the message doesn't contain any downloadable media + + Example: + .. code-block:: python + + # Download from Message + app.download_media(message) + + # Download from file id + app.download_media("CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") """ error_message = "This message doesn't contain any downloadable media" available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note") @@ -169,7 +181,41 @@ class DownloadMedia(BaseClient): done = Event() path = [None] - self.download_queue.put((data, file_name, done, progress, progress_args, path)) + directory, file_name = os.path.split(file_name) + file_name = file_name or data.file_name or "" + + if not os.path.isabs(file_name): + directory = self.PARENT_DIR / (directory or DEFAULT_DOWNLOAD_DIR) + + media_type_str = self.MEDIA_TYPE_ID[data.media_type] + + if not file_name: + guessed_extension = self.guess_extension(data.mime_type) + + if data.media_type in (0, 1, 2, 14): + extension = ".jpg" + elif data.media_type == 3: + extension = guessed_extension or ".ogg" + elif data.media_type in (4, 10, 13): + extension = guessed_extension or ".mp4" + elif data.media_type == 5: + extension = guessed_extension or ".zip" + elif data.media_type == 8: + extension = guessed_extension or ".webp" + elif data.media_type == 9: + extension = guessed_extension or ".mp3" + else: + extension = ".unknown" + + file_name = "{}_{}_{}{}".format( + media_type_str, + datetime.fromtimestamp(data.date or time.time()).strftime("%Y-%m-%d_%H-%M-%S"), + self.rnd_id(), + extension + ) + + # Cast to string because Path objects aren't supported by Python 3.5 + self.download_queue.put((data, str(directory), str(file_name), done, progress, progress_args, path)) if block: done.wait() diff --git a/pyrogram/client/methods/messages/edit_inline_caption.py b/pyrogram/client/methods/messages/edit_inline_caption.py new file mode 100644 index 00000000..57a0ac75 --- /dev/null +++ b/pyrogram/client/methods/messages/edit_inline_caption.py @@ -0,0 +1,66 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram.client.ext import BaseClient + + +class EditInlineCaption(BaseClient): + def edit_inline_caption( + self, + inline_message_id: str, + caption: str, + parse_mode: Union[str, None] = object, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> bool: + """Edit the caption of inline media messages. + + Parameters: + inline_message_id (``str``): + Identifier of the inline message. + + caption (``str``): + New caption of the media message. + + parse_mode (``str``, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + # Bots only + app.edit_inline_caption(inline_message_id, "new media caption") + """ + return self.edit_inline_text( + inline_message_id=inline_message_id, + text=caption, + parse_mode=parse_mode, + reply_markup=reply_markup + ) diff --git a/pyrogram/client/methods/messages/edit_inline_media.py b/pyrogram/client/methods/messages/edit_inline_media.py new file mode 100644 index 00000000..7a82f3a8 --- /dev/null +++ b/pyrogram/client/methods/messages/edit_inline_media.py @@ -0,0 +1,117 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram +from pyrogram.api import functions, types +from pyrogram.client.ext import BaseClient, utils +from pyrogram.client.types import ( + InputMediaPhoto, InputMediaVideo, InputMediaAudio, + InputMediaAnimation, InputMediaDocument +) +from pyrogram.client.types.input_media import InputMedia + + +class EditInlineMedia(BaseClient): + def edit_inline_media( + self, + inline_message_id: str, + media: InputMedia, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> bool: + """Edit inline animation, audio, document, photo or video messages. + + When the inline message is edited, a new file can't be uploaded. Use a previously uploaded file via its file_id + or specify a URL. + + Parameters: + inline_message_id (``str``): + Required if *chat_id* and *message_id* are not specified. + Identifier of the inline message. + + media (:obj:`InputMedia`): + One of the InputMedia objects describing an animation, audio, document, photo or video. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + from pyrogram import InputMediaPhoto, InputMediaVideo, InputMediaAudio + + # Bots only + + # Replace the current media with a local photo + app.edit_inline_media(inline_message_id, InputMediaPhoto("new_photo.jpg")) + + # Replace the current media with a local video + app.edit_inline_media(inline_message_id, InputMediaVideo("new_video.mp4")) + + # Replace the current media with a local audio + app.edit_inline_media(inline_message_id, InputMediaAudio("new_audio.mp3")) + """ + caption = media.caption + parse_mode = media.parse_mode + + if isinstance(media, InputMediaPhoto): + if media.media.startswith("http"): + media = types.InputMediaPhotoExternal( + url=media.media + ) + else: + media = utils.get_input_media_from_file_id(media.media, 2) + elif isinstance(media, InputMediaVideo): + if media.media.startswith("http"): + media = types.InputMediaDocumentExternal( + url=media.media + ) + else: + media = utils.get_input_media_from_file_id(media.media, 4) + elif isinstance(media, InputMediaAudio): + if media.media.startswith("http"): + media = types.InputMediaDocumentExternal( + url=media.media + ) + else: + media = utils.get_input_media_from_file_id(media.media, 9) + elif isinstance(media, InputMediaAnimation): + if media.media.startswith("http"): + media = types.InputMediaDocumentExternal( + url=media.media + ) + else: + media = utils.get_input_media_from_file_id(media.media, 10) + elif isinstance(media, InputMediaDocument): + if media.media.startswith("http"): + media = types.InputMediaDocumentExternal( + url=media.media + ) + else: + media = utils.get_input_media_from_file_id(media.media, 5) + + return self.send( + functions.messages.EditInlineBotMessage( + id=utils.unpack_inline_message_id(inline_message_id), + media=media, + reply_markup=reply_markup.write() if reply_markup else None, + **self.parser.parse(caption, parse_mode) + ) + ) diff --git a/pyrogram/client/methods/messages/edit_inline_reply_markup.py b/pyrogram/client/methods/messages/edit_inline_reply_markup.py new file mode 100644 index 00000000..aae64898 --- /dev/null +++ b/pyrogram/client/methods/messages/edit_inline_reply_markup.py @@ -0,0 +1,58 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram +from pyrogram.api import functions +from pyrogram.client.ext import BaseClient, utils + + +class EditInlineReplyMarkup(BaseClient): + def edit_inline_reply_markup( + self, + inline_message_id: str, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> bool: + """Edit only the reply markup of inline messages sent via the bot (for inline bots). + + Parameters: + inline_message_id (``str``): + Identifier of the inline message. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + from pyrogram import InlineKeyboardMarkup, InlineKeyboardButton + + # Bots only + app.edit_inline_reply_markup( + inline_message_id, + InlineKeyboardMarkup([[ + InlineKeyboardButton("New button", callback_data="new_data")]])) + """ + return self.send( + functions.messages.EditInlineBotMessage( + id=utils.unpack_inline_message_id(inline_message_id), + reply_markup=reply_markup.write() if reply_markup else None, + ) + ) diff --git a/pyrogram/client/methods/messages/edit_inline_text.py b/pyrogram/client/methods/messages/edit_inline_text.py new file mode 100644 index 00000000..c92e13a1 --- /dev/null +++ b/pyrogram/client/methods/messages/edit_inline_text.py @@ -0,0 +1,81 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +import pyrogram +from pyrogram.api import functions +from pyrogram.client.ext import BaseClient, utils + + +class EditInlineText(BaseClient): + def edit_inline_text( + self, + inline_message_id: str, + text: str, + parse_mode: Union[str, None] = object, + disable_web_page_preview: bool = None, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> bool: + """Edit the text of inline messages. + + Parameters: + inline_message_id (``str``): + Identifier of the inline message. + + text (``str``): + New text of the message. + + parse_mode (``str``, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. + + disable_web_page_preview (``bool``, *optional*): + Disables link previews for links in this message. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + # Bots only + + # Simple edit text + app.edit_inline_text(inline_message_id, "new text") + + # Take the same text message, remove the web page preview only + app.edit_inline_text( + inline_message_id, message.text, + disable_web_page_preview=True) + """ + + return self.send( + functions.messages.EditInlineBotMessage( + id=utils.unpack_inline_message_id(inline_message_id), + no_webpage=disable_web_page_preview or None, + reply_markup=reply_markup.write() if reply_markup else None, + **self.parser.parse(text, parse_mode) + ) + ) diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py index e9866573..eae59c62 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/client/methods/messages/edit_message_caption.py @@ -25,52 +25,48 @@ from pyrogram.client.ext import BaseClient class EditMessageCaption(BaseClient): def edit_message_caption( self, + chat_id: Union[int, str], + message_id: int, caption: str, - chat_id: Union[int, str] = None, - message_id: int = None, - inline_message_id: str = None, - parse_mode: str = "", + parse_mode: Union[str, None] = object, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "pyrogram.Message": - """Edit caption of media messages. + """Edit the caption of media messages. Parameters: - caption (``str``): - New caption of the media message. - - chat_id (``int`` | ``str``, *optional*): - Required if *inline_message_id* is not specified. + 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). - message_id (``int``, *optional*): - Required if *inline_message_id* is not specified. + message_id (``int``): Message identifier in the chat specified in chat_id. - inline_message_id (``str``, *optional*): - Required if *chat_id* and *message_id* are not specified. - Identifier of the inline message. + caption (``str``): + New caption of the media message. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: - :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is - returned, otherwise True is returned (message sent via the bot, as inline query result). + :obj:`Message`: On success, the edited message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.edit_message_caption(chat_id, message_id, "new media caption") """ return self.edit_message_text( - text=caption, chat_id=chat_id, message_id=message_id, - inline_message_id=inline_message_id, + text=caption, parse_mode=parse_mode, reply_markup=reply_markup ) diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 2b3ca5d5..f543af2b 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -32,48 +32,50 @@ from pyrogram.client.types.input_media import InputMedia class EditMessageMedia(BaseClient): def edit_message_media( self, + chat_id: Union[int, str], + message_id: int, media: InputMedia, - chat_id: Union[int, str] = None, - message_id: int = None, - inline_message_id: str = None, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "pyrogram.Message": """Edit animation, audio, document, photo or video messages. - If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise, - message type can be changed arbitrarily. When inline message is edited, new file can't be uploaded. - Use previously uploaded file via its file_id or specify a URL. + If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise, the + message type can be changed arbitrarily. Parameters: - media (:obj:`InputMedia`) - One of the InputMedia objects describing an animation, audio, document, photo or video. - - chat_id (``int`` | ``str``, *optional*): - Required if *inline_message_id* is not specified. + 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). - message_id (``int``, *optional*): - Required if *inline_message_id* is not specified. + message_id (``int``): Message identifier in the chat specified in chat_id. - inline_message_id (``str``, *optional*): - Required if *chat_id* and *message_id* are not specified. - Identifier of the inline message. + media (:obj:`InputMedia`): + One of the InputMedia objects describing an animation, audio, document, photo or video. reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: - :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is - returned, otherwise True is returned (message sent via the bot, as inline query result). + :obj:`Message`: On success, the edited message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from pyrogram import InputMediaPhoto, InputMediaVideo, InputMediaAudio + + # Replace the current media with a local photo + app.edit_message_media(chat_id, message_id, InputMediaPhoto("new_photo.jpg")) + + # Replace the current media with a local video + app.edit_message_media(chat_id, message_id, InputMediaVideo("new_video.mp4")) + + # Replace the current media with a local audio + app.edit_message_media(chat_id, message_id, InputMediaAudio("new_audio.mp3")) """ - style = self.html if media.parse_mode.lower() == "html" else self.markdown caption = media.caption + parse_mode = media.parse_mode if isinstance(media, InputMediaPhoto): if os.path.exists(media.media): @@ -242,23 +244,13 @@ class EditMessageMedia(BaseClient): else: media = utils.get_input_media_from_file_id(media.media, 5) - if inline_message_id is not None: - return self.send( - functions.messages.EditInlineBotMessage( - id=utils.unpack_inline_message_id(inline_message_id), - media=media, - reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) - ) - ) - r = self.send( functions.messages.EditMessage( peer=self.resolve_peer(chat_id), id=message_id, media=media, reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) diff --git a/pyrogram/client/methods/messages/edit_message_reply_markup.py b/pyrogram/client/methods/messages/edit_message_reply_markup.py index 516d7b9c..737fc23b 100644 --- a/pyrogram/client/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/client/methods/messages/edit_message_reply_markup.py @@ -20,52 +20,44 @@ from typing import Union import pyrogram from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram.client.ext import BaseClient class EditMessageReplyMarkup(BaseClient): def edit_message_reply_markup( self, + chat_id: Union[int, str], + message_id: int, reply_markup: "pyrogram.InlineKeyboardMarkup" = None, - chat_id: Union[int, str] = None, - message_id: int = None, - inline_message_id: str = None ) -> "pyrogram.Message": - """Edit only the reply markup of messages sent by the bot or via the bot (for inline bots). + """Edit only the reply markup of messages sent by the bot. Parameters: - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): - An InlineKeyboardMarkup object. - - chat_id (``int`` | ``str``, *optional*): - Required if *inline_message_id* is not specified. + 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). - message_id (``int``, *optional*): - Required if *inline_message_id* is not specified. + message_id (``int``): Message identifier in the chat specified in chat_id. - inline_message_id (``str``, *optional*): - Required if *chat_id* and *message_id* are not specified. - Identifier of the inline message. + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. Returns: - :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is - returned, otherwise True is returned (message sent via the bot, as inline query result). + :obj:`Message`: On success, the edited message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from pyrogram import InlineKeyboardMarkup, InlineKeyboardButton + + # Bots only + app.edit_message_reply_markup( + chat_id, message_id, + InlineKeyboardMarkup([[ + InlineKeyboardButton("New button", callback_data="new_data")]])) """ - if inline_message_id is not None: - return self.send( - functions.messages.EditInlineBotMessage( - id=utils.unpack_inline_message_id(inline_message_id), - reply_markup=reply_markup.write() if reply_markup else None, - ) - ) - r = self.send( functions.messages.EditMessage( peer=self.resolve_peer(chat_id), diff --git a/pyrogram/client/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py index 919e5dc1..31022c0e 100644 --- a/pyrogram/client/methods/messages/edit_message_text.py +++ b/pyrogram/client/methods/messages/edit_message_text.py @@ -20,43 +20,39 @@ from typing import Union import pyrogram from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram.client.ext import BaseClient class EditMessageText(BaseClient): def edit_message_text( self, + chat_id: Union[int, str], + message_id: int, text: str, - chat_id: Union[int, str] = None, - message_id: int = None, - inline_message_id: str = None, - parse_mode: str = "", + parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "pyrogram.Message": - """Edit text messages. + """Edit the text of messages. Parameters: - text (``str``): - New text of the message. - - chat_id (``int`` | ``str``, *optional*): - Required if *inline_message_id* is not specified. + 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). - message_id (``int``, *optional*): - Required if *inline_message_id* is not specified. + message_id (``int``): Message identifier in the chat specified in chat_id. - inline_message_id (``str``, *optional*): - Required if *chat_id* and *message_id* are not specified. - Identifier of the inline message. + text (``str``): + New text of the message. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. @@ -65,23 +61,19 @@ class EditMessageText(BaseClient): An InlineKeyboardMarkup object. Returns: - :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is - returned, otherwise True is returned (message sent via the bot, as inline query result). + :obj:`Message`: On success, the edited message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Simple edit text + app.edit_message_text(chat_id, message_id, "new text") + + # Take the same text message, remove the web page preview only + app.edit_message_text( + chat_id, message_id, message.text, + disable_web_page_preview=True) """ - style = self.html if parse_mode.lower() == "html" else self.markdown - - if inline_message_id is not None: - return self.send( - functions.messages.EditInlineBotMessage( - id=utils.unpack_inline_message_id(inline_message_id), - no_webpage=disable_web_page_preview or None, - reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(text) - ) - ) r = self.send( functions.messages.EditMessage( @@ -89,7 +81,7 @@ class EditMessageText(BaseClient): id=message_id, no_webpage=disable_web_page_preview or None, reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(text) + **self.parser.parse(text, parse_mode) ) ) diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py index c69df608..ba74e373 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -55,7 +55,8 @@ class ForwardMessages(BaseClient): Users will receive a notification with no sound. as_copy (``bool``, *optional*): - Pass True to forward messages without the forward header (i.e.: send a copy of the message content). + Pass True to forward messages without the forward header (i.e.: send a copy of the message content so + that it appears as originally sent by you). Defaults to False. remove_caption (``bool``, *optional*): @@ -68,8 +69,18 @@ class ForwardMessages(BaseClient): is returned, otherwise, in case *message_ids* was an iterable, the returned value will be a list of messages, even if such iterable contained just a single element. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + :emphasize-lines: 2,5,8 + + # Forward a single message + app.forward_messages("me", "pyrogram", 20) + + # Forward multiple messages at once + app.forward_messages("me", "pyrogram", [3, 20, 27]) + + # Forward messages as copy + app.forward_messages("me", "pyrogram", 20, as_copy=True) """ is_iterable = not isinstance(message_ids, int) diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index 8adafe22..e471c6fd 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -70,8 +70,17 @@ class GetHistory(BaseClient): Returns: List of :obj:`Message` - On success, a list of the retrieved messages is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Get the last 100 messages of a chat + app.get_history("pyrogramchat") + + # Get the last 3 messages of a chat + app.get_history("pyrogramchat", limit=3) + + # Get 3 messages after skipping the first 5 + app.get_history("pyrogramchat", offset=5, limit=3) """ offset_id = offset_id or (1 if reverse else 0) diff --git a/pyrogram/client/methods/messages/get_history_count.py b/pyrogram/client/methods/messages/get_history_count.py index 9f3e2637..8ceba0ed 100644 --- a/pyrogram/client/methods/messages/get_history_count.py +++ b/pyrogram/client/methods/messages/get_history_count.py @@ -45,8 +45,10 @@ class GetHistoryCount(BaseClient): Returns: ``int``: On success, the chat history count is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.get_history_count("pyrogramchat") """ r = self.send( diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index 0f901174..8f547227 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -28,6 +28,9 @@ from ...ext import BaseClient, utils log = logging.getLogger(__name__) +# TODO: Rewrite using a flag for replied messages and have message_ids non-optional + + class GetMessages(BaseClient): def get_messages( self, @@ -36,7 +39,8 @@ class GetMessages(BaseClient): reply_to_message_ids: Union[int, Iterable[int]] = None, replies: int = 1 ) -> Union["pyrogram.Message", List["pyrogram.Message"]]: - """Get one or more messages that belong to a specific chat. + """Get one or more messages from a chat by using message identifiers. + You can retrieve up to 200 messages at once. Parameters: @@ -64,8 +68,26 @@ class GetMessages(BaseClient): returned, otherwise, in case *message_ids* was an iterable, the returned value will be a list of messages, even if such iterable contained just a single element. + Example: + .. code-block:: python + + # Get one message + app.get_messages("pyrogramchat", 51110) + + # Get more than one message (list of messages) + app.get_messages("pyrogramchat", [44625, 51110]) + + # Get message by ignoring any replied-to message + app.get_messages(chat_id, message_id, replies=0) + + # Get message with all chained replied-to messages + app.get_messages(chat_id, message_id, replies=-1) + + # Get the replied-to message of a message + app.get_messages(chat_id, reply_to_message_ids=message_id) + Raises: - RPCError: In case of a Telegram RPC error. + ValueError: In case of invalid arguments. """ ids, ids_type = ( (message_ids, types.InputMessageID) if message_ids @@ -74,7 +96,7 @@ class GetMessages(BaseClient): ) if ids is None: - raise ValueError("No argument supplied") + raise ValueError("No argument supplied. Either pass message_ids or reply_to_message_ids") peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/messages/iter_history.py b/pyrogram/client/methods/messages/iter_history.py index 15c48c95..735ed162 100644 --- a/pyrogram/client/methods/messages/iter_history.py +++ b/pyrogram/client/methods/messages/iter_history.py @@ -64,8 +64,11 @@ class IterHistory(BaseClient): Returns: ``Generator``: A generator yielding :obj:`Message` objects. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + for message in app.iter_history("pyrogram"): + print(message.text) """ offset_id = offset_id or (1 if reverse else 0) current = 0 diff --git a/pyrogram/client/methods/messages/read_history.py b/pyrogram/client/methods/messages/read_history.py index f0278e91..f5dc8630 100644 --- a/pyrogram/client/methods/messages/read_history.py +++ b/pyrogram/client/methods/messages/read_history.py @@ -43,8 +43,14 @@ class ReadHistory(BaseClient): Returns: ``bool`` - On success, True is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Mark the whole chat as read + app.read_history("pyrogramlounge") + + # Mark messages as read only up to the given message id + app.read_history("pyrogramlounge", 123456) """ peer = self.resolve_peer(chat_id) diff --git a/pyrogram/client/methods/messages/retract_vote.py b/pyrogram/client/methods/messages/retract_vote.py index b52181a6..a273ad7b 100644 --- a/pyrogram/client/methods/messages/retract_vote.py +++ b/pyrogram/client/methods/messages/retract_vote.py @@ -43,8 +43,10 @@ class RetractVote(BaseClient): Returns: :obj:`Poll`: On success, the poll with the retracted vote is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.retract_vote(chat_id, message_id) """ r = self.send( functions.messages.SendVote( diff --git a/pyrogram/client/methods/messages/send_animated_sticker.py b/pyrogram/client/methods/messages/send_animated_sticker.py deleted file mode 100644 index 6fd0c647..00000000 --- a/pyrogram/client/methods/messages/send_animated_sticker.py +++ /dev/null @@ -1,141 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2019 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -import os -from typing import Union - -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils -from pyrogram.errors import FilePartMissing - - -class SendAnimatedSticker(BaseClient): - def send_animated_sticker( - self, - chat_id: Union[int, str], - animated_sticker: str, - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" - ] = None, - progress: callable = None, - progress_args: tuple = () - ) -> Union["pyrogram.Message", None]: - """Send .tgs animated stickers. - - 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). - - animated_sticker (``str``): - Animated sticker to send. - Pass a file_id as string to send a animated sticker that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get a .webp animated sticker file from the Internet, or - pass a file path as string to upload a new animated sticker that exists on your local machine. - - disable_notification (``bool``, *optional*): - Sends the message silently. - Users will receive a notification with no sound. - - reply_to_message_id (``int``, *optional*): - If the message is a reply, ID of the original message. - - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): - Additional interface options. An object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - - progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). - - progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. - - Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - - current (``int``): - The amount of bytes uploaded so far. - - total (``int``): - The size of the file. - - *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. - - Returns: - :obj:`Message` | ``None``: On success, the sent animated sticker message is returned, otherwise, in case the - upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. - """ - file = None - - try: - if os.path.exists(animated_sticker): - file = self.save_file(animated_sticker, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(animated_sticker) or "application/x-tgsticker", - file=file, - attributes=[ - types.DocumentAttributeFilename(file_name=os.path.basename(animated_sticker)) - ] - ) - elif animated_sticker.startswith("http"): - media = types.InputMediaDocumentExternal( - url=animated_sticker - ) - else: - media = utils.get_input_media_from_file_id(animated_sticker, 5) - - while True: - try: - r = self.send( - functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), - media=media, - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id, - random_id=self.rnd_id(), - reply_markup=reply_markup.write() if reply_markup else None, - message="" - ) - ) - except FilePartMissing as e: - self.save_file(animated_sticker, file_id=file.id, file_part=e.x) - else: - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return pyrogram.Message._parse( - self, i.message, - {i.id: i for i in r.users}, - {i.id: i for i in r.chats} - ) - except BaseClient.StopTransmission: - return None diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 0c4649dd..5d345010 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -32,7 +32,7 @@ class SendAnimation(BaseClient): animation: str, caption: str = "", unsave: bool = False, - parse_mode: str = "", + parse_mode: Union[str, None] = object, duration: int = 0, width: int = 0, height: int = 0, @@ -66,12 +66,15 @@ class SendAnimation(BaseClient): Animation caption, 0-1024 characters. unsave (``bool``, *optional*): - By default, the server will save into your own collection any new animation GIF you send. + By default, the server will save into your own collection any new animation you send. Pass True to automatically unsave the sent animation. Defaults to False. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of sent animation in seconds. @@ -100,23 +103,22 @@ class SendAnimation(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -126,11 +128,25 @@ class SendAnimation(BaseClient): :obj:`Message` | ``None``: On success, the sent animation message is returned, otherwise, in case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Send animation by uploading from local file + app.send_animation("me", "animation.gif") + + # Add caption to the animation + app.send_animation("me", "animation.gif", caption="cat") + + # Unsave the animation once is sent + app.send_animation("me", "animation.gif", unsave=True) + + # Keep track of the progress while uploading + def progress(current, total): + print("{:.1f}%".format(current * 100 / total)) + + app.send_animation("me", "animation.gif", progress=progress) """ file = None - style = self.html if parse_mode.lower() == "html" else self.markdown try: if os.path.exists(animation): @@ -168,7 +184,7 @@ class SendAnimation(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 7b218a66..7395718b 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -31,7 +31,7 @@ class SendAudio(BaseClient): chat_id: Union[int, str], audio: str, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, duration: int = 0, performer: str = None, title: str = None, @@ -67,8 +67,11 @@ class SendAudio(BaseClient): Audio caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of the audio in seconds. @@ -97,23 +100,22 @@ class SendAudio(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -123,11 +125,28 @@ class SendAudio(BaseClient): :obj:`Message` | ``None``: On success, the sent audio message is returned, otherwise, in case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + :emphasize-lines: 2,5,8-10,13-16 + + # Send audio file by uploading from file + app.send_audio("me", "audio.mp3") + + # Add caption to the audio + app.send_audio("me", "audio.mp3", caption="shoegaze") + + # Set audio metadata + app.send_audio( + "me", "audio.mp3", + title="Printemps émeraude", performer="Alcest", duration=440) + + # Keep track of the progress while uploading + def progress(current, total): + print("{:.1f}%".format(current * 100 / total)) + + app.send_audio("me", "audio.mp3", progress=progress) """ file = None - style = self.html if parse_mode.lower() == "html" else self.markdown try: if os.path.exists(audio): @@ -163,7 +182,7 @@ class SendAudio(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/client/methods/messages/send_cached_media.py index 99961ca1..9b4fbafa 100644 --- a/pyrogram/client/methods/messages/send_cached_media.py +++ b/pyrogram/client/methods/messages/send_cached_media.py @@ -29,7 +29,7 @@ class SendCachedMedia(BaseClient): chat_id: Union[int, str], file_id: str, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ @@ -59,8 +59,11 @@ class SendCachedMedia(BaseClient): Media caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_notification (``bool``, *optional*): Sends the message silently. @@ -76,10 +79,11 @@ class SendCachedMedia(BaseClient): Returns: :obj:`Message`: On success, the sent media message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.send_cached_media("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") """ - style = self.html if parse_mode.lower() == "html" else self.markdown r = self.send( functions.messages.SendMedia( @@ -89,7 +93,7 @@ class SendCachedMedia(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) diff --git a/pyrogram/client/methods/messages/send_chat_action.py b/pyrogram/client/methods/messages/send_chat_action.py index da974c97..7488fb16 100644 --- a/pyrogram/client/methods/messages/send_chat_action.py +++ b/pyrogram/client/methods/messages/send_chat_action.py @@ -64,8 +64,22 @@ class SendChatAction(BaseClient): ``bool``: On success, True is returned. Raises: - RPCError: In case of a Telegram RPC error. - ValueError: In case the provided string is not a valid ChatAction. + ValueError: In case the provided string is not a valid chat action. + + Example: + .. code-block:: python + + # Send "typing" chat action + app.send_chat_action(chat_id, "typing") + + # Send "upload_video" chat action + app.send_chat_action(chat_id, "upload_video") + + # Send "playing" chat action + app.send_chat_action(chat_id, "playing") + + # Cancel any current chat action + app.send_chat_action(chat_id, "cancel") """ try: diff --git a/pyrogram/client/methods/messages/send_contact.py b/pyrogram/client/methods/messages/send_contact.py index d0b6fb58..c32ca25d 100644 --- a/pyrogram/client/methods/messages/send_contact.py +++ b/pyrogram/client/methods/messages/send_contact.py @@ -74,8 +74,10 @@ class SendContact(BaseClient): Returns: :obj:`Message`: On success, the sent contact message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.send_contact("me", "+39 123 456 7890", "Dan") """ r = self.send( functions.messages.SendMedia( diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index da012b2c..567bc561 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -32,7 +32,7 @@ class SendDocument(BaseClient): document: str, thumb: str = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ @@ -68,8 +68,11 @@ class SendDocument(BaseClient): Document caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_notification (``bool``, *optional*): Sends the message silently. @@ -83,23 +86,22 @@ class SendDocument(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -109,11 +111,22 @@ class SendDocument(BaseClient): :obj:`Message` | ``None``: On success, the sent document message is returned, otherwise, in case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Send document by uploading from local file + app.send_document("me", "document.zip") + + # Add caption to the document file + app.send_document("me", "document.zip", caption="archive") + + # Keep track of the progress while uploading + def progress(current, total): + print("{:.1f}%".format(current * 100 / total)) + + app.send_document("me", "document.zip", progress=progress) """ file = None - style = self.html if parse_mode.lower() == "html" else self.markdown try: if os.path.exists(document): @@ -144,7 +157,7 @@ class SendDocument(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/send_location.py b/pyrogram/client/methods/messages/send_location.py index 2e3681e6..245f61f2 100644 --- a/pyrogram/client/methods/messages/send_location.py +++ b/pyrogram/client/methods/messages/send_location.py @@ -66,8 +66,10 @@ class SendLocation(BaseClient): Returns: :obj:`Message`: On success, the sent location message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.send_location("me", 51.500729, -0.124583) """ r = self.send( functions.messages.SendMedia( diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 194a2202..ac38c0d6 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -31,7 +31,6 @@ log = logging.getLogger(__name__) class SendMediaGroup(BaseClient): # TODO: Add progress parameter - # TODO: Figure out how to send albums using URLs def send_media_group( self, chat_id: Union[int, str], @@ -60,14 +59,23 @@ class SendMediaGroup(BaseClient): Returns: List of :obj:`Message`: On success, a list of the sent messages is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + from pyrogram import InputMediaPhoto, InputMediaVideo + + app.send_media_group( + "me", + [ + InputMediaPhoto("photo1.jpg"), + InputMediaPhoto("photo2.jpg", caption="photo caption"), + InputMediaVideo("video.mp4", caption="a video") + ] + ) """ multi_media = [] for i in media: - style = self.html if i.parse_mode.lower() == "html" else self.markdown - if isinstance(i, pyrogram.InputMediaPhoto): if os.path.exists(i.media): while True: @@ -90,7 +98,24 @@ class SendMediaGroup(BaseClient): id=types.InputPhoto( id=media.photo.id, access_hash=media.photo.access_hash, - file_reference=b"" + file_reference=media.photo.file_reference + ) + ) + elif i.media.startswith("http"): + media = self.send( + functions.messages.UploadMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaPhotoExternal( + url=i.media + ) + ) + ) + + media = types.InputMediaPhoto( + id=types.InputPhoto( + id=media.photo.id, + access_hash=media.photo.access_hash, + file_reference=media.photo.file_reference ) ) else: @@ -128,7 +153,24 @@ class SendMediaGroup(BaseClient): id=types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=b"" + file_reference=media.document.file_reference + ) + ) + elif i.media.startswith("http"): + media = self.send( + functions.messages.UploadMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaDocumentExternal( + url=i.media + ) + ) + ) + + media = types.InputMediaDocument( + id=types.InputDocument( + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference ) ) else: @@ -138,7 +180,7 @@ class SendMediaGroup(BaseClient): types.InputSingleMedia( media=media, random_id=self.rnd_id(), - **style.parse(i.caption) + **self.parser.parse(i.caption, i.parse_mode) ) ) diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index f8caa081..c15b3a84 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -28,7 +28,7 @@ class SendMessage(BaseClient): self, chat_id: Union[int, str], text: str, - parse_mode: str = "", + parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -51,8 +51,11 @@ class SendMessage(BaseClient): Text of the message to be sent. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. @@ -71,11 +74,45 @@ class SendMessage(BaseClient): Returns: :obj:`Message`: On success, the sent text message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + :emphasize-lines: 2,5,8,11,21-23,26-33 + + # Simple example + app.send_message("haskell", "Thanks for creating **Pyrogram**!") + + # Disable web page previews + app.send_message("me", "https://docs.pyrogram.org", disable_web_page_preview=True) + + # Reply to a message using its id + app.send_message("me", "this is a reply", reply_to_message_id=12345) + + # Force HTML-only styles for this request only + app.send_message("me", "**not bold**, italic", parse_mode="html") + + ## + # For bots only, send messages with keyboards attached + ## + + from pyrogram import ( + ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton) + + # Send a normal keyboard + app.send_message( + chat_id, "Look at that button!", + reply_markup=ReplyKeyboardMarkup([["Nice!"]])) + + # Send an inline keyboard + app.send_message( + chat_id, "These are inline buttons", + reply_markup=InlineKeyboardMarkup( + [ + [InlineKeyboardButton("Data", callback_data="hidden_callback_data")], + [InlineKeyboardButton("Docs", url="https://docs.pyrogram.org")] + ])) """ - style = self.html if parse_mode.lower() == "html" else self.markdown - message, entities = style.parse(text).values() + + message, entities = self.parser.parse(text, parse_mode).values() r = self.send( functions.messages.SendMessage( diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index c1fd33d8..0c82ebfc 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -31,7 +31,7 @@ class SendPhoto(BaseClient): chat_id: Union[int, str], photo: str, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, ttl_seconds: int = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -62,8 +62,11 @@ class SendPhoto(BaseClient): Photo caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. ttl_seconds (``int``, *optional*): Self-Destruct Timer. @@ -82,23 +85,22 @@ class SendPhoto(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -108,11 +110,22 @@ class SendPhoto(BaseClient): :obj:`Message` | ``None``: On success, the sent photo message is returned, otherwise, in case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Send photo by uploading from local file + app.send_photo("me", "photo.jpg") + + # Send photo by uploading from URL + app.send_photo("me", "https://i.imgur.com/BQBTP7d.png") + + # Add caption to a photo + app.send_photo("me", "photo.jpg", caption="Holidays!") + + # Send self-destructing photo + app.send_photo("me", "photo.jpg", ttl_seconds=10) """ file = None - style = self.html if parse_mode.lower() == "html" else self.markdown try: if os.path.exists(photo): @@ -139,7 +152,7 @@ class SendPhoto(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py index 4dae53b2..2fa008ab 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/client/methods/messages/send_poll.py @@ -66,8 +66,10 @@ class SendPoll(BaseClient): Returns: :obj:`Message`: On success, the sent poll message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.send_poll(chat_id, "Is this a poll question?", ["Yes", "No", "Maybe"]) """ r = self.send( functions.messages.SendMedia( diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 4f7a99ff..ae5e8551 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -41,7 +41,7 @@ class SendSticker(BaseClient): progress: callable = None, progress_args: tuple = () ) -> Union["pyrogram.Message", None]: - """Send .webp stickers. + """Send static .webp or animated .tgs stickers. Parameters: chat_id (``int`` | ``str``): @@ -67,23 +67,22 @@ class SendSticker(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -92,8 +91,15 @@ class SendSticker(BaseClient): Returns: :obj:`Message` | ``None``: On success, the sent sticker message is returned, otherwise, in case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + + Example: + .. code-block:: python + + # Send sticker by uploading from local file + app.send_sticker("me", "sticker.webp") + + # Send sticker using file_id + app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") """ file = None diff --git a/pyrogram/client/methods/messages/send_venue.py b/pyrogram/client/methods/messages/send_venue.py index 35545c9b..ab630936 100644 --- a/pyrogram/client/methods/messages/send_venue.py +++ b/pyrogram/client/methods/messages/send_venue.py @@ -83,8 +83,12 @@ class SendVenue(BaseClient): Returns: :obj:`Message`: On success, the sent venue message is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.send_venue( + "me", 51.500729, -0.124583, + "Elizabeth Tower", "Westminster, London SW1A 0AA, UK") """ r = self.send( functions.messages.SendMedia( diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 4e1201fc..ca6f0519 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -31,7 +31,7 @@ class SendVideo(BaseClient): chat_id: Union[int, str], video: str, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, duration: int = 0, width: int = 0, height: int = 0, @@ -66,8 +66,11 @@ class SendVideo(BaseClient): Video caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of sent video in seconds. @@ -86,6 +89,7 @@ class SendVideo(BaseClient): supports_streaming (``bool``, *optional*): Pass True, if the uploaded video is suitable for streaming. + Defaults to True. disable_notification (``bool``, *optional*): Sends the message silently. @@ -99,23 +103,22 @@ class SendVideo(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -125,11 +128,22 @@ class SendVideo(BaseClient): :obj:`Message` | ``None``: On success, the sent video message is returned, otherwise, in case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Send video by uploading from local file + app.send_video("me", "video.mp4") + + # Add caption to the video + app.send_video("me", "video.mp4", caption="recording") + + # Keep track of the progress while uploading + def progress(current, total): + print("{:.1f}%".format(current * 100 / total)) + + app.send_video("me", "video.mp4", progress=progress) """ file = None - style = self.html if parse_mode.lower() == "html" else self.markdown try: if os.path.exists(video): @@ -166,7 +180,7 @@ class SendVideo(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index 7bb8803b..65988b36 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -82,23 +82,22 @@ class SendVideoNote(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -106,10 +105,16 @@ class SendVideoNote(BaseClient): Returns: :obj:`Message` | ``None``: On success, the sent video note message is returned, otherwise, in case the - pload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. + upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Send video note by uploading from local file + app.send_video_note("me", "video_note.mp4") + + # Set video note length + app.send_video_note("me", "video_note.mp4", length=25) """ file = None diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 9dace1e0..8d9f6c5f 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -31,7 +31,7 @@ class SendVoice(BaseClient): chat_id: Union[int, str], voice: str, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, duration: int = 0, disable_notification: bool = None, reply_to_message_id: int = None, @@ -62,8 +62,11 @@ class SendVoice(BaseClient): Voice message caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of the voice message in seconds. @@ -80,23 +83,22 @@ class SendVoice(BaseClient): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -106,11 +108,19 @@ class SendVoice(BaseClient): :obj:`Message` | ``None``: On success, the sent voice message is returned, otherwise, in case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Send voice note by uploading from local file + app.send_voice("me", "voice.ogg") + + # Add caption to the voice note + app.send_voice("me", "voice.ogg", caption="voice note") + + # Set voice note duration + app.send_voice("me", "voice.ogg", duration=20) """ file = None - style = self.html if parse_mode.lower() == "html" else self.markdown try: if os.path.exists(voice): @@ -142,7 +152,7 @@ class SendVoice(BaseClient): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), reply_markup=reply_markup.write() if reply_markup else None, - **style.parse(caption) + **self.parser.parse(caption, parse_mode) ) ) except FilePartMissing as e: diff --git a/pyrogram/client/methods/messages/stop_poll.py b/pyrogram/client/methods/messages/stop_poll.py index 6abe6791..308bf587 100644 --- a/pyrogram/client/methods/messages/stop_poll.py +++ b/pyrogram/client/methods/messages/stop_poll.py @@ -49,8 +49,10 @@ class StopPoll(BaseClient): Returns: :obj:`Poll`: On success, the stopped poll with the final results is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.stop_poll(chat_id, message_id) """ poll = self.get_messages(chat_id, message_id).poll diff --git a/pyrogram/client/methods/messages/vote_poll.py b/pyrogram/client/methods/messages/vote_poll.py index a5d77d86..7c976cd8 100644 --- a/pyrogram/client/methods/messages/vote_poll.py +++ b/pyrogram/client/methods/messages/vote_poll.py @@ -47,8 +47,10 @@ class VotePoll(BaseClient): Returns: :obj:`Poll` - On success, the poll with the chosen option is returned. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.vote_poll(chat_id, message_id, 6) """ poll = self.get_messages(chat_id, message_id).poll diff --git a/pyrogram/client/methods/password/change_cloud_password.py b/pyrogram/client/methods/password/change_cloud_password.py index a33b83c7..67e1254f 100644 --- a/pyrogram/client/methods/password/change_cloud_password.py +++ b/pyrogram/client/methods/password/change_cloud_password.py @@ -46,8 +46,16 @@ class ChangeCloudPassword(BaseClient): ``bool``: True on success. Raises: - RPCError: In case of a Telegram RPC error. ValueError: In case there is no cloud password to change. + + Example: + .. code-block:: python + + # Change password only + app.change_cloud_password("current_password", "new_password") + + # Change password and hint + app.change_cloud_password("current_password", "new_password", new_hint="hint") """ r = self.send(functions.account.GetPassword()) diff --git a/pyrogram/client/methods/password/enable_cloud_password.py b/pyrogram/client/methods/password/enable_cloud_password.py index 23ee1608..19683ffc 100644 --- a/pyrogram/client/methods/password/enable_cloud_password.py +++ b/pyrogram/client/methods/password/enable_cloud_password.py @@ -48,8 +48,19 @@ class EnableCloudPassword(BaseClient): ``bool``: True on success. Raises: - RPCError: In case of a Telegram RPC error. ValueError: In case there is already a cloud password enabled. + + Example: + .. code-block:: python + + # Enable password without hint and email + app.enable_cloud_password("password") + + # Enable password with hint and without email + app.enable_cloud_password("password", hint="hint") + + # Enable password with hint and email + app.enable_cloud_password("password", hint="hint", email="user@email.com") """ r = self.send(functions.account.GetPassword()) diff --git a/pyrogram/client/methods/password/remove_cloud_password.py b/pyrogram/client/methods/password/remove_cloud_password.py index 9dcbb005..6b68bd5e 100644 --- a/pyrogram/client/methods/password/remove_cloud_password.py +++ b/pyrogram/client/methods/password/remove_cloud_password.py @@ -36,8 +36,12 @@ class RemoveCloudPassword(BaseClient): ``bool``: True on success. Raises: - RPCError: In case of a Telegram RPC error. ValueError: In case there is no cloud password to remove. + + Example: + .. code-block:: python + + app.remove_cloud_password("password") """ r = self.send(functions.account.GetPassword()) diff --git a/pyrogram/client/methods/users/__init__.py b/pyrogram/client/methods/users/__init__.py index f30245d7..775ccbb2 100644 --- a/pyrogram/client/methods/users/__init__.py +++ b/pyrogram/client/methods/users/__init__.py @@ -21,7 +21,6 @@ from .delete_profile_photos import DeleteProfilePhotos from .get_me import GetMe from .get_profile_photos import GetProfilePhotos from .get_profile_photos_count import GetProfilePhotosCount -from .get_user_dc import GetUserDC from .get_users import GetUsers from .iter_profile_photos import IterProfilePhotos from .set_profile_photo import SetProfilePhoto @@ -38,7 +37,6 @@ class Users( GetMe, UpdateUsername, GetProfilePhotosCount, - GetUserDC, IterProfilePhotos, UnblockUser ): diff --git a/pyrogram/client/methods/users/block_user.py b/pyrogram/client/methods/users/block_user.py index 1b4cc31a..ff29089c 100644 --- a/pyrogram/client/methods/users/block_user.py +++ b/pyrogram/client/methods/users/block_user.py @@ -18,8 +18,7 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types +from pyrogram.api import functions from ...ext import BaseClient @@ -30,15 +29,23 @@ class BlockUser(BaseClient): ) -> bool: """Block a user. + Parameters: + user_id (``int`` | ``str``):: + Unique identifier (int) or username (str) of the target user. + For you yourself 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: ``bool``: True on success - Raises: - RPCError: In case of Telegram RPC Error. + Example: + .. code-block:: python + + app.block_user(user_id) """ return bool( self.send( - functions.contact.Block( + functions.contacts.Block( id=self.resolve_peer(user_id) ) ) diff --git a/pyrogram/client/methods/users/delete_profile_photos.py b/pyrogram/client/methods/users/delete_profile_photos.py index 1b46382c..5c3b26e8 100644 --- a/pyrogram/client/methods/users/delete_profile_photos.py +++ b/pyrogram/client/methods/users/delete_profile_photos.py @@ -16,42 +16,51 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from base64 import b64decode from struct import unpack from typing import List, Union from pyrogram.api import functions, types +from pyrogram.client.ext import utils from ...ext import BaseClient class DeleteProfilePhotos(BaseClient): def delete_profile_photos( self, - id: Union[str, List[str]] + photo_ids: Union[str, List[str]] ) -> bool: """Delete your own profile photos. Parameters: - id (``str`` | ``list``): + photo_ids (``str`` | List of ``str``): A single :obj:`Photo` id as string or multiple ids as list of strings for deleting more than one photos at once. Returns: ``bool``: True on success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Get the photos to be deleted + photos = app.get_profile_photos("me") + + # Delete one photo + app.delete_profile_photos(photos[0].file_id) + + # Delete the rest of the photos + app.delete_profile_photos([p.file_id for p in photos[1:]]) """ - id = id if isinstance(id, list) else [id] + photo_ids = photo_ids if isinstance(photo_ids, list) else [photo_ids] input_photos = [] - for i in id: - s = unpack(" -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from typing import Union - -from pyrogram.api import functions, types -from ...ext import BaseClient - - -class GetUserDC(BaseClient): - def get_user_dc(self, user_id: Union[int, str]) -> Union[int, None]: - """Get the assigned DC (data center) of a user. - - .. note:: - - This information is approximate: it is based on where Telegram stores a user profile pictures and does not - by any means tell you the user location (i.e. a user might travel far away, but will still connect to its - assigned DC). More info at `FAQs <../faq#what-are-the-ip-addresses-of-telegram-data-centers>`_. - - Parameters: - user_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - - Returns: - ``int`` | ``None``: The DC identifier as integer, or None in case it wasn't possible to get it (i.e. the - user has no profile picture or has the privacy setting enabled). - - Raises: - RPCError: In case of a Telegram RPC error. - """ - - r = self.send(functions.users.GetUsers(id=[self.resolve_peer(user_id)])) - - if r: - r = r[0] - - if r.photo: - if isinstance(r.photo, types.UserProfilePhoto): - return r.photo.dc_id - - return None diff --git a/pyrogram/client/methods/users/get_users.py b/pyrogram/client/methods/users/get_users.py index f76e6802..67e58615 100644 --- a/pyrogram/client/methods/users/get_users.py +++ b/pyrogram/client/methods/users/get_users.py @@ -24,7 +24,6 @@ from ...ext import BaseClient class GetUsers(BaseClient): - # TODO: Add Users type and use that def get_users( self, user_ids: Union[Iterable[Union[int, str]], int, str] @@ -43,8 +42,14 @@ class GetUsers(BaseClient): returned, otherwise, in case *user_ids* was an iterable a list of users is returned, even if the iterable contained one item only. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + # Get information about one user + app.get_users("haskell") + + # Get information about multiple users at once + app.get_users([user1, user2, user3]) """ is_iterable = not isinstance(user_ids, (int, str)) user_ids = list(user_ids) if is_iterable else [user_ids] diff --git a/pyrogram/client/methods/users/iter_profile_photos.py b/pyrogram/client/methods/users/iter_profile_photos.py index 49317f87..f812a856 100644 --- a/pyrogram/client/methods/users/iter_profile_photos.py +++ b/pyrogram/client/methods/users/iter_profile_photos.py @@ -51,8 +51,11 @@ class IterProfilePhotos(BaseClient): Returns: ``Generator``: A generator yielding :obj:`Photo` objects. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + for photo in app.iter_profile_photos("haskell"): + print(photo.file_id) """ current = 0 total = limit or (1 << 31) diff --git a/pyrogram/client/methods/users/set_profile_photo.py b/pyrogram/client/methods/users/set_profile_photo.py index a713fd34..975a2ced 100644 --- a/pyrogram/client/methods/users/set_profile_photo.py +++ b/pyrogram/client/methods/users/set_profile_photo.py @@ -38,8 +38,10 @@ class SetProfilePhoto(BaseClient): Returns: ``bool``: True on success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.set_profile_photo("new_photo.jpg") """ return bool( diff --git a/pyrogram/client/methods/users/unblock_user.py b/pyrogram/client/methods/users/unblock_user.py index b2212762..e42fbd24 100644 --- a/pyrogram/client/methods/users/unblock_user.py +++ b/pyrogram/client/methods/users/unblock_user.py @@ -18,8 +18,7 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types +from pyrogram.api import functions from ...ext import BaseClient @@ -30,15 +29,23 @@ class UnblockUser(BaseClient): ) -> bool: """Unblock a user. + Parameters: + user_id (``int`` | ``str``):: + Unique identifier (int) or username (str) of the target user. + For you yourself 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: ``bool``: True on success - Raises: - RPCError: In case of Telegram RPC Error. + Example: + .. code-block:: python + + app.unblock_user(user_id) """ return bool( self.send( - functions.contact.Unblock( + functions.contacts.Unblock( id=self.resolve_peer(user_id) ) ) diff --git a/pyrogram/client/methods/users/update_username.py b/pyrogram/client/methods/users/update_username.py index 002dbf75..07bd62bb 100644 --- a/pyrogram/client/methods/users/update_username.py +++ b/pyrogram/client/methods/users/update_username.py @@ -40,8 +40,10 @@ class UpdateUsername(BaseClient): Returns: ``bool``: True on success. - Raises: - RPCError: In case of a Telegram RPC error. + Example: + .. code-block:: python + + app.update_username("new_username") """ return bool( diff --git a/pyrogram/client/style/__init__.py b/pyrogram/client/parser/__init__.py similarity index 93% rename from pyrogram/client/style/__init__.py rename to pyrogram/client/parser/__init__.py index 768cee7b..53806619 100644 --- a/pyrogram/client/style/__init__.py +++ b/pyrogram/client/parser/__init__.py @@ -16,5 +16,4 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .html import HTML -from .markdown import Markdown +from .parser import Parser diff --git a/pyrogram/client/parser/html.py b/pyrogram/client/parser/html.py new file mode 100644 index 00000000..82499cb3 --- /dev/null +++ b/pyrogram/client/parser/html.py @@ -0,0 +1,184 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import html +import logging +import re +from collections import OrderedDict +from html.parser import HTMLParser +from typing import Union + +import pyrogram +from pyrogram.api import types +from pyrogram.errors import PeerIdInvalid +from . import utils + +log = logging.getLogger(__name__) + + +class Parser(HTMLParser): + MENTION_RE = re.compile(r"tg://user\?id=(\d+)") + + def __init__(self, client: "pyrogram.BaseClient"): + super().__init__() + + self.client = client + + self.text = "" + self.entities = [] + self.tag_entities = {} + + def handle_starttag(self, tag, attrs): + attrs = dict(attrs) + extra = {} + + if tag in ["b", "strong"]: + entity = types.MessageEntityBold + elif tag in ["i", "em"]: + entity = types.MessageEntityItalic + elif tag == "u": + entity = types.MessageEntityUnderline + elif tag in ["s", "del", "strike"]: + entity = types.MessageEntityStrike + elif tag == "blockquote": + entity = types.MessageEntityBlockquote + elif tag == "code": + entity = types.MessageEntityCode + elif tag == "pre": + entity = types.MessageEntityPre + extra["language"] = "" + elif tag == "a": + url = attrs.get("href", "") + + mention = Parser.MENTION_RE.match(url) + + if mention: + entity = types.InputMessageEntityMentionName + extra["user_id"] = int(mention.group(1)) + else: + entity = types.MessageEntityTextUrl + extra["url"] = url + else: + return + + if tag not in self.tag_entities: + self.tag_entities[tag] = [] + + self.tag_entities[tag].append(entity(offset=len(self.text), length=0, **extra)) + + def handle_data(self, data): + data = html.unescape(data) + + for entities in self.tag_entities.values(): + for entity in entities: + entity.offset += len(data) - len(data.lstrip()) # Ignore left whitespaces for offsets + entity.length += len(data.strip()) # Ignore all whitespaces (left + right) for lengths + + self.text += data + + def handle_endtag(self, tag): + try: + self.entities.append(self.tag_entities[tag].pop()) + except (KeyError, IndexError): + line, offset = self.getpos() + offset += 1 + + log.warning("Unmatched closing tag at line {}:{}".format(tag, line, offset)) + else: + if not self.tag_entities[tag]: + self.tag_entities.pop(tag) + + def error(self, message): + pass + + +class HTML: + def __init__(self, client: Union["pyrogram.BaseClient", None]): + self.client = client + + def parse(self, text: str): + text = utils.add_surrogates(text) + + parser = Parser(self.client) + parser.feed(text) + parser.close() + + if parser.tag_entities: + unclosed_tags = [] + + for tag, entities in parser.tag_entities.items(): + unclosed_tags.append("<{}> (x{})".format(tag, len(entities))) + + log.warning("Unclosed tags: {}".format(", ".join(unclosed_tags))) + + entities = [] + + for entity in parser.entities: + if isinstance(entity, types.InputMessageEntityMentionName): + try: + if self.client is not None: + entity.user_id = self.client.resolve_peer(entity.user_id) + except PeerIdInvalid: + continue + + entities.append(entity) + + # TODO: OrderedDict to be removed in Python 3.6 + return OrderedDict([ + ("message", utils.remove_surrogates(parser.text)), + ("entities", sorted(entities, key=lambda e: e.offset)) + ]) + + @staticmethod + def unparse(text: str, entities: list): + text = utils.add_surrogates(text) + + entities_offsets = [] + + for entity in entities: + entity_type = entity.type + start = entity.offset + end = start + entity.length + + if entity_type in ("bold", "italic", "underline", "strike"): + start_tag = "<{}>".format(entity_type[0]) + end_tag = "".format(entity_type[0]) + elif entity_type in ("code", "pre", "blockquote"): + start_tag = "<{}>".format(entity_type) + end_tag = "".format(entity_type) + elif entity_type == "text_link": + url = entity.url + start_tag = ''.format(url) + end_tag = "" + elif entity_type == "text_mention": + user = entity.user + start_tag = ''.format(user.id) + end_tag = "" + else: + continue + + entities_offsets.append((start_tag, start,)) + entities_offsets.append((end_tag, end,)) + + # sorting by offset (desc) + entities_offsets.sort(key=lambda x: -x[1]) + + for entity, offset in entities_offsets: + text = text[:offset] + entity + text[offset:] + + return utils.remove_surrogates(text) diff --git a/pyrogram/client/parser/markdown.py b/pyrogram/client/parser/markdown.py new file mode 100644 index 00000000..1319f6df --- /dev/null +++ b/pyrogram/client/parser/markdown.py @@ -0,0 +1,150 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import html +import re +from typing import Union + +import pyrogram +from . import utils +from .html import HTML + +BOLD_DELIM = "**" +ITALIC_DELIM = "__" +UNDERLINE_DELIM = "--" +STRIKE_DELIM = "~~" +CODE_DELIM = "`" +PRE_DELIM = "```" + +MARKDOWN_RE = re.compile(r"({d})|\[(.+?)\]\((.+?)\)".format( + d="|".join( + ["".join(i) for i in [ + [r"\{}".format(j) for j in i] + for i in [ + PRE_DELIM, + CODE_DELIM, + STRIKE_DELIM, + UNDERLINE_DELIM, + ITALIC_DELIM, + BOLD_DELIM + ] + ]] + ))) + +OPENING_TAG = "<{}>" +CLOSING_TAG = "" +URL_MARKUP = '{}' +FIXED_WIDTH_DELIMS = [CODE_DELIM, PRE_DELIM] + + +class Markdown: + def __init__(self, client: Union["pyrogram.BaseClient", None]): + self.html = HTML(client) + + def parse(self, text: str, strict: bool = False): + if strict: + text = html.escape(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() + full = match.group(0) + + if delim in FIXED_WIDTH_DELIMS: + is_fixed_width = not is_fixed_width + + if is_fixed_width and delim not in FIXED_WIDTH_DELIMS: + continue + + if text_url: + text = utils.replace_once(text, full, URL_MARKUP.format(url, text_url), start) + continue + + if delim == BOLD_DELIM: + tag = "b" + elif delim == ITALIC_DELIM: + tag = "i" + elif delim == UNDERLINE_DELIM: + tag = "u" + elif delim == STRIKE_DELIM: + tag = "s" + elif delim == CODE_DELIM: + tag = "code" + elif delim == PRE_DELIM: + tag = "pre" + else: + continue + + if delim not in delims: + delims.add(delim) + tag = OPENING_TAG.format(tag) + else: + delims.remove(delim) + tag = CLOSING_TAG.format(tag) + + text = utils.replace_once(text, delim, tag, start) + + return self.html.parse(text) + + @staticmethod + def unparse(text: str, entities: list): + text = utils.add_surrogates(text) + + entities_offsets = [] + + for entity in entities: + entity_type = entity.type + start = entity.offset + end = start + entity.length + + if entity_type == "bold": + start_tag = end_tag = BOLD_DELIM + elif entity_type == "italic": + start_tag = end_tag = ITALIC_DELIM + elif entity_type == "underline": + start_tag = end_tag = UNDERLINE_DELIM + elif entity_type == "strike": + start_tag = end_tag = STRIKE_DELIM + elif entity_type == "code": + start_tag = end_tag = CODE_DELIM + elif entity_type in ("pre", "blockquote"): + start_tag = end_tag = PRE_DELIM + elif entity_type == "text_link": + url = entity.url + start_tag = "[" + end_tag = "]({})".format(url) + elif entity_type == "text_mention": + user = entity.user + start_tag = "[" + end_tag = "](tg://user?id={})".format(user.id) + else: + continue + + entities_offsets.append((start_tag, start,)) + entities_offsets.append((end_tag, end,)) + + # sorting by offset (desc) + entities_offsets.sort(key=lambda x: -x[1]) + + for entity, offset in entities_offsets: + text = text[:offset] + entity + text[offset:] + + return utils.remove_surrogates(text) diff --git a/pyrogram/client/parser/parser.py b/pyrogram/client/parser/parser.py new file mode 100644 index 00000000..371c4791 --- /dev/null +++ b/pyrogram/client/parser/parser.py @@ -0,0 +1,69 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from collections import OrderedDict +from typing import Union + +import pyrogram +from .html import HTML +from .markdown import Markdown + + +class Parser: + def __init__(self, client: Union["pyrogram.BaseClient", None]): + self.client = client + self.html = HTML(client) + self.markdown = Markdown(client) + + def parse(self, text: str, mode: Union[str, None] = object): + text = str(text).strip() + + if mode == object: + if self.client: + mode = self.client.parse_mode + else: + mode = "combined" + + if mode is None: + return OrderedDict([ + ("message", text), + ("entities", []) + ]) + + mode = mode.lower() + + if mode == "combined": + return self.markdown.parse(text) + + if mode in ["markdown", "md"]: + return self.markdown.parse(text, True) + + if mode == "html": + return self.html.parse(text) + + raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( + ", ".join('"{}"'.format(m) for m in pyrogram.Client.PARSE_MODES[:-1]), + mode + )) + + @staticmethod + def unparse(text: str, entities: list, is_html: bool): + if is_html: + return HTML.unparse(text, entities) + else: + return Markdown.unparse(text, entities) diff --git a/pyrogram/client/style/utils.py b/pyrogram/client/parser/utils.py similarity index 91% rename from pyrogram/client/style/utils.py rename to pyrogram/client/parser/utils.py index b001f1cf..1fce419f 100644 --- a/pyrogram/client/style/utils.py +++ b/pyrogram/client/parser/utils.py @@ -35,3 +35,7 @@ def add_surrogates(text): def remove_surrogates(text): # Replace each surrogate pair with a SMP code point return text.encode("utf-16", "surrogatepass").decode("utf-16") + + +def replace_once(source: str, old: str, new: str, start: int): + return source[:start] + source[start:].replace(old, new, 1) diff --git a/pyrogram/api/core/primitives/null.py b/pyrogram/client/storage/__init__.py similarity index 75% rename from pyrogram/api/core/primitives/null.py rename to pyrogram/client/storage/__init__.py index ffddea94..657c06eb 100644 --- a/pyrogram/api/core/primitives/null.py +++ b/pyrogram/client/storage/__init__.py @@ -16,17 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from io import BytesIO - -from ..tl_object import TLObject - - -class Null(TLObject): - ID = 0x56730bcc - - @staticmethod - def read(b: BytesIO, *args) -> None: - return None - - def __new__(cls) -> bytes: - return cls.ID.to_bytes(4, "little") +from .file_storage import FileStorage +from .memory_storage import MemoryStorage +from .storage import Storage diff --git a/pyrogram/client/storage/file_storage.py b/pyrogram/client/storage/file_storage.py new file mode 100644 index 00000000..e6ba8420 --- /dev/null +++ b/pyrogram/client/storage/file_storage.py @@ -0,0 +1,110 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import base64 +import json +import logging +import sqlite3 +from pathlib import Path +from threading import Lock + +from .memory_storage import MemoryStorage + +log = logging.getLogger(__name__) + + +class FileStorage(MemoryStorage): + FILE_EXTENSION = ".session" + + def __init__(self, name: str, workdir: Path): + super().__init__(name) + + self.workdir = workdir + self.database = workdir / (self.name + self.FILE_EXTENSION) + self.conn = None # type: sqlite3.Connection + self.lock = Lock() + + # noinspection PyAttributeOutsideInit + def migrate_from_json(self, session_json: dict): + self.open() + + self.dc_id = session_json["dc_id"] + self.test_mode = session_json["test_mode"] + self.auth_key = base64.b64decode("".join(session_json["auth_key"])) + self.user_id = session_json["user_id"] + self.date = session_json.get("date", 0) + self.is_bot = session_json.get("is_bot", False) + + peers_by_id = session_json.get("peers_by_id", {}) + peers_by_phone = session_json.get("peers_by_phone", {}) + + peers = {} + + for k, v in peers_by_id.items(): + if v is None: + type_ = "group" + elif k.startswith("-100"): + type_ = "channel" + else: + type_ = "user" + + peers[int(k)] = [int(k), int(v) if v is not None else None, type_, None, None] + + for k, v in peers_by_phone.items(): + peers[v][4] = k + + # noinspection PyTypeChecker + self.update_peers(peers.values()) + + def open(self): + path = self.database + file_exists = path.is_file() + + if file_exists: + try: + with open(str(path), encoding="utf-8") as f: + session_json = json.load(f) + except ValueError: + pass + else: + log.warning("JSON session storage detected! Converting it into an SQLite session storage...") + + path.rename(path.name + ".OLD") + + log.warning('The old session file has been renamed to "{}.OLD"'.format(path.name)) + + self.migrate_from_json(session_json) + + log.warning("Done! The session has been successfully converted from JSON to SQLite storage") + + return + + if Path(path.name + ".OLD").is_file(): + log.warning('Old session file detected: "{}.OLD". You can remove this file now'.format(path.name)) + + self.conn = sqlite3.connect( + str(path), + timeout=1, + check_same_thread=False + ) + + if not file_exists: + self.create() + + with self.conn: + self.conn.execute("VACUUM") diff --git a/pyrogram/client/storage/memory_storage.py b/pyrogram/client/storage/memory_storage.py new file mode 100644 index 00000000..e69d247f --- /dev/null +++ b/pyrogram/client/storage/memory_storage.py @@ -0,0 +1,239 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import base64 +import inspect +import logging +import sqlite3 +import struct +import time +from pathlib import Path +from threading import Lock +from typing import List, Tuple + +from pyrogram.api import types +from pyrogram.client.storage.storage import Storage + +log = logging.getLogger(__name__) + + +class MemoryStorage(Storage): + SCHEMA_VERSION = 1 + USERNAME_TTL = 8 * 60 * 60 + SESSION_STRING_FMT = ">B?256sI?" + SESSION_STRING_SIZE = 351 + + def __init__(self, name: str): + super().__init__(name) + + self.conn = None # type: sqlite3.Connection + self.lock = Lock() + + def create(self): + with self.lock, self.conn: + with open(str(Path(__file__).parent / "schema.sql"), "r") as schema: + self.conn.executescript(schema.read()) + + self.conn.execute( + "INSERT INTO version VALUES (?)", + (self.SCHEMA_VERSION,) + ) + + self.conn.execute( + "INSERT INTO sessions VALUES (?, ?, ?, ?, ?, ?)", + (1, None, None, 0, None, None) + ) + + def _import_session_string(self, session_string: str): + decoded = base64.urlsafe_b64decode(session_string + "=" * (-len(session_string) % 4)) + return struct.unpack(self.SESSION_STRING_FMT, decoded) + + def export_session_string(self): + packed = struct.pack( + self.SESSION_STRING_FMT, + self.dc_id, + self.test_mode, + self.auth_key, + self.user_id, + self.is_bot + ) + + return base64.urlsafe_b64encode(packed).decode().rstrip("=") + + # noinspection PyAttributeOutsideInit + def open(self): + self.conn = sqlite3.connect(":memory:", check_same_thread=False) + self.create() + + if self.name != ":memory:": + imported_session_string = self._import_session_string(self.name) + + self.dc_id, self.test_mode, self.auth_key, self.user_id, self.is_bot = imported_session_string + self.date = 0 + + # noinspection PyAttributeOutsideInit + def save(self): + self.date = int(time.time()) + + with self.lock: + self.conn.commit() + + def close(self): + with self.lock: + self.conn.close() + + def update_peers(self, peers: List[Tuple[int, int, str, str, str]]): + with self.lock: + self.conn.executemany( + "REPLACE INTO peers (id, access_hash, type, username, phone_number)" + "VALUES (?, ?, ?, ?, ?)", + peers + ) + + def clear_peers(self): + with self.lock, self.conn: + self.conn.execute( + "DELETE FROM peers" + ) + + @staticmethod + def _get_input_peer(peer_id: int, access_hash: int, peer_type: str): + if peer_type in ["user", "bot"]: + return types.InputPeerUser( + user_id=peer_id, + access_hash=access_hash + ) + + if peer_type == "group": + return types.InputPeerChat( + chat_id=-peer_id + ) + + if peer_type in ["channel", "supergroup"]: + return types.InputPeerChannel( + channel_id=int(str(peer_id)[4:]), + access_hash=access_hash + ) + + raise ValueError("Invalid peer type: {}".format(peer_type)) + + def get_peer_by_id(self, peer_id: int): + r = self.conn.execute( + "SELECT id, access_hash, type FROM peers WHERE id = ?", + (peer_id,) + ).fetchone() + + if r is None: + raise KeyError("ID not found: {}".format(peer_id)) + + return self._get_input_peer(*r) + + def get_peer_by_username(self, username: str): + r = self.conn.execute( + "SELECT id, access_hash, type, last_update_on FROM peers WHERE username = ?", + (username,) + ).fetchone() + + if r is None: + raise KeyError("Username not found: {}".format(username)) + + if abs(time.time() - r[3]) > self.USERNAME_TTL: + raise KeyError("Username expired: {}".format(username)) + + return self._get_input_peer(*r[:3]) + + def get_peer_by_phone_number(self, phone_number: str): + r = self.conn.execute( + "SELECT id, access_hash, type FROM peers WHERE phone_number = ?", + (phone_number,) + ).fetchone() + + if r is None: + raise KeyError("Phone number not found: {}".format(phone_number)) + + return self._get_input_peer(*r) + + @property + def peers_count(self): + return self.conn.execute( + "SELECT COUNT(*) FROM peers" + ).fetchone()[0] + + def _get(self): + attr = inspect.stack()[1].function + + return self.conn.execute( + "SELECT {} FROM sessions".format(attr) + ).fetchone()[0] + + def _set(self, value): + attr = inspect.stack()[1].function + + with self.lock, self.conn: + self.conn.execute( + "UPDATE sessions SET {} = ?".format(attr), + (value,) + ) + + @property + def dc_id(self): + return self._get() + + @dc_id.setter + def dc_id(self, value): + self._set(value) + + @property + def test_mode(self): + return self._get() + + @test_mode.setter + def test_mode(self, value): + self._set(value) + + @property + def auth_key(self): + return self._get() + + @auth_key.setter + def auth_key(self, value): + self._set(value) + + @property + def date(self): + return self._get() + + @date.setter + def date(self, value): + self._set(value) + + @property + def user_id(self): + return self._get() + + @user_id.setter + def user_id(self, value): + self._set(value) + + @property + def is_bot(self): + return self._get() + + @is_bot.setter + def is_bot(self, value): + self._set(value) diff --git a/pyrogram/client/storage/schema.sql b/pyrogram/client/storage/schema.sql new file mode 100644 index 00000000..1f5af6d2 --- /dev/null +++ b/pyrogram/client/storage/schema.sql @@ -0,0 +1,34 @@ +CREATE TABLE sessions ( + dc_id INTEGER PRIMARY KEY, + test_mode INTEGER, + auth_key BLOB, + date INTEGER NOT NULL, + user_id INTEGER, + is_bot INTEGER +); + +CREATE TABLE peers ( + id INTEGER PRIMARY KEY, + access_hash INTEGER, + type INTEGER NOT NULL, + username TEXT, + phone_number TEXT, + last_update_on INTEGER NOT NULL DEFAULT (CAST(STRFTIME('%s', 'now') AS INTEGER)) +); + +CREATE TABLE version ( + number INTEGER PRIMARY KEY +); + +CREATE INDEX idx_peers_id ON peers (id); +CREATE INDEX idx_peers_username ON peers (username); +CREATE INDEX idx_peers_phone_number ON peers (phone_number); + +CREATE TRIGGER trg_peers_last_update_on + AFTER UPDATE + ON peers + BEGIN + UPDATE peers + SET last_update_on = CAST(STRFTIME('%s', 'now') AS INTEGER) + WHERE id = NEW.id; + END; \ No newline at end of file diff --git a/pyrogram/client/storage/storage.py b/pyrogram/client/storage/storage.py new file mode 100644 index 00000000..e0810645 --- /dev/null +++ b/pyrogram/client/storage/storage.py @@ -0,0 +1,98 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + + +class Storage: + def __init__(self, name: str): + self.name = name + + def open(self): + raise NotImplementedError + + def save(self): + raise NotImplementedError + + def close(self): + raise NotImplementedError + + def update_peers(self, peers): + raise NotImplementedError + + def get_peer_by_id(self, peer_id): + raise NotImplementedError + + def get_peer_by_username(self, username): + raise NotImplementedError + + def get_peer_by_phone_number(self, phone_number): + raise NotImplementedError + + def export_session_string(self): + raise NotImplementedError + + @property + def peers_count(self): + raise NotImplementedError + + @property + def dc_id(self): + raise NotImplementedError + + @dc_id.setter + def dc_id(self, value): + raise NotImplementedError + + @property + def test_mode(self): + raise NotImplementedError + + @test_mode.setter + def test_mode(self, value): + raise NotImplementedError + + @property + def auth_key(self): + raise NotImplementedError + + @auth_key.setter + def auth_key(self, value): + raise NotImplementedError + + @property + def date(self): + raise NotImplementedError + + @date.setter + def date(self, value): + raise NotImplementedError + + @property + def user_id(self): + raise NotImplementedError + + @user_id.setter + def user_id(self, value): + raise NotImplementedError + + @property + def is_bot(self): + raise NotImplementedError + + @is_bot.setter + def is_bot(self, value): + raise NotImplementedError diff --git a/pyrogram/client/style/html.py b/pyrogram/client/style/html.py deleted file mode 100644 index 9c0a372c..00000000 --- a/pyrogram/client/style/html.py +++ /dev/null @@ -1,127 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2019 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -import re -from collections import OrderedDict - -import pyrogram -from pyrogram.api.types import ( - MessageEntityBold as Bold, - MessageEntityItalic as Italic, - MessageEntityCode as Code, - MessageEntityTextUrl as Url, - MessageEntityPre as Pre, - MessageEntityMentionName as MentionInvalid, - InputMessageEntityMentionName as Mention, -) -from pyrogram.errors import PeerIdInvalid -from . import utils - - -class HTML: - HTML_RE = re.compile(r"<(\w+)(?: href=([\"'])([^<]+)\2)?>([^>]+)") - MENTION_RE = re.compile(r"tg://user\?id=(\d+)") - - def __init__(self, client: "pyrogram.BaseClient" = None): - self.client = client - - def parse(self, message: str): - entities = [] - message = utils.add_surrogates(str(message or "")) - offset = 0 - - for match in self.HTML_RE.finditer(message): - start = match.start() - offset - style, url, body = match.group(1, 3, 4) - - if url: - mention = self.MENTION_RE.match(url) - - if mention: - user_id = int(mention.group(1)) - - try: - input_user = self.client.resolve_peer(user_id) - except PeerIdInvalid: - input_user = None - - entity = ( - Mention(offset=start, length=len(body), user_id=input_user) - if input_user else MentionInvalid(offset=start, length=len(body), user_id=user_id) - ) - else: - entity = Url(offset=start, length=len(body), url=url) - else: - if style == "b" or style == "strong": - entity = Bold(offset=start, length=len(body)) - elif style == "i" or style == "em": - entity = Italic(offset=start, length=len(body)) - elif style == "code": - entity = Code(offset=start, length=len(body)) - elif style == "pre": - entity = Pre(offset=start, length=len(body), language="") - else: - continue - - entities.append(entity) - message = message.replace(match.group(), body) - offset += len(style) * 2 + 5 + (len(url) + 8 if url else 0) - - # TODO: OrderedDict to be removed in Python3.6 - return OrderedDict([ - ("message", utils.remove_surrogates(message)), - ("entities", entities) - ]) - - def unparse(self, message: str, entities: list): - message = utils.add_surrogates(message).strip() - offset = 0 - - for entity in entities: - start = entity.offset + offset - type = entity.type - url = entity.url - user = entity.user - sub = message[start: start + entity.length] - - if type == "bold": - style = "b" - elif type == "italic": - style = "i" - elif type == "code": - style = "code" - elif type == "pre": - style = "pre" - elif type == "text_link": - offset += 15 + len(url) - message = message[:start] + message[start:].replace( - sub, "{}".format(url, sub), 1) - continue - elif type == "text_mention": - offset += 28 + len(str(user.id)) - message = message[:start] + message[start:].replace( - sub, "{}".format(user.id, sub), 1) - continue - else: - continue - - offset += len(style) * 2 + 5 - message = message[:start] + message[start:].replace( - sub, "<{0}>{1}".format(style, sub), 1) - - return utils.remove_surrogates(message) diff --git a/pyrogram/client/style/markdown.py b/pyrogram/client/style/markdown.py deleted file mode 100644 index adb86e94..00000000 --- a/pyrogram/client/style/markdown.py +++ /dev/null @@ -1,148 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2019 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -import re -from collections import OrderedDict - -import pyrogram -from pyrogram.api.types import ( - MessageEntityBold as Bold, - MessageEntityItalic as Italic, - MessageEntityCode as Code, - MessageEntityTextUrl as Url, - MessageEntityPre as Pre, - MessageEntityMentionName as MentionInvalid, - InputMessageEntityMentionName as Mention -) -from pyrogram.errors import PeerIdInvalid -from . import utils - - -class Markdown: - BOLD_DELIMITER = "**" - ITALIC_DELIMITER = "__" - CODE_DELIMITER = "`" - PRE_DELIMITER = "```" - - MARKDOWN_RE = re.compile(r"({d})([\w\W]*?)\1|\[([^[]+?)\]\(([^(]+?)\)".format( - d="|".join( - ["".join(i) for i in [ - ["\{}".format(j) for j in i] - for i in [ - PRE_DELIMITER, - CODE_DELIMITER, - ITALIC_DELIMITER, - BOLD_DELIMITER - ] - ]] - ) - )) - MENTION_RE = re.compile(r"tg://user\?id=(\d+)") - - def __init__(self, client: "pyrogram.BaseClient" = None): - self.client = client - - def parse(self, message: str): - message = utils.add_surrogates(str(message or "")).strip() - entities = [] - offset = 0 - - for match in self.MARKDOWN_RE.finditer(message): - start = match.start() - offset - style, body, text, url = match.groups() - - if url: - mention = self.MENTION_RE.match(url) - - if mention: - user_id = int(mention.group(1)) - - try: - input_user = self.client.resolve_peer(user_id) - except PeerIdInvalid: - input_user = None - - entity = ( - Mention(offset=start, length=len(text), user_id=input_user) - if input_user else MentionInvalid(offset=start, length=len(text), user_id=user_id) - ) - else: - entity = Url(offset=start, length=len(text), url=url) - - body = text - offset += len(url) + 4 - else: - if style == self.BOLD_DELIMITER: - entity = Bold(offset=start, length=len(body)) - elif style == self.ITALIC_DELIMITER: - entity = Italic(offset=start, length=len(body)) - elif style == self.CODE_DELIMITER: - entity = Code(offset=start, length=len(body)) - elif style == self.PRE_DELIMITER: - entity = Pre(offset=start, length=len(body), language="") - else: - continue - - offset += len(style) * 2 - - entities.append(entity) - message = message.replace(match.group(), body) - - # TODO: OrderedDict to be removed in Python3.6 - return OrderedDict([ - ("message", utils.remove_surrogates(message)), - ("entities", entities) - ]) - - def unparse(self, message: str, entities: list): - message = utils.add_surrogates(message).strip() - offset = 0 - - for entity in entities: - start = entity.offset + offset - type = entity.type - url = entity.url - user = entity.user - sub = message[start: start + entity.length] - - if type == "bold": - style = self.BOLD_DELIMITER - elif type == "italic": - style = self.ITALIC_DELIMITER - elif type == "code": - style = self.CODE_DELIMITER - elif type == "pre": - style = self.PRE_DELIMITER - elif type == "text_link": - offset += 4 + len(url) - message = message[:start] + message[start:].replace( - sub, "[{}]({})".format(sub, url), 1) - continue - elif type == "text_mention": - offset += 17 + len(str(user.id)) - message = message[:start] + message[start:].replace( - sub, "[{}](tg://user?id={})".format(sub, user.id), 1) - continue - else: - continue - - offset += len(style) * 2 - message = message[:start] + message[start:].replace( - sub, "{0}{1}{0}".format(style, sub), 1) - - return utils.remove_surrogates(message) diff --git a/pyrogram/client/types/bots_and_keyboards/callback_game.py b/pyrogram/client/types/bots_and_keyboards/callback_game.py index acf6df60..338cfb06 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_game.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_game.py @@ -25,7 +25,5 @@ class CallbackGame(Object): Use BotFather to set up your game. """ - __slots__ = [] - def __init__(self): super().__init__() diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index fa0d8be2..9a5674ae 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -25,6 +25,7 @@ from pyrogram.api import types from ..object import Object from ..update import Update from ..user_and_chats import User +from ...ext import utils class CallbackQuery(Object, Update): @@ -60,8 +61,6 @@ class CallbackQuery(Object, Update): """ - __slots__ = ["id", "from_user", "chat_instance", "message", "inline_message_id", "data", "game_short_name"] - def __init__( self, *, @@ -90,16 +89,7 @@ class CallbackQuery(Object, Update): inline_message_id = None if isinstance(callback_query, types.UpdateBotCallbackQuery): - peer = callback_query.peer - - if isinstance(peer, types.PeerUser): - peer_id = peer.user_id - elif isinstance(peer, types.PeerChat): - peer_id = -peer.chat_id - else: - peer_id = int("-100" + str(peer.channel_id)) - - message = client.get_messages(peer_id, callback_query.msg_id) + message = client.get_messages(utils.get_peer_id(callback_query.peer), callback_query.msg_id) elif isinstance(callback_query, types.UpdateInlineBotCallbackQuery): inline_message_id = b64encode( pack( @@ -172,3 +162,157 @@ class CallbackQuery(Object, Update): url=url, cache_time=cache_time ) + + def edit_message_text( + self, + text: str, + parse_mode: Union[str, None] = object, + disable_web_page_preview: bool = None, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> Union["pyrogram.Message", bool]: + """Edit the text of messages attached to callback queries. + + Bound method *edit_message_text* of :obj:`CallbackQuery`. + + Parameters: + text (``str``): + New text of the message. + + parse_mode (``str``, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. + + disable_web_page_preview (``bool``, *optional*): + Disables link previews for links in this message. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is + returned, otherwise True is returned (message sent via the bot, as inline query result). + + Raises: + RPCError: In case of a Telegram RPC error. + """ + if self.inline_message_id is None: + return self._client.edit_message_text( + chat_id=self.message.chat.id, + message_id=self.message.message_id, + text=text, + parse_mode=parse_mode, + disable_web_page_preview=disable_web_page_preview, + reply_markup=reply_markup + ) + else: + return self._client.edit_inline_text( + inline_message_id=self.inline_message_id, + text=text, + parse_mode=parse_mode, + disable_web_page_preview=disable_web_page_preview, + reply_markup=reply_markup + ) + + def edit_message_caption( + self, + caption: str, + parse_mode: Union[str, None] = object, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> Union["pyrogram.Message", bool]: + """Edit the caption of media messages attached to callback queries. + + Bound method *edit_message_caption* of :obj:`CallbackQuery`. + + Parameters: + caption (``str``): + New caption of the message. + + parse_mode (``str``, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is + returned, otherwise True is returned (message sent via the bot, as inline query result). + + Raises: + RPCError: In case of a Telegram RPC error. + """ + return self.edit_message_text(caption, parse_mode, reply_markup) + + def edit_message_media( + self, + media: "pyrogram.InputMedia", + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> Union["pyrogram.Message", bool]: + """Edit animation, audio, document, photo or video messages attached to callback queries. + + Bound method *edit_message_media* of :obj:`CallbackQuery`. + + Parameters: + media (:obj:`InputMedia`): + One of the InputMedia objects describing an animation, audio, document, photo or video. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + Returns: + :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is + returned, otherwise True is returned (message sent via the bot, as inline query result). + + Raises: + RPCError: In case of a Telegram RPC error. + """ + if self.inline_message_id is None: + return self._client.edit_message_media( + chat_id=self.message.chat.id, + message_id=self.message.message_id, + media=media, + reply_markup=reply_markup + ) + else: + return self._client.edit_inline_media( + inline_message_id=self.inline_message_id, + media=media, + reply_markup=reply_markup + ) + + def edit_message_reply_markup( + self, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None + ) -> Union["pyrogram.Message", bool]: + """Edit only the reply markup of messages attached to callback queries. + + Bound method *edit_message_reply_markup* of :obj:`CallbackQuery`. + + Parameters: + reply_markup (:obj:`InlineKeyboardMarkup`): + An InlineKeyboardMarkup object. + + Returns: + :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is + returned, otherwise True is returned (message sent via the bot, as inline query result). + + Raises: + RPCError: In case of a Telegram RPC error. + """ + if self.inline_message_id is None: + return self._client.edit_message_reply_markup( + chat_id=self.message.chat.id, + message_id=self.message.message_id, + reply_markup=reply_markup + ) + else: + return self._client.edit_inline_reply_markup( + inline_message_id=self.inline_message_id, + reply_markup=reply_markup + ) diff --git a/pyrogram/client/types/bots_and_keyboards/force_reply.py b/pyrogram/client/types/bots_and_keyboards/force_reply.py index 6c542aa8..ef5c0ccb 100644 --- a/pyrogram/client/types/bots_and_keyboards/force_reply.py +++ b/pyrogram/client/types/bots_and_keyboards/force_reply.py @@ -37,8 +37,6 @@ class ForceReply(Object): 2) if the bot's message is a reply (has reply_to_message_id), sender of the original message. """ - __slots__ = ["selective"] - def __init__( self, selective: bool = None diff --git a/pyrogram/client/types/bots_and_keyboards/game_high_score.py b/pyrogram/client/types/bots_and_keyboards/game_high_score.py index 5d576ad4..38e2242a 100644 --- a/pyrogram/client/types/bots_and_keyboards/game_high_score.py +++ b/pyrogram/client/types/bots_and_keyboards/game_high_score.py @@ -37,8 +37,6 @@ class GameHighScore(Object): Position in high score table for the game. """ - __slots__ = ["user", "score", "position"] - def __init__( self, *, diff --git a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py b/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py index 54aa7802..678be614 100644 --- a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py +++ b/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py @@ -22,6 +22,7 @@ from pyrogram.api.types import ( KeyboardButtonUrl, KeyboardButtonCallback, KeyboardButtonSwitchInline, KeyboardButtonGame ) + from .callback_game import CallbackGame from ..object import Object @@ -58,10 +59,6 @@ class InlineKeyboardButton(Object): # TODO: Add callback_game and pay fields - __slots__ = [ - "text", "url", "callback_data", "switch_inline_query", "switch_inline_query_current_chat", "callback_game" - ] - def __init__( self, text: str, diff --git a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py b/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py index 7b811f88..811c4365 100644 --- a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py +++ b/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py @@ -19,6 +19,7 @@ from typing import List from pyrogram.api.types import ReplyInlineMarkup, KeyboardButtonRow + from . import InlineKeyboardButton from ..object import Object @@ -31,8 +32,6 @@ class InlineKeyboardMarkup(Object): List of button rows, each represented by a List of InlineKeyboardButton objects. """ - __slots__ = ["inline_keyboard"] - def __init__( self, inline_keyboard: List[List[InlineKeyboardButton]] diff --git a/pyrogram/client/types/bots_and_keyboards/keyboard_button.py b/pyrogram/client/types/bots_and_keyboards/keyboard_button.py index 8374db1b..21c03613 100644 --- a/pyrogram/client/types/bots_and_keyboards/keyboard_button.py +++ b/pyrogram/client/types/bots_and_keyboards/keyboard_button.py @@ -41,8 +41,6 @@ class KeyboardButton(Object): Available in private chats only. """ - __slots__ = ["text", "request_contact", "request_location"] - def __init__( self, text: str, diff --git a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py b/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py index 4e666d1f..12799bd7 100644 --- a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py +++ b/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py @@ -20,6 +20,7 @@ from typing import List, Union from pyrogram.api.types import KeyboardButtonRow from pyrogram.api.types import ReplyKeyboardMarkup as RawReplyKeyboardMarkup + from . import KeyboardButton from ..object import Object @@ -49,8 +50,6 @@ class ReplyKeyboardMarkup(Object): select the new language. Other users in the group don't see the keyboard. """ - __slots__ = ["keyboard", "resize_keyboard", "one_time_keyboard", "selective"] - def __init__( self, keyboard: List[List[Union[KeyboardButton, str]]], diff --git a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_remove.py b/pyrogram/client/types/bots_and_keyboards/reply_keyboard_remove.py index d451a8e8..1623c9bd 100644 --- a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_remove.py +++ b/pyrogram/client/types/bots_and_keyboards/reply_keyboard_remove.py @@ -38,8 +38,6 @@ class ReplyKeyboardRemove(Object): keyboard for that user, while still showing the keyboard with poll options to users who haven't voted yet. """ - __slots__ = ["selective"] - def __init__( self, selective: bool = None diff --git a/pyrogram/client/types/inline_mode/__init__.py b/pyrogram/client/types/inline_mode/__init__.py index 7a3b3023..4768ecae 100644 --- a/pyrogram/client/types/inline_mode/__init__.py +++ b/pyrogram/client/types/inline_mode/__init__.py @@ -18,8 +18,11 @@ from .inline_query import InlineQuery from .inline_query_result import InlineQueryResult +from .inline_query_result_animation import InlineQueryResultAnimation from .inline_query_result_article import InlineQueryResultArticle +from .inline_query_result_photo import InlineQueryResultPhoto __all__ = [ - "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle" + "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto", + "InlineQueryResultAnimation" ] diff --git a/pyrogram/client/types/inline_mode/inline_query.py b/pyrogram/client/types/inline_mode/inline_query.py index 6bfc58c3..27b73ff4 100644 --- a/pyrogram/client/types/inline_mode/inline_query.py +++ b/pyrogram/client/types/inline_mode/inline_query.py @@ -48,7 +48,6 @@ class InlineQuery(Object, Update): location (:obj:`Location`. *optional*): Sender location, only for bots that request user location. """ - __slots__ = ["id", "from_user", "query", "offset", "location"] def __init__( self, @@ -87,7 +86,8 @@ class InlineQuery(Object, Update): self, results: List[InlineQueryResult], cache_time: int = 300, - is_personal: bool = None, + is_gallery: bool = False, + is_personal: bool = False, next_offset: str = "", switch_pm_text: str = "", switch_pm_parameter: str = "" @@ -116,9 +116,13 @@ class InlineQuery(Object, Update): The maximum amount of time in seconds that the result of the inline query may be cached on the server. Defaults to 300. + is_gallery (``bool``, *optional*): + Pass True, if results should be displayed in gallery mode instead of list mode. + Defaults to False. + is_personal (``bool``, *optional*): Pass True, if results may be cached on the server side only for the user that sent the query. - By default, results may be returned to any user who sends the same query. + By default (False), results may be returned to any user who sends the same query. next_offset (``str``, *optional*): Pass the offset that a client should send in the next query with the same text to receive more results. @@ -145,6 +149,7 @@ class InlineQuery(Object, Update): inline_query_id=self.id, results=results, cache_time=cache_time, + is_gallery=is_gallery, is_personal=is_personal, next_offset=next_offset, switch_pm_text=switch_pm_text, diff --git a/pyrogram/client/types/inline_mode/inline_query_result.py b/pyrogram/client/types/inline_mode/inline_query_result.py index 3fc70885..d44a5ee2 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result.py +++ b/pyrogram/client/types/inline_mode/inline_query_result.py @@ -16,6 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from uuid import uuid4 + +from ..bots_and_keyboards import InlineKeyboardMarkup +from ..input_message_content import InputMessageContent from ..object import Object """- :obj:`InlineQueryResultCachedAudio` @@ -45,15 +49,23 @@ class InlineQueryResult(Object): Pyrogram currently supports results of the following types: - :obj:`InlineQueryResultArticle` + - :obj:`InlineQueryResultPhoto` + - :obj:`InlineQueryResultAnimation` """ - __slots__ = ["type", "id"] - - def __init__(self, type: str, id: str): + def __init__( + self, + type: str, + id: str, + input_message_content: InputMessageContent, + reply_markup: InlineKeyboardMarkup + ): super().__init__() self.type = type - self.id = id + self.id = str(uuid4()) if id is None else str(id) + self.input_message_content = input_message_content + self.reply_markup = reply_markup def write(self): pass diff --git a/pyrogram/client/types/inline_mode/inline_query_result_animation.py b/pyrogram/client/types/inline_mode/inline_query_result_animation.py new file mode 100644 index 00000000..c4cb26e4 --- /dev/null +++ b/pyrogram/client/types/inline_mode/inline_query_result_animation.py @@ -0,0 +1,127 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +from pyrogram.api import types +from .inline_query_result import InlineQueryResult +from ..bots_and_keyboards import InlineKeyboardMarkup +from ..input_message_content import InputMessageContent +from ...parser import Parser + + +class InlineQueryResultAnimation(InlineQueryResult): + """Link to an animated GIF file. + + By default, this animated GIF file will be sent by the user with optional caption. + Alternatively, you can use *input_message_content* to send a message with the specified content instead of the + animation. + + Parameters: + animation_url (``str``): + A valid URL for the animated GIF file. + File size must not exceed 1 MB. + + thumb_url (``str``, *optional*): + URL of the static thumbnail for the result (jpeg or gif) + Defaults to the value passed in *animation_url*. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + title (``str``, *optional*): + Title for the result. + + description (``str``, *optional*): + Short description of the result. + + caption (``str``, *optional*): + Caption of the photo to be sent, 0-1024 characters. + + parse_mode (``str``, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + input_message_content (:obj:`InputMessageContent`): + Content of the message to be sent instead of the photo. + """ + + def __init__( + self, + animation_url: str, + thumb_url: str = None, + id: str = None, + title: str = None, + description: str = None, + caption: str = None, + parse_mode: Union[str, None] = object, + reply_markup: InlineKeyboardMarkup = None, + input_message_content: InputMessageContent = None + ): + super().__init__("gif", id, input_message_content, reply_markup) + + self.animation_url = animation_url + self.thumb_url = thumb_url + self.title = title + self.description = description + self.caption = caption + self.parse_mode = parse_mode + self.reply_markup = reply_markup + self.input_message_content = input_message_content + + def write(self): + animation = types.InputWebDocument( + url=self.animation_url, + size=0, + mime_type="image/gif", + attributes=[] + ) + + if self.thumb_url is None: + thumb = animation + else: + thumb = types.InputWebDocument( + url=self.thumb_url, + size=0, + mime_type="image/gif", + attributes=[] + ) + + return types.InputBotInlineResult( + id=self.id, + type=self.type, + title=self.title, + description=self.description, + thumb=thumb, + content=animation, + send_message=( + self.input_message_content.write(self.reply_markup) + if self.input_message_content + else types.InputBotInlineMessageMediaAuto( + reply_markup=self.reply_markup.write() if self.reply_markup else None, + **(Parser(None)).parse(self.caption, self.parse_mode) + ) + ) + ) diff --git a/pyrogram/client/types/inline_mode/inline_query_result_article.py b/pyrogram/client/types/inline_mode/inline_query_result_article.py index ad0be9e4..735a1e02 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_article.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_article.py @@ -16,29 +16,25 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Any - from pyrogram.api import types from .inline_query_result import InlineQueryResult +from ..bots_and_keyboards import InlineKeyboardMarkup +from ..input_message_content import InputMessageContent class InlineQueryResultArticle(InlineQueryResult): """Link to an article or web page. - TODO: Hide url? - Parameters: - id (``str``): - Unique identifier for this result, 1-64 bytes. - title (``str``): Title for the result. input_message_content (:obj:`InputMessageContent`): Content of the message to be sent. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): - Inline keyboard attached to the message. + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. url (``str``, *optional*): URL of the result. @@ -47,46 +43,32 @@ class InlineQueryResultArticle(InlineQueryResult): Short description of the result. thumb_url (``str``, *optional*): - Url of the thumbnail for the result. + URL of the thumbnail for the result. - thumb_width (``int``, *optional*): - Thumbnail width. - - thumb_height (``int``, *optional*): - Thumbnail height. + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + Inline keyboard attached to the message. """ - __slots__ = [ - "title", "input_message_content", "reply_markup", "url", "description", "thumb_url", "thumb_width", - "thumb_height" - ] - def __init__( self, - id: Any, title: str, - input_message_content, - reply_markup=None, + input_message_content: InputMessageContent, + id: str = None, + reply_markup: InlineKeyboardMarkup = None, url: str = None, description: str = None, - thumb_url: str = None, - thumb_width: int = 0, - thumb_height: int = 0 + thumb_url: str = None ): - super().__init__("article", id) + super().__init__("article", id, input_message_content, reply_markup) self.title = title - self.input_message_content = input_message_content - self.reply_markup = reply_markup self.url = url self.description = description self.thumb_url = thumb_url - self.thumb_width = thumb_width - self.thumb_height = thumb_height def write(self): return types.InputBotInlineResult( - id=str(self.id), + id=self.id, type=self.type, send_message=self.input_message_content.write(self.reply_markup), title=self.title, @@ -96,11 +78,6 @@ class InlineQueryResultArticle(InlineQueryResult): url=self.thumb_url, size=0, mime_type="image/jpeg", - attributes=[ - types.DocumentAttributeImageSize( - w=self.thumb_width, - h=self.thumb_height - ) - ] + attributes=[] ) if self.thumb_url else None ) diff --git a/pyrogram/client/types/inline_mode/inline_query_result_photo.py b/pyrogram/client/types/inline_mode/inline_query_result_photo.py new file mode 100644 index 00000000..ffcc21c0 --- /dev/null +++ b/pyrogram/client/types/inline_mode/inline_query_result_photo.py @@ -0,0 +1,127 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2018 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +from typing import Union + +from pyrogram.api import types +from .inline_query_result import InlineQueryResult +from ..bots_and_keyboards import InlineKeyboardMarkup +from ..input_message_content import InputMessageContent +from ...parser import Parser + + +class InlineQueryResultPhoto(InlineQueryResult): + """Link to a photo. + + By default, this photo will be sent by the user with optional caption. + Alternatively, you can use *input_message_content* to send a message with the specified content instead of the + photo. + + Parameters: + photo_url (``str``): + A valid URL of the photo. + Photo must be in jpeg format an must not exceed 5 MB. + + thumb_url (``str``, *optional*): + URL of the thumbnail for the photo. + Defaults to the value passed in *photo_url*. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + title (``str``, *optional*): + Title for the result. + + description (``str``, *optional*): + Short description of the result. + + caption (``str``, *optional*): + Caption of the photo to be sent, 0-1024 characters. + + parse_mode (``str``, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. + + reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + input_message_content (:obj:`InputMessageContent`): + Content of the message to be sent instead of the photo. + """ + + def __init__( + self, + photo_url: str, + thumb_url: str = None, + id: str = None, + title: str = None, + description: str = None, + caption: str = None, + parse_mode: Union[str, None] = object, + reply_markup: InlineKeyboardMarkup = None, + input_message_content: InputMessageContent = None + ): + super().__init__("photo", id, input_message_content, reply_markup) + + self.photo_url = photo_url + self.thumb_url = thumb_url + self.title = title + self.description = description + self.caption = caption + self.parse_mode = parse_mode + self.reply_markup = reply_markup + self.input_message_content = input_message_content + + def write(self): + photo = types.InputWebDocument( + url=self.photo_url, + size=0, + mime_type="image/jpeg", + attributes=[] + ) + + if self.thumb_url is None: + thumb = photo + else: + thumb = types.InputWebDocument( + url=self.thumb_url, + size=0, + mime_type="image/jpeg", + attributes=[] + ) + + return types.InputBotInlineResult( + id=self.id, + type=self.type, + title=self.title, + description=self.description, + thumb=thumb, + content=photo, + send_message=( + self.input_message_content.write(self.reply_markup) + if self.input_message_content + else types.InputBotInlineMessageMediaAuto( + reply_markup=self.reply_markup.write() if self.reply_markup else None, + **(Parser(None)).parse(self.caption, self.parse_mode) + ) + ) + ) diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_audio.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_audio.py deleted file mode 100644 index d5fb954a..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_audio.py +++ /dev/null @@ -1,72 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultAudio(Object): - """Represents a link to an mp3 audio file. By default, this audio file will be sent by the user. Alternatively, you can use input_message_content to send a message with the specified content instead of the audio. - - Attributes: - ID: ``0xb0700004`` - - Parameters: - type (``str``): - Type of the result, must be audio. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - audio_url (``str``): - A valid URL for the audio file. - - title (``str``): - Title. - - caption (``str``, optional): - Caption, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - performer (``str``, optional): - Performer. - - audio_duration (``int`` ``32-bit``, optional): - Audio duration in seconds. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the audio. - - """ - - def __init__(self, type: str, id: str, audio_url: str, title: str, caption: str = None, parse_mode: str = None, - performer: str = None, audio_duration: int = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.audio_url = audio_url # string - self.title = title # string - self.caption = caption # flags.0?string - self.parse_mode = parse_mode # flags.1?string - self.performer = performer # flags.2?string - self.audio_duration = audio_duration # flags.3?int - self.reply_markup = reply_markup # flags.4?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.5?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_audio.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_audio.py deleted file mode 100644 index 47b9bbe2..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_audio.py +++ /dev/null @@ -1,103 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -import binascii -import struct - -from pyrogram.api import types -from pyrogram.client.ext import utils, BaseClient -from pyrogram.client.style import HTML, Markdown -from pyrogram.client.types.object import Object -from pyrogram.errors import FileIdInvalid - - -class InlineQueryResultCachedAudio(Object): - """Represents a link to an audio file stored on the Telegram servers. - By default, this audio file will be sent by the user. Alternatively, you can use *input_message_content* to send a - message with the specified content instead of the audio. - - Parameters: - id (``str``): - Unique identifier for this result, 1-64 bytes. - - audio_file_id (``str``): - A valid file identifier for the audio file. - - caption (``str``, *optional*): - Caption, 0-200 characters. - - parse_mode (``str``, *optional*): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in - the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup `, *optional*): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent `, *optional*): - Content of the message to be sent instead of the audio. - - """ - - def __init__( - self, - id: str, - audio_file_id: str, - caption: str = "", - parse_mode: str = "", - reply_markup=None, - input_message_content=None - ): - self.id = id - self.audio_file_id = audio_file_id - self.caption = caption - self.parse_mode = parse_mode - self.reply_markup = reply_markup - self.input_message_content = input_message_content - - self.style = HTML() if parse_mode.lower() == "html" else Markdown() - - def write(self): - try: - decoded = utils.decode(self.audio_file_id) - fmt = " 24 else " -# -# 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.client.types.object import Object - - -class InlineQueryResultCachedDocument(Object): - """Represents a link to a file stored on the Telegram servers. By default, this file will be sent by the user with an optional caption. Alternatively, you can use input_message_content to send a message with the specified content instead of the file. - - Attributes: - ID: ``0xb0700015`` - - Parameters: - type (``str``): - Type of the result, must be document. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - title (``str``): - Title for the result. - - document_file_id (``str``): - A valid file identifier for the file. - - description (``str``, optional): - Short description of the result. - - caption (``str``, optional): - Caption of the document to be sent, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the file. - - """ - ID = 0xb0700015 - - def __init__(self, type: str, id: str, title: str, document_file_id: str, description: str = None, - caption: str = None, parse_mode: str = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.title = title # string - self.document_file_id = document_file_id # string - self.description = description # flags.0?string - self.caption = caption # flags.1?string - self.parse_mode = parse_mode # flags.2?string - self.reply_markup = reply_markup # flags.3?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.4?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_gif.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_gif.py deleted file mode 100644 index 28a3595b..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_gif.py +++ /dev/null @@ -1,65 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultCachedGif(Object): - """Represents a link to an animated GIF file stored on the Telegram servers. By default, this animated GIF file will be sent by the user with an optional caption. Alternatively, you can use input_message_content to send a message with specified content instead of the animation. - - Attributes: - ID: ``0xb0700012`` - - Parameters: - type (``str``): - Type of the result, must be gif. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - gif_file_id (``str``): - A valid file identifier for the GIF file. - - title (``str``, optional): - Title for the result. - - caption (``str``, optional): - Caption of the GIF file to be sent, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the GIF animation. - - """ - ID = 0xb0700012 - - def __init__(self, type: str, id: str, gif_file_id: str, title: str = None, caption: str = None, - parse_mode: str = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.gif_file_id = gif_file_id # string - self.title = title # flags.0?string - self.caption = caption # flags.1?string - self.parse_mode = parse_mode # flags.2?string - self.reply_markup = reply_markup # flags.3?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.4?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_mpeg4_gif.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_mpeg4_gif.py deleted file mode 100644 index 95ab03a0..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_mpeg4_gif.py +++ /dev/null @@ -1,65 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultCachedMpeg4Gif(Object): - """Represents a link to a video animation (H.264/MPEG-4 AVC video without sound) stored on the Telegram servers. By default, this animated MPEG-4 file will be sent by the user with an optional caption. Alternatively, you can use input_message_content to send a message with the specified content instead of the animation. - - Attributes: - ID: ``0xb0700013`` - - Parameters: - type (``str``): - Type of the result, must be mpeg4_gif. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - mpeg4_file_id (``str``): - A valid file identifier for the MP4 file. - - title (``str``, optional): - Title for the result. - - caption (``str``, optional): - Caption of the MPEG-4 file to be sent, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup `, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the video animation. - - """ - ID = 0xb0700013 - - def __init__(self, type: str, id: str, mpeg4_file_id: str, title: str = None, caption: str = None, - parse_mode: str = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.mpeg4_file_id = mpeg4_file_id # string - self.title = title # flags.0?string - self.caption = caption # flags.1?string - self.parse_mode = parse_mode # flags.2?string - self.reply_markup = reply_markup # flags.3?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.4?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_photo.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_photo.py deleted file mode 100644 index 22793cef..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_photo.py +++ /dev/null @@ -1,69 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultCachedPhoto(Object): - """Represents a link to a photo stored on the Telegram servers. By default, this photo will be sent by the user with an optional caption. Alternatively, you can use input_message_content to send a message with the specified content instead of the photo. - - Attributes: - ID: ``0xb0700011`` - - Parameters: - type (``str``): - Type of the result, must be photo. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - photo_file_id (``str``): - A valid file identifier of the photo. - - title (``str``, optional): - Title for the result. - - description (``str``, optional): - Short description of the result. - - caption (``str``, optional): - Caption of the photo to be sent, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup `, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent `, optional): - Content of the message to be sent instead of the photo. - - """ - ID = 0xb0700011 - - def __init__(self, type: str, id: str, photo_file_id: str, title: str = None, description: str = None, - caption: str = None, parse_mode: str = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.photo_file_id = photo_file_id # string - self.title = title # flags.0?string - self.description = description # flags.1?string - self.caption = caption # flags.2?string - self.parse_mode = parse_mode # flags.3?string - self.reply_markup = reply_markup # flags.4?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.5?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_sticker.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_sticker.py deleted file mode 100644 index 6b2b37c9..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_sticker.py +++ /dev/null @@ -1,52 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultCachedSticker(Object): - """Represents a link to a sticker stored on the Telegram servers. By default, this sticker will be sent by the user. Alternatively, you can use input_message_content to send a message with the specified content instead of the sticker. - - Attributes: - ID: ``0xb0700014`` - - Parameters: - type (``str``): - Type of the result, must be sticker. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - sticker_file_id (``str``): - A valid file identifier of the sticker. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the sticker. - - """ - ID = 0xb0700014 - - def __init__(self, type: str, id: str, sticker_file_id: str, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.sticker_file_id = sticker_file_id # string - self.reply_markup = reply_markup # flags.0?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.1?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_video.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_video.py deleted file mode 100644 index 77dcd6dd..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_video.py +++ /dev/null @@ -1,69 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultCachedVideo(Object): - """Represents a link to a video file stored on the Telegram servers. By default, this video file will be sent by the user with an optional caption. Alternatively, you can use input_message_content to send a message with the specified content instead of the video. - - Attributes: - ID: ``0xb0700016`` - - Parameters: - type (``str``): - Type of the result, must be video. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - video_file_id (``str``): - A valid file identifier for the video file. - - title (``str``): - Title for the result. - - description (``str``, optional): - Short description of the result. - - caption (``str``, optional): - Caption of the video to be sent, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup `, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent `, optional): - Content of the message to be sent instead of the video. - - """ - ID = 0xb0700016 - - def __init__(self, type: str, id: str, video_file_id: str, title: str, description: str = None, caption: str = None, - parse_mode: str = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.video_file_id = video_file_id # string - self.title = title # string - self.description = description # flags.0?string - self.caption = caption # flags.1?string - self.parse_mode = parse_mode # flags.2?string - self.reply_markup = reply_markup # flags.3?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.4?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_voice.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_voice.py deleted file mode 100644 index a80d5a20..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_cached_voice.py +++ /dev/null @@ -1,65 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultCachedVoice(Object): - """Represents a link to a voice message stored on the Telegram servers. By default, this voice message will be sent by the user. Alternatively, you can use input_message_content to send a message with the specified content instead of the voice message. - - Attributes: - ID: ``0xb0700017`` - - Parameters: - type (``str``): - Type of the result, must be voice. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - voice_file_id (``str``): - A valid file identifier for the voice message. - - title (``str``): - Voice message title. - - caption (``str``, optional): - Caption, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the voice message. - - """ - ID = 0xb0700017 - - def __init__(self, type: str, id: str, voice_file_id: str, title: str, caption: str = None, parse_mode: str = None, - reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.voice_file_id = voice_file_id # string - self.title = title # string - self.caption = caption # flags.0?string - self.parse_mode = parse_mode # flags.1?string - self.reply_markup = reply_markup # flags.2?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.3?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_contact.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_contact.py deleted file mode 100644 index afddb9ec..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_contact.py +++ /dev/null @@ -1,78 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultContact(Object): - """Represents a contact with a phone number. By default, this contact will be sent by the user. Alternatively, you can use input_message_content to send a message with the specified content instead of the contact. - - Attributes: - ID: ``0xb0700009`` - - Parameters: - type (``str``): - Type of the result, must be contact. - - id (``str``): - Unique identifier for this result, 1-64 Bytes. - - phone_number (``str``): - Contact's phone number. - - first_name (``str``): - Contact's first name. - - last_name (``str``, optional): - Contact's last name. - - vcard (``str``, optional): - Additional data about the contact in the form of a vCard, 0-2048 bytes. - - reply_markup (:obj:`InlineKeyboardMarkup `, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent `, optional): - Content of the message to be sent instead of the contact. - - thumb_url (``str``, optional): - Url of the thumbnail for the result. - - thumb_width (``int`` ``32-bit``, optional): - Thumbnail width. - - thumb_height (``int`` ``32-bit``, optional): - Thumbnail height. - - """ - ID = 0xb0700009 - - def __init__(self, type: str, id: str, phone_number: str, first_name: str, last_name: str = None, vcard: str = None, - reply_markup=None, input_message_content=None, thumb_url: str = None, thumb_width: int = None, - thumb_height: int = None): - self.type = type # string - self.id = id # string - self.phone_number = phone_number # string - self.first_name = first_name # string - self.last_name = last_name # flags.0?string - self.vcard = vcard # flags.1?string - self.reply_markup = reply_markup # flags.2?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.3?InputMessageContent - self.thumb_url = thumb_url # flags.4?string - self.thumb_width = thumb_width # flags.5?int - self.thumb_height = thumb_height # flags.6?int diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_document.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_document.py deleted file mode 100644 index 370dc3c6..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_document.py +++ /dev/null @@ -1,86 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultDocument(Object): - """Represents a link to a file. By default, this file will be sent by the user with an optional caption. Alternatively, you can use input_message_content to send a message with the specified content instead of the file. Currently, only .PDF and .ZIP files can be sent using this method. - - Attributes: - ID: ``0xb0700006`` - - Parameters: - type (``str``): - Type of the result, must be document. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - title (``str``): - Title for the result. - - document_url (``str``, optional): - Caption of the document to be sent, 0-200 characters. - - mime_type (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - caption (``str``): - A valid URL for the file. - - parse_mode (``str``): - Mime type of the content of the file, either "application/pdf" or "application/zip". - - description (``str``, optional): - Short description of the result. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the file. - - thumb_url (``str``, optional): - URL of the thumbnail (jpeg only) for the file. - - thumb_width (``int`` ``32-bit``, optional): - Thumbnail width. - - thumb_height (``int`` ``32-bit``, optional): - Thumbnail height. - - """ - ID = 0xb0700006 - - def __init__(self, type: str, id: str, title: str, document_url: str, mime_type: str, caption: str = None, - parse_mode: str = None, description: str = None, reply_markup=None, input_message_content=None, - thumb_url: str = None, thumb_width: int = None, thumb_height: int = None): - self.type = type # string - self.id = id # string - self.title = title # string - self.caption = caption # flags.0?string - self.parse_mode = parse_mode # flags.1?string - self.document_url = document_url # string - self.mime_type = mime_type # string - self.description = description # flags.2?string - self.reply_markup = reply_markup # flags.3?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.4?InputMessageContent - self.thumb_url = thumb_url # flags.5?string - self.thumb_width = thumb_width # flags.6?int - self.thumb_height = thumb_height # flags.7?int diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_game.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_game.py deleted file mode 100644 index bd6f25d2..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_game.py +++ /dev/null @@ -1,48 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultGame(Object): - """Represents a Game. - - Attributes: - ID: ``0xb0700010`` - - Parameters: - type (``str``): - Type of the result, must be game. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - game_short_name (``str``): - Short name of the game. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - """ - ID = 0xb0700010 - - def __init__(self, type: str, id: str, game_short_name: str, reply_markup=None): - self.type = type # string - self.id = id # string - self.game_short_name = game_short_name # string - self.reply_markup = reply_markup # flags.0?InlineKeyboardMarkup diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_gif.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_gif.py deleted file mode 100644 index 56817d76..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_gif.py +++ /dev/null @@ -1,82 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultGif(Object): - """Represents a link to an animated GIF file. By default, this animated GIF file will be sent by the user with optional caption. Alternatively, you can use input_message_content to send a message with the specified content instead of the animation. - - Attributes: - ID: ``0xb0700001`` - - Parameters: - type (``str``): - Type of the result, must be gif. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - gif_url (``str``): - A valid URL for the GIF file. File size must not exceed 1MB. - - thumb_url (``str``, optional): - Width of the GIF. - - gif_width (``int`` ``32-bit``, optional): - Height of the GIF. - - gif_height (``int`` ``32-bit``, optional): - Duration of the GIF. - - gif_duration (``int`` ``32-bit``): - URL of the static thumbnail for the result (jpeg or gif). - - title (``str``, optional): - Title for the result. - - caption (``str``, optional): - Caption of the GIF file to be sent, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup `, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent `, optional): - Content of the message to be sent instead of the GIF animation. - - """ - ID = 0xb0700001 - - def __init__(self, type: str, id: str, gif_url: str, thumb_url: str, gif_width: int = None, gif_height: int = None, - gif_duration: int = None, title: str = None, caption: str = None, parse_mode: str = None, - reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.gif_url = gif_url # string - self.gif_width = gif_width # flags.0?int - self.gif_height = gif_height # flags.1?int - self.gif_duration = gif_duration # flags.2?int - self.thumb_url = thumb_url # string - self.title = title # flags.3?string - self.caption = caption # flags.4?string - self.parse_mode = parse_mode # flags.5?string - self.reply_markup = reply_markup # flags.6?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.7?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_location.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_location.py deleted file mode 100644 index 74c63ede..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_location.py +++ /dev/null @@ -1,78 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultLocation(Object): - """Represents a location on a map. By default, the location will be sent by the user. Alternatively, you can use input_message_content to send a message with the specified content instead of the location. - - Attributes: - ID: ``0xb0700007`` - - Parameters: - type (``str``): - Type of the result, must be location. - - id (``str``): - Unique identifier for this result, 1-64 Bytes. - - latitude (``float`` ``64-bit``): - Location latitude in degrees. - - longitude (``float`` ``64-bit``): - Location longitude in degrees. - - title (``str``): - Location title. - - live_period (``int`` ``32-bit``, optional): - Period in seconds for which the location can be updated, should be between 60 and 86400. - - reply_markup (:obj:`InlineKeyboardMarkup `, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent `, optional): - Content of the message to be sent instead of the location. - - thumb_url (``str``, optional): - Url of the thumbnail for the result. - - thumb_width (``int`` ``32-bit``, optional): - Thumbnail width. - - thumb_height (``int`` ``32-bit``, optional): - Thumbnail height. - - """ - ID = 0xb0700007 - - def __init__(self, type: str, id: str, latitude: float, longitude: float, title: str, live_period: int = None, - reply_markup=None, input_message_content=None, thumb_url: str = None, thumb_width: int = None, - thumb_height: int = None): - self.type = type # string - self.id = id # string - self.latitude = latitude # double - self.longitude = longitude # double - self.title = title # string - self.live_period = live_period # flags.0?int - self.reply_markup = reply_markup # flags.1?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.2?InputMessageContent - self.thumb_url = thumb_url # flags.3?string - self.thumb_width = thumb_width # flags.4?int - self.thumb_height = thumb_height # flags.5?int diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_mpeg4_gif.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_mpeg4_gif.py deleted file mode 100644 index e4da6b89..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_mpeg4_gif.py +++ /dev/null @@ -1,82 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultMpeg4Gif(Object): - """Represents a link to a video animation (H.264/MPEG-4 AVC video without sound). By default, this animated MPEG-4 file will be sent by the user with optional caption. Alternatively, you can use input_message_content to send a message with the specified content instead of the animation. - - Attributes: - ID: ``0xb0700002`` - - Parameters: - type (``str``): - Type of the result, must be mpeg4_gif. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - mpeg4_url (``str``): - A valid URL for the MP4 file. File size must not exceed 1MB. - - thumb_url (``str``, optional): - Video width. - - mpeg4_width (``int`` ``32-bit``, optional): - Video height. - - mpeg4_height (``int`` ``32-bit``, optional): - Video duration. - - mpeg4_duration (``int`` ``32-bit``): - URL of the static thumbnail (jpeg or gif) for the result. - - title (``str``, optional): - Title for the result. - - caption (``str``, optional): - Caption of the MPEG-4 file to be sent, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup `, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent `, optional): - Content of the message to be sent instead of the video animation. - - """ - ID = 0xb0700002 - - def __init__(self, type: str, id: str, mpeg4_url: str, thumb_url: str, mpeg4_width: int = None, - mpeg4_height: int = None, mpeg4_duration: int = None, title: str = None, caption: str = None, - parse_mode: str = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.mpeg4_url = mpeg4_url # string - self.mpeg4_width = mpeg4_width # flags.0?int - self.mpeg4_height = mpeg4_height # flags.1?int - self.mpeg4_duration = mpeg4_duration # flags.2?int - self.thumb_url = thumb_url # string - self.title = title # flags.3?string - self.caption = caption # flags.4?string - self.parse_mode = parse_mode # flags.5?string - self.reply_markup = reply_markup # flags.6?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.7?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_photo.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_photo.py deleted file mode 100644 index 570bd55d..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_photo.py +++ /dev/null @@ -1,126 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.api import types -from pyrogram.client.style import HTML, Markdown -from pyrogram.client.types.object import Object - - -class InlineQueryResultPhoto(Object): - """Represents a link to a photo. By default, this photo will be sent by the user with optional caption. - Alternatively, you can use input_message_content to send a message with the specified content instead of the photo. - - Parameters: - id (``str``): - Unique identifier for this result, 1-64 bytes. - - photo_url (``str``): - A valid URL of the photo. Photo must be in jpeg format. Photo size must not exceed 5MB. - - thumb_url (``str``): - URL of the thumbnail for the photo. - - photo_width (``int``, *optional*): - Width of the photo. - - photo_height (``int``, *optional*): - Height of the photo. - - title (``str``, *optional*): - Title for the result. - - description (``str``, *optional*): - Short description of the result. - - caption (``str``, *optional*): - Caption of the photo to be sent, 0-200 characters. - - parse_mode (``str``, *optional*): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in - the media caption. - - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, *optional*): - Content of the message to be sent instead of the photo. - - """ - - def __init__( - self, - id: str, - photo_url: str, - thumb_url: str, - photo_width: int = 0, - photo_height: int = 0, - title: str = None, - description: str = None, - caption: str = "", - parse_mode: str = "", - reply_markup=None, - input_message_content=None - ): - self.id = id # string - self.photo_url = photo_url # string - self.thumb_url = thumb_url # string - self.photo_width = photo_width # flags.0?int - self.photo_height = photo_height # flags.1?int - self.title = title # flags.2?string - self.description = description # flags.3?string - self.caption = caption # flags.4?string - self.parse_mode = parse_mode # flags.5?string - self.reply_markup = reply_markup # flags.6?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.7?InputMessageContent - - self.style = HTML() if parse_mode.lower() == "html" else Markdown() - - def write(self): - return types.InputBotInlineResult( - id=self.id, - type="photo", - send_message=types.InputBotInlineMessageMediaAuto( - reply_markup=self.reply_markup.write() if self.reply_markup else None, - **self.style.parse(self.caption) - ), - title=self.title, - description=self.description, - url=self.photo_url, - thumb=types.InputWebDocument( - url=self.thumb_url, - size=0, - mime_type="image/jpeg", - attributes=[ - types.DocumentAttributeImageSize( - w=0, - h=0 - ) - ] - ), - content=types.InputWebDocument( - url=self.thumb_url, - size=0, - mime_type="image/jpeg", - attributes=[ - types.DocumentAttributeImageSize( - w=self.photo_width, - h=self.photo_height - ) - ] - ) - ) diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_venue.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_venue.py deleted file mode 100644 index 29eb86a6..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_venue.py +++ /dev/null @@ -1,86 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultVenue(Object): - """Represents a venue. By default, the venue will be sent by the user. Alternatively, you can use input_message_content to send a message with the specified content instead of the venue. - - Attributes: - ID: ``0xb0700008`` - - Parameters: - type (``str``): - Type of the result, must be venue. - - id (``str``): - Unique identifier for this result, 1-64 Bytes. - - latitude (``float`` ``64-bit``): - Latitude of the venue location in degrees. - - longitude (``float`` ``64-bit``): - Longitude of the venue location in degrees. - - title (``str``): - Title of the venue. - - address (``str``): - Address of the venue. - - foursquare_id (``str``, optional): - Foursquare identifier of the venue if known. - - foursquare_type (``str``, optional): - Foursquare type of the venue, if known. (For example, "arts_entertainment/default", "arts_entertainment/aquarium" or "food/icecream".). - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the venue. - - thumb_url (``str``, optional): - Url of the thumbnail for the result. - - thumb_width (``int`` ``32-bit``, optional): - Thumbnail width. - - thumb_height (``int`` ``32-bit``, optional): - Thumbnail height. - - """ - ID = 0xb0700008 - - def __init__(self, type: str, id: str, latitude: float, longitude: float, title: str, address: str, - foursquare_id: str = None, foursquare_type: str = None, reply_markup=None, input_message_content=None, - thumb_url: str = None, thumb_width: int = None, thumb_height: int = None): - self.type = type # string - self.id = id # string - self.latitude = latitude # double - self.longitude = longitude # double - self.title = title # string - self.address = address # string - self.foursquare_id = foursquare_id # flags.0?string - self.foursquare_type = foursquare_type # flags.1?string - self.reply_markup = reply_markup # flags.2?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.3?InputMessageContent - self.thumb_url = thumb_url # flags.4?string - self.thumb_width = thumb_width # flags.5?int - self.thumb_height = thumb_height # flags.6?int diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_video.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_video.py deleted file mode 100644 index 61984d48..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_video.py +++ /dev/null @@ -1,90 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultVideo(Object): - """Represents a link to a page containing an embedded video player or a video file. By default, this video file will be sent by the user with an optional caption. Alternatively, you can use input_message_content to send a message with the specified content instead of the video. - - Attributes: - ID: ``0xb0700003`` - - Parameters: - type (``str``): - Type of the result, must be video. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - video_url (``str``): - A valid URL for the embedded video player or video file. - - mime_type (``str``): - Mime type of the content of video url, "text/html" or "video/mp4". - - thumb_url (``str``): - URL of the thumbnail (jpeg only) for the video. - - title (``str``): - Title for the result. - - caption (``str``, optional): - Caption of the video to be sent, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - video_width (``int`` ``32-bit``, optional): - Video width. - - video_height (``int`` ``32-bit``, optional): - Video height. - - video_duration (``int`` ``32-bit``, optional): - Video duration in seconds. - - description (``str``, optional): - Short description of the result. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the video. This field is required if InlineQueryResultVideo is used to send an HTML-page as a result (e.g., a YouTube video). - - """ - ID = 0xb0700003 - - def __init__(self, type: str, id: str, video_url: str, mime_type: str, thumb_url: str, title: str, - caption: str = None, parse_mode: str = None, video_width: int = None, video_height: int = None, - video_duration: int = None, description: str = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.video_url = video_url # string - self.mime_type = mime_type # string - self.thumb_url = thumb_url # string - self.title = title # string - self.caption = caption # flags.0?string - self.parse_mode = parse_mode # flags.1?string - self.video_width = video_width # flags.2?int - self.video_height = video_height # flags.3?int - self.video_duration = video_duration # flags.4?int - self.description = description # flags.5?string - self.reply_markup = reply_markup # flags.6?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.7?InputMessageContent diff --git a/pyrogram/client/types/inline_mode/todo/inline_query_result_voice.py b/pyrogram/client/types/inline_mode/todo/inline_query_result_voice.py deleted file mode 100644 index 7a5f3cd1..00000000 --- a/pyrogram/client/types/inline_mode/todo/inline_query_result_voice.py +++ /dev/null @@ -1,69 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2018 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -from pyrogram.client.types.object import Object - - -class InlineQueryResultVoice(Object): - """Represents a link to a voice recording in an .ogg container encoded with OPUS. By default, this voice recording will be sent by the user. Alternatively, you can use input_message_content to send a message with the specified content instead of the the voice message. - - Attributes: - ID: ``0xb0700005`` - - Parameters: - type (``str``): - Type of the result, must be voice. - - id (``str``): - Unique identifier for this result, 1-64 bytes. - - voice_url (``str``): - A valid URL for the voice recording. - - title (``str``): - Recording title. - - caption (``str``, optional): - Caption, 0-200 characters. - - parse_mode (``str``, optional): - Send Markdown or HTML, if you want Telegram apps to show bold, italic, fixed-width text or inline URLs in the media caption. - - voice_duration (``int`` ``32-bit``, optional): - Recording duration in seconds. - - reply_markup (:obj:`InlineKeyboardMarkup`, optional): - Inline keyboard attached to the message. - - input_message_content (:obj:`InputMessageContent`, optional): - Content of the message to be sent instead of the voice recording. - - """ - ID = 0xb0700005 - - def __init__(self, type: str, id: str, voice_url: str, title: str, caption: str = None, parse_mode: str = None, - voice_duration: int = None, reply_markup=None, input_message_content=None): - self.type = type # string - self.id = id # string - self.voice_url = voice_url # string - self.title = title # string - self.caption = caption # flags.0?string - self.parse_mode = parse_mode # flags.1?string - self.voice_duration = voice_duration # flags.2?int - self.reply_markup = reply_markup # flags.3?InlineKeyboardMarkup - self.input_message_content = input_message_content # flags.4?InputMessageContent diff --git a/pyrogram/client/types/input_media/input_media.py b/pyrogram/client/types/input_media/input_media.py index 2b5d7f0f..9b89fe12 100644 --- a/pyrogram/client/types/input_media/input_media.py +++ b/pyrogram/client/types/input_media/input_media.py @@ -30,7 +30,6 @@ class InputMedia(Object): - :obj:`InputMediaPhoto` - :obj:`InputMediaVideo` """ - __slots__ = ["media", "caption", "parse_mode"] def __init__(self, media: str, caption: str, parse_mode: str): super().__init__() diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/client/types/input_media/input_media_animation.py index 23fcb967..dc70cbec 100644 --- a/pyrogram/client/types/input_media/input_media_animation.py +++ b/pyrogram/client/types/input_media/input_media_animation.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 . import InputMedia @@ -38,8 +40,11 @@ class InputMediaAnimation(InputMedia): Caption of the animation to be sent, 0-1024 characters parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline URLs - in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. width (``int``, *optional*): Animation width. @@ -51,14 +56,12 @@ class InputMediaAnimation(InputMedia): Animation duration. """ - __slots__ = ["thumb", "width", "height", "duration"] - def __init__( self, media: str, thumb: str = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, width: int = 0, height: int = 0, duration: int = 0 diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/client/types/input_media/input_media_audio.py index 3fb45d8f..5ed670a6 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/client/types/input_media/input_media_audio.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 . import InputMedia @@ -40,8 +42,11 @@ class InputMediaAudio(InputMedia): Caption of the audio to be sent, 0-1024 characters parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline URLs - in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of the audio in seconds @@ -53,14 +58,12 @@ class InputMediaAudio(InputMedia): Title of the audio """ - __slots__ = ["thumb", "duration", "performer", "title"] - def __init__( self, media: str, thumb: str = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, duration: int = 0, performer: int = "", title: str = "" diff --git a/pyrogram/client/types/input_media/input_media_document.py b/pyrogram/client/types/input_media/input_media_document.py index 0de8dedf..14756e02 100644 --- a/pyrogram/client/types/input_media/input_media_document.py +++ b/pyrogram/client/types/input_media/input_media_document.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 . import InputMedia @@ -38,18 +40,19 @@ class InputMediaDocument(InputMedia): Caption of the document to be sent, 0-1024 characters parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline URLs - in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. """ - __slots__ = ["thumb"] - def __init__( self, media: str, thumb: str = None, caption: str = "", - parse_mode: str = "" + parse_mode: Union[str, None] = object ): super().__init__(media, caption, parse_mode) diff --git a/pyrogram/client/types/input_media/input_media_photo.py b/pyrogram/client/types/input_media/input_media_photo.py index ce134af2..5e18cdd6 100644 --- a/pyrogram/client/types/input_media/input_media_photo.py +++ b/pyrogram/client/types/input_media/input_media_photo.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 . import InputMedia @@ -34,16 +36,17 @@ class InputMediaPhoto(InputMedia): Caption of the photo to be sent, 0-1024 characters parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline URLs - in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. """ - __slots__ = [] - def __init__( self, media: str, caption: str = "", - parse_mode: str = "" + parse_mode: Union[str, None] = object ): super().__init__(media, caption, parse_mode) diff --git a/pyrogram/client/types/input_media/input_media_video.py b/pyrogram/client/types/input_media/input_media_video.py index 9764dd1a..6b64caa8 100644 --- a/pyrogram/client/types/input_media/input_media_video.py +++ b/pyrogram/client/types/input_media/input_media_video.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 . import InputMedia @@ -40,8 +42,11 @@ class InputMediaVideo(InputMedia): Caption of the video to be sent, 0-1024 characters parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline URLs - in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. width (``int``, *optional*): Video width. @@ -56,14 +61,12 @@ class InputMediaVideo(InputMedia): Pass True, if the uploaded video is suitable for streaming. """ - __slots__ = ["thumb", "width", "height", "duration", "supports_streaming"] - def __init__( self, media: str, thumb: str = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, width: int = 0, height: int = 0, duration: int = 0, diff --git a/pyrogram/client/types/input_media/input_phone_contact.py b/pyrogram/client/types/input_media/input_phone_contact.py index 9c03694d..7498768d 100644 --- a/pyrogram/client/types/input_media/input_phone_contact.py +++ b/pyrogram/client/types/input_media/input_phone_contact.py @@ -37,8 +37,6 @@ class InputPhoneContact(Object): Contact's last name """ - __slots__ = [] - def __init__(self, phone: str, first_name: str, last_name: str = ""): super().__init__(None) diff --git a/pyrogram/client/types/input_message_content/input_message_content.py b/pyrogram/client/types/input_message_content/input_message_content.py index fe11ef7a..6561b5a8 100644 --- a/pyrogram/client/types/input_message_content/input_message_content.py +++ b/pyrogram/client/types/input_message_content/input_message_content.py @@ -31,7 +31,8 @@ class InputMessageContent(Object): - :obj:`InputTextMessageContent` """ - __slots__ = [] - def __init__(self): super().__init__() + + def write(self, reply_markup): + raise NotImplementedError diff --git a/pyrogram/client/types/input_message_content/input_text_message_content.py b/pyrogram/client/types/input_message_content/input_text_message_content.py index 4b294aab..3ab67d96 100644 --- a/pyrogram/client/types/input_message_content/input_text_message_content.py +++ b/pyrogram/client/types/input_message_content/input_text_message_content.py @@ -16,9 +16,11 @@ # 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.api import types from .input_message_content import InputMessageContent -from ...style import HTML, Markdown +from ...parser import Parser class InputTextMessageContent(InputMessageContent): @@ -29,16 +31,17 @@ class InputTextMessageContent(InputMessageContent): Text of the message to be sent, 1-4096 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline URLs - in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. """ - __slots__ = ["message_text", "parse_mode", "disable_web_page_preview"] - - def __init__(self, message_text: str, parse_mode: str = "", disable_web_page_preview: bool = None): + def __init__(self, message_text: str, parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None): super().__init__() self.message_text = message_text @@ -49,5 +52,5 @@ class InputTextMessageContent(InputMessageContent): return types.InputBotInlineMessageText( no_webpage=self.disable_web_page_preview or None, reply_markup=reply_markup.write() if reply_markup else None, - **(HTML() if self.parse_mode.lower() == "html" else Markdown()).parse(self.message_text) + **(Parser(None)).parse(self.message_text, self.parse_mode) ) diff --git a/pyrogram/client/types/messages_and_media/__init__.py b/pyrogram/client/types/messages_and_media/__init__.py index b9bcb460..f5db82e5 100644 --- a/pyrogram/client/types/messages_and_media/__init__.py +++ b/pyrogram/client/types/messages_and_media/__init__.py @@ -34,8 +34,9 @@ from .venue import Venue from .video import Video from .video_note import VideoNote from .voice import Voice +from .webpage import WebPage __all__ = [ "Animation", "Audio", "Contact", "Document", "Game", "Location", "Message", "MessageEntity", "Photo", "Thumbnail", - "StrippedThumbnail", "Poll", "PollOption", "Sticker", "Venue", "Video", "VideoNote", "Voice" + "StrippedThumbnail", "Poll", "PollOption", "Sticker", "Venue", "Video", "VideoNote", "Voice", "WebPage" ] diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py index 5441a114..ba6744ce 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -58,8 +58,6 @@ class Animation(Object): Animation thumbnails. """ - __slots__ = ["file_id", "file_name", "mime_type", "file_size", "date", "width", "height", "duration", "thumbs"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py index 3d9cf8a6..6d8a12e9 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -58,10 +58,6 @@ class Audio(Object): Thumbnails of the music file album cover. """ - __slots__ = [ - "file_id", "file_name", "mime_type", "file_size", "date", "duration", "performer", "title", "thumbs" - ] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py index d18f5e18..ad263397 100644 --- a/pyrogram/client/types/messages_and_media/contact.py +++ b/pyrogram/client/types/messages_and_media/contact.py @@ -42,8 +42,6 @@ class Contact(Object): Additional data about the contact in the form of a vCard. """ - __slots__ = ["phone_number", "first_name", "last_name", "user_id", "vcard"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py index 45994e16..4bb40980 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -49,8 +49,6 @@ class Document(Object): Document thumbnails as defined by sender. """ - __slots__ = ["file_id", "file_name", "mime_type", "file_size", "date", "thumbs"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/game.py b/pyrogram/client/types/messages_and_media/game.py index 2b400e65..38c00fdf 100644 --- a/pyrogram/client/types/messages_and_media/game.py +++ b/pyrogram/client/types/messages_and_media/game.py @@ -48,8 +48,6 @@ class Game(Object): Upload via BotFather. """ - __slots__ = ["id", "title", "short_name", "description", "photo", "animation"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py index 5af55f0f..4dec0277 100644 --- a/pyrogram/client/types/messages_and_media/location.py +++ b/pyrogram/client/types/messages_and_media/location.py @@ -33,8 +33,6 @@ class Location(Object): Latitude as defined by sender. """ - __slots__ = ["longitude", "latitude"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index c6f3cc77..7616a6ec 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -31,32 +31,31 @@ from ..object import Object from ..update import Update from ..user_and_chats.chat import Chat from ..user_and_chats.user import User +from ...ext import utils +from ...parser import utils as parser_utils, Parser class Str(str): def __init__(self, *args): super().__init__() - self._client = None - self._entities = None + self.entities = None - def init(self, client, entities): - self._client = client - self._entities = entities + def init(self, entities): + self.entities = entities return self - @property - def text(self): - return self - @property def markdown(self): - return self._client.markdown.unparse(self, self._entities) + return Parser.unparse(self, self.entities, False) @property def html(self): - return self._client.html.unparse(self, self._entities) + return Parser.unparse(self, self.entities, True) + + def __getitem__(self, item): + return parser_utils.remove_surrogates(parser_utils.add_surrogates(self)[item]) class Message(Object, Update): @@ -179,11 +178,8 @@ class Message(Object, Update): venue (:obj:`Venue`, *optional*): Message is a venue, information about the venue. - web_page (``bool``, *optional*): + web_page (:obj:`WebPage`, *optional*): Message was sent with a webpage preview. - **Note:** Support for web pages is still basic; a simple boolean is set in case the message contains a - web page preview. In future versions this property could turn into a full web page object that contains - more details. poll (:obj:`Poll`, *optional*): Message is a native poll, information about the poll. @@ -268,17 +264,6 @@ class Message(Object, Update): # TODO: Add game missing field. Also invoice, successful_payment, connected_website - __slots__ = [ - "message_id", "date", "chat", "from_user", "forward_from", "forward_sender_name", "forward_from_chat", - "forward_from_message_id", "forward_signature", "forward_date", "reply_to_message", "mentioned", "empty", - "service", "media", "edit_date", "media_group_id", "author_signature", "text", "entities", "caption_entities", - "audio", "document", "photo", "sticker", "animation", "game", "video", "voice", "video_note", "caption", - "contact", "location", "venue", "web_page", "poll", "new_chat_members", "left_chat_member", "new_chat_title", - "new_chat_photo", "delete_chat_photo", "group_chat_created", "supergroup_chat_created", "channel_chat_created", - "migrate_to_chat_id", "migrate_from_chat_id", "pinned_message", "game_high_score", "views", "via_bot", - "outgoing", "matches", "command", "reply_markup" - ] - def __init__( self, *, @@ -451,7 +436,7 @@ class Message(Object, Update): new_chat_title=new_chat_title, new_chat_photo=new_chat_photo, delete_chat_photo=delete_chat_photo, - migrate_to_chat_id=int("-100" + str(migrate_to_chat_id)) if migrate_to_chat_id else None, + migrate_to_chat_id=utils.get_channel_id(migrate_to_chat_id) if migrate_to_chat_id else None, migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None, group_chat_created=group_chat_created, channel_chat_created=channel_chat_created, @@ -486,7 +471,7 @@ class Message(Object, Update): if isinstance(message, types.Message): entities = [MessageEntity._parse(client, entity, users) for entity in message.entities] - entities = list(filter(lambda x: x is not None, entities)) + entities = pyrogram.List(filter(lambda x: x is not None, entities)) forward_from = None forward_sender_name = None @@ -577,10 +562,14 @@ class Message(Object, Update): else: document = pyrogram.Document._parse(client, doc, file_name) elif isinstance(media, types.MessageMediaWebPage): - web_page = True - media = None + if isinstance(media.webpage, types.WebPage): + web_page = pyrogram.WebPage._parse(client, media.webpage) + else: + media = None + elif isinstance(media, types.MessageMediaPoll): poll = pyrogram.Poll._parse(client, media) + else: media = None @@ -603,10 +592,26 @@ class Message(Object, Update): date=message.date, chat=Chat._parse(client, message, users, chats), from_user=User._parse(client, users.get(message.from_id, None)), - text=Str(message.message).init(client, entities) or None if media is None else None, - caption=Str(message.message).init(client, entities) or None if media is not None else None, - entities=entities or None if media is None else None, - caption_entities=entities or None if media is not None else None, + text=( + Str(message.message).init(entities) or None + if media is None or web_page is not None + else None + ), + caption=( + Str(message.message).init(entities) or None + if media is not None and web_page is None + else None + ), + entities=( + entities or None + if media is None or web_page is not None + else None + ), + caption_entities=( + entities or None + if media is not None and web_page is None + else None + ), author_signature=message.post_author, forward_from=forward_from, forward_sender_name=forward_sender_name, @@ -651,17 +656,17 @@ class Message(Object, Update): return parsed_message - def reply( + def reply_text( self, text: str, quote: bool = None, - parse_mode: str = "", + parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None, disable_notification: bool = None, reply_to_message_id: int = None, reply_markup=None ) -> "Message": - """Bound method *reply* of :obj:`Message`. + """Bound method *reply_text* of :obj:`Message`. Use as a shortcut for: @@ -676,7 +681,7 @@ class Message(Object, Update): Example: .. code-block:: python - message.reply("hello", quote=True) + message.reply_text("hello", quote=True) Parameters: text (``str``): @@ -688,8 +693,11 @@ class Message(Object, Update): Defaults to ``True`` in group chats and ``False`` in private chats. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. @@ -727,12 +735,14 @@ class Message(Object, Update): reply_markup=reply_markup ) + reply = reply_text + def reply_animation( self, animation: str, quote: bool = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, duration: int = 0, width: int = 0, height: int = 0, @@ -780,8 +790,11 @@ class Message(Object, Update): Animation caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of sent animation in seconds. @@ -810,23 +823,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -866,7 +878,7 @@ class Message(Object, Update): audio: str, quote: bool = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, duration: int = 0, performer: str = None, title: str = None, @@ -914,8 +926,11 @@ class Message(Object, Update): Audio caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of the audio in seconds. @@ -944,23 +959,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -1000,7 +1014,7 @@ class Message(Object, Update): file_id: str, quote: bool = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ @@ -1040,8 +1054,11 @@ class Message(Object, Update): Media caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_notification (``bool``, *optional*): Sends the message silently. @@ -1205,7 +1222,7 @@ class Message(Object, Update): quote: bool = None, thumb: str = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ @@ -1255,8 +1272,11 @@ class Message(Object, Update): Document caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_notification (``bool``, *optional*): Sends the message silently. @@ -1270,23 +1290,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -1597,7 +1616,7 @@ class Message(Object, Update): photo: str, quote: bool = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, ttl_seconds: int = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -1642,8 +1661,11 @@ class Message(Object, Update): Photo caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. ttl_seconds (``int``, *optional*): Self-Destruct Timer. @@ -1662,23 +1684,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -1840,23 +1861,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -1988,7 +2008,7 @@ class Message(Object, Update): video: str, quote: bool = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, duration: int = 0, width: int = 0, height: int = 0, @@ -2037,8 +2057,11 @@ class Message(Object, Update): Video caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of sent video in seconds. @@ -2070,23 +2093,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -2192,23 +2214,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -2245,7 +2266,7 @@ class Message(Object, Update): voice: str, quote: bool = None, caption: str = "", - parse_mode: str = "", + parse_mode: Union[str, None] = object, duration: int = 0, disable_notification: bool = None, reply_to_message_id: int = None, @@ -2290,8 +2311,11 @@ class Message(Object, Update): Voice message caption, 0-1024 characters. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your caption. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. duration (``int``, *optional*): Duration of the voice message in seconds. @@ -2308,23 +2332,22 @@ class Message(Object, Update): instructions to remove reply keyboard or to force a reply from the user. progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - current (``int``): - The amount of bytes uploaded so far. + The amount of bytes transmitted so far. total (``int``): - The size of the file. + The total size of the file. *args (``tuple``, *optional*): Extra custom arguments as defined in the *progress_args* parameter. @@ -2356,19 +2379,14 @@ class Message(Object, Update): progress_args=progress_args ) - def edit( + def edit_text( self, text: str, - parse_mode: str = "", + parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None, - reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" - ] = None + reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "Message": - """Bound method *edit* of :obj:`Message`. + """Bound method *edit_text* of :obj:`Message`. Use as a shortcut for: @@ -2383,15 +2401,18 @@ class Message(Object, Update): Example: .. code-block:: python - message.edit("hello") + message.edit_text("hello") Parameters: text (``str``): New text of the message. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. @@ -2414,16 +2435,13 @@ class Message(Object, Update): reply_markup=reply_markup ) + edit = edit_text + def edit_caption( self, caption: str, - parse_mode: str = "", - reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" - ] = None + parse_mode: Union[str, None] = object, + reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> "Message": """Bound method *edit_caption* of :obj:`Message`. @@ -2447,8 +2465,11 @@ class Message(Object, Update): New caption of the message. parse_mode (``str``, *optional*): - Pass "markdown" or "html" if you want Telegram apps to show bold, italic, fixed-width text or inline - URLs in your message. Defaults to "markdown". + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. @@ -2486,7 +2507,7 @@ class Message(Object, Update): message.edit_media(media) Parameters: - media (:obj:`InputMediaAnimation` | :obj:`InputMediaAudio` | :obj:`InputMediaDocument` | :obj:`InputMediaPhoto` | :obj:`InputMediaVideo`) + media (:obj:`InputMedia`): One of the InputMedia objects describing an animation, audio, document, photo or video. reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): @@ -2596,9 +2617,6 @@ class Message(Object, Update): if self.game and not self._client.is_bot: raise ValueError("Users cannot send messages with Game media type") - # TODO: Improve markdown parser. Currently html appears to be more stable, thus we use it here because users - # can"t choose. - if self.text: return self._client.send_message( chat_id, @@ -2608,7 +2626,7 @@ class Message(Object, Update): disable_notification=disable_notification ) elif self.media: - caption = self.caption.html if self.caption and not remove_caption else None + caption = self.caption.html if self.caption and not remove_caption else "" send_media = partial( self._client.send_cached_media, @@ -2846,6 +2864,37 @@ class Message(Object, Update): else: self.reply(button, quote=quote) + def retract_vote( + self, + ) -> "pyrogram.Poll": + """Bound method *retract_vote* of :obj:`Message`. + + Use as a shortcut for: + + .. code-block:: python + + client.retract_vote( + chat_id=message.chat.id, + message_id=message_id, + ) + + Example: + .. code-block:: python + + message.retract_vote() + + Returns: + :obj:`Poll`: On success, the poll with the retracted vote is returned. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.retract_vote( + chat_id=self.chat.id, + message_id=self.message_id + ) + def download( self, file_name: str = "", @@ -2877,14 +2926,27 @@ class Message(Object, Update): Blocks the code execution until the file has been downloaded. Defaults to True. - progress (``callable``): - Pass a callback function to view the download progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). + progress (``callable``, *optional*): + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. - progress_args (``tuple``): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. + + Other Parameters: + current (``int``): + The amount of bytes transmitted so far. + + total (``int``): + The total size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. Returns: On success, the absolute path of the downloaded file as string is returned, None otherwise. @@ -2901,6 +2963,44 @@ class Message(Object, Update): progress_args=progress_args, ) + def vote( + self, + option: int, + ) -> "pyrogram.Poll": + """Bound method *vote* of :obj:`Message`. + + Use as a shortcut for: + + .. code-block:: python + + client.vote_poll( + chat_id=message.chat.id, + message_id=message.message_id, + option=1 + ) + + Example: + .. code-block:: python + + message.vote(6) + + Parameters: + option (``int``): + Index of the poll option you want to vote for (0 to 9). + + Returns: + :obj:`Poll`: On success, the poll with the chosen option is returned. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.vote_poll( + chat_id=self.chat.id, + message_id=self.message_id, + option=option + ) + def pin(self, disable_notification: bool = None) -> "Message": """Bound method *pin* of :obj:`Message`. diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py index 420bd914..63aeb447 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -47,8 +47,6 @@ class MessageEntity(Object): For "text_mention" only, the mentioned user. """ - __slots__ = ["type", "offset", "length", "url", "user"] - ENTITIES = { types.MessageEntityMention.ID: "mention", types.MessageEntityHashtag.ID: "hashtag", @@ -60,6 +58,9 @@ class MessageEntity(Object): types.MessageEntityItalic.ID: "italic", types.MessageEntityCode.ID: "code", types.MessageEntityPre.ID: "pre", + types.MessageEntityUnderline.ID: "underline", + types.MessageEntityStrike.ID: "strike", + types.MessageEntityBlockquote.ID: "blockquote", types.MessageEntityTextUrl.ID: "text_link", types.MessageEntityMentionName.ID: "text_mention", types.MessageEntityPhone.ID: "phone_number" diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index 653fe4c0..8ccaaf19 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -49,8 +49,6 @@ class Photo(Object): Available thumbnails of this photo. """ - __slots__ = ["file_id", "width", "height", "file_size", "date", "thumbs"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/poll.py b/pyrogram/client/types/messages_and_media/poll.py index 2570fdf1..fecc5f7d 100644 --- a/pyrogram/client/types/messages_and_media/poll.py +++ b/pyrogram/client/types/messages_and_media/poll.py @@ -48,8 +48,6 @@ class Poll(Object, Update): Index of your chosen option (0-9), None in case you haven't voted yet. """ - __slots__ = ["id", "question", "options", "is_closed", "total_voters", "chosen_option"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/poll_option.py b/pyrogram/client/types/messages_and_media/poll_option.py index 35f6b071..2882860a 100644 --- a/pyrogram/client/types/messages_and_media/poll_option.py +++ b/pyrogram/client/types/messages_and_media/poll_option.py @@ -35,8 +35,6 @@ class PollOption(Object): The data this poll option is holding. """ - __slots__ = ["text", "voter_count", "data"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index 3c171543..cb5c34b2 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -41,6 +41,9 @@ class Sticker(Object): height (``int``): Sticker height. + is_animated (``bool``): + True, if the sticker is animated + file_name (``str``, *optional*): Sticker file name. @@ -65,10 +68,6 @@ class Sticker(Object): # TODO: Add mask position - __slots__ = [ - "file_id", "file_name", "mime_type", "file_size", "date", "width", "height", "emoji", "set_name", "thumbs" - ] - def __init__( self, *, @@ -76,6 +75,7 @@ class Sticker(Object): file_id: str, width: int, height: int, + is_animated: bool, file_name: str = None, mime_type: str = None, file_size: int = None, @@ -93,8 +93,9 @@ class Sticker(Object): self.date = date self.width = width self.height = height + self.is_animated = is_animated self.emoji = emoji - self.set_name = set_name, + self.set_name = set_name self.thumbs = thumbs # self.mask_position = mask_position @@ -134,8 +135,9 @@ class Sticker(Object): sticker.access_hash ) ), - width=image_size_attributes.w if image_size_attributes else 0, - height=image_size_attributes.h if image_size_attributes else 0, + width=image_size_attributes.w if image_size_attributes else 512, + height=image_size_attributes.h if image_size_attributes else 512, + is_animated=sticker.mime_type == "application/x-tgsticker", # TODO: mask_position set_name=set_name, emoji=sticker_attributes.alt or None, diff --git a/pyrogram/client/types/messages_and_media/stripped_thumbnail.py b/pyrogram/client/types/messages_and_media/stripped_thumbnail.py index 1c967042..ea24e071 100644 --- a/pyrogram/client/types/messages_and_media/stripped_thumbnail.py +++ b/pyrogram/client/types/messages_and_media/stripped_thumbnail.py @@ -29,8 +29,6 @@ class StrippedThumbnail(Object): Thumbnail data """ - __slots__ = ["data"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/thumbnail.py b/pyrogram/client/types/messages_and_media/thumbnail.py index ee173b1c..936241c6 100644 --- a/pyrogram/client/types/messages_and_media/thumbnail.py +++ b/pyrogram/client/types/messages_and_media/thumbnail.py @@ -43,8 +43,6 @@ class Thumbnail(Object): File size. """ - __slots__ = ["file_id", "width", "height", "file_size"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py index 45d9368f..419af318 100644 --- a/pyrogram/client/types/messages_and_media/venue.py +++ b/pyrogram/client/types/messages_and_media/venue.py @@ -44,8 +44,6 @@ class Venue(Object): """ - __slots__ = ["location", "title", "address", "foursquare_id", "foursquare_type"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index 0a7f47cd..d9c2c37f 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -61,11 +61,6 @@ class Video(Object): Video thumbnails. """ - __slots__ = [ - "file_id", "width", "height", "duration", "file_name", "mime_type", "supports_streaming", "file_size", "date", - "thumbs" - ] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py index 54c9ec8d..e419d692 100644 --- a/pyrogram/client/types/messages_and_media/video_note.py +++ b/pyrogram/client/types/messages_and_media/video_note.py @@ -52,8 +52,6 @@ class VideoNote(Object): Video thumbnails. """ - __slots__ = ["file_id", "mime_type", "file_size", "date", "length", "duration", "thumbs"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/client/types/messages_and_media/voice.py index e4256197..0f480ad5 100644 --- a/pyrogram/client/types/messages_and_media/voice.py +++ b/pyrogram/client/types/messages_and_media/voice.py @@ -47,8 +47,6 @@ class Voice(Object): Date the voice was sent in Unix time. """ - __slots__ = ["file_id", "duration", "waveform", "mime_type", "file_size", "date"] - def __init__( self, *, diff --git a/pyrogram/client/types/messages_and_media/webpage.py b/pyrogram/client/types/messages_and_media/webpage.py new file mode 100644 index 00000000..d65d34d8 --- /dev/null +++ b/pyrogram/client/types/messages_and_media/webpage.py @@ -0,0 +1,186 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2019 Dan Tès +# +# This file is part of Pyrogram. +# +# Pyrogram is free software: you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Pyrogram is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . + +import pyrogram +from pyrogram.api import types +from ..object import Object + + +class WebPage(Object): + # TODO: hash, cached_page + """A webpage preview + + Parameters: + id (``str``): + Unique identifier for this webpage. + + url (``str``): + Full URL for this webpage. + + display_url (``str``): + Display URL for this webpage. + + type (``str``, *optional*): + Type of webpage preview, known types (at the time of writing) are: + *"article"*, *"photo"*, *"gif"*, *"video"* and *"document"*, + *"telegram_user"*, *"telegram_bot"*, *"telegram_channel"*, *"telegram_megagroup"*. + + site_name (``str``, *optional*): + Webpage site name. + + title (``str``, *optional*): + Title of this webpage. + + description (``str``, *optional*): + Description of this webpage. + + audio (:obj:`Audio`, *optional*): + Webpage preview is an audio file, information about the file. + + document (:obj:`Document`, *optional*): + Webpage preview is a general file, information about the file. + + photo (:obj:`Photo`, *optional*): + Webpage preview is a photo, information about the photo. + + animation (:obj:`Animation`, *optional*): + Webpage preview is an animation, information about the animation. + + video (:obj:`Video`, *optional*): + Webpage preview is a video, information about the video. + + embed_url (``str``, *optional*): + Embedded content URL. + + embed_type (``str``, *optional*): + Embedded content type, like `iframe` + + embed_width (``int``, *optional*): + Embedded content width. + + embed_height (``int``, *optional*): + Embedded content height. + + duration (``int``, *optional*): + Uknown at the time of writing. + + author (``str``, *optional*): + Author of the webpage, eg the Twitter user for a tweet, or the author in an article. + """ + + def __init__( + self, + *, + client: "pyrogram.BaseClient" = None, + id: str, + url: str, + display_url: str, + type: str = None, + site_name: str = None, + title: str = None, + description: str = None, + audio: "pyrogram.Audio" = None, + document: "pyrogram.Document" = None, + photo: "pyrogram.Photo" = None, + animation: "pyrogram.Animation" = None, + video: "pyrogram.Video" = None, + embed_url: str = None, + embed_type: str = None, + embed_width: int = None, + embed_height: int = None, + duration: int = None, + author: str = None + ) -> "pyrogram.WebPage": + super().__init__(client) + + self.id = id + self.url = url + self.display_url = display_url + self.type = type + self.site_name = site_name + self.title = title + self.description = description + self.audio = audio + self.document = document + self.photo = photo + self.animation = animation + self.video = video + self.embed_url = embed_url + self.embed_type = embed_type + self.embed_width = embed_width + self.embed_height = embed_height + self.duration = duration + self.author = author + + @staticmethod + def _parse(client, webpage: types.WebPage) -> "WebPage": + audio = None + document = None + photo = None + animation = None + video = None + + if isinstance(webpage.photo, types.Photo): + photo = pyrogram.Photo._parse(client, webpage.photo) + + doc = webpage.document + + if isinstance(doc, types.Document): + attributes = {type(i): i for i in doc.attributes} + + file_name = getattr( + attributes.get( + types.DocumentAttributeFilename, None + ), "file_name", None + ) + + if types.DocumentAttributeAudio in attributes: + audio_attributes = attributes[types.DocumentAttributeAudio] + audio = pyrogram.Audio._parse(client, doc, audio_attributes, file_name) + + elif types.DocumentAttributeAnimated in attributes: + video_attributes = attributes.get(types.DocumentAttributeVideo, None) + animation = pyrogram.Animation._parse(client, doc, video_attributes, file_name) + + elif types.DocumentAttributeVideo in attributes: + video_attributes = attributes[types.DocumentAttributeVideo] + video = pyrogram.Video._parse(client, doc, video_attributes, file_name) + + else: + document = pyrogram.Document._parse(client, doc, file_name) + + return WebPage( + id=str(webpage.id), + url=webpage.url, + display_url=webpage.display_url, + type=webpage.type, + site_name=webpage.site_name, + title=webpage.title, + description=webpage.description, + audio=audio, + document=document, + photo=photo, + animation=animation, + video=video, + embed_url=webpage.embed_url, + embed_type=webpage.embed_type, + embed_width=webpage.embed_width, + embed_height=webpage.embed_height, + duration=webpage.duration, + author=webpage.author + ) diff --git a/pyrogram/client/types/object.py b/pyrogram/client/types/object.py index 4d482e63..5978203f 100644 --- a/pyrogram/client/types/object.py +++ b/pyrogram/client/types/object.py @@ -29,8 +29,6 @@ class Meta(type, metaclass=type("", (type,), {"__str__": lambda _: "~hi"})): class Object(metaclass=Meta): - __slots__ = ["_client"] - def __init__(self, client: "pyrogram.BaseClient" = None): self._client = client @@ -50,7 +48,7 @@ class Object(metaclass=Meta): else (attr, str(datetime.fromtimestamp(getattr(obj, attr)))) if attr.endswith("date") else (attr, getattr(obj, attr)) - for attr in obj.__slots__ + for attr in filter(lambda x: not x.startswith("_"), obj.__dict__) if getattr(obj, attr) is not None ] ) @@ -63,13 +61,13 @@ class Object(metaclass=Meta): self.__class__.__name__, ", ".join( "{}={}".format(attr, repr(getattr(self, attr))) - for attr in self.__slots__ + for attr in filter(lambda x: not x.startswith("_"), self.__dict__) if getattr(self, attr) is not None ) ) def __eq__(self, other: "Object") -> bool: - for attr in self.__slots__: + for attr in self.__dict__: try: if getattr(self, attr) != getattr(other, attr): return False diff --git a/pyrogram/client/types/update.py b/pyrogram/client/types/update.py index 48179ac0..2ec22f5a 100644 --- a/pyrogram/client/types/update.py +++ b/pyrogram/client/types/update.py @@ -26,8 +26,6 @@ class ContinuePropagation(StopIteration): class Update: - __slots__ = [] - def stop_propagation(self): raise StopPropagation diff --git a/pyrogram/client/types/user_and_chats/__init__.py b/pyrogram/client/types/user_and_chats/__init__.py index 922ac86a..29c45e09 100644 --- a/pyrogram/client/types/user_and_chats/__init__.py +++ b/pyrogram/client/types/user_and_chats/__init__.py @@ -23,8 +23,7 @@ from .chat_photo import ChatPhoto from .chat_preview import ChatPreview from .dialog import Dialog from .user import User -from .user_status import UserStatus __all__ = [ - "Chat", "ChatMember", "ChatPermissions", "ChatPhoto", "ChatPreview", "Dialog", "User", "UserStatus" + "Chat", "ChatMember", "ChatPermissions", "ChatPhoto", "ChatPreview", "Dialog", "User" ] diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 2d88d3ed..546485f6 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -23,6 +23,7 @@ from pyrogram.api import types from .chat_permissions import ChatPermissions from .chat_photo import ChatPhoto from ..object import Object +from ...ext import utils class Chat(Object): @@ -91,15 +92,9 @@ class Chat(Object): This field is available only in case *is_restricted* is True. permissions (:obj:`ChatPermissions` *optional*): - Information about the chat default permissions, for groups and supergroups. + Default chat member permissions, for groups and supergroups. """ - __slots__ = [ - "id", "type", "is_verified", "is_restricted", "is_scam", "is_support", "title", "username", "first_name", - "last_name", "photo", "description", "invite_link", "pinned_message", "sticker_set_name", "can_set_sticker_set", - "members_count", "restriction_reason", "permissions" - ] - def __init__( self, *, @@ -180,7 +175,7 @@ class Chat(Object): @staticmethod def _parse_channel_chat(client, channel: types.Channel) -> "Chat": - peer_id = int("-100" + str(channel.id)) + peer_id = utils.get_channel_id(channel.id) return Chat( id=peer_id, @@ -236,6 +231,7 @@ class Chat(Object): if isinstance(full_chat, types.ChatFull): parsed_chat = Chat._parse_chat_chat(client, chat) + parsed_chat.description = full_chat.about or None if isinstance(full_chat.participants, types.ChatParticipants): parsed_chat.members_count = len(full_chat.participants.participants) @@ -312,3 +308,409 @@ class Chat(Object): """ return self._client.unarchive_chats(self.id) + + # TODO: Remove notes about "All Members Are Admins" for basic groups, the attribute doesn't exist anymore + def set_title(self, title: str) -> bool: + """Bound method *set_title* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.set_chat_title( + chat_id=chat_id, + title=title + ) + + Example: + .. code-block:: python + + chat.set_title("Lounge") + + Note: + In regular groups (non-supergroups), this method will only work if the "All Members Are Admins" + setting is off. + + Parameters: + title (``str``): + New chat title, 1-255 characters. + + Returns: + ``bool``: True on success. + + Raises: + RPCError: In case of Telegram RPC error. + ValueError: In case a chat_id belongs to user. + """ + + return self._client.set_chat_title( + chat_id=self.id, + title=title + ) + + def set_description(self, description: str) -> bool: + """Bound method *set_description* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.set_chat_description( + chat_id=chat_id, + description=description + ) + + Example: + .. code-block:: python + + chat.set_chat_description("Don't spam!") + + Parameters: + description (``str``): + New chat description, 0-255 characters. + + Returns: + ``bool``: True on success. + + Raises: + RPCError: In case of Telegram RPC error. + ValueError: If a chat_id doesn't belong to a supergroup or a channel. + """ + + return self._client.set_chat_description( + chat_id=self.id, + description=description + ) + + def set_photo(self, photo: str) -> bool: + """Bound method *set_photo* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.set_chat_photo( + chat_id=chat_id, + photo=photo + ) + + Example: + .. code-block:: python + + chat.set_photo("photo.png") + + Parameters: + photo (``str``): + New chat photo. You can pass a :obj:`Photo` id or a file path to upload a new photo. + + Returns: + ``bool``: True on success. + + Raises: + RPCError: In case of a Telegram RPC error. + ValueError: if a chat_id belongs to user. + """ + + return self._client.set_chat_photo( + chat_id=self.id, + photo=photo + ) + + def kick_member( + self, + user_id: Union[int, str], + until_date: int = 0 + ) -> Union["pyrogram.Message", bool]: + """Bound method *kick_member* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.kick_chat_member( + chat_id=chat_id, + user_id=user_id + ) + + Example: + .. code-block:: python + + chat.kick_member(123456789) + + Note: + In regular groups (non-supergroups), this method will only work if the "All Members Are Admins" setting is + off in the target group. Otherwise members may only be removed by the group's creator or by the member + that added them. + + Parameters: + user_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target user. + For a contact that exists in your Telegram address book you can use his phone number (str). + + until_date (``int``, *optional*): + Date when the user will be unbanned, unix time. + If user is banned for more than 366 days or less than 30 seconds from the current time they are + considered to be banned forever. Defaults to 0 (ban forever). + + Returns: + :obj:`Message` | ``bool``: On success, a service message will be returned (when applicable), otherwise, in + case a message object couldn't be returned, True is returned. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.kick_chat_member( + chat_id=self.id, + user_id=user_id, + until_date=until_date + ) + + def unban_member( + self, + user_id: Union[int, str] + ) -> bool: + """Bound method *unban_member* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.unban_chat_member( + chat_id=chat_id, + user_id=user_id + ) + + Example: + .. code-block:: python + + chat.unban_member(123456789) + + Parameters: + user_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target user. + For a contact that exists in your Telegram address book you can use his phone number (str). + + Returns: + ``bool``: True on success. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.unban_chat_member( + chat_id=self.id, + user_id=user_id, + ) + + def restrict_member( + self, + user_id: Union[int, str], + until_date: int = 0, + can_send_messages: bool = False, + can_send_media_messages: bool = False, + can_send_other_messages: bool = False, + can_add_web_page_previews: bool = False, + can_send_polls: bool = False, + can_change_info: bool = False, + can_invite_users: bool = False, + can_pin_messages: bool = False + ) -> "pyrogram.Chat": + """Bound method *unban_member* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.restrict_chat_member( + chat_id=chat_id, + user_id=user_id + ) + + Example: + .. code-block:: python + + chat.restrict_member(123456789) + + Parameters: + user_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target user. + For a contact that exists in your Telegram address book you can use his phone number (str). + + until_date (``int``, *optional*): + Date when the user will be unbanned, unix time. + If user is banned for more than 366 days or less than 30 seconds from the current time they are + considered to be banned forever. Defaults to 0 (ban forever). + + can_send_messages (``bool``, *optional*): + Pass True, if the user can send text messages, contacts, locations and venues. + + can_send_media_messages (``bool``, *optional*): + Pass True, if the user can send audios, documents, photos, videos, video notes and voice notes, + implies can_send_messages. + + can_send_other_messages (``bool``, *optional*): + Pass True, if the user can send animations, games, stickers and use inline bots, + implies can_send_media_messages. + + can_add_web_page_previews (``bool``, *optional*): + Pass True, if the user may add web page previews to their messages, implies can_send_media_messages. + + can_send_polls (``bool``, *optional*): + Pass True, if the user can send polls, implies can_send_media_messages. + + can_change_info (``bool``, *optional*): + Pass True, if the user can change the chat title, photo and other settings. + + can_invite_users (``bool``, *optional*): + Pass True, if the user can invite new users to the chat. + + can_pin_messages (``bool``, *optional*): + Pass True, if the user can pin messages. + + Returns: + :obj:`Chat`: On success, a chat object is returned. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.restrict_chat_member( + chat_id=self.id, + user_id=user_id, + until_date=until_date, + can_send_messages=can_send_messages, + can_send_media_messages=can_send_media_messages, + can_send_other_messages=can_send_other_messages, + can_add_web_page_previews=can_add_web_page_previews, + can_send_polls=can_send_polls, + can_change_info=can_change_info, + can_invite_users=can_invite_users, + can_pin_messages=can_pin_messages + ) + + def promote_member( + self, + user_id: Union[int, str], + can_change_info: bool = True, + can_post_messages: bool = False, + can_edit_messages: bool = False, + can_delete_messages: bool = True, + can_restrict_members: bool = True, + can_invite_users: bool = True, + can_pin_messages: bool = False, + can_promote_members: bool = False + ) -> bool: + """Bound method *promote_member* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.promote_chat_member( + chat_id=chat_id, + user_id=user_id + ) + + Example: + + .. code-block:: python + + chat.promote_member(123456789) + + Parameters: + user_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target user. + For a contact that exists in your Telegram address book you can use his phone number (str). + + can_change_info (``bool``, *optional*): + Pass True, if the administrator can change chat title, photo and other settings. + + can_post_messages (``bool``, *optional*): + Pass True, if the administrator can create channel posts, channels only. + + can_edit_messages (``bool``, *optional*): + Pass True, if the administrator can edit messages of other users and can pin messages, channels only. + + can_delete_messages (``bool``, *optional*): + Pass True, if the administrator can delete messages of other users. + + can_restrict_members (``bool``, *optional*): + Pass True, if the administrator can restrict, ban or unban chat members. + + can_invite_users (``bool``, *optional*): + Pass True, if the administrator can invite new users to the chat. + + can_pin_messages (``bool``, *optional*): + Pass True, if the administrator can pin messages, supergroups only. + + can_promote_members (``bool``, *optional*): + Pass True, if the administrator can add new administrators with a subset of his own privileges or + demote administrators that he has promoted, directly or indirectly (promoted by administrators that + were appointed by him). + + Returns: + ``bool``: True on success. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.promote_chat_member( + chat_id=self.id, + user_id=user_id, + can_change_info=can_change_info, + can_post_messages=can_post_messages, + can_edit_messages=can_edit_messages, + can_delete_messages=can_delete_messages, + can_restrict_members=can_restrict_members, + can_invite_users=can_invite_users, + can_pin_messages=can_pin_messages, + can_promote_members=can_promote_members + ) + + def join(self): + """Bound method *join* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.join_chat(123456789) + + Example: + .. code-block:: python + + chat.join() + + Note: + This only works for public groups and channels that have set a username. + + Returns: + :obj:`Chat`: On success, a chat object is returned. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.join_chat(self.username) + + def leave(self): + """Bound method *leave* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.leave_chat(123456789) + + Example: + .. code-block:: python + + chat.leave() + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.leave_chat(self.id) diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index 7451012c..812a3204 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -33,11 +33,13 @@ class ChatMember(Object): The member's status in the chat. Can be "creator", "administrator", "member", "restricted", "left" or "kicked". - date (``int``, *optional*): - Date when the user joined, unix time. Not available for creator. + until_date (``int``, *optional*): + Restricted and kicked only. + Date when restrictions will be lifted for this user; unix time. - is_member (``bool``, *optional*): - Restricted only. True, if the user is a member of the chat at the moment of the request. + joined_date (``int``, *optional*): + Date when the user joined, unix time. + Not available for creator. invited_by (:obj:`User`, *optional*): Administrators and self member only. Information about the user who invited this member. @@ -49,12 +51,67 @@ class ChatMember(Object): restricted_by (:obj:`User`, *optional*): Restricted and kicked only. Information about the user who restricted or kicked this member. - permissions (:obj:`ChatPermissions` *optional*): - Administrators, restricted and kicked members only. - Information about the member permissions. - """ + is_member (``bool``, *optional*): + Restricted only. True, if the user is a member of the chat at the moment of the request. - __slots__ = ["user", "status", "date", "is_member", "invited_by", "promoted_by", "restricted_by", "permissions"] + can_be_edited (``bool``, *optional*): + Administrators only. + True, if you are allowed to edit administrator privileges of the user. + + can_post_messages (``bool``, *optional*): + Administrators only. Channels only. + True, if the administrator can post messages in the channel. + + can_edit_messages (``bool``, *optional*): + Administrators only. Channels only. + True, if the administrator can edit messages of other users and can pin messages. + + can_delete_messages (``bool``, *optional*): + Administrators only. + True, if the administrator can delete messages of other users. + + can_restrict_members (``bool``, *optional*): + Administrators only. + True, if the administrator can restrict, ban or unban chat members. + + can_promote_members (``bool``, *optional*): + Administrators only. + True, if the administrator can add new administrators with a subset of his own privileges or demote + administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed + by the user). + + can_change_info (``bool``, *optional*): + Administrators and restricted only. + True, if the user is allowed to change the chat title, photo and other settings. + + can_invite_users (``bool``, *optional*): + Administrators and restricted only. + True, if the user is allowed to invite new users to the chat. + + can_pin_messages (``bool``, *optional*): + Administrators and restricted only. Groups and supergroups only. + True, if the user is allowed to pin messages. + + can_send_messages (``bool``, *optional*): + Restricted only. + True, if the user is allowed to send text messages, contacts, locations and venues. + + can_send_media_messages (``bool``, *optional*): + Restricted only. + True, if the user is allowed to send audios, documents, photos, videos, video notes and voice notes. + + can_send_other_messages (``bool``, *optional*): + Restricted only. + True, if the user is allowed to send animations, games, stickers and use inline bots. + + can_add_web_page_previews (``bool``, *optional*): + Restricted only. + True, if the user is allowed to add web page previews to their messages. + + can_send_polls (``bool``, *optional*): + Restricted only. + True, if the user is allowed to send polls. + """ def __init__( self, @@ -62,23 +119,57 @@ class ChatMember(Object): client: "pyrogram.BaseClient" = None, user: "pyrogram.User", status: str, - date: int = None, - is_member: bool = None, + until_date: int = None, + joined_date: int = None, invited_by: "pyrogram.User" = None, promoted_by: "pyrogram.User" = None, restricted_by: "pyrogram.User" = None, - permissions: "pyrogram.ChatPermissions" = None + is_member: bool = None, + + # Admin permissions + can_be_edited: bool = None, + can_post_messages: bool = None, # Channels only + can_edit_messages: bool = None, # Channels only + can_delete_messages: bool = None, + can_restrict_members: bool = None, + can_promote_members: bool = None, + can_change_info: bool = None, + can_invite_users: bool = None, + can_pin_messages: bool = None, # Groups and supergroups only + + # Restricted user permissions + can_send_messages: bool = None, # Text, contacts, locations and venues + can_send_media_messages: bool = None, # Audios, documents, photos, videos, video notes and voice notes + can_send_other_messages: bool = None, # Animations (GIFs), games, stickers, inline bot results + can_add_web_page_previews: bool = None, + can_send_polls: bool = None ): super().__init__(client) self.user = user self.status = status - self.date = date - self.is_member = is_member + self.until_date = until_date + self.joined_date = joined_date self.invited_by = invited_by self.promoted_by = promoted_by self.restricted_by = restricted_by - self.permissions = permissions + self.is_member = is_member + + self.can_be_edited = can_be_edited + self.can_post_messages = can_post_messages + self.can_edit_messages = can_edit_messages + self.can_delete_messages = can_delete_messages + self.can_restrict_members = can_restrict_members + self.can_promote_members = can_promote_members + self.can_change_info = can_change_info + self.can_invite_users = can_invite_users + self.can_pin_messages = can_pin_messages + + self.can_send_messages = can_send_messages + self.can_send_media_messages = can_send_media_messages + self.can_send_other_messages = can_send_other_messages + self.can_add_web_page_previews = can_add_web_page_previews + self.can_send_polls = can_send_polls @staticmethod def _parse(client, member, users) -> "ChatMember": @@ -93,7 +184,7 @@ class ChatMember(Object): return ChatMember( user=user, status="member", - date=member.date, + joined_date=member.date, invited_by=invited_by, client=client ) @@ -109,29 +200,52 @@ class ChatMember(Object): return ChatMember( user=user, status="administrator", - date=member.date, + joined_date=member.date, invited_by=invited_by, client=client ) if isinstance(member, types.ChannelParticipantAdmin): + permissions = member.admin_rights + return ChatMember( user=user, status="administrator", - date=member.date, + joined_date=member.date, invited_by=invited_by, promoted_by=pyrogram.User._parse(client, users[member.promoted_by]), - permissions=pyrogram.ChatPermissions._parse(member), + can_be_edited=member.can_edit, + can_change_info=permissions.change_info, + can_post_messages=permissions.post_messages, + can_edit_messages=permissions.edit_messages, + can_delete_messages=permissions.delete_messages, + can_restrict_members=permissions.ban_users, + can_invite_users=permissions.invite_users, + can_pin_messages=permissions.pin_messages, + can_promote_members=permissions.add_admins, client=client ) if isinstance(member, types.ChannelParticipantBanned): + denied_permissions = member.banned_rights + return ChatMember( user=user, status="kicked" if member.banned_rights.view_messages else "restricted", - date=member.date, + until_date=denied_permissions.until_date, + joined_date=member.date, is_member=not member.left, restricted_by=pyrogram.User._parse(client, users[member.kicked_by]), - permissions=pyrogram.ChatPermissions._parse(member), + can_send_messages=not denied_permissions.send_messages, + can_send_media_messages=not denied_permissions.send_media, + can_send_other_messages=( + not denied_permissions.send_stickers or not denied_permissions.send_gifs or + not denied_permissions.send_games or not denied_permissions.send_inline + ), + can_add_web_page_previews=not denied_permissions.embed_links, + can_send_polls=not denied_permissions.send_polls, + can_change_info=not denied_permissions.change_info, + can_invite_users=not denied_permissions.invite_users, + can_pin_messages=not denied_permissions.pin_messages, client=client ) diff --git a/pyrogram/client/types/user_and_chats/chat_permissions.py b/pyrogram/client/types/user_and_chats/chat_permissions.py index 84099955..09c33089 100644 --- a/pyrogram/client/types/user_and_chats/chat_permissions.py +++ b/pyrogram/client/types/user_and_chats/chat_permissions.py @@ -16,8 +16,6 @@ # 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.api import types from ..object import Object @@ -29,152 +27,62 @@ class ChatPermissions(Object): administrators in groups or channels. Parameters: - until_date (``int``, *optional*): - Applicable to restricted and kicked members only. - Date when user restrictions will be lifted, unix time. - 0 means the restrictions will never be lifted (user restricted forever). - - can_be_edited (``bool``, *optional*): - Applicable to administrators only. - True, if you are allowed to edit administrator privileges of the user. - - can_change_info (``bool``, *optional*): - Applicable to default chat permissions in private groups and administrators in public groups only. - True, if the chat title, photo and other settings can be changed. - - can_post_messages (``bool``, *optional*): - Applicable to channel administrators only. - True, if the administrator can post messages in the channel, channels only. - - can_edit_messages (``bool``, *optional*): - Applicable to channel administrators only. - True, if the administrator can edit messages of other users and can pin messages, channels only. - - can_delete_messages (``bool``, *optional*): - Applicable to administrators only. - True, if the administrator can delete messages of other users. - - can_restrict_members (``bool``, *optional*): - Applicable to administrators only. - True, if the administrator can restrict, ban or unban chat members. - - can_invite_users (``bool``, *optional*): - Applicable to default chat permissions and administrators only. - True, if new users can be invited to the chat. - - can_pin_messages (``bool``, *optional*): - Applicable to default chat permissions in private groups and administrators in public groups only. - True, if messages can be pinned, supergroups only. - - can_promote_members (``bool``, *optional*): - Applicable to administrators only. - True, if the administrator can add new administrators with a subset of his own privileges or demote - administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed - by the user). - can_send_messages (``bool``, *optional*): - Applicable to default chat permissions and restricted members only. - True, if text messages, contacts, locations and venues can be sent. + True, if the user is allowed to send text messages, contacts, locations and venues. can_send_media_messages (``bool``, *optional*): - Applicable to default chat permissions and restricted members only. - True, if audios, documents, photos, videos, video notes and voice notes can be sent, implies + True, if the user is allowed to send audios, documents, photos, videos, video notes and voice notes, implies can_send_messages. can_send_other_messages (``bool``, *optional*): - Applicable to default chat permissions and restricted members only. - True, if animations, games, stickers and inline bot results can be sent, implies can_send_media_messages. + True, if the user is allowed to send animations, games, stickers and use inline bots, implies + can_send_media_messages can_add_web_page_previews (``bool``, *optional*): - Applicable to default chat permissions and restricted members only. - True, if web page previews can be attached to text messages, implies can_send_media_messages. + True, if the user is allowed to add web page previews to their messages, implies can_send_media_messages. can_send_polls (``bool``, *optional*): - Applicable to default chat permissions and restricted members only. - True, if polls can be sent, implies can_send_media_messages. - """ + True, if the user is allowed to send polls, implies can_send_messages. - __slots__ = [ - "until_date", "can_be_edited", "can_change_info", "can_post_messages", "can_edit_messages", - "can_delete_messages", "can_restrict_members", "can_invite_users", "can_pin_messages", "can_promote_members", - "can_send_messages", "can_send_media_messages", "can_send_other_messages", "can_add_web_page_previews", - "can_send_polls" - ] + can_change_info (``bool``, *optional*): + True, if the user is allowed to change the chat title, photo and other settings. + Ignored in public supergroups. + + can_invite_users (``bool``, *optional*): + True, if the user is allowed to invite new users to the chat. + + can_pin_messages (``bool``, *optional*): + True, if the user is allowed to pin messages. + Ignored in public supergroups. + """ def __init__( self, *, - until_date: int = None, - - # Admin permissions - can_be_edited: bool = None, - can_change_info: bool = None, - can_post_messages: bool = None, # Channels only - can_edit_messages: bool = None, # Channels only - can_delete_messages: bool = None, - can_restrict_members: bool = None, - can_invite_users: bool = None, - can_pin_messages: bool = None, # Supergroups only - can_promote_members: bool = None, - - # Restricted user permissions can_send_messages: bool = None, # Text, contacts, locations and venues can_send_media_messages: bool = None, # Audios, documents, photos, videos, video notes and voice notes can_send_other_messages: bool = None, # Animations (GIFs), games, stickers, inline bot results can_add_web_page_previews: bool = None, - can_send_polls: bool = None + can_send_polls: bool = None, + can_change_info: bool = None, + can_invite_users: bool = None, + can_pin_messages: bool = None ): super().__init__(None) - self.until_date = until_date - self.can_be_edited = can_be_edited - - self.can_change_info = can_change_info - self.can_post_messages = can_post_messages - self.can_edit_messages = can_edit_messages - self.can_delete_messages = can_delete_messages - self.can_restrict_members = can_restrict_members - self.can_invite_users = can_invite_users - self.can_pin_messages = can_pin_messages - self.can_promote_members = can_promote_members - self.can_send_messages = can_send_messages self.can_send_media_messages = can_send_media_messages self.can_send_other_messages = can_send_other_messages self.can_add_web_page_previews = can_add_web_page_previews self.can_send_polls = can_send_polls + self.can_change_info = can_change_info + self.can_invite_users = can_invite_users + self.can_pin_messages = can_pin_messages @staticmethod - def _parse( - entity: Union[ - types.ChannelParticipantAdmin, - types.ChannelParticipantBanned, - types.ChatBannedRights - ] - ) -> "ChatPermissions": - if isinstance(entity, types.ChannelParticipantAdmin): - permissions = entity.admin_rights - + def _parse(denied_permissions: types.ChatBannedRights) -> "ChatPermissions": + if isinstance(denied_permissions, types.ChatBannedRights): return ChatPermissions( - can_be_edited=entity.can_edit, - can_change_info=permissions.change_info, - can_post_messages=permissions.post_messages, - can_edit_messages=permissions.edit_messages, - can_delete_messages=permissions.delete_messages, - can_restrict_members=permissions.ban_users, - can_invite_users=permissions.invite_users, - can_pin_messages=permissions.pin_messages, - can_promote_members=permissions.add_admins - ) - - if isinstance(entity, (types.ChannelParticipantBanned, types.ChatBannedRights)): - if isinstance(entity, types.ChannelParticipantBanned): - denied_permissions = entity.banned_rights # type: types.ChatBannedRights - else: - denied_permissions = entity - - return ChatPermissions( - until_date=0 if denied_permissions.until_date == (1 << 31) - 1 else denied_permissions.until_date, can_send_messages=not denied_permissions.send_messages, can_send_media_messages=not denied_permissions.send_media, can_send_other_messages=( diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index 1584a286..623aaca8 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -29,14 +29,14 @@ class ChatPhoto(Object): Parameters: small_file_id (``str``): - Unique file identifier of small (160x160) chat photo. This file_id can be used only for photo download. + File identifier of small (160x160) chat photo. + This file_id can be used only for photo download and only for as long as the photo is not changed. big_file_id (``str``): - Unique file identifier of big (640x640) chat photo. This file_id can be used only for photo download. + File identifier of big (640x640) chat photo. + This file_id can be used only for photo download and only for as long as the photo is not changed. """ - __slots__ = ["small_file_id", "big_file_id"] - def __init__( self, *, diff --git a/pyrogram/client/types/user_and_chats/chat_preview.py b/pyrogram/client/types/user_and_chats/chat_preview.py index 312bdfe6..10754170 100644 --- a/pyrogram/client/types/user_and_chats/chat_preview.py +++ b/pyrogram/client/types/user_and_chats/chat_preview.py @@ -20,7 +20,7 @@ from typing import List import pyrogram from pyrogram.api import types -from .chat_photo import ChatPhoto +from ..messages_and_media import Photo from ..object import Object from ..user_and_chats.user import User @@ -32,48 +32,46 @@ class ChatPreview(Object): title (``str``): Title of the chat. - photo (:obj:`ChatPhoto`, *optional*): - Chat photo. Suitable for downloads only. - type (``str``): Type of chat, can be either, "group", "supergroup" or "channel". members_count (``int``): Chat members count. + photo (:obj:`Photo`, *optional*): + Chat photo. + members (List of :obj:`User`, *optional*): Preview of some of the chat members. """ - __slots__ = ["title", "photo", "type", "members_count", "members"] - def __init__( self, *, client: "pyrogram.BaseClient" = None, title: str, - photo: ChatPhoto = None, type: str, members_count: int, + photo: Photo = None, members: List[User] = None ): super().__init__(client) self.title = title - self.photo = photo self.type = type self.members_count = members_count + self.photo = photo self.members = members @staticmethod def _parse(client, chat_invite: types.ChatInvite) -> "ChatPreview": return ChatPreview( title=chat_invite.title, - photo=ChatPhoto._parse(client, chat_invite.photo), type=("group" if not chat_invite.channel else "channel" if chat_invite.broadcast else "supergroup"), members_count=chat_invite.participants_count, + photo=Photo._parse(client, chat_invite.photo), members=[User._parse(client, user) for user in chat_invite.participants] or None, client=client ) diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index 4ea82184..471c4319 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -21,6 +21,7 @@ import pyrogram from pyrogram.api import types from ..object import Object from ..user_and_chats import Chat +from ...ext import utils class Dialog(Object): @@ -46,8 +47,6 @@ class Dialog(Object): True, if the dialog is pinned. """ - __slots__ = ["chat", "top_message", "unread_messages_count", "unread_mentions_count", "unread_mark", "is_pinned"] - def __init__( self, *, @@ -70,18 +69,9 @@ class Dialog(Object): @staticmethod def _parse(client, dialog: types.Dialog, messages, users, chats) -> "Dialog": - chat_id = dialog.peer - - if isinstance(chat_id, types.PeerUser): - chat_id = chat_id.user_id - elif isinstance(chat_id, types.PeerChat): - chat_id = -chat_id.chat_id - else: - chat_id = int("-100" + str(chat_id.channel_id)) - return Dialog( chat=Chat._parse_dialog(client, dialog.peer, users, chats), - top_message=messages.get(chat_id), + top_message=messages.get(utils.get_peer_id(dialog.peer)), unread_messages_count=dialog.unread_count, unread_mentions_count=dialog.unread_mentions_count, unread_mark=dialog.unread_mark, diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index f47e8c42..783c0566 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -16,63 +16,85 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import html + import pyrogram from pyrogram.api import types from .chat_photo import ChatPhoto -from .user_status import UserStatus from ..object import Object +from ..update import Update -class User(Object): +class User(Object, Update): """A Telegram user or bot. Parameters: id (``int``): Unique identifier for this user or bot. - is_self(``bool``): + is_self(``bool``, *optional*): True, if this user is you yourself. - is_contact(``bool``): + is_contact(``bool``, *optional*): True, if this user is in your contacts. - is_mutual_contact(``bool``): + is_mutual_contact(``bool``, *optional*): True, if you both have each other's contact. - is_deleted(``bool``): + is_deleted(``bool``, *optional*): True, if this user is deleted. - is_bot (``bool``): + is_bot (``bool``, *optional*): True, if this user is a bot. - is_verified (``bool``): + is_verified (``bool``, *optional*): True, if this user has been verified by Telegram. - is_restricted (``bool``): + is_restricted (``bool``, *optional*): True, if this user has been restricted. Bots only. See *restriction_reason* for details. - is_scam (``bool``): + is_scam (``bool``, *optional*): True, if this user has been flagged for scam. - is_support (``bool``): + is_support (``bool``, *optional*): True, if this user is part of the Telegram support team. - first_name (``str``): + first_name (``str``, *optional*): User's or bot's first name. - status (:obj:`UserStatus `, *optional*): - User's Last Seen status. Empty for bots. - last_name (``str``, *optional*): User's or bot's last name. + status (``str``, *optional*): + User's Last Seen & Online status. + Can be one of the following: + "*online*", user is online right now. + "*offline*", user is currently offline. + "*recently*", user with hidden last seen time who was online between 1 second and 2-3 days ago. + "*within_week*", user with hidden last seen time who was online between 2-3 and seven days ago. + "*within_month*", user with hidden last seen time who was online between 6-7 days and a month ago. + "*long_time_ago*", blocked user or user with hidden last seen time who was online more than a month ago. + *None*, for bots. + + last_online_date (``int``, *optional*): + Last online date of a user. Only available in case status is "*offline*". + + next_offline_date (``int``, *optional*): + Date when a user will automatically go offline. Only available in case status is "*online*". + username (``str``, *optional*): User's or bot's username. language_code (``str``, *optional*): IETF language tag of the user's language. + dc_id (``int``, *optional*): + User's or bot's assigned DC (data center). Available only in case the user has set a public profile photo. + Note that this information is approximate; it is based on where Telegram stores a user profile pictures and + does not by any means tell you the user location (i.e. a user might travel far away, but will still connect + to its assigned DC). More info at `FAQs `_. + phone_number (``str``, *optional*): User's phone number. @@ -84,31 +106,28 @@ class User(Object): This field is available only in case *is_restricted* is True. """ - __slots__ = [ - "id", "is_self", "is_contact", "is_mutual_contact", "is_deleted", "is_bot", "is_verified", "is_restricted", - "is_scam", "is_support", "first_name", "last_name", "status", "username", "language_code", "phone_number", - "photo", "restriction_reason" - ] - def __init__( self, *, client: "pyrogram.BaseClient" = None, id: int, - is_self: bool, - is_contact: bool, - is_mutual_contact: bool, - is_deleted: bool, - is_bot: bool, - is_verified: bool, - is_restricted: bool, - is_scam: bool, - is_support: bool, - first_name: str, + is_self: bool = None, + is_contact: bool = None, + is_mutual_contact: bool = None, + is_deleted: bool = None, + is_bot: bool = None, + is_verified: bool = None, + is_restricted: bool = None, + is_scam: bool = None, + is_support: bool = None, + first_name: str = None, last_name: str = None, - status: UserStatus = None, + status: str = None, + last_online_date: int = None, + next_offline_date: int = None, username: str = None, language_code: str = None, + dc_id: int = None, phone_number: str = None, photo: ChatPhoto = None, restriction_reason: str = None @@ -128,12 +147,21 @@ class User(Object): self.first_name = first_name self.last_name = last_name self.status = status + self.last_online_date = last_online_date + self.next_offline_date = next_offline_date self.username = username self.language_code = language_code + self.dc_id = dc_id self.phone_number = phone_number self.photo = photo self.restriction_reason = restriction_reason + def __format__(self, format_spec): + if format_spec == "mention": + return '{1}'.format(self.id, html.escape(self.first_name)) + + return html.escape(str(self)) + @staticmethod def _parse(client, user: types.User) -> "User" or None: if user is None: @@ -152,15 +180,57 @@ class User(Object): is_support=user.support, first_name=user.first_name, last_name=user.last_name, - status=UserStatus._parse(client, user.status, user.id, user.bot), + **User._parse_status(user.status, user.bot), username=user.username, language_code=user.lang_code, + dc_id=getattr(user.photo, "dc_id", None), phone_number=user.phone, photo=ChatPhoto._parse(client, user.photo, user.id), restriction_reason=user.restriction_reason, client=client ) + @staticmethod + def _parse_status(user_status: types.UpdateUserStatus, is_bot: bool = False): + if isinstance(user_status, types.UserStatusOnline): + status, date = "online", user_status.expires + elif isinstance(user_status, types.UserStatusOffline): + status, date = "offline", user_status.was_online + elif isinstance(user_status, types.UserStatusRecently): + status, date = "recently", None + elif isinstance(user_status, types.UserStatusLastWeek): + status, date = "within_week", None + elif isinstance(user_status, types.UserStatusLastMonth): + status, date = "within_month", None + else: + status, date = "long_time_ago", None + + last_online_date = None + next_offline_date = None + + if is_bot: + status = None + + if status == "online": + next_offline_date = date + + if status == "offline": + last_online_date = date + + return { + "status": status, + "last_online_date": last_online_date, + "next_offline_date": next_offline_date + } + + @staticmethod + def _parse_user_status(client, user_status: types.UpdateUserStatus): + return User( + id=user_status.user_id, + **User._parse_status(user_status.status), + client=client + ) + def archive(self): """Bound method *archive* of :obj:`User`. @@ -206,3 +276,49 @@ class User(Object): """ return self._client.unarchive_chats(self.id) + + def block(self): + """Bound method *block* of :obj:`User`. + + Use as a shortcut for: + + .. code-block:: python + + client.block_user(123456789) + + Example: + .. code-block:: python + + user.block() + + Returns: + True on success. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.block_user(self.id) + + def unblock(self): + """Bound method *unblock* of :obj:`User`. + + Use as a shortcut for: + + .. code-block:: python + + client.unblock_user(123456789) + + Example: + .. code-block:: python + + user.unblock() + + Returns: + True on success. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + + return self._client.unblock_user(self.id) diff --git a/pyrogram/client/types/user_and_chats/user_status.py b/pyrogram/client/types/user_and_chats/user_status.py deleted file mode 100644 index 4d12afc1..00000000 --- a/pyrogram/client/types/user_and_chats/user_status.py +++ /dev/null @@ -1,116 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2019 Dan Tès -# -# This file is part of Pyrogram. -# -# Pyrogram is free software: you can redistribute it and/or modify -# it under the terms of the GNU Lesser General Public License as published -# by the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# Pyrogram is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with Pyrogram. If not, see . - -import pyrogram - -from pyrogram.api import types -from ..object import Object -from ..update import Update - - -class UserStatus(Object, Update): - """A User status (Last Seen privacy). - - .. note:: - - You won't see exact last seen timestamps for people with whom you don't share your own. Instead, you get - "recently", "within_week", "within_month" or "long_time_ago" fields set. - - Parameters: - user_id (``int``): - User's id. - - online (``bool``, *optional*): - True if the user is online in this very moment, None otherwise. - If True, the "date" field will be also set containing the online expiration date (i.e.: the date when a - user will automatically go offline in case of no action by his client). - - offline (``bool``, *optional*): - True if the user is offline in this moment and has the Last Seen privacy setting public, None otherwise. - If True, the "date" field will be also set containing the last seen date (i.e.: the date when a user - was online the last time). - - date (``int``, *optional*): - Exact date in unix time. Available only in case "online" or "offline" equals to True. - - recently (``bool``, *optional*): - True for users with hidden Last Seen privacy that have been online between 1 second and 2-3 days ago, - None otherwise. - - within_week (``bool``, *optional*): - True for users with hidden Last Seen privacy that have been online between 2-3 and seven days ago, - None otherwise. - - within_month (``bool``, *optional*): - True for users with hidden Last Seen privacy that have been online between 6-7 days and a month ago, - None otherwise. - - long_time_ago (``bool``, *optional*): - True for users with hidden Last Seen privacy that have been online more than a month ago (this is also - always shown to blocked users), None otherwise. - """ - - __slots__ = ["user_id", "online", "offline", "date", "recently", "within_week", "within_month", "long_time_ago"] - - def __init__( - self, - *, - client: "pyrogram.BaseClient" = None, - user_id: int, - online: bool = None, - offline: bool = None, - date: int = None, - recently: bool = None, - within_week: bool = None, - within_month: bool = None, - long_time_ago: bool = None - ): - super().__init__(client) - - self.user_id = user_id - self.online = online - self.offline = offline - self.date = date - self.recently = recently - self.within_week = within_week - self.within_month = within_month - self.long_time_ago = long_time_ago - - @staticmethod - def _parse(client, user_status, user_id: int, is_bot: bool = False): - if is_bot: - return None - - status = UserStatus(user_id=user_id, client=client) - - if isinstance(user_status, types.UserStatusOnline): - status.online = True - status.date = user_status.expires - elif isinstance(user_status, types.UserStatusOffline): - status.offline = True - status.date = user_status.was_online - elif isinstance(user_status, types.UserStatusRecently): - status.recently = True - elif isinstance(user_status, types.UserStatusLastWeek): - status.within_week = True - elif isinstance(user_status, types.UserStatusLastMonth): - status.within_month = True - else: - status.long_time_ago = True - - return status diff --git a/pyrogram/errors/rpc_error.py b/pyrogram/errors/rpc_error.py index c1799f50..9969d3fa 100644 --- a/pyrogram/errors/rpc_error.py +++ b/pyrogram/errors/rpc_error.py @@ -17,26 +17,28 @@ # along with Pyrogram. If not, see . import re +from datetime import datetime from importlib import import_module +from typing import Type from pyrogram.api.types import RpcError as RawRPCError + +from pyrogram.api.core import TLObject from .exceptions.all import exceptions class RPCError(Exception): - """This is the base exception class for all Telegram API related errors. - For a finer grained control, see the specific errors below. - """ ID = None CODE = None NAME = None - MESSAGE = None + MESSAGE = "{x}" - def __init__(self, x: int or RawRPCError = None, query_type: type = None): - super().__init__("[{} {}]: {}".format( + def __init__(self, x: int or RawRPCError = None, rpc_name: str = None, is_unknown: bool = False): + super().__init__("[{} {}]: {} {}".format( self.CODE, self.ID or self.NAME, - str(self) or self.MESSAGE.format(x=x) + self.MESSAGE.format(x=x), + '(caused by "{}")'.format(rpc_name) if rpc_name else "" )) try: @@ -44,38 +46,45 @@ class RPCError(Exception): except (ValueError, TypeError): self.x = x - # TODO: Proper log unknown errors - if self.CODE == 520: + if is_unknown: with open("unknown_errors.txt", "a", encoding="utf-8") as f: - f.write("{}\t{}\t{}\n".format(x.error_code, x.error_message, query_type)) + f.write("{}\t{}\t{}\n".format(datetime.now(), x, rpc_name)) @staticmethod - def raise_it(rpc_error: RawRPCError, query_type: type): - code = rpc_error.error_code + def raise_it(rpc_error: RawRPCError, rpc_type: Type[TLObject]): + error_code = rpc_error.error_code + error_message = rpc_error.error_message + rpc_name = ".".join(rpc_type.QUALNAME.split(".")[1:]) - if code not in exceptions: - raise UnknownError(x=rpc_error, query_type=query_type) + if error_code not in exceptions: + raise UnknownError( + x="[{} {}]".format(error_code, error_message), + rpc_name=rpc_name, + is_unknown=True + ) - message = rpc_error.error_message - id = re.sub(r"_\d+", "_X", message) + error_id = re.sub(r"_\d+", "_X", error_message) - if id not in exceptions[code]: - raise UnknownError(x=rpc_error, query_type=query_type) + if error_id not in exceptions[error_code]: + raise getattr( + import_module("pyrogram.errors"), + exceptions[error_code]["_"] + )(x="[{} {}]".format(error_code, error_message), + rpc_name=rpc_name, + is_unknown=True) - x = re.search(r"_(\d+)", message) + x = re.search(r"_(\d+)", error_message) x = x.group(1) if x is not None else x raise getattr( import_module("pyrogram.errors"), - exceptions[code][id] - )(x=x) + exceptions[error_code][error_id] + )(x=x, + rpc_name=rpc_name, + is_unknown=False) class UnknownError(RPCError): - """This object represents an Unknown Error, that is, an error which - Pyrogram does not know anything about, yet. - """ CODE = 520 """:obj:`int`: Error code""" NAME = "Unknown error" - MESSAGE = "{x}" diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index fb6e7ca3..f6d137fa 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -22,6 +22,7 @@ from hashlib import sha1 from io import BytesIO from os import urandom +import pyrogram from pyrogram.api import functions, types from pyrogram.api.core import TLObject, Long, Int from pyrogram.connection import Connection @@ -34,11 +35,11 @@ log = logging.getLogger(__name__) class Auth: MAX_RETRIES = 5 - def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict): + def __init__(self, client: "pyrogram.Client", dc_id: int): self.dc_id = dc_id - self.test_mode = test_mode - self.ipv6 = ipv6 - self.proxy = proxy + self.test_mode = client.storage.test_mode + self.ipv6 = client.ipv6 + self.proxy = client.proxy self.connection = None diff --git a/pyrogram/session/internals/data_center.py b/pyrogram/session/internals/data_center.py index fd51932a..acff723b 100644 --- a/pyrogram/session/internals/data_center.py +++ b/pyrogram/session/internals/data_center.py @@ -26,11 +26,11 @@ class DataCenter: } PROD = { - 1: "149.154.175.50", + 1: "149.154.175.53", 2: "149.154.167.51", 3: "149.154.175.100", 4: "149.154.167.91", - 5: "91.108.56.149", + 5: "91.108.56.130", 121: "95.213.217.195" } diff --git a/pyrogram/session/internals/msg_factory.py b/pyrogram/session/internals/msg_factory.py index 2b833ce8..453eefc1 100644 --- a/pyrogram/session/internals/msg_factory.py +++ b/pyrogram/session/internals/msg_factory.py @@ -16,9 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.core import Message, MsgContainer, TLObject from pyrogram.api.functions import Ping from pyrogram.api.types import MsgsAck, HttpWait + +from pyrogram.api.core import Message, MsgContainer, TLObject from .msg_id import MsgId from .seq_no import SeqNo diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 8ef5570c..689fe584 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -26,10 +26,11 @@ from os import urandom from queue import Queue from threading import Event, Thread +from pyrogram.api.all import layer + import pyrogram from pyrogram import __copyright__, __license__, __version__ from pyrogram.api import functions, types, core -from pyrogram.api.all import layer from pyrogram.api.core import Message, TLObject, MsgContainer, Long, FutureSalt, Int from pyrogram.connection import Connection from pyrogram.crypto import AES, KDF @@ -70,12 +71,14 @@ class Session: 64: "[64] invalid container" } - def __init__(self, - client: pyrogram, - dc_id: int, - auth_key: bytes, - is_media: bool = False, - is_cdn: bool = False): + def __init__( + self, + client: pyrogram, + dc_id: int, + auth_key: bytes, + is_media: bool = False, + is_cdn: bool = False + ): if not Session.notice_displayed: print("Pyrogram v{}, {}".format(__version__, __copyright__)) print("Licensed under the terms of the " + __license__, end="\n\n") @@ -113,7 +116,12 @@ class Session: def start(self): while True: - self.connection = Connection(self.dc_id, self.client.test_mode, self.client.ipv6, self.client.proxy) + self.connection = Connection( + self.dc_id, + self.client.storage.test_mode, + self.client.ipv6, + self.client.proxy + ) try: self.connection.connect() @@ -431,9 +439,9 @@ class Session: if retries == 0: raise e from None - (log.warning if retries < 3 else log.info)( + (log.warning if retries < 2 else log.info)( "{}: {} Retrying {}".format( - Session.MAX_RETRIES - retries, + Session.MAX_RETRIES - retries + 1, datetime.now(), type(data))) time.sleep(0.5) diff --git a/setup.py b/setup.py index 146dae9e..508bca90 100644 --- a/setup.py +++ b/setup.py @@ -39,8 +39,12 @@ with open("README.md", encoding="utf-8") as f: class Clean(Command): DIST = ["./build", "./dist", "./Pyrogram.egg-info"] - API = ["pyrogram/api/errors/exceptions", "pyrogram/api/functions", "pyrogram/api/types", "pyrogram/api/all.py"] - DOCS = ["docs/source/telegram", "docs/build"] + API = ["pyrogram/errors/exceptions", "pyrogram/api/functions", "pyrogram/api/types", "pyrogram/api/all.py"] + DOCS = [ + "docs/source/telegram", "docs/build", "docs/source/api/methods", "docs/source/api/types", + "docs/source/api/bound-methods" + ] + ALL = DIST + API + DOCS description = "Clean generated files" @@ -122,7 +126,6 @@ class Generate(Command): if len(argv) > 1 and argv[1] in ["bdist_wheel", "install", "develop"]: api_compiler.start() error_compiler.start() - docs_compiler.start() setup( name="Pyrogram", @@ -168,12 +171,13 @@ setup( python_requires="~=3.4", packages=find_packages(exclude=["compiler*"]), package_data={ - "pyrogram.client.ext": ["mime.types"] + "pyrogram.client.ext": ["mime.types"], + "pyrogram.client.storage": ["schema.sql"] }, zip_safe=False, install_requires=requires, extras_require={ - "fast": ["tgcrypto==1.1.1"] + "fast": ["tgcrypto==1.2.0"] }, cmdclass={ "clean": Clean,