2
0
mirror of https://github.com/pyrogram/pyrogram synced 2025-08-28 04:48:06 +00:00

Merge branch 'develop' into develop

This commit is contained in:
Dan 2018-12-27 23:52:40 +01:00 committed by GitHub
commit 233e0920a2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
245 changed files with 12740 additions and 9025 deletions

View File

@ -17,8 +17,8 @@ Pyrogram
app.run()
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for building
custom Telegram applications that interact with the MTProto API as both User and Bot.
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for
building custom Telegram applications that interact with the MTProto API as both User and Bot.
Features
--------
@ -26,7 +26,7 @@ Features
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
- **Updated** to the latest Telegram API version, currently Layer 81 on top of MTProto 2.0.
- **Updated** to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0.
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
- **Full API**, allowing to execute any advanced action an official client is able to do, and more.
@ -78,8 +78,7 @@ Copyright & License
<h1 align="center">
<a href="https://github.com/pyrogram/pyrogram">
<div><img src="https://media.pyrogram.ml/images/icon.png" alt="Pyrogram Icon"></div>
<div><img src="https://media.pyrogram.ml/images/label.png" alt="Pyrogram Label"></div>
<div><img src="https://raw.githubusercontent.com/pyrogram/logos/master/logos/pyrogram_logo2.png" alt="Pyrogram Logo"></div>
</a>
</h1>
@ -98,27 +97,27 @@ Copyright & License
<a href="https://t.me/PyrogramChat">
Community
</a>
<br><br>
<br>
<a href="compiler/api/source/main_api.tl">
<img src="https://img.shields.io/badge/SCHEME-LAYER%2081-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
alt="Scheme Layer">
<img src="https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30"
alt="Schema Layer">
</a>
<a href="https://github.com/pyrogram/tgcrypto">
<img src="https://img.shields.io/badge/TGCRYPTO-V1.0.4-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
<img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
alt="TgCrypto">
</a>
</p>
.. |logo| image:: https://pyrogram.ml/images/logo.png
.. |logo| image:: https://raw.githubusercontent.com/pyrogram/logos/master/logos/pyrogram_logo2.png
:target: https://pyrogram.ml
:alt: Pyrogram
.. |description| replace:: **Telegram MTProto API Client Library for Python**
.. |scheme| image:: "https://img.shields.io/badge/SCHEME-LAYER%2081-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
.. |scheme| image:: "https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30"
:target: compiler/api/source/main_api.tl
:alt: Scheme Layer
.. |tgcrypto| image:: "https://img.shields.io/badge/TGCRYPTO-V1.0.4-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
.. |tgcrypto| image:: "https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
:target: https://github.com/pyrogram/tgcrypto
:alt: TgCrypto

View File

@ -172,9 +172,8 @@ def start():
with open("{}/source/auth_key.tl".format(HOME), encoding="utf-8") as auth, \
open("{}/source/sys_msgs.tl".format(HOME), encoding="utf-8") as system, \
open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api, \
open("{}/source/pyrogram.tl".format(HOME), encoding="utf-8") as pyrogram:
schema = (auth.read() + system.read() + api.read() + pyrogram.read()).splitlines()
open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api:
schema = (auth.read() + system.read() + api.read()).splitlines()
with open("{}/template/mtproto.txt".format(HOME), encoding="utf-8") as f:
mtproto_template = f.read()
@ -476,35 +475,6 @@ def start():
f.write("\n 0x3072cfa1: \"pyrogram.api.core.GzipPacked\",")
f.write("\n 0x5bb8e511: \"pyrogram.api.core.Message\",")
f.write("\n 0xb0700000: \"pyrogram.client.types.Update\",")
f.write("\n 0xb0700001: \"pyrogram.client.types.User\",")
f.write("\n 0xb0700002: \"pyrogram.client.types.Chat\",")
f.write("\n 0xb0700003: \"pyrogram.client.types.Message\",")
f.write("\n 0xb0700004: \"pyrogram.client.types.MessageEntity\",")
f.write("\n 0xb0700005: \"pyrogram.client.types.PhotoSize\",")
f.write("\n 0xb0700006: \"pyrogram.client.types.Audio\",")
f.write("\n 0xb0700007: \"pyrogram.client.types.Document\",")
f.write("\n 0xb0700008: \"pyrogram.client.types.Video\",")
f.write("\n 0xb0700009: \"pyrogram.client.types.Voice\",")
f.write("\n 0xb0700010: \"pyrogram.client.types.VideoNote\",")
f.write("\n 0xb0700011: \"pyrogram.client.types.Contact\",")
f.write("\n 0xb0700012: \"pyrogram.client.types.Location\",")
f.write("\n 0xb0700013: \"pyrogram.client.types.Venue\",")
f.write("\n 0xb0700014: \"pyrogram.client.types.UserProfilePhotos\",")
f.write("\n 0xb0700015: \"pyrogram.client.types.ChatPhoto\",")
f.write("\n 0xb0700016: \"pyrogram.client.types.ChatMember\",")
f.write("\n 0xb0700017: \"pyrogram.client.types.Sticker\",")
f.write("\n 0xb0700018: \"pyrogram.client.types.reply_markup.ForceReply\",")
f.write("\n 0xb0700019: \"pyrogram.client.types.reply_markup.InlineKeyboardButton\",")
f.write("\n 0xb0700020: \"pyrogram.client.types.reply_markup.InlineKeyboardMarkup\",")
f.write("\n 0xb0700021: \"pyrogram.client.types.reply_markup.KeyboardButton\",")
f.write("\n 0xb0700022: \"pyrogram.client.types.reply_markup.ReplyKeyboardMarkup\",")
f.write("\n 0xb0700023: \"pyrogram.client.types.reply_markup.ReplyKeyboardRemove\",")
f.write("\n 0xb0700024: \"pyrogram.client.types.CallbackQuery\",")
f.write("\n 0xb0700025: \"pyrogram.client.types.GIF\",")
f.write("\n 0xb0700026: \"pyrogram.client.types.Messages\",")
f.write("\n 0xb0700027: \"pyrogram.client.types.Photo\",")
f.write("\n}\n")
for k, v in namespaces.items():

View File

@ -36,7 +36,7 @@ inputMediaEmpty#9664f57f = InputMedia;
inputMediaUploadedPhoto#1e287d04 flags:# file:InputFile stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMedia;
inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia;
inputMediaContact#a6e45987 phone_number:string first_name:string last_name:string = InputMedia;
inputMediaContact#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia;
inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector<DocumentAttribute> stickers:flags.0?Vector<InputDocument> ttl_seconds:flags.1?int = InputMedia;
inputMediaDocument#23ab23d2 flags:# id:InputDocument ttl_seconds:flags.0?int = InputMedia;
inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia;
@ -45,7 +45,8 @@ inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = In
inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia;
inputMediaGame#d33f43f3 id:InputGame = InputMedia;
inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia;
inputMediaGeoLive#7b1a118f geo_point:InputGeoPoint period:int = InputMedia;
inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia;
inputMediaPoll#6b3765b poll:Poll = InputMedia;
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto;
@ -55,14 +56,13 @@ inputGeoPointEmpty#e4c123d6 = InputGeoPoint;
inputGeoPoint#f3b7acc9 lat:double long:double = InputGeoPoint;
inputPhotoEmpty#1cd7bf0d = InputPhoto;
inputPhoto#fb95c6c4 id:long access_hash:long = InputPhoto;
inputPhoto#3bb3b94a id:long access_hash:long file_reference:bytes = InputPhoto;
inputFileLocation#14637196 volume_id:long local_id:int secret:long = InputFileLocation;
inputFileLocation#dfdaabe1 volume_id:long local_id:int secret:long file_reference:bytes = InputFileLocation;
inputEncryptedFileLocation#f5235d55 id:long access_hash:long = InputFileLocation;
inputDocumentFileLocation#430f0724 id:long access_hash:long version:int = InputFileLocation;
inputDocumentFileLocation#196683d9 id:long access_hash:long file_reference:bytes = InputFileLocation;
inputSecureFileLocation#cbc7ee28 id:long access_hash:long = InputFileLocation;
inputAppEvent#770656a8 time:double type:string peer:long data:string = InputAppEvent;
inputTakeoutFileLocation#29be5899 = InputFileLocation;
peerUser#9db1bc6d user_id:int = Peer;
peerChat#bad0e5bb chat_id:int = Peer;
@ -80,7 +80,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType;
storage.fileWebp#1081464c = storage.FileType;
fileLocationUnavailable#7c596b46 volume_id:long local_id:int secret:long = FileLocation;
fileLocation#53d69076 dc_id:int volume_id:long local_id:int secret:long = FileLocation;
fileLocation#91d11eb dc_id:int volume_id:long local_id:int secret:long file_reference:bytes = FileLocation;
userEmpty#200250ba id:int = User;
user#2e13f4c3 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?string bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
@ -101,8 +101,8 @@ chatForbidden#7328bdb id:int title:string = Chat;
channel#c88974ac flags:# creator:flags.0?true left:flags.2?true editor:flags.3?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true democracy:flags.10?true signatures:flags.11?true min:flags.12?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?ChannelAdminRights banned_rights:flags.15?ChannelBannedRights 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#2e02a614 id:int participants:ChatParticipants chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector<BotInfo> = ChatFull;
channelFull#76af5481 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?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 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<BotInfo> 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 = ChatFull;
chatFull#edd2a791 flags:# id:int participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int = ChatFull;
channelFull#1c87a71a 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<BotInfo> 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 = ChatFull;
chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant;
chatParticipantCreator#da13538a user_id:int = ChatParticipant;
@ -115,13 +115,13 @@ chatPhotoEmpty#37c1011c = ChatPhoto;
chatPhoto#6153276a photo_small:FileLocation photo_big:FileLocation = ChatPhoto;
messageEmpty#83e5de54 id:int = Message;
message#44f9b43d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long = Message;
message#44f9b43d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long = Message;
messageService#9e19a1f6 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer reply_to_msg_id:flags.3?int date:int action:MessageAction = Message;
messageMediaEmpty#3ded6320 = MessageMedia;
messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia;
messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia;
messageMediaContact#5e7d2f39 phone_number:string first_name:string last_name:string user_id:int = MessageMedia;
messageMediaContact#cbf24940 phone_number:string first_name:string last_name:string vcard:string user_id:int = MessageMedia;
messageMediaUnsupported#9f84f49e = MessageMedia;
messageMediaDocument#9cb070d7 flags:# document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia;
messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia;
@ -129,6 +129,7 @@ messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:str
messageMediaGame#fdb19008 game:Game = MessageMedia;
messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;
messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia;
messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia;
messageActionEmpty#b6aef7b0 = MessageAction;
messageActionChatCreate#a6638b9a title:string users:Vector<int> = MessageAction;
@ -152,18 +153,19 @@ messageActionCustomAction#fae69f56 message:string = MessageAction;
messageActionBotAllowed#abe9affe domain:string = MessageAction;
messageActionSecureValuesSentMe#1b287353 values:Vector<SecureValue> credentials:SecureCredentialsEncrypted = MessageAction;
messageActionSecureValuesSent#d95c6154 types:Vector<SecureValueType> = MessageAction;
messageActionContactSignUp#f3f25f76 = MessageAction;
dialog#e4def5db flags:# pinned:flags.2?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage = Dialog;
dialog#e4def5db flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage = Dialog;
photoEmpty#2331b22d id:long = Photo;
photo#9288dd29 flags:# has_stickers:flags.0?true id:long access_hash:long date:int sizes:Vector<PhotoSize> = Photo;
photo#9c477dd8 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector<PhotoSize> = Photo;
photoSizeEmpty#e17e23c type:string = PhotoSize;
photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize;
photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize;
geoPointEmpty#1117dd5f = GeoPoint;
geoPoint#2049d70c long:double lat:double = GeoPoint;
geoPoint#296f104 long:double lat:double access_hash:long = GeoPoint;
auth.checkedPhone#811ea28e phone_registered:Bool = auth.CheckedPhone;
@ -176,6 +178,7 @@ auth.exportedAuthorization#df969c2d id:int bytes:bytes = auth.ExportedAuthorizat
inputNotifyPeer#b8bc5b0c peer:InputPeer = InputNotifyPeer;
inputNotifyUsers#193b4417 = InputNotifyPeer;
inputNotifyChats#4a95e84e = InputNotifyPeer;
inputNotifyBroadcasts#b1db7c7e = InputNotifyPeer;
inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = InputPeerNotifySettings;
@ -189,9 +192,11 @@ wallPaperSolid#63117f24 id:int title:string bg_color:int color:int = WallPaper;
inputReportReasonSpam#58dbcab8 = ReportReason;
inputReportReasonViolence#1e22c78d = ReportReason;
inputReportReasonPornography#2e59d922 = ReportReason;
inputReportReasonChildAbuse#adf44ee3 = ReportReason;
inputReportReasonOther#e1746d0a text:string = ReportReason;
inputReportReasonCopyright#9b89f93a = ReportReason;
userFull#f220f3f flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true user:User about:flags.1?string link:contacts.Link profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo common_chats_count:int = UserFull;
userFull#8ea4a881 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 = UserFull;
contact#f911c994 user_id:int mutual:Bool = Contact;
@ -213,10 +218,11 @@ contacts.blockedSlice#900802a1 count:int blocked:Vector<ContactBlocked> users:Ve
messages.dialogs#15ba6c40 dialogs:Vector<Dialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Dialogs;
messages.dialogsSlice#71e094f3 count:int dialogs:Vector<Dialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Dialogs;
messages.dialogsNotModified#f0e3e596 count:int = messages.Dialogs;
messages.messages#8c718e87 messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
messages.messagesSlice#b446ae3 count:int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
messages.channelMessages#99262e37 flags:# pts:int count:int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
messages.channelMessages#99262e37 flags:# inexact:flags.1?true pts:int count:int messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.Messages;
messages.messagesNotModified#74535f21 count:int = messages.Messages;
messages.chats#64ff9fd5 chats:Vector<Chat> = messages.Chats;
@ -252,7 +258,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;
updateContactRegistered#2575bbb9 user_id:int date:int = 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;
@ -303,12 +308,16 @@ updateBotWebhookJSONQuery#9b9240a6 query_id:long data:DataJSON timeout:int = Upd
updateBotShippingQuery#e0cdc940 query_id:long user_id:int payload:bytes shipping_address:PostAddress = Update;
updateBotPrecheckoutQuery#5d2f3aa9 flags:# query_id:long user_id:int payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string currency:string total_amount:long = Update;
updatePhoneCall#ab0f6b1e phone_call:PhoneCall = Update;
updateLangPackTooLong#10c2404b = Update;
updateLangPackTooLong#46560264 lang_code:string = Update;
updateLangPack#56022f4d difference:LangPackDifference = Update;
updateFavedStickers#e511996d = Update;
updateChannelReadMessagesContents#89893b45 channel_id:int messages:Vector<int> = Update;
updateContactsReset#7084a7be = Update;
updateChannelAvailableMessages#70db6837 channel_id:int available_min_id:int = Update;
updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Update;
updateUserPinnedMessage#4c43da18 user_id:int id:int = Update;
updateChatPinnedMessage#22893b26 chat_id:int id:int = Update;
updateMessagePoll#aca1657b flags:# poll_id:long poll:flags.0?Poll results:PollResults = Update;
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
@ -335,11 +344,11 @@ upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes
dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption;
config#eb7bb160 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string suggested_lang_code:flags.2?string lang_pack_version:flags.2?int = Config;
config#e6ca25f6 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector<DcOption> dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config;
nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
help.appUpdate#8987f311 id:int critical:Bool url:string text:string = help.AppUpdate;
help.appUpdate#1da7158f flags:# popup:flags.0?true id:int version:string text:string entities:Vector<MessageEntity> document:flags.1?Document url:flags.2?string = help.AppUpdate;
help.noAppUpdate#c45a6536 = help.AppUpdate;
help.inviteText#18cb9f78 message:string = help.InviteText;
@ -370,16 +379,17 @@ messages.sentEncryptedMessage#560f8935 date:int = messages.SentEncryptedMessage;
messages.sentEncryptedFile#9493ff32 date:int file:EncryptedFile = messages.SentEncryptedMessage;
inputDocumentEmpty#72f0eaae = InputDocument;
inputDocument#18798952 id:long access_hash:long = InputDocument;
inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument;
documentEmpty#36f8c871 id:long = Document;
document#87232bc7 id:long access_hash:long date:int mime_type:string size:int thumb:PhotoSize dc_id:int version:int attributes:Vector<DocumentAttribute> = Document;
document#59534e4c id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumb:PhotoSize dc_id:int attributes:Vector<DocumentAttribute> = Document;
help.support#17c6b5f6 phone_number:string user:User = help.Support;
notifyPeer#9fd40bd8 peer:Peer = NotifyPeer;
notifyUsers#b4c83b4c = NotifyPeer;
notifyChats#c007cec3 = NotifyPeer;
notifyBroadcasts#d612e8ef = NotifyPeer;
sendMessageTypingAction#16bf744e = SendMessageAction;
sendMessageCancelAction#fd5ec8f5 = SendMessageAction;
@ -400,10 +410,12 @@ contacts.found#b3134d9d my_results:Vector<Peer> results:Vector<Peer> chats:Vecto
inputPrivacyKeyStatusTimestamp#4f96cb18 = InputPrivacyKey;
inputPrivacyKeyChatInvite#bdfb0426 = InputPrivacyKey;
inputPrivacyKeyPhoneCall#fabadc5f = InputPrivacyKey;
inputPrivacyKeyPhoneP2P#db9e70d2 = InputPrivacyKey;
privacyKeyStatusTimestamp#bc2eab30 = PrivacyKey;
privacyKeyChatInvite#500e6dfa = PrivacyKey;
privacyKeyPhoneCall#3d662b7b = PrivacyKey;
privacyKeyPhoneP2P#39491cc8 = PrivacyKey;
inputPrivacyValueAllowContacts#d09e07b = InputPrivacyRule;
inputPrivacyValueAllowAll#184b35ce = InputPrivacyRule;
@ -451,16 +463,15 @@ 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;
webPageNotModified#85849473 = WebPage;
authorization#7bf2e6f6 hash:long flags:int device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization;
authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization;
account.authorizations#1250abde authorizations:Vector<Authorization> = account.Authorizations;
account.noPassword#5ea182f6 new_salt:bytes new_secure_salt:bytes secure_random:bytes email_unconfirmed_pattern:string = account.Password;
account.password#ca39b447 flags:# has_recovery:flags.0?true has_secure_values:flags.1?true current_salt:bytes new_salt:bytes new_secure_salt:bytes secure_random:bytes hint:string email_unconfirmed_pattern:string = account.Password;
account.password#ad2641f8 flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes = account.Password;
account.passwordSettings#7bd9c3f1 email:string secure_salt:bytes secure_secret:bytes secure_secret_id:long = account.PasswordSettings;
account.passwordSettings#9a5c33e5 flags:# email:flags.0?string secure_settings:flags.1?SecureSecretSettings = account.PasswordSettings;
account.passwordInputSettings#21ffa60d flags:# new_salt:flags.0?bytes new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string new_secure_salt:flags.2?bytes new_secure_secret:flags.2?bytes new_secure_secret_id:flags.2?long = account.PasswordInputSettings;
account.passwordInputSettings#c23727c9 flags:# new_algo:flags.0?PasswordKdfAlgo new_password_hash:flags.0?bytes hint:flags.0?string email:flags.1?string new_secure_settings:flags.2?SecureSecretSettings = account.PasswordInputSettings;
auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery;
@ -562,7 +573,7 @@ inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1?
inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageMediaGeo#c1b15d65 flags:# geo_point:InputGeoPoint period:int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageMediaContact#2daf01a7 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage;
inputBotInlineResult#88bf9319 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?InputWebDocument content:flags.5?InputWebDocument send_message:InputBotInlineMessage = InputBotInlineResult;
@ -574,7 +585,7 @@ botInlineMessageMediaAuto#764cf810 flags:# message:string entities:flags.1?Vecto
botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector<MessageEntity> reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaGeo#b722de65 flags:# geo:GeoPoint period:int reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaContact#35edb4d4 flags:# phone_number:string first_name:string last_name:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineMessageMediaContact#18d1cdc2 flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage;
botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult;
botInlineMediaResult#17db940b flags:# id:string type:string photo:flags.0?Photo document:flags.1?Document title:flags.2?string description:flags.3?string send_message:BotInlineMessage = BotInlineResult;
@ -617,8 +628,9 @@ topPeerCategoryPeers#fb834291 category:TopPeerCategory count:int peers:Vector<To
contacts.topPeersNotModified#de266ef5 = contacts.TopPeers;
contacts.topPeers#70b772a8 categories:Vector<TopPeerCategoryPeers> chats:Vector<Chat> users:Vector<User> = contacts.TopPeers;
contacts.topPeersDisabled#b52c939d = contacts.TopPeers;
draftMessageEmpty#ba4baec5 = DraftMessage;
draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage;
draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector<MessageEntity> date:int = DraftMessage;
messages.featuredStickersNotModified#4ede3cf = messages.FeaturedStickers;
@ -659,6 +671,12 @@ textFixed#6c3f19b9 text:RichText = RichText;
textUrl#3c2884c1 text:RichText url:string webpage_id:long = RichText;
textEmail#de5a0dd6 text:RichText email:string = RichText;
textConcat#7e6260d7 texts:Vector<RichText> = RichText;
textSubscript#ed6a8504 text:RichText = RichText;
textSuperscript#c7fb5e01 text:RichText = RichText;
textMarked#34b8621 text:RichText = RichText;
textPhone#1ccb966a text:RichText phone:string = RichText;
textImage#81ccf4f document_id:long w:int h:int = RichText;
textAnchor#35553762 text:RichText name:string = RichText;
pageBlockUnsupported#13567e8a = PageBlock;
pageBlockTitle#70abc3fd text:RichText = PageBlock;
@ -671,21 +689,24 @@ pageBlockPreformatted#c070d93e text:RichText language:string = PageBlock;
pageBlockFooter#48870999 text:RichText = PageBlock;
pageBlockDivider#db20b188 = PageBlock;
pageBlockAnchor#ce0d37b0 name:string = PageBlock;
pageBlockList#3a58c7f4 ordered:Bool items:Vector<RichText> = PageBlock;
pageBlockList#e4e88011 items:Vector<PageListItem> = PageBlock;
pageBlockBlockquote#263d7c26 text:RichText caption:RichText = PageBlock;
pageBlockPullquote#4f4456d3 text:RichText caption:RichText = PageBlock;
pageBlockPhoto#e9c69982 photo_id:long caption:RichText = PageBlock;
pageBlockVideo#d9d71866 flags:# autoplay:flags.0?true loop:flags.1?true video_id:long caption:RichText = PageBlock;
pageBlockPhoto#1759c560 flags:# photo_id:long caption:PageCaption url:flags.0?string webpage_id:flags.0?long = PageBlock;
pageBlockVideo#7c8fe7b6 flags:# autoplay:flags.0?true loop:flags.1?true video_id:long caption:PageCaption = PageBlock;
pageBlockCover#39f23300 cover:PageBlock = PageBlock;
pageBlockEmbed#cde200d1 flags:# full_width:flags.0?true allow_scrolling:flags.3?true url:flags.1?string html:flags.2?string poster_photo_id:flags.4?long w:int h:int caption:RichText = PageBlock;
pageBlockEmbedPost#292c7be9 url:string webpage_id:long author_photo_id:long author:string date:int blocks:Vector<PageBlock> caption:RichText = PageBlock;
pageBlockCollage#8b31c4f items:Vector<PageBlock> caption:RichText = PageBlock;
pageBlockSlideshow#130c8963 items:Vector<PageBlock> caption:RichText = PageBlock;
pageBlockEmbed#a8718dc5 flags:# full_width:flags.0?true allow_scrolling:flags.3?true url:flags.1?string html:flags.2?string poster_photo_id:flags.4?long w:flags.5?int h:flags.5?int caption:PageCaption = PageBlock;
pageBlockEmbedPost#f259a80b url:string webpage_id:long author_photo_id:long author:string date:int blocks:Vector<PageBlock> caption:PageCaption = PageBlock;
pageBlockCollage#65a0fa4d items:Vector<PageBlock> caption:PageCaption = PageBlock;
pageBlockSlideshow#31f9590 items:Vector<PageBlock> caption:PageCaption = PageBlock;
pageBlockChannel#ef1751b5 channel:Chat = PageBlock;
pageBlockAudio#31b81a7f audio_id:long caption:RichText = PageBlock;
pagePart#8e3f9ebe blocks:Vector<PageBlock> photos:Vector<Photo> documents:Vector<Document> = Page;
pageFull#556ec7aa blocks:Vector<PageBlock> photos:Vector<Photo> documents:Vector<Document> = Page;
pageBlockAudio#804361ea audio_id:long caption:PageCaption = PageBlock;
pageBlockKicker#1e148390 text:RichText = PageBlock;
pageBlockTable#bf4dea82 flags:# bordered:flags.0?true striped:flags.1?true title:RichText rows:Vector<PageTableRow> = PageBlock;
pageBlockOrderedList#9a8ae1e1 items:Vector<PageListOrderedItem> = PageBlock;
pageBlockDetails#76768bed flags:# open:flags.0?true blocks:Vector<PageBlock> title:RichText = PageBlock;
pageBlockRelatedArticles#16115a96 title:RichText articles:Vector<PageRelatedArticle> = PageBlock;
pageBlockMap#a44f3ef6 geo:GeoPoint zoom:int w:int h:int caption:PageCaption = PageBlock;
phoneCallDiscardReasonMissed#85e42301 = PhoneCallDiscardReason;
phoneCallDiscardReasonDisconnect#e095c1a0 = PhoneCallDiscardReason;
@ -706,14 +727,13 @@ paymentRequestedInfo#909c3f94 flags:# name:flags.0?string phone:flags.1?string e
paymentSavedCredentialsCard#cdc27a1f id:string title:string = PaymentSavedCredentials;
webDocument#c61acbd8 url:string access_hash:long size:int mime_type:string attributes:Vector<DocumentAttribute> dc_id:int = WebDocument;
webDocument#1c570ed1 url:string access_hash:long size:int mime_type:string attributes:Vector<DocumentAttribute> = WebDocument;
webDocumentNoProxy#f9c8bcc6 url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = WebDocument;
inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector<DocumentAttribute> = InputWebDocument;
inputWebFileLocation#c239d686 url:string access_hash:long = InputWebFileLocation;
inputWebFileGeoPointLocation#66275a62 geo_point:InputGeoPoint w:int h:int zoom:int scale:int = InputWebFileLocation;
inputWebFileGeoMessageLocation#553f32eb peer:InputPeer msg_id:int w:int h:int zoom:int scale:int = InputWebFileLocation;
inputWebFileGeoPointLocation#9f2221c9 geo_point:InputGeoPoint access_hash:long w:int h:int zoom:int scale:int = InputWebFileLocation;
upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile;
@ -745,7 +765,7 @@ phoneCallEmpty#5366c915 id:long = PhoneCall;
phoneCallWaiting#1b8f4ad1 flags:# id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall;
phoneCallRequested#83761ce4 id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall;
phoneCallAccepted#6d003d3f id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall;
phoneCall#ffe6ab67 id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connection:PhoneConnection alternative_connections:Vector<PhoneConnection> start_date:int = PhoneCall;
phoneCall#e6f9ddf3 flags:# p2p_allowed:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connection:PhoneConnection alternative_connections:Vector<PhoneConnection> start_date:int = PhoneCall;
phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall;
phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection;
@ -767,7 +787,7 @@ langPackStringDeleted#2979eeb2 key:string = LangPackString;
langPackDifference#f385c1f6 lang_code:string from_version:int version:int strings:Vector<LangPackString> = LangPackDifference;
langPackLanguage#117698f1 name:string native_name:string lang_code:string = LangPackLanguage;
langPackLanguage#eeca5ce3 flags:# official:flags.0?true rtl:flags.2?true beta:flags.3?true name:string native_name:string lang_code:string base_lang_code:flags.1?string plural_code:string strings_count:int translated_count:int translations_url:string = LangPackLanguage;
channelAdminRights#5d7ceba5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true invite_link:flags.6?true pin_messages:flags.7?true add_admins:flags.9?true manage_call:flags.10?true = ChannelAdminRights;
@ -861,9 +881,9 @@ secureValueTypeTemporaryRegistration#ea02ec33 = SecureValueType;
secureValueTypePhone#b320aadb = SecureValueType;
secureValueTypeEmail#8e3ca7ee = SecureValueType;
secureValue#b4b4b699 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?SecureFile reverse_side:flags.2?SecureFile selfie:flags.3?SecureFile files:flags.4?Vector<SecureFile> plain_data:flags.5?SecurePlainData hash:bytes = SecureValue;
secureValue#187fa0ca flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?SecureFile reverse_side:flags.2?SecureFile selfie:flags.3?SecureFile translation:flags.6?Vector<SecureFile> files:flags.4?Vector<SecureFile> plain_data:flags.5?SecurePlainData hash:bytes = SecureValue;
inputSecureValue#67872e8 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?InputSecureFile reverse_side:flags.2?InputSecureFile selfie:flags.3?InputSecureFile files:flags.4?Vector<InputSecureFile> plain_data:flags.5?SecurePlainData = InputSecureValue;
inputSecureValue#db21d0a7 flags:# type:SecureValueType data:flags.0?SecureData front_side:flags.1?InputSecureFile reverse_side:flags.2?InputSecureFile selfie:flags.3?InputSecureFile translation:flags.6?Vector<InputSecureFile> files:flags.4?Vector<InputSecureFile> plain_data:flags.5?SecurePlainData = InputSecureValue;
secureValueHash#ed1ecdb0 type:SecureValueType hash:bytes = SecureValueHash;
@ -873,16 +893,85 @@ secureValueErrorReverseSide#868a2aa5 type:SecureValueType file_hash:bytes text:s
secureValueErrorSelfie#e537ced6 type:SecureValueType file_hash:bytes text:string = SecureValueError;
secureValueErrorFile#7a700873 type:SecureValueType file_hash:bytes text:string = SecureValueError;
secureValueErrorFiles#666220e9 type:SecureValueType file_hash:Vector<bytes> text:string = SecureValueError;
secureValueError#869d758f type:SecureValueType hash:bytes text:string = SecureValueError;
secureValueErrorTranslationFile#a1144770 type:SecureValueType file_hash:bytes text:string = SecureValueError;
secureValueErrorTranslationFiles#34636dd8 type:SecureValueType file_hash:Vector<bytes> text:string = SecureValueError;
secureCredentialsEncrypted#33f0ea47 data:bytes hash:bytes secret:bytes = SecureCredentialsEncrypted;
account.authorizationForm#cb976d53 flags:# selfie_required:flags.1?true required_types:Vector<SecureValueType> values:Vector<SecureValue> errors:Vector<SecureValueError> users:Vector<User> privacy_policy_url:flags.0?string = account.AuthorizationForm;
account.authorizationForm#ad2e1cd8 flags:# required_types:Vector<SecureRequiredType> values:Vector<SecureValue> errors:Vector<SecureValueError> users:Vector<User> privacy_policy_url:flags.0?string = account.AuthorizationForm;
account.sentEmailCode#811f854f email_pattern:string length:int = account.SentEmailCode;
help.deepLinkInfoEmpty#66afa166 = help.DeepLinkInfo;
help.deepLinkInfo#6a4ee832 flags:# update_app:flags.0?true message:string entities:flags.1?Vector<MessageEntity> = help.DeepLinkInfo;
savedPhoneContact#1142bd56 phone:string first_name:string last_name:string date:int = SavedContact;
account.takeout#4dba4501 id:long = account.Takeout;
passwordKdfAlgoUnknown#d45ab096 = PasswordKdfAlgo;
passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow#3a912d4a salt1:bytes salt2:bytes g:int p:bytes = PasswordKdfAlgo;
securePasswordKdfAlgoUnknown#4a8537 = SecurePasswordKdfAlgo;
securePasswordKdfAlgoPBKDF2HMACSHA512iter100000#bbf2dda0 salt:bytes = SecurePasswordKdfAlgo;
securePasswordKdfAlgoSHA512#86471d92 salt:bytes = SecurePasswordKdfAlgo;
secureSecretSettings#1527bcac secure_algo:SecurePasswordKdfAlgo secure_secret:bytes secure_secret_id:long = SecureSecretSettings;
inputCheckPasswordEmpty#9880f658 = InputCheckPasswordSRP;
inputCheckPasswordSRP#d27ff082 srp_id:long A:bytes M1:bytes = InputCheckPasswordSRP;
secureRequiredType#829d99da flags:# native_names:flags.0?true selfie_required:flags.1?true translation_required:flags.2?true type:SecureValueType = SecureRequiredType;
secureRequiredTypeOneOf#27477b4 types:Vector<SecureRequiredType> = SecureRequiredType;
help.passportConfigNotModified#bfb9f457 = help.PassportConfig;
help.passportConfig#a098d6af hash:int countries_langs:DataJSON = help.PassportConfig;
inputAppEvent#1d1b1245 time:double type:string peer:long data:JSONValue = InputAppEvent;
jsonObjectValue#c0de1bd9 key:string value:JSONValue = JSONObjectValue;
jsonNull#3f6d7b68 = JSONValue;
jsonBool#c7345e6a value:Bool = JSONValue;
jsonNumber#2be0dfa4 value:double = JSONValue;
jsonString#b71e767a value:string = JSONValue;
jsonArray#f7444763 value:Vector<JSONValue> = JSONValue;
jsonObject#99c1d49d value:Vector<JSONObjectValue> = JSONValue;
pageTableCell#34566b6a flags:# header:flags.0?true align_center:flags.3?true align_right:flags.4?true valign_middle:flags.5?true valign_bottom:flags.6?true text:flags.7?RichText colspan:flags.1?int rowspan:flags.2?int = PageTableCell;
pageTableRow#e0c0c5e5 cells:Vector<PageTableCell> = PageTableRow;
pageCaption#6f747657 text:RichText credit:RichText = PageCaption;
pageListItemText#b92fb6cd text:RichText = PageListItem;
pageListItemBlocks#25e073fc blocks:Vector<PageBlock> = PageListItem;
pageListOrderedItemText#5e068047 num:string text:RichText = PageListOrderedItem;
pageListOrderedItemBlocks#98dd8936 num:string blocks:Vector<PageBlock> = PageListOrderedItem;
pageRelatedArticle#b390dc08 flags:# url:string webpage_id:long title:flags.0?string description:flags.1?string photo_id:flags.2?long author:flags.3?string published_date:flags.4?int = PageRelatedArticle;
page#ae891bec flags:# part:flags.0?true rtl:flags.1?true v2:flags.2?true url:string blocks:Vector<PageBlock> photos:Vector<Photo> documents:Vector<Document> = Page;
help.supportName#8c05f1c9 name:string = help.SupportName;
help.userInfoEmpty#f3ae2eed = help.UserInfo;
help.userInfo#1eb3758 message:string entities:Vector<MessageEntity> author:string date:int = help.UserInfo;
pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer;
poll#d5529d06 id:long flags:# closed:flags.0?true question:string answers:Vector<PollAnswer> = Poll;
pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true option:bytes voters:int = PollAnswerVoters;
pollResults#5755785a flags:# min:flags.0?true results:flags.1?Vector<PollAnswerVoters> total_voters:flags.2?int = PollResults;
chatOnlines#f041e250 onlines:int = ChatOnlines;
statsURL#47a971e0 url:string = StatsURL;
---functions---
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@ -890,18 +979,19 @@ invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector<long> query:!X = X;
initConnection#785188b8 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy query:!X = X;
invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X;
invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X;
invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X;
invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X;
auth.sendCode#86aef0ec flags:# allow_flashcall:flags.0?true phone_number:string current_number:flags.0?Bool api_id:int api_hash:string = auth.SentCode;
auth.signUp#1b067634 phone_number:string phone_code_hash:string phone_code:string first_name:string last_name:string = auth.Authorization;
auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization;
auth.logOut#5717da40 = Bool;
auth.resetAuthorizations#9fab0d1a = Bool;
auth.sendInvites#771c1d97 phone_numbers:Vector<string> message:string = Bool;
auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization;
auth.importAuthorization#e3ef9613 id:int bytes:bytes = auth.Authorization;
auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool;
auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_auth_token:string = auth.Authorization;
auth.checkPassword#a63011e password_hash:bytes = auth.Authorization;
auth.checkPassword#d18b4d16 password:InputCheckPasswordSRP = auth.Authorization;
auth.requestPasswordRecovery#d897bc66 = auth.PasswordRecovery;
auth.recoverPassword#4ea56e92 code:string = auth.Authorization;
auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentCode;
@ -930,11 +1020,11 @@ account.updateDeviceLocked#38df3532 period:int = Bool;
account.getAuthorizations#e320c158 = account.Authorizations;
account.resetAuthorization#df77f3bc hash:long = Bool;
account.getPassword#548a30f5 = account.Password;
account.getPasswordSettings#bc8d11bb current_password_hash:bytes = account.PasswordSettings;
account.updatePasswordSettings#fa7c4b86 current_password_hash:bytes new_settings:account.PasswordInputSettings = Bool;
account.getPasswordSettings#9cd4eaf9 password:InputCheckPasswordSRP = account.PasswordSettings;
account.updatePasswordSettings#a59b102f password:InputCheckPasswordSRP new_settings:account.PasswordInputSettings = Bool;
account.sendConfirmPhoneCode#1516d7bd flags:# allow_flashcall:flags.0?true hash:string current_number:flags.0?Bool = auth.SentCode;
account.confirmPhone#5f2178c3 phone_code_hash:string phone_code:string = Bool;
account.getTmpPassword#4a82327e password_hash:bytes period:int = account.TmpPassword;
account.getTmpPassword#449e0b51 password:InputCheckPasswordSRP period:int = account.TmpPassword;
account.getWebAuthorizations#182e6d6f = account.WebAuthorizations;
account.resetWebAuthorization#2d01b9ef hash:long = Bool;
account.resetWebAuthorizations#682d2594 = Bool;
@ -948,29 +1038,39 @@ account.sendVerifyPhoneCode#823380b4 flags:# allow_flashcall:flags.0?true phone_
account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool;
account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode;
account.verifyEmail#ecba39db email:string code:string = Bool;
account.initTakeoutSession#f05b4804 flags:# contacts:flags.0?true message_users:flags.1?true message_chats:flags.2?true message_megagroups:flags.3?true message_channels:flags.4?true files:flags.5?true file_max_size:flags.5?int = account.Takeout;
account.finishTakeoutSession#1d2652ee flags:# success:flags.0?true = Bool;
account.confirmPasswordEmail#8fdf1920 code:string = Bool;
account.resendPasswordEmail#7a7f2a15 = Bool;
account.cancelPasswordEmail#c1cbd5b6 = Bool;
account.getContactSignUpNotification#9f07c728 = Bool;
account.setContactSignUpNotification#cff43f61 silent:Bool = Bool;
account.getNotifyExceptions#53577479 flags:# compare_sound:flags.1?true peer:flags.0?InputNotifyPeer = Updates;
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
users.getFullUser#ca30a5b1 id:InputUser = UserFull;
users.setSecureValueErrors#90c894b5 id:InputUser errors:Vector<SecureValueError> = Bool;
contacts.getContactIDs#2caa4a42 hash:int = Vector<int>;
contacts.getStatuses#c4a353ee = Vector<ContactStatus>;
contacts.getContacts#c023849f hash:int = contacts.Contacts;
contacts.importContacts#2c800be5 contacts:Vector<InputContact> = contacts.ImportedContacts;
contacts.deleteContact#8e953744 id:InputUser = contacts.Link;
contacts.deleteContacts#59ab389e id:Vector<InputUser> = Bool;
contacts.deleteByPhones#1013fd9e phones:Vector<string> = Bool;
contacts.block#332b49fc id:InputUser = Bool;
contacts.unblock#e54100bd id:InputUser = Bool;
contacts.getBlocked#f57c350f offset:int limit:int = contacts.Blocked;
contacts.exportCard#84e53737 = Vector<int>;
contacts.importCard#4fe196fe export_card:Vector<int> = User;
contacts.search#11f812d8 q:string limit:int = contacts.Found;
contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer;
contacts.getTopPeers#d4982db5 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:int = contacts.TopPeers;
contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = Bool;
contacts.resetSaved#879537f1 = Bool;
contacts.getSaved#82f1e39f = Vector<SavedContact>;
contacts.toggleTopPeers#8514bdda enabled:Bool = Bool;
messages.getMessages#63c66506 id:Vector<InputMessage> = messages.Messages;
messages.getDialogs#191ba9c5 flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int = messages.Dialogs;
messages.getDialogs#b098aee6 flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:int = messages.Dialogs;
messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
messages.search#8614ef68 flags:# peer:InputPeer q:string from_id:flags.0?InputUser filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages;
messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages;
@ -1026,10 +1126,10 @@ messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs;
messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool;
messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults;
messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector<InputBotInlineResult> cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool;
messages.sendInlineBotResult#b16e06fe flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string = Updates;
messages.sendInlineBotResult#b16e06fe flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string = Updates;
messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData;
messages.editMessage#c000e4c8 flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> geo_point:flags.13?InputGeoPoint = Updates;
messages.editInlineBotMessage#adc3e828 flags:# no_webpage:flags.1?true stop_geo_live:flags.12?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> geo_point:flags.13?InputGeoPoint = Bool;
messages.editMessage#d116f31e flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> = Updates;
messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector<MessageEntity> = Bool;
messages.getBotCallbackAnswer#810a9fec flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes = messages.BotCallbackAnswer;
messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool;
messages.getPeerDialogs#e470bcfd peers:Vector<InputDialogPeer> = messages.PeerDialogs;
@ -1065,6 +1165,15 @@ messages.getRecentLocations#bbc45b09 peer:InputPeer limit:int hash:int = message
messages.sendMultiMedia#2095512f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector<InputSingleMedia> = Updates;
messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile;
messages.searchStickerSets#c2b7d08b flags:# exclude_featured:flags.0?true q:string hash:int = messages.FoundStickerSets;
messages.getSplitRanges#1cff7e08 = Vector<MessageRange>;
messages.markDialogUnread#c286d98f flags:# unread:flags.0?true peer:InputDialogPeer = Bool;
messages.getDialogUnreadMarks#22e24e22 = Vector<DialogPeer>;
messages.clearAllDrafts#7e58ee9c = Bool;
messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true peer:InputPeer id:int = Updates;
messages.sendVote#10ea6184 peer:InputPeer msg_id:int options:Vector<bytes> = Updates;
messages.getPollResults#73bb643b peer:InputPeer msg_id:int = Updates;
messages.getOnlines#6e2be050 peer:InputPeer = ChatOnlines;
messages.getStatsURL#83f6c0cd peer:InputPeer = StatsURL;
updates.getState#edd4882a = updates.State;
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@ -1086,8 +1195,7 @@ upload.getFileHashes#c7025931 location:InputFileLocation offset:int = Vector<Fil
help.getConfig#c4f9186b = Config;
help.getNearestDc#1fb33026 = NearestDc;
help.getAppUpdate#ae2de196 = help.AppUpdate;
help.saveAppLog#6f02f748 events:Vector<InputAppEvent> = Bool;
help.getAppUpdate#522d5a7d source:string = help.AppUpdate;
help.getInviteText#4d392343 = help.InviteText;
help.getSupport#9cdf08cd = help.Support;
help.getAppChangelog#9010ef6f prev_app_version:string = Updates;
@ -1098,6 +1206,12 @@ help.getProxyData#3d7758e1 = help.ProxyData;
help.getTermsOfServiceUpdate#2ca51fd1 = help.TermsOfServiceUpdate;
help.acceptTermsOfService#ee72f79a id:DataJSON = Bool;
help.getDeepLinkInfo#3fedc75f path:string = help.DeepLinkInfo;
help.getAppConfig#98914110 = JSONValue;
help.saveAppLog#6f02f748 events:Vector<InputAppEvent> = Bool;
help.getPassportConfig#c661ad08 hash:int = help.PassportConfig;
help.getSupportName#d360e72c = help.SupportName;
help.getUserInfo#38a08d3 user_id:InputUser = help.UserInfo;
help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector<MessageEntity> = help.UserInfo;
channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool;
channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector<int> = messages.AffectedMessages;
@ -1123,7 +1237,6 @@ channels.deleteChannel#c0111fe3 channel:InputChannel = Updates;
channels.toggleInvites#49609307 channel:InputChannel enabled:Bool = Updates;
channels.exportMessageLink#ceb77163 channel:InputChannel id:int grouped:Bool = ExportedMessageLink;
channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates;
channels.updatePinnedMessage#a72ded52 flags:# silent:flags.0?true channel:InputChannel id:int = Updates;
channels.getAdminedPublicChannels#8d8d82d7 = messages.Chats;
channels.editBanned#bfd915cd channel:InputChannel user_id:InputUser banned_rights:ChannelBannedRights = Updates;
channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector<InputUser> max_id:long min_id:long limit:int = channels.AdminLogResults;
@ -1131,6 +1244,7 @@ channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet =
channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector<int> = Bool;
channels.deleteHistory#af369d42 channel:InputChannel max_id:int = Bool;
channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates;
channels.getLeftChannels#8341ecc0 offset:int = messages.Chats;
bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
@ -1156,9 +1270,10 @@ phone.discardCall#78d413a6 peer:InputPhoneCall duration:int reason:PhoneCallDisc
phone.setCallRating#1c536a34 peer:InputPhoneCall rating:int comment:string = Updates;
phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool;
langpack.getLangPack#9ab5c58e lang_code:string = LangPackDifference;
langpack.getStrings#2e1ee318 lang_code:string keys:Vector<string> = Vector<LangPackString>;
langpack.getDifference#b2e4d7d from_version:int = LangPackDifference;
langpack.getLanguages#800fd57d = Vector<LangPackLanguage>;
langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference;
langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector<string> = Vector<LangPackString>;
langpack.getDifference#9d51e814 lang_code:string from_version:int = LangPackDifference;
langpack.getLanguages#42c6978f lang_pack:string = Vector<LangPackLanguage>;
langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLanguage;
// LAYER 81
// LAYER 91

View File

@ -1,22 +0,0 @@
// Pyrogram
---types---
//pyrogram.update#b0700000 flags:# update_id:int message:flags.0?Message edited_message:flags.1?Message channel_post:flags.2?Message edited_channel_post:flags.3?Message inline_query:flags.4?InlineQuery chosen_inline_result:flags.5?ChosenInlineResult callback_query:flags.6?CallbackQuery shipping_query:flags.7?ShippingQuery pre_checkout_query:flags.8?PreCheckoutQuery = pyrogram.Update;
//pyrogram.user#b0700001 flags:# id:int is_bot:Bool first_name:string last_name:flags.0?string username:flags.1?string language_code:flags.2?string phone_number:flags.3?string photo:flags.4?ChatPhoto = pyrogram.User;
//pyrogram.chat#b0700002 flags:# id:int type:string title:flags.0?string username:flags.1?string first_name:flags.2?string last_name:flags.3?string all_members_are_administrators:flags.4?Bool photo:flags.5?ChatPhoto description:flags.6?string invite_link:flags.7?string pinned_message:flags.8?Message sticker_set_name:flags.9?string can_set_sticker_set:flags.10?Bool = pyrogram.Chat;
//pyrogram.message#b0700003 flags:# message_id:int from_user:flags.0?User date:int chat:Chat forward_from:flags.1?User forward_from_chat:flags.2?Chat forward_from_message_id:flags.3?int forward_signature:flags.4?string forward_date:flags.5?int reply_to_message:flags.6?Message edit_date:flags.7?int media_group_id:flags.8?string author_signature:flags.9?string text:flags.10?string entities:flags.11?Vector<MessageEntity> caption_entities:flags.12?Vector<MessageEntity> audio:flags.13?Audio document:flags.14?Document game:flags.15?Game photo:flags.16?Vector<PhotoSize> sticker:flags.17?Sticker video:flags.18?Video voice:flags.19?Voice video_note:flags.20?VideoNote caption:flags.21?string contact:flags.22?Contact location:flags.23?Location venue:flags.24?Venue new_chat_members:flags.25?Vector<User> left_chat_member:flags.26?User new_chat_title:flags.27?string new_chat_photo:flags.28?Vector<PhotoSize> delete_chat_photo:flags.29?true group_chat_created:flags.30?true supergroup_chat_created:flags.31?true channel_chat_created:flags.32?true migrate_to_chat_id:flags.33?int migrate_from_chat_id:flags.34?int pinned_message:flags.35?Message invoice:flags.36?Invoice successful_payment:flags.37?SuccessfulPayment connected_website:flags.38?string views:flags.39?int via_bot:flags.40?User = pyrogram.Message;
//pyrogram.messageEntity#b0700004 flags:# type:string offset:int length:int url:flags.0?string user:flags.1?User = pyrogram.MessageEntity;
//pyrogram.photoSize#b0700005 flags:# file_id:string file_size:flags.0?int date:flags.1?int width:int height:int = pyrogram.PhotoSize;
//pyrogram.audio#b0700006 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int duration:int performer:flags.5?string title:flags.6?string = pyrogram.Audio;
//pyrogram.document#b0700007 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int = pyrogram.Document;
//pyrogram.video#b0700008 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int width:int height:int duration:int = pyrogram.Video;
//pyrogram.voice#b0700009 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int duration:int = pyrogram.Voice;
//pyrogram.videoNote#b0700010 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int length:int duration:int = pyrogram.VideoNote;
//pyrogram.contact#b0700011 flags:# phone_number:string first_name:string last_name:flags.0?string user_id:flags.1?int = pyrogram.Contact;
//pyrogram.location#b0700012 longitude:double latitude:double = pyrogram.Location;
//pyrogram.venue#b0700013 flags:# location:Location title:string address:string foursquare_id:flags.0?string = pyrogram.Venue;
//pyrogram.userProfilePhotos#b0700014 total_count:int photos:Vector<Vector<PhotoSize>> = pyrogram.UserProfilePhotos;
//pyrogram.chatPhoto#b0700015 small_file_id:string big_file_id:string = pyrogram.ChatPhoto;
//pyrogram.chatMember#b0700016 flags:# user:User status:string until_date:flags.0?int can_be_edited:flags.1?Bool can_change_info:flags.2?Bool can_post_messages:flags.3?Bool can_edit_messages:flags.4?Bool can_delete_messages:flags.5?Bool can_invite_users:flags.6?Bool can_restrict_members:flags.7?Bool can_pin_messages:flags.8?Bool can_promote_members:flags.9?Bool can_send_messages:flags.10?Bool can_send_media_messages:flags.11?Bool can_send_other_messages:flags.12?Bool can_add_web_page_previews:flags.13?Bool = pyrogram.ChatMember;
//pyrogram.sticker#b0700017 flags:# file_id:string thumb:flags.0?PhotoSize file_name:flags.1?string mime_type:flags.2?string file_size:flags.3?int date:flags.4?int width:int height:int emoji:flags.5?string set_name:flags.6?string mask_position:flags.7?MaskPosition = pyrogram.Sticker;

View File

@ -88,7 +88,7 @@ def generate(source_path, base):
inner_path = base + "/" + k + "/index" + ".rst"
module = "pyrogram.api.{}.{}".format(base, k)
else:
for i in list(all_entities)[::-1]:
for i in sorted(list(all_entities), reverse=True):
if i != base:
entities.insert(0, "{0}/index".format(i))

View File

@ -51,7 +51,7 @@ BOT_INLINE_DISABLED The inline feature of the bot is disabled
INLINE_RESULT_EXPIRED The inline bot query expired
INVITE_HASH_INVALID The invite link hash is invalid
USER_ALREADY_PARTICIPANT The user is already a participant of this chat
TTL_MEDIA_INVALID This kind of media does not support self-destruction
TTL_MEDIA_INVALID The media does not support self-destruction
MAX_ID_INVALID The max_id parameter is invalid
CHANNEL_INVALID The channel parameter is invalid
DC_ID_INVALID The dc_id parameter is invalid
@ -59,7 +59,25 @@ LIMIT_INVALID The limit parameter is invalid
OFFSET_INVALID The offset parameter is invalid
EMAIL_INVALID The email provided is invalid
USER_IS_BOT A bot cannot send messages to other bots or to itself
WEBPAGE_CURL_FAILED Telegram could not fetch the provided URL
WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL
STICKERSET_INVALID The requested sticker set is invalid
PEER_FLOOD The method can't be used because your account is limited
MEDIA_CAPTION_TOO_LONG The media caption is longer than 200 characters
USER_NOT_MUTUAL_CONTACT The user is not a mutual contact
USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups
API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side
USER_NOT_PARTICIPANT The user is not a member of this chat
CHANNEL_PRIVATE The channel/supergroup is not accessible
MESSAGE_IDS_EMPTY The requested message doesn't exist
WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media
QUERY_ID_INVALID The callback query id is invalid
MEDIA_EMPTY The media is invalid
USER_IS_BLOCKED The user blocked you
YOU_BLOCKED_USER You blocked this user
ADMINS_TOO_MUCH The chat has too many administrators
BOTS_TOO_MUCH The chat has too many bots
USER_ADMIN_INVALID The action requires admin privileges
INPUT_USER_DEACTIVATED The target user has been deactivated
PASSWORD_RECOVERY_NA The password recovery e-mail is not available
PASSWORD_EMPTY The password entered is empty
PHONE_NUMBER_FLOOD This number has tried to login too many times
1 id message
51 INLINE_RESULT_EXPIRED The inline bot query expired
52 INVITE_HASH_INVALID The invite link hash is invalid
53 USER_ALREADY_PARTICIPANT The user is already a participant of this chat
54 TTL_MEDIA_INVALID This kind of media does not support self-destruction The media does not support self-destruction
55 MAX_ID_INVALID The max_id parameter is invalid
56 CHANNEL_INVALID The channel parameter is invalid
57 DC_ID_INVALID The dc_id parameter is invalid
59 OFFSET_INVALID The offset parameter is invalid
60 EMAIL_INVALID The email provided is invalid
61 USER_IS_BOT A bot cannot send messages to other bots or to itself
62 WEBPAGE_CURL_FAILED Telegram could not fetch the provided URL Telegram server could not fetch the provided URL
63 STICKERSET_INVALID The requested sticker set is invalid
64 PEER_FLOOD The method can't be used because your account is limited
65 MEDIA_CAPTION_TOO_LONG The media caption is longer than 200 characters
66 USER_NOT_MUTUAL_CONTACT The user is not a mutual contact
67 USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups
68 API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side
69 USER_NOT_PARTICIPANT The user is not a member of this chat
70 CHANNEL_PRIVATE The channel/supergroup is not accessible
71 MESSAGE_IDS_EMPTY The requested message doesn't exist
72 WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media
73 QUERY_ID_INVALID The callback query id is invalid
74 MEDIA_EMPTY The media is invalid
75 USER_IS_BLOCKED The user blocked you
76 YOU_BLOCKED_USER You blocked this user
77 ADMINS_TOO_MUCH The chat has too many administrators
78 BOTS_TOO_MUCH The chat has too many bots
79 USER_ADMIN_INVALID The action requires admin privileges
80 INPUT_USER_DEACTIVATED The target user has been deactivated
81 PASSWORD_RECOVERY_NA The password recovery e-mail is not available
82 PASSWORD_EMPTY The password entered is empty
83 PHONE_NUMBER_FLOOD This number has tried to login too many times

View File

@ -0,0 +1,5 @@
id message
CHAT_WRITE_FORBIDDEN You don't have rights to send messages in this chat
RIGHT_FORBIDDEN One or more admin rights can't be applied to this kind of chat (channel/supergroup)
CHAT_ADMIN_INVITE_REQUIRED You don't have rights to invite other users
MESSAGE_DELETE_FORBIDDEN You don't have rights to delete messages in this chat
1 id message
2 CHAT_WRITE_FORBIDDEN You don't have rights to send messages in this chat
3 RIGHT_FORBIDDEN One or more admin rights can't be applied to this kind of chat (channel/supergroup)
4 CHAT_ADMIN_INVITE_REQUIRED You don't have rights to invite other users
5 MESSAGE_DELETE_FORBIDDEN You don't have rights to delete messages in this chat

View File

@ -1,2 +1,3 @@
id message
AUTH_KEY_DUPLICATED Authorization error. You must log out and log in again with your phone number. We apologize for the inconvenience.
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
1 id message
2 AUTH_KEY_DUPLICATED Authorization error. You must log out and log in again with your phone number. We apologize for the inconvenience. Authorization error - you must delete your session file and log in again with your phone number
3 FILEREF_UPGRADE_NEEDED The file reference has expired - you must obtain the original media message

View File

@ -4,3 +4,5 @@ RPC_CALL_FAIL Telegram is having internal problems. Please try again later
RPC_MCGET_FAIL Telegram is having internal problems. Please try again later
PERSISTENT_TIMESTAMP_OUTDATED Telegram is having internal problems. Please try again later
HISTORY_GET_FAILED Telegram is having internal problems. Please try again later
REG_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later
RANDOM_ID_DUPLICATE Telegram is having internal problems. Please try again later
1 id message
4 RPC_MCGET_FAIL Telegram is having internal problems. Please try again later
5 PERSISTENT_TIMESTAMP_OUTDATED Telegram is having internal problems. Please try again later
6 HISTORY_GET_FAILED Telegram is having internal problems. Please try again later
7 REG_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later
8 RANDOM_ID_DUPLICATE Telegram is having internal problems. Please try again later

View File

@ -3,7 +3,7 @@
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = ~/PycharmProjects/pyrogram/venv3.6/bin/sphinx-build
SPHINXBUILD = sphinx-build
SPHINXPROJ = Pyrogram
SOURCEDIR = source
BUILDDIR = build

View File

@ -1,20 +0,0 @@
# Minimal makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
SPHINXPROJ = Pyrogram
SOURCEDIR = source
BUILDDIR = build
# Put it first so that "make" without argument is like "make help".
help:
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
.PHONY: help Makefile
# Catch-all target: route all unknown targets to Sphinx using the new
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
%: Makefile
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)

View File

@ -5,8 +5,7 @@ Welcome to Pyrogram
<div align="center">
<a href="https://docs.pyrogram.ml">
<div><img src="https://media.pyrogram.ml/images/icon.png" alt="Pyrogram Icon"></div>
<div><img src="https://media.pyrogram.ml/images/label.png" alt="Pyrogram Label"></div>
<div><img src="_static/logo.png" alt="Pyrogram Logo"></div>
</a>
</div>
@ -24,13 +23,13 @@ Welcome to Pyrogram
<a href="https://t.me/PyrogramChat">
Community
</a>
<br><br>
<br>
<a href="https://github.com/pyrogram/pyrogram/blob/master/compiler/api/source/main_api.tl">
<img src="https://img.shields.io/badge/SCHEME-LAYER%2081-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
<img src="https://img.shields.io/badge/schema-layer%2091-eda738.svg?longCache=true&colorA=262b30"
alt="Scheme Layer">
</a>
<a href="https://github.com/pyrogram/tgcrypto">
<img src="https://img.shields.io/badge/TGCRYPTO-V1.0.4-eda738.svg?longCache=true&style=for-the-badge&colorA=262b30"
<img src="https://img.shields.io/badge/tgcrypto-v1.1.1-eda738.svg?longCache=true&colorA=262b30"
alt="TgCrypto">
</a>
</p>
@ -50,14 +49,14 @@ Welcome to Pyrogram
app.run()
Welcome to Pyrogram's Documentation! Here you can find resources for learning how to use the library.
Contents are organized by topic and can be accessed from the sidebar, or by following them one by one using the Next
button at the end of each page. But first, here's a brief overview of what is this all about.
Contents are organized into self-contained topics and can be accessed from the sidebar, or by following them in order
using the Next button at the end of each page. But first, here's a brief overview of what is this all about.
About
-----
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for building
custom Telegram applications that interact with the MTProto API as both User and Bot.
**Pyrogram** is a brand new Telegram_ Client Library written from the ground up in Python and C. It can be used for
building custom Telegram applications that interact with the MTProto API as both User and Bot.
Features
--------
@ -65,7 +64,7 @@ Features
- **Easy to use**: You can easily install Pyrogram using pip and start building your app right away.
- **High-level**: The low-level details of MTProto are abstracted and automatically handled.
- **Fast**: Crypto parts are boosted up by TgCrypto_, a high-performance library written in pure C.
- **Updated** to the latest Telegram API version, currently Layer 81 on top of MTProto 2.0.
- **Updated** to the latest Telegram API version, currently Layer 91 on top of MTProto 2.0.
- **Documented**: The Pyrogram API is well documented and resembles the Telegram Bot API.
- **Full API**, allowing to execute any advanced action an official client is able to do, and more.
@ -84,6 +83,8 @@ To get started, press the Next button.
:caption: Resources
resources/UpdateHandling
resources/UsingFilters
resources/SmartPlugins
resources/AutoAuthorization
resources/CustomizeSessions
resources/TgCrypto
@ -91,6 +92,8 @@ To get started, press the Next button.
resources/SOCKS5Proxy
resources/BotsInteraction
resources/ErrorHandling
resources/TestServers
resources/Changelog
.. toctree::
:hidden:

View File

@ -1,31 +1,45 @@
Client
======
.. currentmodule:::: pyrogram.Client
.. currentmodule:: pyrogram.Client
.. autoclass:: pyrogram.Client
:inherited-members:
:members:
.. _available-methods:
Utilities
---------
**Available methods**
.. autosummary::
.. autosummary::
:nosignatures:
start
stop
idle
run
on_message
on_callback_query
on_raw_update
add_handler
remove_handler
send
resolve_peer
get_me
download_media
Decorators
----------
.. autosummary::
:nosignatures:
on_message
on_callback_query
on_deleted_messages
on_user_status
on_disconnect
on_raw_update
Messages
--------
.. autosummary::
:nosignatures:
send_message
forward_messages
send_photo
@ -33,6 +47,7 @@ Client
send_document
send_sticker
send_video
send_animation
send_voice
send_video_note
send_media_group
@ -40,31 +55,86 @@ Client
send_venue
send_contact
send_chat_action
download_media
get_user_profile_photos
delete_profile_photos
edit_message_text
edit_message_caption
edit_message_reply_markup
edit_message_media
delete_messages
get_messages
get_history
send_poll
vote_poll
retract_vote
Chats
-----
.. autosummary::
:nosignatures:
join_chat
leave_chat
export_chat_invite_link
enable_cloud_password
change_cloud_password
remove_cloud_password
kick_chat_member
unban_chat_member
restrict_chat_member
promote_chat_member
export_chat_invite_link
set_chat_photo
delete_chat_photo
set_chat_title
set_chat_description
pin_chat_message
unpin_chat_message
get_chat
get_chat_member
get_chat_members
get_chat_members_count
get_dialogs
Users
-----
.. autosummary::
:nosignatures:
get_me
get_users
get_user_profile_photos
set_user_profile_photo
delete_user_profile_photos
Contacts
--------
.. autosummary::
:nosignatures:
add_contacts
get_contacts
delete_contacts
Password
--------
.. autosummary::
:nosignatures:
enable_cloud_password
change_cloud_password
remove_cloud_password
Bots
----
.. autosummary::
:nosignatures:
get_inline_bot_results
send_inline_bot_result
answer_callback_query
request_callback_answer
get_users
get_chat
get_messages
get_history
.. autoclass:: pyrogram.Client
:inherited-members:
:members:

View File

@ -1,5 +1,3 @@
:tocdepth: 1
Error
=====

View File

@ -3,4 +3,3 @@ Filters
.. autoclass:: pyrogram.Filters
:members:
:undoc-members:

View File

@ -0,0 +1,33 @@
Handlers
========
.. currentmodule:: pyrogram
.. autosummary::
:nosignatures:
MessageHandler
DeletedMessagesHandler
CallbackQueryHandler
UserStatusHandler
DisconnectHandler
RawUpdateHandler
.. autoclass:: MessageHandler
:members:
.. autoclass:: DeletedMessagesHandler
:members:
.. autoclass:: CallbackQueryHandler
:members:
.. autoclass:: UserStatusHandler
:members:
.. autoclass:: DisconnectHandler
:members:
.. autoclass:: RawUpdateHandler
:members:

View File

@ -0,0 +1,200 @@
Types
=====
.. currentmodule:: pyrogram
Users & Chats
-------------
.. autosummary::
:nosignatures:
User
UserStatus
Chat
ChatPhoto
ChatMember
ChatMembers
Dialog
Dialogs
Messages & Media
----------------
.. autosummary::
:nosignatures:
Message
Messages
MessageEntity
Photo
PhotoSize
UserProfilePhotos
Audio
Document
Animation
Video
Voice
VideoNote
Contact
Location
Venue
Sticker
Poll
PollOption
Bots
----
.. autosummary::
:nosignatures:
ReplyKeyboardMarkup
KeyboardButton
ReplyKeyboardRemove
InlineKeyboardMarkup
InlineKeyboardButton
ForceReply
CallbackQuery
Input Media
-----------
.. autosummary::
:nosignatures:
InputMediaPhoto
InputMediaVideo
InputMediaAudio
InputMediaAnimation
InputMediaDocument
InputPhoneContact
.. User & Chats
------------
.. autoclass:: User
:members:
.. autoclass:: UserStatus
:members:
.. autoclass:: Chat
:members:
.. autoclass:: ChatPhoto
:members:
.. autoclass:: ChatMember
:members:
.. autoclass:: ChatMembers
:members:
.. autoclass:: Dialog
:members:
.. autoclass:: Dialogs
:members:
.. Messages & Media
----------------
.. autoclass:: Message
:members:
.. autoclass:: Messages
:members:
.. autoclass:: MessageEntity
:members:
.. autoclass:: Photo
:members:
.. autoclass:: PhotoSize
:members:
.. autoclass:: UserProfilePhotos
:members:
.. autoclass:: Audio
:members:
.. autoclass:: Document
:members:
.. autoclass:: Animation
:members:
.. autoclass:: Video
:members:
.. autoclass:: Voice
:members:
.. autoclass:: VideoNote
:members:
.. autoclass:: Contact
:members:
.. autoclass:: Location
:members:
.. autoclass:: Venue
:members:
.. autoclass:: Sticker
:members:
.. autoclass:: Poll
:members:
.. autoclass:: PollOption
:members:
.. Bots
----
.. autoclass:: ReplyKeyboardMarkup
:members:
.. autoclass:: KeyboardButton
:members:
.. autoclass:: ReplyKeyboardRemove
:members:
.. autoclass:: InlineKeyboardMarkup
:members:
.. autoclass:: InlineKeyboardButton
:members:
.. autoclass:: ForceReply
:members:
.. autoclass:: CallbackQuery
:members:
.. Input Media
-----------
.. autoclass:: InputMediaPhoto
:members:
.. autoclass:: InputMediaVideo
:members:
.. autoclass:: InputMediaAudio
:members:
.. autoclass:: InputMediaAnimation
:members:
.. autoclass:: InputMediaDocument
:members:
.. autoclass:: InputPhoneContact
:members:

View File

@ -1,6 +0,0 @@
CallbackQueryHandler
====================
.. autoclass:: pyrogram.CallbackQueryHandler
:members:
:undoc-members:

View File

@ -1,6 +0,0 @@
DeletedMessagesHandler
======================
.. autoclass:: pyrogram.DeletedMessagesHandler
:members:
:undoc-members:

View File

@ -1,6 +0,0 @@
DisconnectHandler
=================
.. autoclass:: pyrogram.DisconnectHandler
:members:
:undoc-members:

View File

@ -1,6 +0,0 @@
MessageHandler
==============
.. autoclass:: pyrogram.MessageHandler
:members:
:undoc-members:

View File

@ -1,6 +0,0 @@
RawUpdateHandler
================
.. autoclass:: pyrogram.RawUpdateHandler
:members:
:undoc-members:

View File

@ -1,11 +0,0 @@
:tocdepth: 1
Handlers
========
.. toctree::
MessageHandler
DeletedMessagesHandler
CallbackQueryHandler
DisconnectHandler
RawUpdateHandler

View File

@ -7,9 +7,11 @@ In this section you can find a detailed description of the Pyrogram package and
after the well established `Telegram Bot API`_ methods, thus offering a familiar look to Bot developers.
.. toctree::
:maxdepth: 1
Client
types/index
handlers/index
Types
Handlers
Filters
ChatAction
ParseMode

View File

@ -1,5 +0,0 @@
Audio
=====
.. autoclass:: pyrogram.Audio
:members:

View File

@ -1,5 +0,0 @@
CallbackQuery
=============
.. autoclass:: pyrogram.CallbackQuery
:members:

View File

@ -1,5 +0,0 @@
Chat
====
.. autoclass:: pyrogram.Chat
:members:

View File

@ -1,5 +0,0 @@
ChatMember
==========
.. autoclass:: pyrogram.ChatMember
:members:

View File

@ -1,5 +0,0 @@
ChatPhoto
=========
.. autoclass:: pyrogram.ChatPhoto
:members:

View File

@ -1,5 +0,0 @@
Contact
=======
.. autoclass:: pyrogram.Contact
:members:

View File

@ -1,5 +0,0 @@
Document
========
.. autoclass:: pyrogram.Document
:members:

View File

@ -1,5 +0,0 @@
GIF
===
.. autoclass:: pyrogram.GIF
:members:

View File

@ -1,5 +0,0 @@
InputMediaPhoto
===============
.. autoclass:: pyrogram.InputMediaPhoto
:members:

View File

@ -1,5 +0,0 @@
InputMediaVideo
===============
.. autoclass:: pyrogram.InputMediaVideo
:members:

View File

@ -1,5 +0,0 @@
InputPhoneContact
=================
.. autoclass:: pyrogram.InputPhoneContact
:members:

View File

@ -1,5 +0,0 @@
Location
========
.. autoclass:: pyrogram.Location
:members:

View File

@ -1,5 +0,0 @@
Message
=======
.. autoclass:: pyrogram.Message
:members:

View File

@ -1,5 +0,0 @@
MessageEntity
=============
.. autoclass:: pyrogram.MessageEntity
:members:

View File

@ -1,5 +0,0 @@
Messages
========
.. autoclass:: pyrogram.Messages
:members:

View File

@ -1,5 +0,0 @@
Photo
=====
.. autoclass:: pyrogram.Photo
:members:

View File

@ -1,5 +0,0 @@
PhotoSize
=========
.. autoclass:: pyrogram.PhotoSize
:members:

View File

@ -1,5 +0,0 @@
Sticker
=======
.. autoclass:: pyrogram.Sticker
:members:

View File

@ -1,5 +0,0 @@
Update
======
.. autoclass:: pyrogram.Update
:members:

View File

@ -1,5 +0,0 @@
User
====
.. autoclass:: pyrogram.User
:members:

View File

@ -1,5 +0,0 @@
UserProfilePhotos
=================
.. autoclass:: pyrogram.UserProfilePhotos
:members:

View File

@ -1,5 +0,0 @@
Venue
=====
.. autoclass:: pyrogram.Venue
:members:

View File

@ -1,5 +0,0 @@
Video
=====
.. autoclass:: pyrogram.Video
:members:

View File

@ -1,5 +0,0 @@
VideoNote
=========
.. autoclass:: pyrogram.VideoNote
:members:

View File

@ -1,5 +0,0 @@
Voice
=====
.. autoclass:: pyrogram.Voice
:members:

View File

@ -1,36 +0,0 @@
:tocdepth: 1
Types
=====
.. toctree::
User
Chat
Message
MessageEntity
Messages
Photo
PhotoSize
Audio
Document
GIF
Video
Voice
VideoNote
Contact
Location
Venue
UserProfilePhotos
ChatPhoto
ChatMember
InputMediaPhoto
InputMediaVideo
InputPhoneContact
Sticker
reply_markup/ForceReply
reply_markup/InlineKeyboardButton
reply_markup/InlineKeyboardMarkup
reply_markup/KeyboardButton
reply_markup/ReplyKeyboardMarkup
reply_markup/ReplyKeyboardRemove
CallbackQuery

View File

@ -1,5 +0,0 @@
ForceReply
==========
.. autoclass:: pyrogram.ForceReply
:members:

View File

@ -1,5 +0,0 @@
InlineKeyboardButton
====================
.. autoclass:: pyrogram.InlineKeyboardButton
:members:

View File

@ -1,5 +0,0 @@
InlineKeyboardMarkup
====================
.. autoclass:: pyrogram.InlineKeyboardMarkup
:members:

View File

@ -1,5 +0,0 @@
KeyboardButton
==============
.. autoclass:: pyrogram.KeyboardButton
:members:

View File

@ -1,5 +0,0 @@
ReplyKeyboardMarkup
===================
.. autoclass:: pyrogram.ReplyKeyboardMarkup
:members:

View File

@ -1,5 +0,0 @@
ReplyKeyboardRemove
===================
.. autoclass:: pyrogram.ReplyKeyboardRemove
:members:

View File

@ -35,12 +35,9 @@ Example:
password="password" # (if you have one)
)
app.start()
with app:
print(app.get_me())
app.stop()
Sign Up
-------
@ -67,8 +64,5 @@ Example:
last_name="" # Can be an empty string
)
app.start()
with app:
print(app.get_me())
app.stop()

View File

@ -0,0 +1,11 @@
Changelog
=========
Currently, all Pyrogram release notes live inside the GitHub repository web page:
https://github.com/pyrogram/pyrogram/releases
(You will be automatically redirected in 10 seconds.)
.. raw:: html
<meta http-equiv="refresh" content="10; URL=https://github.com/pyrogram/pyrogram/releases"/>

View File

@ -0,0 +1,126 @@
Smart Plugins
=============
Pyrogram embeds a **smart** (automatic) and lightweight plugin system that is meant to further simplify the organization
of large projects and to provide a way for creating pluggable components that can be **easily shared** across different
Pyrogram applications with **minimal boilerplate code**.
.. tip::
Smart Plugins are completely optional and disabled by default.
Introduction
------------
Prior to the Smart Plugin system, pluggable handlers were already possible. For example, if you wanted to modularize
your applications, you had to do something like this...
.. note::
This is an example application that replies in private chats with two messages: one containing the same
text message you sent and the other containing the reversed text message.
Example: *"Pyrogram"* replies with *"Pyrogram"* and *"margoryP"*
.. code-block:: text
myproject/
config.ini
handlers.py
main.py
- ``handlers.py``
.. code-block:: python
def echo(client, message):
message.reply(message.text)
def echo_reversed(client, message):
message.reply(message.text[::-1])
- ``main.py``
.. code-block:: python
from pyrogram import Client, MessageHandler, Filters
from handlers import echo, echo_reversed
app = Client("my_account")
app.add_handler(
MessageHandler(
echo,
Filters.text & Filters.private))
app.add_handler(
MessageHandler(
echo_reversed,
Filters.text & Filters.private),
group=1)
app.run()
...which is already nice and doesn't add *too much* boilerplate code, but things can get boring still; you have to
manually ``import``, manually :meth:`add_handler <pyrogram.Client.add_handler>` and manually instantiate each
:obj:`MessageHandler <pyrogram.MessageHandler>` object because **you can't use those cool decorators** for your
functions. So... What if you could?
Using Smart Plugins
-------------------
Setting up your Pyrogram project to accommodate Smart Plugins is pretty straightforward:
#. Create a new folder to store all the plugins (e.g.: "plugins").
#. Put your files full of plugins inside.
#. Enable plugins in your Client.
.. note::
This is the same example application `as shown above <#introduction>`_, written using the Smart Plugin system.
.. code-block:: text
:emphasize-lines: 2, 3
myproject/
plugins/
handlers.py
config.ini
main.py
- ``plugins/handlers.py``
.. code-block:: python
:emphasize-lines: 4, 9
from pyrogram import Client, Filters
@Client.on_message(Filters.text & Filters.private)
def echo(client, message):
message.reply(message.text)
@Client.on_message(Filters.text & Filters.private, group=1)
def echo_reversed(client, message):
message.reply(message.text[::-1])
- ``main.py``
.. code-block:: python
from pyrogram import Client
Client("my_account", plugins_dir="plugins").run()
The first important thing to note is the new ``plugins`` folder, whose name is passed to the the ``plugins_dir``
parameter when creating a :obj:`Client <pyrogram.Client>` in the ``main.py`` file — you can put *any python file* in
there and each file can contain *any decorated function* (handlers) with only one limitation: within a single plugin
file you must use different names for each decorated function. Your Pyrogram Client instance will **automatically**
scan the folder upon creation to search for valid handlers and register them for you.
Then you'll notice you can now use decorators. That's right, you can apply the usual decorators to your callback
functions in a static way, i.e. **without having the Client instance around**: simply use ``@Client`` (Client class)
instead of the usual ``@app`` (Client instance) namespace and things will work just the same.

View File

@ -0,0 +1,39 @@
Test Servers
============
If you wish to test your application in a separate environment, Pyrogram is able to authorize your account into
Telegram's test servers without hassle. All you need to do is start a new session (e.g.: "my_account_test") using
``test_mode=True``:
.. code-block:: python
from pyrogram import Client
with Client("my_account_test", test_mode=True) as app:
print(app.get_me())
.. note::
If this is the first time you login into test servers, you will be asked to register your account first.
Don't worry about your contacts and chats, they will be kept untouched inside the production environment;
accounts authorized on test servers reside in a different, parallel instance of a Telegram database.
Test Mode in Official Apps
--------------------------
You can also login yourself into test servers using official desktop apps, such as Webogram and TDesktop:
- **Webogram**: Login here: https://web.telegram.org/?test=1
- **TDesktop**: Open settings and type ``testmode``.
Test Numbers
------------
Beside normal numbers, the test environment allows you to login with reserved test numbers.
Valid phone numbers follow the pattern ``99966XYYYY``, where ``X`` is the DC number (1 to 3) and ``YYYY`` are random
numbers. Users with such numbers always get ``XXXXX`` as the confirmation code (the DC number, repeated five times).
.. important::
Do not store any important or private information in such test users' accounts; anyone can make use of the
simplified authorization mechanism and login at any time.

View File

@ -1,8 +1,11 @@
Text Formatting
===============
Pyrogram, just like `Telegram Bot API`_, supports basic Markdown and HTML formatting styles for text messages and
media captions; Markdown uses the same syntax as Telegram Desktop's and is enabled by default.
Pyrogram, just like the `Telegram Bot API`_, natively supports basic Markdown and HTML formatting styles for text
messages and media captions.
Markdown style uses the same syntax as Telegram Desktop's and is enabled by default.
Beside bold, italic, and pre-formatted code, **Pyrogram does also support inline URLs and inline mentions of users**.
Markdown Style
@ -11,7 +14,7 @@ Markdown Style
To use this mode, pass :obj:`MARKDOWN <pyrogram.ParseMode.MARKDOWN>` or "markdown" in the *parse_mode* field when using
:obj:`send_message() <pyrogram.Client.send_message>`. Use the following syntax in your message:
.. code-block:: txt
.. code-block:: text
**bold text**
@ -34,7 +37,7 @@ HTML Style
To use this mode, pass :obj:`HTML <pyrogram.ParseMode.HTML>` or "html" in the *parse_mode* field when using
:obj:`send_message() <pyrogram.Client.send_message>`. The following tags are currently supported:
.. code-block:: txt
.. code-block:: text
<b>bold</b>, <strong>bold</strong>
@ -46,9 +49,7 @@ To use this mode, pass :obj:`HTML <pyrogram.ParseMode.HTML>` or "html" in the *p
<code>inline fixed-width code</code>
<pre>pre-formatted fixed-width
code block
</pre>
<pre>pre-formatted fixed-width code block</pre>
.. note:: Mentions are only guaranteed to work if you have already met the user (in groups or private chats).

View File

@ -2,26 +2,28 @@ Update Handling
===============
Updates are events that happen in your Telegram account (incoming messages, new channel posts, new members join, ...)
and are handled by registering one or more callback functions with an Handler. There are multiple Handlers to choose
from, one for each kind of update:
and can be handled by registering one or more callback functions in your app by using `Handlers <../pyrogram/Handlers.html>`_.
- `MessageHandler <../pyrogram/handlers/MessageHandler.html>`_
- `DeletedMessagesHandler <../pyrogram/handlers/DeletedMessagesHandler.html>`_
- `CallbackQueryHandler <../pyrogram/handlers/CallbackQueryHandler.html>`_
- `RawUpdateHandler <../pyrogram/handlers/RawUpdateHandler.html>`_
- `DisconnectHandler <../pyrogram/handlers/DisconnectHandler.html>`_
To put it simply, whenever an update is received from Telegram it will be dispatched and your previously defined callback
function(s) matching it will be called back with the update itself as argument.
Registering an Handler
----------------------
We shall examine the :obj:`MessageHandler <pyrogram.MessageHandler>`, which will be in charge for handling
:obj:`Message <pyrogram.Message>` objects.
To explain how handlers work let's have a look at the most used one, the
:obj:`MessageHandler <pyrogram.MessageHandler>`, which will be in charge for handling :obj:`Message <pyrogram.Message>`
updates coming from all around your chats. Every other handler shares the same setup logic; you should not have troubles
settings them up once you learn from this section.
- The easiest and nicest way to register a MessageHandler is by decorating your function with the
:meth:`on_message() <pyrogram.Client.on_message>` decorator. Here's a full example that prints out the content
of a message as soon as it arrives.
.. code-block:: python
Using Decorators
----------------
The easiest and nicest way to register a MessageHandler is by decorating your function with the
:meth:`on_message() <pyrogram.Client.on_message>` decorator. Here's a full example that prints out the content
of a message as soon as it arrives.
.. code-block:: python
from pyrogram import Client
@ -35,10 +37,13 @@ We shall examine the :obj:`MessageHandler <pyrogram.MessageHandler>`, which will
app.run()
- If you prefer not to use decorators, there is an alternative way for registering Handlers.
This is useful, for example, when you want to keep your callback functions in separate files.
Using add_handler()
-------------------
.. code-block:: python
If you prefer not to use decorators for any reason, there is an alternative way for registering Handlers.
This is useful, for example, when you want to keep your callback functions in separate files.
.. code-block:: python
from pyrogram import Client, MessageHandler
@ -52,138 +57,3 @@ We shall examine the :obj:`MessageHandler <pyrogram.MessageHandler>`, which will
app.add_handler(MessageHandler(my_handler))
app.run()
Using Filters
-------------
For a finer grained control over what kind of messages will be allowed or not in your callback functions, you can use
:class:`Filters <pyrogram.Filters>`.
- This example will show you how to **only** handle messages containing an
:obj:`Audio <pyrogram.Audio>` object and filter out any other message:
.. code-block:: python
from pyrogram import Filters
@app.on_message(Filters.audio)
def my_handler(client, message):
print(message)
- or, without decorators:
.. code-block:: python
from pyrogram import Filters, MessageHandler
def my_handler(client, message):
print(message)
app.add_handler(MessageHandler(my_handler, Filters.audio))
Combining Filters
-----------------
Filters can also be used in a more advanced way by combining more filters together using bitwise operators:
- Use ``~`` to invert a filter (behaves like the ``not`` operator).
- Use ``&`` and ``|`` to merge two filters (behave like ``and``, ``or`` operators respectively).
Here are some examples:
- Message is a **text** message **and** is **not edited**.
.. code-block:: python
@app.on_message(Filters.text & ~Filters.edited)
def my_handler(client, message):
print(message)
- Message is a **sticker** **and** is coming from a **channel or** a **private** chat.
.. code-block:: python
@app.on_message(Filters.sticker & (Filters.channel | Filters.private))
def my_handler(client, message):
print(message)
Advanced Filters
----------------
Some filters, like :obj:`command() <pyrogram.Filters.command>` or :obj:`regex() <pyrogram.Filters.regex>`
can also accept arguments:
- Message is either a */start* or */help* **command**.
.. code-block:: python
@app.on_message(Filters.command(["start", "help"]))
def my_handler(client, message):
print(message)
- Message is a **text** message matching the given **regex** pattern.
.. code-block:: python
@app.on_message(Filters.regex("pyrogram"))
def my_handler(client, message):
print(message)
More handlers using different filters can also live together.
.. code-block:: python
@app.on_message(Filters.command("start"))
def start_command(client, message):
print("This is the /start command")
@app.on_message(Filters.command("help"))
def help_command(client, message):
print("This is the /help command")
@app.on_message(Filters.chat("PyrogramChat"))
def from_pyrogramchat(client, message):
print("New message in @PyrogramChat")
Handler Groups
--------------
If you register handlers with overlapping filters, only the first one is executed and any other handler will be ignored.
In order to process the same message more than once, you can register your handler in a different group.
Groups are identified by a number (number 0 being the default) and are sorted. This means that a lower group number has
a higher priority.
For example, in:
.. code-block:: python
@app.on_message(Filters.text | Filters.sticker)
def text_or_sticker(client, message):
print("Text or Sticker")
@app.on_message(Filters.text)
def just_text(client, message):
print("Just Text")
``just_text`` is never executed. To enable it, simply register the function using a different group:
.. code-block:: python
@app.on_message(Filters.text, group=1)
def just_text(client, message):
print("Just Text")
or, if you want ``just_text`` to be fired *before* ``text_or_sticker``:
.. code-block:: python
@app.on_message(Filters.text, group=-1)
def just_text(client, message):
print("Just Text")

View File

@ -0,0 +1,228 @@
Using Filters
=============
For a finer grained control over what kind of messages will be allowed or not in your callback functions, you can use
:class:`Filters <pyrogram.Filters>`.
.. note::
This section makes use of Handlers to handle updates. Learn more at `Update Handling <UpdateHandling.html>`_.
- This example will show you how to **only** handle messages containing an :obj:`Audio <pyrogram.Audio>` object and
ignore any other message:
.. code-block:: python
from pyrogram import Filters
@app.on_message(Filters.audio)
def my_handler(client, message):
print(message)
- or, without decorators:
.. code-block:: python
from pyrogram import Filters, MessageHandler
def my_handler(client, message):
print(message)
app.add_handler(MessageHandler(my_handler, Filters.audio))
Combining Filters
-----------------
Filters can also be used in a more advanced way by inverting and combining more filters together using bitwise
operators:
- Use ``~`` to invert a filter (behaves like the ``not`` operator).
- Use ``&`` and ``|`` to merge two filters (behave like ``and``, ``or`` operators respectively).
Here are some examples:
- Message is a **text** message **and** is **not edited**.
.. code-block:: python
@app.on_message(Filters.text & ~Filters.edited)
def my_handler(client, message):
print(message)
- Message is a **sticker** **and** is coming from a **channel or** a **private** chat.
.. code-block:: python
@app.on_message(Filters.sticker & (Filters.channel | Filters.private))
def my_handler(client, message):
print(message)
Advanced Filters
----------------
Some filters, like :meth:`command() <pyrogram.Filters.command>` or :meth:`regex() <pyrogram.Filters.regex>`
can also accept arguments:
- Message is either a */start* or */help* **command**.
.. code-block:: python
@app.on_message(Filters.command(["start", "help"]))
def my_handler(client, message):
print(message)
- Message is a **text** message matching the given **regex** pattern.
.. code-block:: python
@app.on_message(Filters.regex("pyrogram"))
def my_handler(client, message):
print(message)
More handlers using different filters can also live together.
.. code-block:: python
@app.on_message(Filters.command("start"))
def start_command(client, message):
print("This is the /start command")
@app.on_message(Filters.command("help"))
def help_command(client, message):
print("This is the /help command")
@app.on_message(Filters.chat("PyrogramChat"))
def from_pyrogramchat(client, message):
print("New message in @PyrogramChat")
Handler Groups
--------------
If you register handlers with overlapping filters, only the first one is executed and any other handler will be ignored.
In order to process the same message more than once, you can register your handler in a different group.
Groups are identified by a number (number 0 being the default) and are sorted. This means that a lower group number has
a higher priority.
For example, in:
.. code-block:: python
@app.on_message(Filters.text | Filters.sticker)
def text_or_sticker(client, message):
print("Text or Sticker")
@app.on_message(Filters.text)
def just_text(client, message):
print("Just Text")
``just_text`` is never executed because ``text_or_sticker`` already handles texts. To enable it, simply register the
function using a different group:
.. code-block:: python
@app.on_message(Filters.text, group=1)
def just_text(client, message):
print("Just Text")
or, if you want ``just_text`` to be fired *before* ``text_or_sticker`` (note ``-1``, which is less than ``0``):
.. code-block:: python
@app.on_message(Filters.text, group=-1)
def just_text(client, message):
print("Just Text")
Custom Filters
--------------
Pyrogram already provides lots of built-in :class:`Filters <pyrogram.Filters>` to work with, but in case you can't find
a specific one for your needs or want to build a custom filter by yourself (to be used in a different handler, for
example) you can use :meth:`Filters.create() <pyrogram.Filters.create>`.
.. note::
At the moment, the built-in filters are intended to be used with the :obj:`MessageHandler <pyrogram.MessageHandler>`
only.
An example to demonstrate how custom filters work is to show how to create and use one for the
:obj:`CallbackQueryHandler <pyrogram.CallbackQueryHandler>`. Note that callback queries updates are only received by Bots;
create and `authorize your bot <../start/Setup.html#bot-authorization>`_, then send a message with an inline keyboard to
yourself. This allows you to test your filter by pressing the inline button:
.. code-block:: python
from pyrogram import InlineKeyboardMarkup, InlineKeyboardButton
app.send_message(
"username", # Change this to your username or id
"Pyrogram's custom filter test",
reply_markup=InlineKeyboardMarkup(
[[InlineKeyboardButton("Press me", b"pyrogram")]]
)
)
Basic Filters
^^^^^^^^^^^^^
For this basic filter we will be using only the first two parameters of :meth:`Filters.create() <pyrogram.Filters.create>`.
The code below creates a simple filter for hardcoded callback data. This filter will only allow callback queries
containing "pyrogram" as data:
.. code-block:: python
hardcoded_data = Filters.create(
name="HardcodedData",
func=lambda filter, callback_query: callback_query.data == b"pyrogram"
)
The ``lambda`` operator in python is used to create small anonymous functions and is perfect for this example, the same
could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter itself:
.. code-block:: python
def func(filter, callback_query):
return callback_query.data == b"pyrogram"
hardcoded_data = Filters.create(
name="HardcodedData",
func=func
)
The filter usage remains the same:
.. code-block:: python
@app.on_callback_query(hardcoded_data)
def pyrogram_data(client, callback_query):
client.answer_callback_query(callback_query.id, "it works!")
Filters with Arguments
^^^^^^^^^^^^^^^^^^^^^^
A much cooler filter would be one that accepts "pyrogram" or any other data as argument at usage time.
A dynamic filter like this will make use of the third parameter of :meth:`Filters.create() <pyrogram.Filters.create>`.
This is how a dynamic custom filter looks like:
.. code-block:: python
def dynamic_data(data):
return Filters.create(
name="DynamicData",
func=lambda filter, callback_query: filter.data == callback_query.data,
data=data # "data" kwarg is accessed with "filter.data"
)
And its usage:
.. code-block:: python
@app.on_callback_query(dynamic_data(b"pyrogram"))
def pyrogram_data(client, callback_query):
client.answer_callback_query(callback_query.id, "it works!")

View File

@ -4,47 +4,85 @@ Installation
Being a Python library, Pyrogram requires Python to be installed in your system.
We recommend using the latest version of Python 3 and pip.
Get Python 3 from https://www.python.org/downloads/ or with your package manager and pip
Get Python 3 from https://www.python.org/downloads/ (or with your package manager) and pip
by following the instructions at https://pip.pypa.io/en/latest/installing/.
Pyrogram supports Python 3 only, starting from version 3.4 and PyPy.
.. important::
Pyrogram supports **Python 3** only, starting from version 3.4. **PyPy** is supported too.
Install Pyrogram
----------------
- The easiest way to install and upgrade Pyrogram is by using **pip**:
- The easiest way to install and upgrade Pyrogram to its latest stable version is by using **pip**:
.. code-block:: bash
.. code-block:: text
$ pip3 install --upgrade pyrogram
- or, with TgCrypto_ (recommended):
- or, with TgCrypto_ as extra requirement (recommended):
.. code-block:: bash
.. code-block:: text
$ pip3 install --upgrade pyrogram[tgcrypto]
$ pip3 install --upgrade pyrogram[fast]
Bleeding Edge
-------------
If you want the latest development version of Pyrogram, you can install it straight from the develop_
branch using this command:
branch using this command (you might need to install **git** first):
.. code-block:: bash
.. code-block:: text
$ pip3 install --upgrade git+https://github.com/pyrogram/pyrogram.git
Asynchronous
------------
Pyrogram heavily depends on IO-bound network code (it's a cloud-based messaging client library after all), and here's
where asyncio shines the most by providing extra performance while running on a single OS-level thread only.
**A fully asynchronous variant of Pyrogram is therefore available** (Python 3.5+ required).
Use this command to install:
.. code-block:: text
$ pip3 install --upgrade git+https://github.com/pyrogram/pyrogram.git@asyncio
Pyrogram API remains the same and features are kept up to date from the non-async, default develop branch, but you
are obviously required Python asyncio knowledge in order to take full advantage of it.
.. tip::
The idea to turn Pyrogram fully asynchronous is still under consideration, but is wise to expect that in future this
would be the one and only way to work with Pyrogram.
You can start using Pyrogram Async variant right now as an excuse to learn more about asynchronous programming and
do experiments with it!
.. raw:: html
<script async
src="https://telegram.org/js/telegram-widget.js?4"
data-telegram-post="Pyrogram/4"
data-width="100%">
</script>
.. centered:: Subscribe to `@Pyrogram <https://t.me/Pyrogram>`_ for news and announcements
Verifying
---------
To verify that Pyrogram is correctly installed, open a Python shell and import it.
If no error shows up you are good to go.
.. code-block:: bash
.. code-block:: python
>>> import pyrogram
>>> pyrogram.__version__
'0.7.5'
'0.9.4'
.. _TgCrypto: https://docs.pyrogram.ml/resources/TgCrypto
.. _develop: http://github.com/pyrogram/pyrogram

View File

@ -8,22 +8,29 @@ with Pyrogram.
API Keys
--------
The very first step requires you to obtain a valid Telegram API key.
The very first step requires you to obtain a valid Telegram API key (API id/hash pair).
If you already have one you can skip this step, otherwise:
#. Visit https://my.telegram.org/apps and log in with your Telegram Account.
#. Fill out the form to register a new Telegram application.
#. Done. The Telegram API key consists of two parts: the **App api_id** and the **App api_hash**.
#. Done. The API key consists of two parts: **App api_id** and **App api_hash**.
.. important:: This key should be kept secret.
.. important::
This API key is personal and should be kept secret.
Configuration
-------------
There are two ways to configure a Pyrogram application project, and you can choose the one that fits better for you:
The API key obtained in the `previous step <#api-keys>`_ defines a token for your application allowing you to access
the Telegram database using the MTProto API — **it is therefore required for all authorizations of both Users and Bots**.
Having it handy, it's time to configure your Pyrogram project. There are two ways to do so, and you can choose what
fits better for you:
- Create a new ``config.ini`` file at the root of your working directory, copy-paste the following and replace the
**api_id** and **api_hash** values with `your own <#api-keys>`_. This is the preferred method because allows you
**api_id** and **api_hash** values with your own. This is the preferred method because allows you
to keep your credentials out of your code without having to deal with how to load them:
.. code-block:: ini
@ -45,7 +52,9 @@ There are two ways to configure a Pyrogram application project, and you can choo
api_hash="0123456789abcdef0123456789abcdef"
)
.. note:: The examples below assume you have created a ``config.ini`` file, thus they won't show the *api_id*
.. note::
The examples below assume you have created a ``config.ini`` file, thus they won't show the *api_id*
and *api_hash* parameters usage.
User Authorization
@ -66,7 +75,7 @@ the :class:`Client <pyrogram.Client>` class by passing to it a ``session_name``
This starts an interactive shell asking you to input your **phone number** (including your `Country Code`_)
and the **phone code** you will receive:
.. code::
.. code-block:: text
Enter phone number: +39**********
Is "+39**********" correct? (y/n): y
@ -76,16 +85,19 @@ After successfully authorizing yourself, a new file called ``my_account.session`
Pyrogram executing API calls with your identity. This file will be loaded again when you restart your app,
and as long as you keep the session alive, Pyrogram won't ask you again to enter your phone number.
.. important:: Your ``*.session`` file(s) must be kept secret.
.. important::
Your ``*.session`` files are personal and must be kept secret.
Bot Authorization
-----------------
Being written entirely from the ground up, Pyrogram is also able to authorize Bots.
Bots are a special kind of users which also make use of MTProto, the underlying Telegram protocol.
This means that you can use Pyrogram to execute API calls with a Bot identity.
Bots are a special kind of users and are authorized via their tokens (instead of phone numbers), which are created by
BotFather_. Bot tokens replace the Users' phone numbers only — you still need to
`configure a Telegram API key <#configuration>`_ with Pyrogram, even when using Bots.
Instead of phone numbers, Bots are authorized via their tokens which are created by BotFather_:
The authorization process is automatically managed. All you need to do is pass the bot token as ``session_name``.
The session file will be named after the Bot user_id, which is ``123456.session`` for the example below.
.. code-block:: python
@ -94,9 +106,6 @@ Instead of phone numbers, Bots are authorized via their tokens which are created
app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
app.run()
That's all, no further action is needed. The session file will be named after the Bot user_id, which is
``123456.session`` for the example above.
.. _installed Pyrogram: Installation.html
.. _`Country Code`: https://en.wikipedia.org/wiki/List_of_country_calling_codes
.. _BotFather: https://t.me/botfather

View File

@ -10,35 +10,50 @@ High-level API
The easiest and recommended way to interact with Telegram is via the high-level Pyrogram methods_ and types_, which are
named after the `Telegram Bot API`_.
Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_):
- Get information about the authorized user:
Here's a simple example:
.. code-block:: python
from pyrogram import Client
app = Client("my_account")
app.start()
print(app.get_me())
app.send_message("me", "Hi there! I'm using **Pyrogram**")
app.send_location("me", 51.500729, -0.124583)
- Send a message to yourself (Saved Messages):
app.stop()
You can also use Pyrogram in a context manager with the ``with`` statement. The Client will automatically
:meth:`start <pyrogram.Client.start>` and :meth:`stop <pyrogram.Client.stop>` gracefully, even in case of unhandled
exceptions in your code:
.. code-block:: python
app.send_message("me", "Hi there! I'm using Pyrogram")
from pyrogram import Client
- Upload a new photo (with caption):
app = Client("my_account")
.. code-block:: python
with app:
print(app.get_me())
app.send_message("me", "Hi there! I'm using **Pyrogram**")
app.send_location("me", 51.500729, -0.124583)
app.send_photo("me", "/home/dan/perla.jpg", "Cute!")
More examples on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/examples>`_.
Raw Functions
-------------
If you can't find a high-level method for your needs or if want complete, low-level access to the whole Telegram API,
If you can't find a high-level method for your needs or if you want complete, low-level access to the whole Telegram API,
you have to use the raw :mod:`functions <pyrogram.api.functions>` and :mod:`types <pyrogram.api.types>` exposed by the
``pyrogram.api`` package and call any Telegram API method you wish using the :meth:`send() <pyrogram.Client.send>`
method provided by the Client class.
.. hint:: Every high-level method mentioned in the section above is built on top of these raw functions.
.. hint::
Every high-level method mentioned in the section above is built on top of these raw functions.
Nothing stops you from using the raw functions only, but they are rather complex and `plenty of them`_ are already
re-implemented by providing a much simpler and cleaner interface which is very similar to the Bot API.
@ -54,9 +69,7 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
from pyrogram import Client
from pyrogram.api import functions
app = Client("my_account")
app.start()
with Client("my_account") as app:
app.send(
functions.account.UpdateProfile(
first_name="Dan", last_name="Tès",
@ -64,8 +77,6 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
)
)
app.stop()
- Share your Last Seen time only with your contacts:
.. code-block:: python
@ -73,9 +84,7 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
from pyrogram import Client
from pyrogram.api import functions, types
app = Client("my_account")
app.start()
with Client("my_account") as app:
app.send(
functions.account.SetPrivacy(
key=types.InputPrivacyKeyStatusTimestamp(),
@ -83,8 +92,6 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
)
)
app.stop()
- Invite users to your channel/supergroup:
.. code-block:: python
@ -92,9 +99,7 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
from pyrogram import Client
from pyrogram.api import functions, types
app = Client("my_account")
app.start()
with Client("my_account") as app:
app.send(
functions.channels.InviteToChannel(
channel=app.resolve_peer(123456789), # ID or Username
@ -106,11 +111,9 @@ Examples (more on `GitHub <https://github.com/pyrogram/pyrogram/tree/develop/exa
)
)
app.stop()
.. _methods: ../pyrogram/Client.html#available-methods
.. _plenty of them: ../pyrogram/Client.html#available-methods
.. _types: ../pyrogram/types/index.html
.. _methods: ../pyrogram/Client.html#messages
.. _plenty of them: ../pyrogram/Client.html#messages
.. _types: ../pyrogram/Types.html
.. _Raw Functions: Usage.html#using-raw-functions
.. _Community: https://t.me/PyrogramChat
.. _project set up: Setup.html

121
examples/LICENSE Normal file
View File

@ -0,0 +1,121 @@
Creative Commons Legal Code
CC0 1.0 Universal
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
HEREUNDER.
Statement of Purpose
The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator
and subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").
Certain owners wish to permanently relinquish those rights to a Work for
the purpose of contributing to a commons of creative, cultural and
scientific works ("Commons") that the public can reliably and without fear
of later claims of infringement build upon, modify, incorporate in other
works, reuse and redistribute as freely as possible in any form whatsoever
and for any purposes, including without limitation commercial purposes.
These owners may contribute to the Commons to promote the ideal of a free
culture and the further production of creative, cultural and scientific
works, or to gain reputation or greater distribution for their Work in
part through the use and efforts of others.
For these and/or other purposes and motivations, and without any
expectation of additional consideration or compensation, the person
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
is an owner of Copyright and Related Rights in the Work, voluntarily
elects to apply CC0 to the Work and publicly distribute the Work under its
terms, with knowledge of his or her Copyright and Related Rights in the
Work and the meaning and intended legal effect of CC0 on those rights.
1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not
limited to, the following:
i. the right to reproduce, adapt, distribute, perform, display,
communicate, and translate a Work;
ii. moral rights retained by the original author(s) and/or performer(s);
iii. publicity and privacy rights pertaining to a person's image or
likeness depicted in a Work;
iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;
v. rights protecting the extraction, dissemination, use and reuse of data
in a Work;
vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation
thereof, including any amended or successor version of such
directive); and
vii. other similar, equivalent or corresponding rights throughout the
world based on applicable law or treaty, and any national
implementations thereof.
2. Waiver. To the greatest extent permitted by, but not in contravention
of, applicable law, Affirmer hereby overtly, fully, permanently,
irrevocably and unconditionally waives, abandons, and surrenders all of
Affirmer's Copyright and Related Rights and associated claims and causes
of action, whether now known or unknown (including existing as well as
future claims and causes of action), in the Work (i) in all territories
worldwide, (ii) for the maximum duration provided by applicable law or
treaty (including future time extensions), (iii) in any current or future
medium and for any number of copies, and (iv) for any purpose whatsoever,
including without limitation commercial, advertising or promotional
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
member of the public at large and to the detriment of Affirmer's heirs and
successors, fully intending that such Waiver shall not be subject to
revocation, rescission, cancellation, termination, or any other legal or
equitable action to disrupt the quiet enjoyment of the Work by the public
as contemplated by Affirmer's express Statement of Purpose.
3. Public License Fallback. Should any part of the Waiver for any reason
be judged legally invalid or ineffective under applicable law, then the
Waiver shall be preserved to the maximum extent permitted taking into
account Affirmer's express Statement of Purpose. In addition, to the
extent the Waiver is so judged Affirmer hereby grants to each affected
person a royalty-free, non transferable, non sublicensable, non exclusive,
irrevocable and unconditional license to exercise Affirmer's Copyright and
Related Rights in the Work (i) in all territories worldwide, (ii) for the
maximum duration provided by applicable law or treaty (including future
time extensions), (iii) in any current or future medium and for any number
of copies, and (iv) for any purpose whatsoever, including without
limitation commercial, advertising or promotional purposes (the
"License"). The License shall be deemed effective as of the date CC0 was
applied by Affirmer to the Work. Should any part of the License for any
reason be judged legally invalid or ineffective under applicable law, such
partial invalidity or ineffectiveness shall not invalidate the remainder
of the License, and in such case Affirmer hereby affirms that he or she
will not (i) exercise any of his or her remaining Copyright and Related
Rights in the Work or (ii) assert any associated claims and causes of
action with respect to the Work, in either case contrary to Affirmer's
express Statement of Purpose.
4. Limitations and Disclaimers.
a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.
b. Affirmer offers the Work as-is and makes no representations or
warranties of any kind concerning the Work, express, implied,
statutory or otherwise, including without limitation warranties of
title, merchantability, fitness for a particular purpose, non
infringement, or the absence of latent or other defects, accuracy, or
the present or absence of errors, whether or not discoverable, all to
the greatest extent permissible under applicable law.
c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without
limitation any person's Copyright and Related Rights in the Work.
Further, Affirmer disclaims responsibility for obtaining any necessary
consents, permissions or other rights required for any use of the
Work.
d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to
this CC0 or use of the Work.

View File

@ -5,14 +5,17 @@ This folder contains example scripts to show you how **Pyrogram** looks like.
Every script is working right away (provided you correctly set up your credentials), meaning
you can simply copy-paste and run. The only things you have to change are session names and target chats.
All the examples listed in this directory are licensed under the terms of the [CC0 1.0 Universal](LICENSE) license and
can be freely used as basic building blocks for your own applications without worrying about copyrights.
Example | Description
---: | :---
[**hello_world**](hello_world.py) | Demonstration of basic API usages
[**echo_bot**](echo_bot.py) | Echo bot that replies to every private text message
[**welcome_bot**](welcome_bot.py) | The Welcome Bot source code in [@PyrogramChat](https://t.me/pyrogramchat)
[**get_history**](get_history.py) | How to retrieve the full message history of a chat
[**get_participants**](get_participants.py) | How to get the first 10.000 members of a supergroup/channel
[**get_participants2**](get_participants2.py) | Improved version to get more than 10.000 users
[**get_chat_members**](get_chat_members.py) | How to get the first 10.000 members of a supergroup/channel
[**get_chat_members2**](get_chat_members2.py) | Improved version to get more than 10.000 members
[**query_inline_bots**](query_inline_bots.py) | How to query an inline bot and send a result to a chat
[**send_bot_keyboards**](send_bot_keyboards.py) | How to send normal and inline keyboards using regular bots
[**callback_query_handler**](callback_query_handler.py) | How to handle queries coming from inline button presses

View File

@ -1,37 +1,16 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
from pyrogram import Client
"""This example shows how to handle callback queries, i.e.: queries coming from inline button presses.
It uses the @on_callback_query decorator to register a CallbackQueryHandler."""
It uses the @on_callback_query decorator to register a CallbackQueryHandler.
"""
from pyrogram import Client
app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
@app.on_callback_query()
def answer(client, callback_query):
client.answer_callback_query(
callback_query.id,
text='Button contains: "{}"'.format(callback_query.data),
show_alert=True
)
callback_query.answer('Button contains: "{}"'.format(callback_query.data), show_alert=True)
app.run() # Automatically start() and idle()

View File

@ -1,39 +1,17 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
from pyrogram import Client, Filters
"""This simple echo bot replies to every private text message.
It uses the @on_message decorator to register a MessageHandler
and applies two filters on it, Filters.text and Filters.private to make
sure it will only reply to private text messages.
It uses the @on_message decorator to register a MessageHandler and applies two filters on it:
Filters.text and Filters.private to make sure it will reply to private text messages only.
"""
from pyrogram import Client, Filters
app = Client("my_account")
@app.on_message(Filters.text & Filters.private)
def echo(client, message):
client.send_message(
message.chat.id, message.text,
reply_to_message_id=message.message_id
)
message.reply(message.text, quote=True)
app.run() # Automatically start() and idle()

View File

@ -0,0 +1,31 @@
"""This example shows you how to get the first 10.000 members of a chat.
Refer to get_chat_members2.py for more than 10.000 members.
"""
import time
from pyrogram import Client
from pyrogram.api.errors import FloodWait
app = Client("my_account")
target = "pyrogramchat" # Target channel/supergroup
members = [] # List that will contain all the members of the target chat
offset = 0 # Offset starts at 0
limit = 200 # Amount of users to retrieve for each API call (max 200)
with app:
while True:
try:
chunk = app.get_chat_members(target, offset)
except FloodWait as e: # Very large chats could trigger FloodWait
time.sleep(e.x) # When it happens, wait X seconds and try again
continue
if not chunk.chat_members:
break # No more members left
members.extend(chunk.chat_members)
offset += len(chunk.chat_members)
# Now the "members" list contains all the members of the target chat

View File

@ -0,0 +1,50 @@
"""This is an improved version of get_chat_members.py
Since Telegram will return at most 10.000 members for a single query, this script
repeats the search using numbers ("0" to "9") and all the available ascii letters ("a" to "z").
This can be further improved by also searching for non-ascii characters (e.g.: Japanese script),
as some user names may not contain ascii letters at all.
"""
import time
from string import ascii_lowercase
from pyrogram import Client
from pyrogram.api.errors import FloodWait
app = Client("my_account")
target = "pyrogramchat" # Target channel/supergroup
members = {} # List that will contain all the members of the target chat
limit = 200 # Amount of users to retrieve for each API call (max 200)
# "" + "0123456789" + "abcdefghijklmnopqrstuvwxyz" (as list)
queries = [""] + [str(i) for i in range(10)] + list(ascii_lowercase)
with app:
for q in queries:
print('Searching for "{}"'.format(q))
offset = 0 # For each query, offset restarts from 0
while True:
try:
chunk = app.get_chat_members(target, offset, query=q)
except FloodWait as e: # Very large chats could trigger FloodWait
print("Flood wait: {} seconds".format(e.x))
time.sleep(e.x) # When it happens, wait X seconds and try again
continue
if not chunk.chat_members:
print('Done searching for "{}"'.format(q))
print()
break # No more members left
members.update({i.user.id: i for i in chunk.chat_members})
offset += len(chunk.chat_members)
print("Total members: {}".format(len(members)))
print("Grand total: {}".format(len(members)))
# Now the "members" list contains all the members of the target chat

View File

@ -1,40 +1,20 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
"""This example shows how to retrieve the full message history of a chat"""
import time
from pyrogram import Client
from pyrogram.api.errors import FloodWait
"""This example shows how to retrieve the full message history of a chat"""
app = Client("my_account")
target = "me" # "me" refers to your own chat (Saved Messages)
messages = [] # List that will contain all the messages of the target chat
offset_id = 0 # ID of the last message of the chunk
app.start()
while True:
with app:
while True:
try:
m = app.get_history(target, offset_id=offset_id)
except FloodWait as e:
# For very large chats the method call can raise a FloodWait
except FloodWait as e: # For very large chats the method call can raise a FloodWait
print("waiting {}".format(e.x))
time.sleep(e.x) # Sleep X seconds before continuing
continue
@ -47,7 +27,5 @@ while True:
print("Messages: {}".format(len(messages)))
app.stop()
# Now the "messages" list contains all the messages sorted by date in
# descending order (from the most recent to the oldest one)

View File

@ -1,63 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
import time
from pyrogram import Client
from pyrogram.api import functions, types
from pyrogram.api.errors import FloodWait
"""This simple GetParticipants method usage shows you how to get the first 10.000 users of a chat.
Refer to get_participants2.py for more than 10.000 users.
"""
app = Client("my_account")
target = "pyrogramchat" # Target channel/supergroup
users = [] # List that will contain all the users of the target chat
limit = 200 # Amount of users to retrieve for each API call
offset = 0 # Offset starts at 0
app.start()
while True:
try:
participants = app.send(
functions.channels.GetParticipants(
channel=app.resolve_peer(target),
filter=types.ChannelParticipantsSearch(""), # Filter by empty string (search for all)
offset=offset,
limit=limit,
hash=0
)
)
except FloodWait as e:
# Very large channels will trigger FloodWait.
# When happens, wait X seconds before continuing
time.sleep(e.x)
continue
if not participants.participants:
break # No more participants left
users.extend(participants.users)
offset += limit
app.stop()
# Now the "users" list contains all the members of the target chat

View File

@ -1,79 +0,0 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
import time
from string import ascii_lowercase
from pyrogram import Client
from pyrogram.api import functions, types
from pyrogram.api.errors import FloodWait
"""This is an improved version of get_participants.py
Since Telegram will return at most 10.000 users for a single query, this script
repeats the search using numbers ("0" to "9") and all the available ascii letters ("a" to "z").
This can be further improved by also searching for non-ascii characters (e.g.: Japanese script),
as some user names may not contain ascii letters at all.
"""
app = Client("my_account")
target = "pyrogramchat" # Target channel/supergroup username or id
users = {} # To ensure uniqueness, users will be stored in a dictionary with user_id as key
limit = 200 # Amount of users to retrieve for each API call (200 is the maximum)
# "" + "0123456789" + "abcdefghijklmnopqrstuvwxyz" (as list)
queries = [""] + [str(i) for i in range(10)] + list(ascii_lowercase)
app.start()
for q in queries:
print("Searching for '{}'".format(q))
offset = 0 # For each query, offset restarts from 0
while True:
try:
participants = app.send(
functions.channels.GetParticipants(
channel=app.resolve_peer(target),
filter=types.ChannelParticipantsSearch(q),
offset=offset,
limit=limit,
hash=0
)
)
except FloodWait as e:
# Very large chats could trigger FloodWait.
# When happens, wait X seconds before continuing
print("Flood wait: {} seconds".format(e.x))
time.sleep(e.x)
continue
if not participants.participants:
print("Done searching for '{}'".format(q))
print()
break # No more participants left
# User information are stored in the participants.users list.
# Add those users to the dictionary
users.update({i.id: i for i in participants.users})
offset += len(participants.participants)
print("Total users: {}".format(len(users)))
app.stop()

View File

@ -1,25 +1,7 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
"""This example demonstrates a basic API usage"""
from pyrogram import Client
"""This example demonstrates a basic API usage"""
# Create a new Client instance
app = Client("my_account")

View File

@ -1,25 +1,7 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
"""This example shows how to query an inline bot"""
from pyrogram import Client
"""This example shows how to query an inline bot"""
# Create a new Client
app = Client("my_account")

View File

@ -1,25 +1,7 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
"""This example shows how to handle raw updates"""
from pyrogram import Client
"""This example shows how to handle raw updates"""
app = Client("my_account")

View File

@ -1,23 +1,3 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
"""This example will show you how to send normal and inline keyboards.
You must log-in as a regular bot in order to send keyboards (use the token from @BotFather).
@ -27,6 +7,8 @@ send_message() is used as example, but a keyboard can be sent with any other sen
like send_audio(), send_document(), send_location(), etc...
"""
from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
# Create a client using your bot token
app = Client("123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
app.start()

View File

@ -1,52 +1,45 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
from pyrogram import Client, Emoji, Filters
"""This is the Welcome Bot in @PyrogramChat.
It uses the Emoji module to easily add emojis in your text messages and Filters
to make it only work for specific messages in a specific chat
to make it only work for specific messages in a specific chat.
"""
from pyrogram import Client, Emoji, Filters
USER = "**{}**"
MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {{}}!".format(Emoji.SPARKLES)
enabled_groups = Filters.chat("PyrogramChat")
last_welcomes = {}
app = Client("my_account")
@app.on_message(Filters.chat("PyrogramChat") & Filters.new_chat_members)
@app.on_message(enabled_groups & Filters.new_chat_members)
def welcome(client, message):
# Build the new members list (with mentions) by using their first_name
new_members = ", ".join([
"[{}](tg://user?id={})".format(i.first_name, i.id)
for i in message.new_chat_members
])
chat_id = message.chat.id
# Build the welcome message by using an emoji and the list we built above
text = "{} Welcome to [Pyrogram](https://docs.pyrogram.ml/)'s group chat {}!".format(
Emoji.SPARKLES,
new_members
)
# Get the previous welcome message and members, if any
previous_welcome, previous_members = last_welcomes.pop(chat_id, (None, []))
# Send the welcome message
client.send_message(
message.chat.id, text,
reply_to_message_id=message.message_id,
disable_web_page_preview=True
)
# Delete the previous message, if exists
if previous_welcome:
previous_welcome.delete()
# Build the new members list by using their first_name. Also append the previous members, if any
new_members = [USER.format(i.first_name) for i in message.new_chat_members] + previous_members
# Build the welcome message by using an emoji and the list we created above
text = MESSAGE.format(", ".join(new_members))
# Actually send the welcome and save the new message and the new members list
last_welcomes[message.chat.id] = message.reply(text, disable_web_page_preview=True), new_members
@app.on_message(enabled_groups)
def reset(client, message):
# Don't make the bot delete the previous welcome in case someone talks in the middle
last_welcomes.pop(message.chat.id, None)
app.run() # Automatically start() and idle()

View File

@ -23,21 +23,19 @@ __copyright__ = "Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance
"e" if sys.getfilesystemencoding() != "utf-8" else "\xe8"
)
__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
__version__ = "0.7.5"
__version__ = "0.9.4.future"
from .api.errors import Error
from .client.types import (
Audio, Chat, ChatMember, ChatPhoto, Contact, Document, InputMediaPhoto,
InputMediaVideo, InputPhoneContact, Location, Message, MessageEntity,
Photo, PhotoSize, Sticker, Update, User, UserProfilePhotos, Venue, GIF,
Video, VideoNote, Voice, CallbackQuery, Messages
)
from .client.types.reply_markup import (
ForceReply, InlineKeyboardButton, InlineKeyboardMarkup,
KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove
Audio, Chat, ChatMember, ChatMembers, ChatPhoto, Contact, Document, InputMediaPhoto,
InputMediaVideo, InputMediaDocument, InputMediaAudio, InputMediaAnimation, InputPhoneContact,
Location, Message, MessageEntity, Dialog, Dialogs, Photo, PhotoSize, Sticker, User, UserStatus,
UserProfilePhotos, Venue, Animation, Video, VideoNote, Voice, CallbackQuery, Messages, ForceReply,
InlineKeyboardButton, InlineKeyboardMarkup, KeyboardButton, ReplyKeyboardMarkup, ReplyKeyboardRemove,
Poll, PollOption
)
from .client import (
Client, ChatAction, ParseMode, Emoji,
MessageHandler, DeletedMessagesHandler, CallbackQueryHandler,
RawUpdateHandler, DisconnectHandler, Filters
RawUpdateHandler, DisconnectHandler, UserStatusHandler, Filters
)

View File

@ -30,7 +30,7 @@ class BoolFalse(Object):
return cls.value
def __new__(cls) -> bytes:
return int.to_bytes(cls.ID, 4, "little")
return cls.ID.to_bytes(4, "little")
class BoolTrue(BoolFalse):

View File

@ -48,7 +48,7 @@ class Bytes(Object):
else:
return (
bytes([254])
+ int.to_bytes(length, 3, "little")
+ length.to_bytes(3, "little")
+ value
+ bytes(-length % 4)
)

View File

@ -29,15 +29,12 @@ class Int(Object):
return int.from_bytes(b.read(cls.SIZE), "little", signed=signed)
def __new__(cls, value: int, signed: bool = True) -> bytes:
return int.to_bytes(value, cls.SIZE, "little", signed=signed)
return value.to_bytes(cls.SIZE, "little", signed=signed)
class Long(Int):
SIZE = 8
def __new__(cls, *args):
return super().__new__(cls, *args)
class Int128(Int):
SIZE = 16

View File

@ -29,4 +29,4 @@ class Null(Object):
return None
def __new__(cls) -> bytes:
return int.to_bytes(cls.ID, 4, "little")
return cls.ID.to_bytes(4, "little")

View File

@ -24,7 +24,7 @@ from . import Bytes
class String(Bytes):
@staticmethod
def read(b: BytesIO, *args) -> str:
return super(String, String).read(b).decode()
return super(String, String).read(b).decode(errors="replace")
def __new__(cls, value: str) -> bytes:
return super().__new__(cls, value.encode())

View File

@ -22,5 +22,5 @@ from .filters import Filters
from .handlers import (
MessageHandler, DeletedMessagesHandler,
CallbackQueryHandler, RawUpdateHandler,
DisconnectHandler
DisconnectHandler, UserStatusHandler
)

View File

@ -18,7 +18,6 @@
import base64
import binascii
import getpass
import json
import logging
import math
@ -33,8 +32,11 @@ import time
from configparser import ConfigParser
from datetime import datetime
from hashlib import sha256, md5
from importlib import import_module
from pathlib import Path
from signal import signal, SIGINT, SIGTERM, SIGABRT
from threading import Thread
from typing import Union, List
from pyrogram.api import functions, types
from pyrogram.api.core import Object
@ -43,8 +45,12 @@ from pyrogram.api.errors import (
PhoneNumberUnoccupied, PhoneCodeInvalid, PhoneCodeHashEmpty,
PhoneCodeExpired, PhoneCodeEmpty, SessionPasswordNeeded,
PasswordHashInvalid, FloodWait, PeerIdInvalid, FirstnameInvalid, PhoneNumberBanned,
VolumeLocNotFound, UserMigrate, FileIdInvalid)
VolumeLocNotFound, UserMigrate, FileIdInvalid, ChannelPrivate, PhoneNumberOccupied,
PasswordRecoveryNa, PasswordEmpty
)
from pyrogram.client.handlers import DisconnectHandler
from pyrogram.client.handlers.handler import Handler
from pyrogram.client.methods.password.utils import compute_check
from pyrogram.crypto import AES
from pyrogram.session import Auth, Session
from .dispatcher import Dispatcher
@ -90,6 +96,10 @@ class Client(Methods, BaseClient):
Code of the language used on the client, in ISO 639-1 standard. Defaults to "en".
This is an alternative way to set it if you don't want to use the *config.ini* file.
ipv6 (``bool``, *optional*):
Pass True to connect to Telegram using IPv6.
Defaults to False (IPv4).
proxy (``dict``, *optional*):
Your SOCKS5 Proxy settings as dict,
e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*.
@ -136,27 +146,34 @@ class Client(Methods, BaseClient):
config_file (``str``, *optional*):
Path of the configuration file. Defaults to ./config.ini
plugins_dir (``str``, *optional*):
Define a custom directory for your plugins. The plugins directory is the location in your
filesystem where Pyrogram will automatically load your update handlers.
Defaults to None (plugins disabled).
"""
def __init__(self,
session_name: str,
api_id: int or str = None,
api_id: Union[int, str] = None,
api_hash: str = None,
app_version: str = None,
device_model: str = None,
system_version: str = None,
lang_code: str = None,
ipv6: bool = False,
proxy: dict = None,
test_mode: bool = False,
phone_number: str = None,
phone_code: str or callable = None,
phone_code: Union[str, callable] = None,
password: str = None,
force_sms: bool = False,
first_name: str = None,
last_name: str = None,
workers: int = 4,
workdir: str = ".",
config_file: str = "./config.ini"):
workers: int = BaseClient.WORKERS,
workdir: str = BaseClient.WORKDIR,
config_file: str = BaseClient.CONFIG_FILE,
plugins_dir: str = None):
super().__init__()
self.session_name = session_name
@ -166,6 +183,7 @@ class Client(Methods, BaseClient):
self.device_model = device_model
self.system_version = system_version
self.lang_code = lang_code
self.ipv6 = ipv6
# TODO: Make code consistent, use underscore for private/protected fields
self._proxy = proxy
self.test_mode = test_mode
@ -178,9 +196,16 @@ class Client(Methods, BaseClient):
self.workers = workers
self.workdir = workdir
self.config_file = config_file
self.plugins_dir = plugins_dir
self.dispatcher = Dispatcher(self, workers)
def __enter__(self):
return self.start()
def __exit__(self, *args):
self.stop()
@property
def proxy(self):
return self._proxy
@ -195,17 +220,19 @@ class Client(Methods, BaseClient):
Requires no parameters.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``ConnectionError`` in case you try to start an already started Client.
"""
if self.is_started:
raise ConnectionError("Client has already been started")
if self.BOT_TOKEN_RE.match(self.session_name):
self.token = self.session_name
self.bot_token = self.session_name
self.session_name = self.session_name.split(":")[0]
self.load_config()
self.load_session()
self.load_plugins()
self.session = Session(
self,
@ -216,28 +243,33 @@ class Client(Methods, BaseClient):
self.session.start()
self.is_started = True
try:
if self.user_id is None:
if self.token is None:
if self.bot_token is None:
self.authorize_user()
else:
self.authorize_bot()
self.save_session()
if self.token is None:
if self.bot_token is None:
now = time.time()
if abs(now - self.date) > Client.OFFLINE_SLEEP:
self.peers_by_username = {}
self.peers_by_phone = {}
self.get_dialogs()
self.get_initial_dialogs()
self.get_contacts()
else:
self.send(functions.messages.GetPinnedDialogs())
self.get_dialogs_chunk(0)
self.get_initial_dialogs_chunk()
else:
self.send(functions.updates.GetState())
except Exception as e:
self.is_started = False
self.session.stop()
raise e
for i in range(self.UPDATES_WORKERS):
self.updates_workers_list.append(
@ -264,9 +296,14 @@ class Client(Methods, BaseClient):
mimetypes.init()
Syncer.add(self)
return self
def stop(self):
"""Use this method to manually stop the Client.
Requires no parameters.
Raises:
``ConnectionError`` in case you try to stop an already stopped Client.
"""
if not self.is_started:
raise ConnectionError("Client is already stopped")
@ -298,6 +335,8 @@ class Client(Methods, BaseClient):
self.is_started = False
self.session.stop()
return self
def idle(self, stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)):
"""Blocks the program execution until one of the signals are received,
then gently stop the Client by closing the underlying connection.
@ -326,12 +365,12 @@ class Client(Methods, BaseClient):
Requires no parameters.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
self.start()
self.idle()
def add_handler(self, handler, group: int = 0):
def add_handler(self, handler: Handler, group: int = 0):
"""Use this method to register an update handler.
You can register multiple handlers, but at most one handler within a group
@ -355,7 +394,7 @@ class Client(Methods, BaseClient):
return handler, group
def remove_handler(self, handler, group: int = 0):
def remove_handler(self, handler: Handler, group: int = 0):
"""Removes a previously-added update handler.
Make sure to provide the right group that the handler was added in. You can use
@ -381,14 +420,14 @@ class Client(Methods, BaseClient):
flags=0,
api_id=self.api_id,
api_hash=self.api_hash,
bot_auth_token=self.token
bot_auth_token=self.bot_token
)
)
except UserMigrate as e:
self.session.stop()
self.dc_id = e.x
self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create()
self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
self.session = Session(
self,
@ -401,6 +440,8 @@ class Client(Methods, BaseClient):
else:
self.user_id = r.user.id
print("Logged in successfully as @{}".format(r.user.username))
def authorize_user(self):
phone_number_invalid_raises = self.phone_number is not None
phone_code_invalid_raises = self.phone_code is not None
@ -433,7 +474,7 @@ class Client(Methods, BaseClient):
self.session.stop()
self.dc_id = e.x
self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create()
self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
self.session = Session(
self,
@ -491,6 +532,7 @@ class Client(Methods, BaseClient):
try:
if phone_registered:
try:
r = self.send(
functions.auth.SignIn(
self.phone_number,
@ -498,21 +540,15 @@ class Client(Methods, BaseClient):
self.phone_code
)
)
else:
try:
self.send(
functions.auth.SignIn(
self.phone_number,
phone_code_hash,
self.phone_code
)
)
except PhoneNumberUnoccupied:
pass
log.warning("Phone number unregistered")
phone_registered = False
continue
else:
self.first_name = self.first_name if self.first_name is not None else input("First name: ")
self.last_name = self.last_name if self.last_name is not None else input("Last name: ")
try:
r = self.send(
functions.auth.SignUp(
self.phone_number,
@ -522,6 +558,10 @@ class Client(Methods, BaseClient):
self.last_name
)
)
except PhoneNumberOccupied:
log.warning("Phone number already registered")
phone_registered = True
continue
except (PhoneCodeInvalid, PhoneCodeEmpty, PhoneCodeExpired, PhoneCodeHashEmpty) as e:
if phone_code_invalid_raises:
raise
@ -536,21 +576,46 @@ class Client(Methods, BaseClient):
self.first_name = None
except SessionPasswordNeeded as e:
print(e.MESSAGE)
r = self.send(functions.account.GetPassword())
while True:
try:
r = self.send(functions.account.GetPassword())
if self.password is None:
print("Hint: {}".format(r.hint))
self.password = getpass.getpass("Enter password: ")
if type(self.password) is str:
self.password = r.current_salt + self.password.encode() + r.current_salt
self.password = input("Enter password (empty to recover): ")
password_hash = sha256(self.password).digest()
if self.password == "":
r = self.send(functions.auth.RequestPasswordRecovery())
r = self.send(functions.auth.CheckPassword(password_hash))
print("An e-mail containing the recovery code has been sent to {}".format(
r.email_pattern
))
r = self.send(
functions.auth.RecoverPassword(
code=input("Enter password recovery code: ")
)
)
else:
r = self.send(
functions.auth.CheckPassword(
password=compute_check(r, self.password)
)
)
except PasswordEmpty as e:
if password_hash_invalid_raises:
raise
else:
print(e.MESSAGE)
self.password = None
except PasswordRecoveryNa as e:
if password_hash_invalid_raises:
raise
else:
print(e.MESSAGE)
self.password = None
except PasswordHashInvalid as e:
if password_hash_invalid_raises:
raise
@ -563,6 +628,7 @@ class Client(Methods, BaseClient):
else:
print(e.MESSAGE.format(x=e.x))
time.sleep(e.x)
self.password = None
except Exception as e:
log.error(e, exc_info=True)
else:
@ -585,9 +651,11 @@ class Client(Methods, BaseClient):
self.password = None
self.user_id = r.user.id
print("Login successful")
print("Logged in successfully as {}".format(r.user.first_name))
def fetch_peers(self, entities: list):
def fetch_peers(self, entities: List[Union[types.User,
types.Chat, types.ChatForbidden,
types.Channel, types.ChannelForbidden]]):
for entity in entities:
if isinstance(entity, types.User):
user_id = entity.id
@ -613,7 +681,7 @@ class Client(Methods, BaseClient):
if phone is not None:
self.peers_by_phone[phone] = input_peer
if isinstance(entity, types.Chat):
if isinstance(entity, (types.Chat, types.ChatForbidden)):
chat_id = entity.id
peer_id = -chat_id
@ -623,7 +691,7 @@ class Client(Methods, BaseClient):
self.peers_by_id[peer_id] = input_peer
if isinstance(entity, types.Channel):
if isinstance(entity, (types.Channel, types.ChannelForbidden)):
channel_id = entity.id
peer_id = int("-100" + str(channel_id))
@ -632,7 +700,7 @@ class Client(Methods, BaseClient):
if access_hash is None:
continue
username = entity.username
username = getattr(entity, "username", None)
input_peer = types.InputPeerChannel(
channel_id=channel_id,
@ -711,7 +779,9 @@ class Client(Methods, BaseClient):
file_name = "{}_{}_{}{}".format(
media_type_str,
datetime.fromtimestamp(media.date or time.time()).strftime("%Y-%m-%d_%H-%M-%S"),
datetime.fromtimestamp(
getattr(media, "date", None) or time.time()
).strftime("%Y-%m-%d_%H-%M-%S"),
self.rnd_id(),
extension
)
@ -776,10 +846,14 @@ class Client(Methods, BaseClient):
pts = getattr(update, "pts", None)
pts_count = getattr(update, "pts_count", None)
if isinstance(update, types.UpdateChannelTooLong):
log.warning(update)
if isinstance(update, types.UpdateNewChannelMessage):
message = update.message
if not isinstance(message, types.MessageEmpty):
try:
diff = self.send(
functions.updates.GetChannelDifference(
channel=self.resolve_peer(int("-100" + str(channel_id))),
@ -793,7 +867,9 @@ class Client(Methods, BaseClient):
limit=pts
)
)
except ChannelPrivate:
pass
else:
if not isinstance(diff, types.updates.ChannelDifferenceEmpty):
updates.users += diff.users
updates.chats += diff.chats
@ -810,7 +886,7 @@ class Client(Methods, BaseClient):
if len(self.channels_pts[channel_id]) > 50:
self.channels_pts[channel_id] = self.channels_pts[channel_id][25:]
self.dispatcher.updates.put((update, updates.users, updates.chats))
self.dispatcher.updates_queue.put((update, updates.users, updates.chats))
elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)):
diff = self.send(
functions.updates.GetDifference(
@ -821,7 +897,7 @@ class Client(Methods, BaseClient):
)
if diff.new_messages:
self.dispatcher.updates.put((
self.dispatcher.updates_queue.put((
types.UpdateNewMessage(
message=diff.new_messages[0],
pts=updates.pts,
@ -831,15 +907,20 @@ class Client(Methods, BaseClient):
diff.chats
))
else:
self.dispatcher.updates.put((diff.other_updates[0], [], []))
self.dispatcher.updates_queue.put((diff.other_updates[0], [], []))
elif isinstance(updates, types.UpdateShort):
self.dispatcher.updates.put((updates.update, [], []))
self.dispatcher.updates_queue.put((updates.update, [], []))
elif isinstance(updates, types.UpdatesTooLong):
log.warning(updates)
except Exception as e:
log.error(e, exc_info=True)
log.debug("{} stopped".format(name))
def send(self, data: Object, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT):
def send(self,
data: Object,
retries: int = Session.MAX_RETRIES,
timeout: float = Session.WAIT_TIMEOUT):
"""Use this method to send Raw Function queries.
This method makes possible to manually call every single Telegram API method in a low-level manner.
@ -848,7 +929,7 @@ class Client(Methods, BaseClient):
Args:
data (``Object``):
The API Scheme function filled with proper arguments.
The API Schema function filled with proper arguments.
retries (``int``):
Number of retries.
@ -857,7 +938,7 @@ class Client(Methods, BaseClient):
Timeout in seconds.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
if not self.is_started:
raise ConnectionError("Client has not been started")
@ -885,30 +966,18 @@ class Client(Methods, BaseClient):
"More info: https://docs.pyrogram.ml/start/ProjectSetup#configuration"
)
for option in {"app_version", "device_model", "system_version", "lang_code"}:
for option in ["app_version", "device_model", "system_version", "lang_code"]:
if getattr(self, option):
pass
else:
setattr(self, option, Client.APP_VERSION)
if parser.has_section("pyrogram"):
setattr(self, option, parser.get(
"pyrogram",
option,
fallback=getattr(Client, option.upper())
))
if self.lang_code:
pass
else:
self.lang_code = Client.LANG_CODE
if parser.has_section("pyrogram"):
self.lang_code = parser.get(
"pyrogram",
"lang_code",
fallback=Client.LANG_CODE
)
setattr(self, option, getattr(Client, option.upper()))
if self._proxy:
self._proxy["enabled"] = True
@ -929,7 +998,7 @@ class Client(Methods, BaseClient):
except FileNotFoundError:
self.dc_id = 1
self.date = 0
self.auth_key = Auth(self.dc_id, self.test_mode, self._proxy).create()
self.auth_key = Auth(self.dc_id, self.test_mode, self.ipv6, self._proxy).create()
else:
self.dc_id = s["dc_id"]
self.test_mode = s["test_mode"]
@ -952,6 +1021,45 @@ class Client(Methods, BaseClient):
if peer:
self.peers_by_phone[k] = peer
def load_plugins(self):
if self.plugins_dir is not None:
plugins_count = 0
for path in Path(self.plugins_dir).rglob("*.py"):
file_path = os.path.splitext(str(path))[0]
import_path = []
while file_path:
file_path, tail = os.path.split(file_path)
import_path.insert(0, tail)
import_path = ".".join(import_path)
module = import_module(import_path)
for name in dir(module):
# noinspection PyBroadException
try:
handler, group = getattr(module, name)
if isinstance(handler, Handler) and isinstance(group, int):
self.add_handler(handler, group)
log.info('{}("{}") from "{}" loaded in group {}'.format(
type(handler).__name__, name, import_path, group))
plugins_count += 1
except Exception:
pass
if plugins_count > 0:
log.warning('Successfully loaded {} plugin{} from "{}"'.format(
plugins_count,
"s" if plugins_count > 1 else "",
self.plugins_dir
))
else:
log.warning('No plugin loaded: "{}" doesn\'t contain any valid plugin'.format(self.plugins_dir))
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)]
@ -971,13 +1079,18 @@ class Client(Methods, BaseClient):
indent=4
)
def get_dialogs_chunk(self, offset_date):
def get_initial_dialogs_chunk(self,
offset_date: int = 0):
while True:
try:
r = self.send(
functions.messages.GetDialogs(
offset_date, 0, types.InputPeerEmpty(),
self.DIALOGS_AT_ONCE, True
offset_date=offset_date,
offset_id=0,
offset_peer=types.InputPeerEmpty(),
limit=self.DIALOGS_AT_ONCE,
hash=0,
exclude_pinned=True
)
)
except FloodWait as e:
@ -987,59 +1100,57 @@ class Client(Methods, BaseClient):
log.info("Total peers: {}".format(len(self.peers_by_id)))
return r
def get_dialogs(self):
def get_initial_dialogs(self):
self.send(functions.messages.GetPinnedDialogs())
dialogs = self.get_dialogs_chunk(0)
dialogs = self.get_initial_dialogs_chunk()
offset_date = utils.get_offset_date(dialogs)
while len(dialogs.dialogs) == self.DIALOGS_AT_ONCE:
dialogs = self.get_dialogs_chunk(offset_date)
dialogs = self.get_initial_dialogs_chunk(offset_date)
offset_date = utils.get_offset_date(dialogs)
self.get_dialogs_chunk(0)
self.get_initial_dialogs_chunk()
def resolve_peer(self, peer_id: int or str):
"""Use this method to get the *InputPeer* of a known *peer_id*.
def resolve_peer(self,
peer_id: Union[int, str]):
"""Use this method to get the InputPeer of a known peer_id.
It is intended to be used when working with Raw Functions (i.e: a Telegram API method you wish to use which is
not available yet in the Client class as an easy-to-use method).
This is a utility method intended to be used only when working with Raw Functions (i.e: a Telegram API method
you wish to use which is not available yet in the Client class as an easy-to-use method), whenever an InputPeer
type is required.
Args:
peer_id (``int`` | ``str`` | ``Peer``):
The Peer ID you want to extract the InputPeer from. Can be one of these types: ``int`` (direct ID),
``str`` (@username), :obj:`PeerUser <pyrogram.api.types.PeerUser>`,
:obj:`PeerChat <pyrogram.api.types.PeerChat>`, :obj:`PeerChannel <pyrogram.api.types.PeerChannel>`
peer_id (``int`` | ``str``):
The peer id you want to extract the InputPeer from.
Can be a direct id (int), a username (str) or a phone number (str).
Returns:
:obj:`InputPeerUser <pyrogram.api.types.InputPeerUser>` or
:obj:`InputPeerChat <pyrogram.api.types.InputPeerChat>` or
:obj:`InputPeerChannel <pyrogram.api.types.InputPeerChannel>` depending on the *peer_id*.
On success, the resolved peer id is returned in form of an InputPeer object.
Raises:
:class:`Error <pyrogram.Error>`
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
``KeyError`` in case the peer doesn't exist in the internal database.
"""
try:
return self.peers_by_id[peer_id]
except KeyError:
if type(peer_id) is str:
if peer_id in ("self", "me"):
return types.InputPeerSelf()
match = self.INVITE_LINK_RE.match(peer_id)
try:
decoded = base64.b64decode(match.group(1) + "=" * (-len(match.group(1)) % 4), "-_")
return self.resolve_peer(struct.unpack(">2iq", decoded)[1])
except (AttributeError, binascii.Error, struct.error):
pass
peer_id = re.sub(r"[@+\s]", "", peer_id.lower())
try:
int(peer_id)
except ValueError:
try:
return self.peers_by_username[peer_id]
except KeyError:
self.send(functions.contacts.ResolveUsername(peer_id))
if peer_id not in self.peers_by_username:
self.send(
functions.contacts.ResolveUsername(
username=peer_id
)
)
return self.peers_by_username[peer_id]
else:
try:
@ -1047,23 +1158,31 @@ class Client(Methods, BaseClient):
except KeyError:
raise PeerIdInvalid
if type(peer_id) is not int:
if isinstance(peer_id, types.PeerUser):
peer_id = peer_id.user_id
elif isinstance(peer_id, types.PeerChat):
peer_id = -peer_id.chat_id
elif isinstance(peer_id, types.PeerChannel):
peer_id = int("-100" + str(peer_id.channel_id))
if peer_id > 0:
self.fetch_peers(
self.send(
functions.users.GetUsers(
id=[types.InputUser(peer_id, 0)]
)
)
)
else:
if str(peer_id).startswith("-100"):
self.send(
functions.channels.GetChannels(
id=[types.InputChannel(int(str(peer_id)[4:]), 0)]
)
)
else:
self.send(
functions.messages.GetChats(
id=[-peer_id]
)
)
try: # User
try:
return self.peers_by_id[peer_id]
except KeyError:
try: # Chat
return self.peers_by_id[-peer_id]
except KeyError:
try: # Channel
return self.peers_by_id[int("-100" + str(peer_id))]
except (KeyError, ValueError):
raise PeerIdInvalid
def save_file(self,
@ -1074,6 +1193,13 @@ class Client(Methods, BaseClient):
progress_args: tuple = ()):
part_size = 512 * 1024
file_size = os.path.getsize(path)
if file_size == 0:
raise ValueError("File size equals to 0 B")
if file_size > 1500 * 1024 * 1024:
raise ValueError("Telegram doesn't support uploading files bigger than 1500 MiB")
file_total_parts = int(math.ceil(file_size / part_size))
is_big = True if file_size > 10 * 1024 * 1024 else False
is_missing_part = True if file_id is not None else False
@ -1148,10 +1274,9 @@ class Client(Methods, BaseClient):
volume_id: int = None,
local_id: int = None,
secret: int = None,
version: int = 0,
size: int = None,
progress: callable = None,
progress_args: tuple = None) -> str:
progress_args: tuple = ()) -> str:
with self.media_sessions_lock:
session = self.media_sessions.get(dc_id, None)
@ -1166,7 +1291,7 @@ class Client(Methods, BaseClient):
session = Session(
self,
dc_id,
Auth(dc_id, self.test_mode, self._proxy).create(),
Auth(dc_id, self.test_mode, self.ipv6, self._proxy).create(),
is_media=True
)
@ -1196,13 +1321,14 @@ class Client(Methods, BaseClient):
location = types.InputFileLocation(
volume_id=volume_id,
local_id=local_id,
secret=secret
secret=secret,
file_reference=b""
)
else: # Any other file can be more easily accessed by id and access_hash
location = types.InputDocumentFileLocation(
id=id,
access_hash=access_hash,
version=version
file_reference=b""
)
limit = 1024 * 1024
@ -1229,13 +1355,11 @@ class Client(Methods, BaseClient):
break
f.write(chunk)
f.flush()
os.fsync(f.fileno())
offset += limit
if progress:
progress(self, min(offset, size), size, *progress_args)
progress(self, min(offset, size) if size != 0 else offset, size, *progress_args)
r = session.send(
functions.upload.GetFile(
@ -1253,7 +1377,7 @@ class Client(Methods, BaseClient):
cdn_session = Session(
self,
r.dc_id,
Auth(r.dc_id, self.test_mode, self._proxy).create(),
Auth(r.dc_id, self.test_mode, self.ipv6, self._proxy).create(),
is_media=True,
is_cdn=True
)
@ -1313,13 +1437,11 @@ class Client(Methods, BaseClient):
assert h.hash == sha256(cdn_chunk).digest(), "Invalid CDN hash part {}".format(i)
f.write(decrypted_chunk)
f.flush()
os.fsync(f.fileno())
offset += limit
if progress:
progress(self, min(offset, size), size, *progress_args)
progress(self, min(offset, size) if size != 0 else offset, size, *progress_args)
if len(chunk) < limit:
break

View File

@ -24,8 +24,7 @@ from threading import Thread
import pyrogram
from pyrogram.api import types
from ..ext import utils
from ..handlers import RawUpdateHandler, CallbackQueryHandler, MessageHandler, DeletedMessagesHandler
from ..handlers import CallbackQueryHandler, MessageHandler, RawUpdateHandler, UserStatusHandler, DeletedMessagesHandler
log = logging.getLogger(__name__)
@ -41,20 +40,44 @@ class Dispatcher:
types.UpdateEditChannelMessage
)
DELETE_MESSAGE_UPDATES = (
DELETE_MESSAGES_UPDATES = (
types.UpdateDeleteMessages,
types.UpdateDeleteChannelMessages
)
CALLBACK_QUERY_UPDATES = (
types.UpdateBotCallbackQuery,
types.UpdateInlineBotCallbackQuery
)
MESSAGE_UPDATES = NEW_MESSAGE_UPDATES + EDIT_MESSAGE_UPDATES
def __init__(self, client, workers):
def __init__(self, client, workers: int):
self.client = client
self.workers = workers
self.workers_list = []
self.updates = Queue()
self.updates_queue = Queue()
self.groups = OrderedDict()
self.update_parsers = {
Dispatcher.MESSAGE_UPDATES:
lambda upd, usr, cht: (pyrogram.Message._parse(self.client, upd.message, usr, cht), MessageHandler),
Dispatcher.DELETE_MESSAGES_UPDATES:
lambda upd, usr, cht: (pyrogram.Messages._parse_deleted(self.client, upd), DeletedMessagesHandler),
Dispatcher.CALLBACK_QUERY_UPDATES:
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
)
}
self.update_parsers = {key: value for key_tuple, value in self.update_parsers.items() for key in key_tuple}
def start(self):
for i in range(self.workers):
self.workers_list.append(
@ -68,10 +91,10 @@ class Dispatcher:
def stop(self):
for _ in range(self.workers):
self.updates.put(None)
self.updates_queue.put(None)
for i in self.workers_list:
i.join()
for worker in self.workers_list:
worker.join()
self.workers_list.clear()
@ -84,56 +107,16 @@ class Dispatcher:
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))
raise ValueError("Group {} does not exist. Handler was not removed.".format(group))
self.groups[group].remove(handler)
def dispatch(self, update, users: dict = None, chats: dict = None, is_raw: bool = False):
for group in self.groups.values():
for handler in group:
if is_raw:
if not isinstance(handler, RawUpdateHandler):
continue
args = (self.client, update, users, chats)
else:
message = (update.message
or update.channel_post
or update.edited_message
or update.edited_channel_post)
deleted_messages = (update.deleted_channel_posts
or update.deleted_messages)
callback_query = update.callback_query
if message and isinstance(handler, MessageHandler):
if not handler.check(message):
continue
args = (self.client, message)
elif deleted_messages and isinstance(handler, DeletedMessagesHandler):
if not handler.check(deleted_messages):
continue
args = (self.client, deleted_messages)
elif callback_query and isinstance(handler, CallbackQueryHandler):
if not handler.check(callback_query):
continue
args = (self.client, callback_query)
else:
continue
handler.callback(*args)
break
def update_worker(self):
name = threading.current_thread().name
log.debug("{} started".format(name))
while True:
update = self.updates.get()
update = self.updates_queue.get()
if update is None:
break
@ -143,71 +126,32 @@ class Dispatcher:
chats = {i.id: i for i in update[2]}
update = update[0]
self.dispatch(update, users=users, chats=chats, is_raw=True)
parser = self.update_parsers.get(type(update), None)
if isinstance(update, Dispatcher.MESSAGE_UPDATES):
if isinstance(update.message, types.MessageEmpty):
if parser is None:
continue
message = utils.parse_messages(
self.client,
update.message,
users,
chats
)
parsed_update, handler_type = parser(update, users, chats)
is_edited_message = isinstance(update, Dispatcher.EDIT_MESSAGE_UPDATES)
for group in self.groups.values():
for handler in group:
args = None
self.dispatch(
pyrogram.Update(
message=((message if message.chat.type != "channel"
else None) if not is_edited_message
else None),
edited_message=((message if message.chat.type != "channel"
else None) if is_edited_message
else None),
channel_post=((message if message.chat.type == "channel"
else None) if not is_edited_message
else None),
edited_channel_post=((message if message.chat.type == "channel"
else None) if is_edited_message
else None)
)
)
if isinstance(handler, RawUpdateHandler):
args = (update, users, chats)
elif isinstance(handler, handler_type):
if handler.check(parsed_update):
args = (parsed_update,)
elif isinstance(update, Dispatcher.DELETE_MESSAGE_UPDATES):
is_channel = hasattr(update, 'channel_id')
messages = utils.parse_deleted_messages(
update.messages,
(update.channel_id if is_channel else None)
)
self.dispatch(
pyrogram.Update(
deleted_messages=(messages if not is_channel else None),
deleted_channel_posts=(messages if is_channel else None)
)
)
elif isinstance(update, types.UpdateBotCallbackQuery):
self.dispatch(
pyrogram.Update(
callback_query=utils.parse_callback_query(
self.client, update, users
)
)
)
elif isinstance(update, types.UpdateInlineBotCallbackQuery):
self.dispatch(
pyrogram.Update(
callback_query=utils.parse_inline_callback_query(
update, users
)
)
)
else:
if args is None:
continue
try:
handler.callback(self.client, *args)
except Exception as e:
log.error(e, exc_info=True)
finally:
break
except Exception as e:
log.error(e, exc_info=True)

View File

@ -41,7 +41,6 @@ class BaseClient:
platform.release()
)
SYSTEM_LANG_CODE = "en"
LANG_CODE = "en"
INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/joinchat/)([\w-]+)$")
@ -50,6 +49,9 @@ class BaseClient:
UPDATES_WORKERS = 1
DOWNLOAD_WORKERS = 1
OFFLINE_SLEEP = 300
WORKERS = 4
WORKDIR = "."
CONFIG_FILE = "./config.ini"
MEDIA_TYPE_ID = {
0: "thumbnail",
@ -60,12 +62,12 @@ class BaseClient:
5: "document",
8: "sticker",
9: "audio",
10: "gif",
10: "animation",
13: "video_note"
}
def __init__(self):
self.token = None
self.bot_token = None
self.dc_id = None
self.auth_key = None
self.user_id = None
@ -117,7 +119,8 @@ class BaseClient:
def get_messages(
self,
chat_id: int or str,
message_ids,
message_ids: int or list = None,
reply_to_message_ids: int or list = None,
replies: int = 1
):
pass

File diff suppressed because it is too large Load Diff

View File

@ -16,200 +16,9 @@
# You should have received a copy of the GNU Lesser General Public License
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
import logging
import time
from base64 import b64decode, b64encode
from struct import pack
from weakref import proxy
from pyrogram.api.errors import FloodWait
from pyrogram.client import types as pyrogram_types
from ...api import types, functions
from ...api.errors import StickersetInvalid
log = logging.getLogger(__name__)
# TODO: Organize the code better?
class Str(str):
__slots__ = "_client", "_entities"
def __init__(self, *args):
super().__init__()
self._client = None
self._entities = None
def init(self, client, entities):
self._client = client
self._entities = entities
@property
def text(self):
return self
@property
def markdown(self):
return self._client.markdown.unparse(self, self._entities)
@property
def html(self):
return self._client.html.unparse(self, self._entities)
ENTITIES = {
types.MessageEntityMention.ID: "mention",
types.MessageEntityHashtag.ID: "hashtag",
types.MessageEntityBotCommand.ID: "bot_command",
types.MessageEntityUrl.ID: "url",
types.MessageEntityEmail.ID: "email",
types.MessageEntityBold.ID: "bold",
types.MessageEntityItalic.ID: "italic",
types.MessageEntityCode.ID: "code",
types.MessageEntityPre.ID: "pre",
types.MessageEntityTextUrl.ID: "text_link",
types.MessageEntityMentionName.ID: "text_mention"
}
def parse_entities(entities: list, users: dict) -> list:
output_entities = []
for entity in entities:
entity_type = ENTITIES.get(entity.ID, None)
if entity_type:
output_entities.append(pyrogram_types.MessageEntity(
type=entity_type,
offset=entity.offset,
length=entity.length,
url=getattr(entity, "url", None),
user=parse_user(
users.get(
getattr(entity, "user_id", None),
None
)
)
))
return output_entities
def parse_chat_photo(photo):
if not isinstance(photo, (types.UserProfilePhoto, types.ChatPhoto)):
return None
if not isinstance(photo.photo_small, types.FileLocation):
return None
if not isinstance(photo.photo_big, types.FileLocation):
return None
photo_id = getattr(photo, "photo_id", 0)
loc_small = photo.photo_small
loc_big = photo.photo_big
return pyrogram_types.ChatPhoto(
small_file_id=encode(
pack(
"<iiqqqqi", 1, loc_small.dc_id, photo_id, 0, loc_small.volume_id,
loc_small.secret, loc_small.local_id
)
),
big_file_id=encode(
pack(
"<iiqqqqi", 1, loc_big.dc_id, photo_id, 0, loc_big.volume_id,
loc_big.secret, loc_big.local_id
)
)
)
def parse_user(user: types.User) -> pyrogram_types.User or None:
return pyrogram_types.User(
id=user.id,
is_bot=user.bot,
first_name=user.first_name,
last_name=user.last_name,
username=user.username,
language_code=user.lang_code,
phone_number=user.phone,
photo=parse_chat_photo(user.photo)
) if user else None
def parse_chat(message: types.Message, users: dict, chats: dict) -> pyrogram_types.Chat:
if isinstance(message.to_id, types.PeerUser):
return parse_user_chat(users[message.to_id.user_id if message.out else message.from_id])
elif isinstance(message.to_id, types.PeerChat):
return parse_chat_chat(chats[message.to_id.chat_id])
else:
return parse_channel_chat(chats[message.to_id.channel_id])
def parse_user_chat(user: types.User) -> pyrogram_types.Chat:
return pyrogram_types.Chat(
id=user.id,
type="private",
username=user.username,
first_name=user.first_name,
last_name=user.last_name,
photo=parse_chat_photo(user.photo)
)
def parse_chat_chat(chat: types.Chat) -> pyrogram_types.Chat:
admins_enabled = getattr(chat, "admins_enabled", None)
if admins_enabled is not None:
admins_enabled = not admins_enabled
return pyrogram_types.Chat(
id=-chat.id,
type="group",
title=chat.title,
all_members_are_administrators=admins_enabled,
photo=parse_chat_photo(getattr(chat, "photo", None))
)
def parse_channel_chat(channel: types.Channel) -> pyrogram_types.Chat:
return pyrogram_types.Chat(
id=int("-100" + str(channel.id)),
type="supergroup" if channel.megagroup else "channel",
title=channel.title,
username=getattr(channel, "username", None),
photo=parse_chat_photo(getattr(channel, "photo", None))
)
def parse_thumb(thumb: types.PhotoSize or types.PhotoCachedSize) -> pyrogram_types.PhotoSize or None:
if isinstance(thumb, (types.PhotoSize, types.PhotoCachedSize)):
loc = thumb.location
if isinstance(thumb, types.PhotoSize):
file_size = thumb.size
else:
file_size = len(thumb.bytes)
if isinstance(loc, types.FileLocation):
return pyrogram_types.PhotoSize(
file_id=encode(
pack(
"<iiqqqqi",
0,
loc.dc_id,
0,
0,
loc.volume_id,
loc.secret,
loc.local_id
)
),
width=thumb.w,
height=thumb.h,
file_size=file_size
)
from ...api import types
def decode(s: str) -> bytes:
@ -248,508 +57,6 @@ def encode(s: bytes) -> str:
return b64encode(r, b"-_").decode().rstrip("=")
# TODO: Reorganize code, maybe split parts as well
def parse_messages(
client,
messages: list or types.Message or types.MessageService or types.MessageEmpty,
users: dict,
chats: dict,
replies: int = 1
) -> pyrogram_types.Message or list:
is_list = isinstance(messages, list)
messages = messages if is_list else [messages]
parsed_messages = []
for message in messages:
if isinstance(message, types.Message):
entities = parse_entities(message.entities, users)
forward_from = None
forward_from_chat = None
forward_from_message_id = None
forward_signature = None
forward_date = None
forward_header = message.fwd_from # type: types.MessageFwdHeader
if forward_header:
forward_date = forward_header.date
if forward_header.from_id:
forward_from = parse_user(users[forward_header.from_id])
else:
forward_from_chat = parse_channel_chat(chats[forward_header.channel_id])
forward_from_message_id = forward_header.channel_post
forward_signature = forward_header.post_author
photo = None
location = None
contact = None
venue = None
audio = None
voice = None
gif = None
video = None
video_note = None
sticker = None
document = None
media = message.media
if media:
if isinstance(media, types.MessageMediaPhoto):
photo = media.photo
if isinstance(photo, types.Photo):
sizes = photo.sizes
photo_sizes = []
for size in sizes:
if isinstance(size, (types.PhotoSize, types.PhotoCachedSize)):
loc = size.location
if isinstance(size, types.PhotoSize):
file_size = size.size
else:
file_size = len(size.bytes)
if isinstance(loc, types.FileLocation):
photo_size = pyrogram_types.PhotoSize(
file_id=encode(
pack(
"<iiqqqqi",
2,
loc.dc_id,
photo.id,
photo.access_hash,
loc.volume_id,
loc.secret,
loc.local_id
)
),
width=size.w,
height=size.h,
file_size=file_size
)
photo_sizes.append(photo_size)
photo = pyrogram_types.Photo(
id=b64encode(
pack(
"<qq",
photo.id,
photo.access_hash
),
b"-_"
).decode().rstrip("="),
date=photo.date,
sizes=photo_sizes
)
elif isinstance(media, types.MessageMediaGeo):
geo_point = media.geo
if isinstance(geo_point, types.GeoPoint):
location = pyrogram_types.Location(
longitude=geo_point.long,
latitude=geo_point.lat
)
elif isinstance(media, types.MessageMediaContact):
contact = pyrogram_types.Contact(
phone_number=media.phone_number,
first_name=media.first_name,
last_name=media.last_name or None,
user_id=media.user_id or None
)
elif isinstance(media, types.MessageMediaVenue):
venue = pyrogram_types.Venue(
location=pyrogram_types.Location(
longitude=media.geo.long,
latitude=media.geo.lat
),
title=media.title,
address=media.address,
foursquare_id=media.venue_id or None
)
elif isinstance(media, types.MessageMediaDocument):
doc = media.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]
if audio_attributes.voice:
voice = pyrogram_types.Voice(
file_id=encode(
pack(
"<iiqq",
3,
doc.dc_id,
doc.id,
doc.access_hash
)
),
duration=audio_attributes.duration,
mime_type=doc.mime_type,
file_size=doc.size,
thumb=parse_thumb(doc.thumb),
file_name=file_name,
date=doc.date
)
else:
audio = pyrogram_types.Audio(
file_id=encode(
pack(
"<iiqq",
9,
doc.dc_id,
doc.id,
doc.access_hash
)
),
duration=audio_attributes.duration,
performer=audio_attributes.performer,
title=audio_attributes.title,
mime_type=doc.mime_type,
file_size=doc.size,
thumb=parse_thumb(doc.thumb),
file_name=file_name,
date=doc.date
)
elif types.DocumentAttributeAnimated in attributes:
video_attributes = attributes.get(types.DocumentAttributeVideo, None)
gif = pyrogram_types.GIF(
file_id=encode(
pack(
"<iiqq",
10,
doc.dc_id,
doc.id,
doc.access_hash
)
),
width=getattr(video_attributes, "w", 0),
height=getattr(video_attributes, "h", 0),
duration=getattr(video_attributes, "duration", 0),
thumb=parse_thumb(doc.thumb),
mime_type=doc.mime_type,
file_size=doc.size,
file_name=file_name,
date=doc.date
)
elif types.DocumentAttributeVideo in attributes:
video_attributes = attributes[types.DocumentAttributeVideo]
if video_attributes.round_message:
video_note = pyrogram_types.VideoNote(
file_id=encode(
pack(
"<iiqq",
13,
doc.dc_id,
doc.id,
doc.access_hash
)
),
length=video_attributes.w,
duration=video_attributes.duration,
thumb=parse_thumb(doc.thumb),
file_size=doc.size,
file_name=file_name,
mime_type=doc.mime_type,
date=doc.date
)
else:
video = pyrogram_types.Video(
file_id=encode(
pack(
"<iiqq",
4,
doc.dc_id,
doc.id,
doc.access_hash
)
),
width=video_attributes.w,
height=video_attributes.h,
duration=video_attributes.duration,
thumb=parse_thumb(doc.thumb),
mime_type=doc.mime_type,
file_size=doc.size,
file_name=file_name,
date=doc.date
)
elif types.DocumentAttributeSticker in attributes:
image_size_attributes = attributes.get(types.DocumentAttributeImageSize, None)
sticker_attribute = attributes[types.DocumentAttributeSticker]
if isinstance(sticker_attribute.stickerset, types.InputStickerSetID):
try:
set_name = client.send(
functions.messages.GetStickerSet(sticker_attribute.stickerset)
).set.short_name
except StickersetInvalid:
set_name = None
else:
set_name = None
sticker = pyrogram_types.Sticker(
file_id=encode(
pack(
"<iiqq",
8,
doc.dc_id,
doc.id,
doc.access_hash
)
),
width=image_size_attributes.w if image_size_attributes else 0,
height=image_size_attributes.h if image_size_attributes else 0,
thumb=parse_thumb(doc.thumb),
# TODO: mask_position
set_name=set_name,
emoji=sticker_attribute.alt or None,
file_size=doc.size,
mime_type=doc.mime_type,
file_name=file_name,
date=doc.date
)
else:
document = pyrogram_types.Document(
file_id=encode(
pack(
"<iiqq",
5,
doc.dc_id,
doc.id,
doc.access_hash
)
),
thumb=parse_thumb(doc.thumb),
file_name=file_name,
mime_type=doc.mime_type,
file_size=doc.size,
date=doc.date
)
else:
media = None
reply_markup = message.reply_markup
if reply_markup:
if isinstance(reply_markup, types.ReplyKeyboardForceReply):
reply_markup = pyrogram_types.ForceReply.read(reply_markup)
elif isinstance(reply_markup, types.ReplyKeyboardMarkup):
reply_markup = pyrogram_types.ReplyKeyboardMarkup.read(reply_markup)
elif isinstance(reply_markup, types.ReplyInlineMarkup):
reply_markup = pyrogram_types.InlineKeyboardMarkup.read(reply_markup)
elif isinstance(reply_markup, types.ReplyKeyboardHide):
reply_markup = pyrogram_types.ReplyKeyboardRemove.read(reply_markup)
else:
reply_markup = None
m = pyrogram_types.Message(
message_id=message.id,
date=message.date,
chat=parse_chat(message, users, chats),
from_user=parse_user(users.get(message.from_id, None)),
text=Str(message.message) or None if media is None else None,
caption=Str(message.message) 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,
author_signature=message.post_author,
forward_from=forward_from,
forward_from_chat=forward_from_chat,
forward_from_message_id=forward_from_message_id,
forward_signature=forward_signature,
forward_date=forward_date,
edit_date=message.edit_date,
media_group_id=message.grouped_id,
photo=photo,
location=location,
contact=contact,
venue=venue,
audio=audio,
voice=voice,
gif=gif,
video=video,
video_note=video_note,
sticker=sticker,
document=document,
views=message.views,
via_bot=parse_user(users.get(message.via_bot_id, None)),
outgoing=message.out,
client=proxy(client),
reply_markup=reply_markup
)
if m.text:
m.text.init(m._client, m.entities or [])
if m.caption:
m.caption.init(m._client, m.caption_entities or [])
if message.reply_to_msg_id and replies:
while True:
try:
m.reply_to_message = client.get_messages(
m.chat.id, message.reply_to_msg_id,
replies=replies - 1
)
except FloodWait as e:
log.warning("get_messages flood: waiting {} seconds".format(e.x))
time.sleep(e.x)
continue
else:
break
elif isinstance(message, types.MessageService):
action = message.action
new_chat_members = None
left_chat_member = None
new_chat_title = None
delete_chat_photo = None
migrate_to_chat_id = None
migrate_from_chat_id = None
group_chat_created = None
channel_chat_created = None
new_chat_photo = None
if isinstance(action, types.MessageActionChatAddUser):
new_chat_members = [parse_user(users[i]) for i in action.users]
elif isinstance(action, types.MessageActionChatJoinedByLink):
new_chat_members = [parse_user(users[message.from_id])]
elif isinstance(action, types.MessageActionChatDeleteUser):
left_chat_member = parse_user(users[action.user_id])
elif isinstance(action, types.MessageActionChatEditTitle):
new_chat_title = action.title
elif isinstance(action, types.MessageActionChatDeletePhoto):
delete_chat_photo = True
elif isinstance(action, types.MessageActionChatMigrateTo):
migrate_to_chat_id = action.channel_id
elif isinstance(action, types.MessageActionChannelMigrateFrom):
migrate_from_chat_id = action.chat_id
elif isinstance(action, types.MessageActionChatCreate):
group_chat_created = True
elif isinstance(action, types.MessageActionChannelCreate):
channel_chat_created = True
elif isinstance(action, types.MessageActionChatEditPhoto):
photo = action.photo
if isinstance(photo, types.Photo):
sizes = photo.sizes
photo_sizes = []
for size in sizes:
if isinstance(size, (types.PhotoSize, types.PhotoCachedSize)):
loc = size.location
if isinstance(size, types.PhotoSize):
file_size = size.size
else:
file_size = len(size.bytes)
if isinstance(loc, types.FileLocation):
photo_size = pyrogram_types.PhotoSize(
file_id=encode(
pack(
"<iiqqqqi",
2,
loc.dc_id,
photo.id,
photo.access_hash,
loc.volume_id,
loc.secret,
loc.local_id
)
),
width=size.w,
height=size.h,
file_size=file_size
)
photo_sizes.append(photo_size)
new_chat_photo = pyrogram_types.Photo(
id=b64encode(
pack(
"<qq",
photo.id,
photo.access_hash
),
b"-_"
).decode().rstrip("="),
date=photo.date,
sizes=photo_sizes
)
m = pyrogram_types.Message(
message_id=message.id,
date=message.date,
chat=parse_chat(message, users, chats),
from_user=parse_user(users.get(message.from_id, None)),
new_chat_members=new_chat_members,
left_chat_member=left_chat_member,
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_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,
client=proxy(client)
# TODO: supergroup_chat_created
)
if isinstance(action, types.MessageActionPinMessage):
while True:
try:
m.pinned_message = client.get_messages(
m.chat.id, message.reply_to_msg_id,
replies=0
)
except FloodWait as e:
log.warning("get_messages flood: waiting {} seconds".format(e.x))
time.sleep(e.x)
continue
else:
break
else:
m = pyrogram_types.Message(message_id=message.id, client=proxy(client))
parsed_messages.append(m)
return parsed_messages if is_list else parsed_messages[0]
def parse_deleted_messages(
messages: list,
channel_id: int
) -> pyrogram_types.Messages:
parsed_messages = []
for message in messages:
parsed_messages.append(
pyrogram_types.Message(
message_id=message,
chat=(pyrogram_types.Chat(id=int("-100" + str(channel_id)), type="channel")
if channel_id is not None
else None)
)
)
return pyrogram_types.Messages(len(parsed_messages), parsed_messages)
def get_peer_id(input_peer) -> int:
return (
input_peer.user_id if isinstance(input_peer, types.InputPeerUser)
@ -775,141 +82,3 @@ def get_offset_date(dialogs):
return m.date
else:
return 0
def parse_profile_photos(photos):
if isinstance(photos, types.photos.Photos):
total_count = len(photos.photos)
else:
total_count = photos.count
user_profile_photos = []
for photo in photos.photos:
if isinstance(photo, types.Photo):
sizes = photo.sizes
photo_sizes = []
for size in sizes:
if isinstance(size, (types.PhotoSize, types.PhotoCachedSize)):
loc = size.location
if isinstance(size, types.PhotoSize):
file_size = size.size
else:
file_size = len(size.bytes)
if isinstance(loc, types.FileLocation):
photo_size = pyrogram_types.PhotoSize(
file_id=encode(
pack(
"<iiqqqqi",
2,
loc.dc_id,
photo.id,
photo.access_hash,
loc.volume_id,
loc.secret,
loc.local_id
)
),
width=size.w,
height=size.h,
file_size=file_size
)
photo_sizes.append(photo_size)
user_profile_photos.append(
pyrogram_types.Photo(
id=b64encode(
pack(
"<qq",
photo.id,
photo.access_hash
),
b"-_"
).decode().rstrip("="),
date=photo.date,
sizes=photo_sizes
)
)
return pyrogram_types.UserProfilePhotos(
total_count=total_count,
photos=user_profile_photos
)
def parse_callback_query(client, callback_query, users):
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))
return pyrogram_types.CallbackQuery(
id=str(callback_query.query_id),
from_user=parse_user(users[callback_query.user_id]),
message=client.get_messages(peer_id, callback_query.msg_id),
chat_instance=str(callback_query.chat_instance),
data=callback_query.data.decode(),
game_short_name=callback_query.game_short_name
)
def parse_inline_callback_query(callback_query, users):
return pyrogram_types.CallbackQuery(
id=str(callback_query.query_id),
from_user=parse_user(users[callback_query.user_id]),
chat_instance=str(callback_query.chat_instance),
inline_message_id=b64encode(
pack(
"<iqq",
callback_query.msg_id.dc_id,
callback_query.msg_id.id,
callback_query.msg_id.access_hash
),
b"-_"
).decode().rstrip("="),
game_short_name=callback_query.game_short_name
)
def parse_chat_full(
client,
chat_full: types.messages.ChatFull or types.UserFull
) -> pyrogram_types.Chat:
if isinstance(chat_full, types.UserFull):
chat = parse_user_chat(chat_full.user)
chat.description = chat_full.about
else:
full_chat = chat_full.full_chat
chat = None
for i in chat_full.chats:
if full_chat.id == i.id:
chat = i
if isinstance(full_chat, types.ChatFull):
chat = parse_chat_chat(chat)
else:
chat = parse_channel_chat(chat)
chat.description = full_chat.about or None
# TODO: Add StickerSet type
chat.can_set_sticker_set = full_chat.can_set_stickers
chat.sticker_set_name = full_chat.stickerset
if full_chat.pinned_msg_id:
chat.pinned_message = client.get_messages(
int("-100" + str(full_chat.id)),
full_chat.pinned_msg_id
)
if isinstance(full_chat.exported_invite, types.ChatInviteExported):
chat.invite_link = full_chat.exported_invite.link
return chat

View File

@ -19,10 +19,32 @@
import re
from .filter import Filter
from ..types.reply_markup import InlineKeyboardMarkup, ReplyKeyboardMarkup
from ..types.bots import InlineKeyboardMarkup, ReplyKeyboardMarkup
def build(name: str, func: callable, **kwargs) -> type:
def create(name: str, func: callable, **kwargs) -> type:
"""Use this method to create a Filter.
Custom filters give you extra control over which updates are allowed or not to be processed by your handlers.
Args:
name (``str``):
Your filter's name. Can be anything you like.
func (``callable``):
A function that accepts two arguments *(filter, update)* and returns a Boolean: True if the update should be
handled, False otherwise.
The "update" argument type will vary depending on which `Handler <Handlers.html>`_ is coming from.
For example, in a :obj:`MessageHandler <pyrogram.MessageHandler>` the update type will be
a :obj:`Message <pyrogram.Message>`; in a :obj:`CallbackQueryHandler <pyrogram.CallbackQueryHandler>` the
update type will be a :obj:`CallbackQuery <pyrogram.CallbackQuery>`. Your function body can then access the
incoming update and decide whether to allow it or not.
**kwargs (``any``, *optional*):
Any keyword argument you would like to pass. Useful for custom filters that accept parameters (e.g.:
:meth:`Filters.command`, :meth:`Filters.regex`).
"""
# TODO: unpack kwargs using **kwargs into the dict itself. For Python 3.5+ only
d = {"__call__": func}
d.update(kwargs)
@ -30,117 +52,164 @@ def build(name: str, func: callable, **kwargs) -> type:
class Filters:
"""This class provides access to all Filters available in Pyrogram.
Filters are intended to be used with the :obj:`MessageHandler <pyrogram.MessageHandler>`."""
"""This class provides access to all library-defined Filters available in Pyrogram.
bot = build("Bot", lambda _, m: bool(m.from_user and m.from_user.is_bot))
The Filters listed here are intended to be used with the :obj:`MessageHandler <pyrogram.MessageHandler>` only.
At the moment, if you want to filter updates coming from different `Handlers <Handlers.html>`_ you have to create
your own filters with :meth:`Filters.create` and use them in the same way.
"""
create = create
bot = create("Bot", lambda _, m: bool(m.from_user and m.from_user.is_bot))
"""Filter messages coming from bots"""
incoming = build("Incoming", lambda _, m: not m.outgoing)
incoming = create("Incoming", lambda _, m: not m.outgoing)
"""Filter incoming messages."""
outgoing = build("Outgoing", lambda _, m: m.outgoing)
outgoing = create("Outgoing", lambda _, m: m.outgoing)
"""Filter outgoing messages."""
text = build("Text", lambda _, m: bool(m.text))
text = create("Text", lambda _, m: bool(m.text))
"""Filter text messages."""
reply = build("Reply", lambda _, m: bool(m.reply_to_message))
reply = create("Reply", lambda _, m: bool(m.reply_to_message))
"""Filter messages that are replies to other messages."""
forwarded = build("Forwarded", lambda _, m: bool(m.forward_date))
forwarded = create("Forwarded", lambda _, m: bool(m.forward_date))
"""Filter messages that are forwarded."""
caption = build("Caption", lambda _, m: bool(m.caption))
caption = create("Caption", lambda _, m: bool(m.caption))
"""Filter media messages that contain captions."""
edited = build("Edited", lambda _, m: bool(m.edit_date))
edited = create("Edited", lambda _, m: bool(m.edit_date))
"""Filter edited messages."""
audio = build("Audio", lambda _, m: bool(m.audio))
"""Filter messages that contain :obj:`Audio <pyrogram.api.types.pyrogram.Audio>` objects."""
audio = create("Audio", lambda _, m: bool(m.audio))
"""Filter messages that contain :obj:`Audio <pyrogram.Audio>` objects."""
document = build("Document", lambda _, m: bool(m.document))
"""Filter messages that contain :obj:`Document <pyrogram.api.types.pyrogram.Document>` objects."""
document = create("Document", lambda _, m: bool(m.document))
"""Filter messages that contain :obj:`Document <pyrogram.Document>` objects."""
photo = build("Photo", lambda _, m: bool(m.photo))
"""Filter messages that contain :obj:`Photo <pyrogram.api.types.pyrogram.PhotoSize>` objects."""
photo = create("Photo", lambda _, m: bool(m.photo))
"""Filter messages that contain :obj:`Photo <pyrogram.PhotoSize>` objects."""
sticker = build("Sticker", lambda _, m: bool(m.sticker))
"""Filter messages that contain :obj:`Sticker <pyrogram.api.types.pyrogram.Sticker>` objects."""
sticker = create("Sticker", lambda _, m: bool(m.sticker))
"""Filter messages that contain :obj:`Sticker <pyrogram.Sticker>` objects."""
gif = build("GIF", lambda _, m: bool(m.gif))
"""Filter messages that contain :obj:`GIF <pyrogram.api.types.pyrogram.GIF>` objects."""
animation = create("GIF", lambda _, m: bool(m.animation))
"""Filter messages that contain :obj:`Animation <pyrogram.Animation>` objects."""
video = build("Video", lambda _, m: bool(m.video))
"""Filter messages that contain :obj:`Video <pyrogram.api.types.pyrogram.Video>` objects."""
video = create("Video", lambda _, m: bool(m.video))
"""Filter messages that contain :obj:`Video <pyrogram.Video>` objects."""
voice = build("Voice", lambda _, m: bool(m.voice))
"""Filter messages that contain :obj:`Voice <pyrogram.api.types.pyrogram.Voice>` note objects."""
voice = create("Voice", lambda _, m: bool(m.voice))
"""Filter messages that contain :obj:`Voice <pyrogram.Voice>` note objects."""
video_note = build("Voice", lambda _, m: bool(m.video_note))
"""Filter messages that contain :obj:`VideoNote <pyrogram.api.types.pyrogram.VideoNote>` objects."""
video_note = create("Voice", lambda _, m: bool(m.video_note))
"""Filter messages that contain :obj:`VideoNote <pyrogram.VideoNote>` objects."""
contact = build("Contact", lambda _, m: bool(m.contact))
"""Filter messages that contain :obj:`Contact <pyrogram.api.types.pyrogram.Contact>` objects."""
contact = create("Contact", lambda _, m: bool(m.contact))
"""Filter messages that contain :obj:`Contact <pyrogram.Contact>` objects."""
location = build("Location", lambda _, m: bool(m.location))
"""Filter messages that contain :obj:`Location <pyrogram.api.types.pyrogram.Location>` objects."""
location = create("Location", lambda _, m: bool(m.location))
"""Filter messages that contain :obj:`Location <pyrogram.Location>` objects."""
venue = build("Venue", lambda _, m: bool(m.venue))
"""Filter messages that contain :obj:`Venue <pyrogram.api.types.pyrogram.Venue>` objects."""
venue = create("Venue", lambda _, m: bool(m.venue))
"""Filter messages that contain :obj:`Venue <pyrogram.Venue>` objects."""
private = build("Private", lambda _, m: bool(m.chat and m.chat.type == "private"))
web_page = create("WebPage", lambda _, m: m.web_page)
"""Filter messages sent with a webpage preview."""
poll = create("Poll", lambda _, m: m.poll)
"""Filter messages that contain :obj:`Poll <pyrogram.Poll>` objects."""
private = create("Private", lambda _, m: bool(m.chat and m.chat.type == "private"))
"""Filter messages sent in private chats."""
group = build("Group", lambda _, m: bool(m.chat and m.chat.type in {"group", "supergroup"}))
group = create("Group", lambda _, m: bool(m.chat and m.chat.type in {"group", "supergroup"}))
"""Filter messages sent in group or supergroup chats."""
channel = build("Channel", lambda _, m: bool(m.chat and m.chat.type == "channel"))
channel = create("Channel", lambda _, m: bool(m.chat and m.chat.type == "channel"))
"""Filter messages sent in channels."""
new_chat_members = build("NewChatMembers", lambda _, m: bool(m.new_chat_members))
new_chat_members = create("NewChatMembers", lambda _, m: bool(m.new_chat_members))
"""Filter service messages for new chat members."""
left_chat_member = build("LeftChatMember", lambda _, m: bool(m.left_chat_member))
left_chat_member = create("LeftChatMember", lambda _, m: bool(m.left_chat_member))
"""Filter service messages for members that left the chat."""
new_chat_title = build("NewChatTitle", lambda _, m: bool(m.new_chat_title))
new_chat_title = create("NewChatTitle", lambda _, m: bool(m.new_chat_title))
"""Filter service messages for new chat titles."""
new_chat_photo = build("NewChatPhoto", lambda _, m: bool(m.new_chat_photo))
new_chat_photo = create("NewChatPhoto", lambda _, m: bool(m.new_chat_photo))
"""Filter service messages for new chat photos."""
delete_chat_photo = build("DeleteChatPhoto", lambda _, m: bool(m.delete_chat_photo))
delete_chat_photo = create("DeleteChatPhoto", lambda _, m: bool(m.delete_chat_photo))
"""Filter service messages for deleted photos."""
group_chat_created = build("GroupChatCreated", lambda _, m: bool(m.group_chat_created))
group_chat_created = create("GroupChatCreated", lambda _, m: bool(m.group_chat_created))
"""Filter service messages for group chat creations."""
supergroup_chat_created = build("SupergroupChatCreated", lambda _, m: bool(m.supergroup_chat_created))
supergroup_chat_created = create("SupergroupChatCreated", lambda _, m: bool(m.supergroup_chat_created))
"""Filter service messages for supergroup chat creations."""
channel_chat_created = build("ChannelChatCreated", lambda _, m: bool(m.channel_chat_created))
channel_chat_created = create("ChannelChatCreated", lambda _, m: bool(m.channel_chat_created))
"""Filter service messages for channel chat creations."""
migrate_to_chat_id = build("MigrateToChatId", lambda _, m: bool(m.migrate_to_chat_id))
migrate_to_chat_id = create("MigrateToChatId", lambda _, m: bool(m.migrate_to_chat_id))
"""Filter service messages that contain migrate_to_chat_id."""
migrate_from_chat_id = build("MigrateFromChatId", lambda _, m: bool(m.migrate_from_chat_id))
migrate_from_chat_id = create("MigrateFromChatId", lambda _, m: bool(m.migrate_from_chat_id))
"""Filter service messages that contain migrate_from_chat_id."""
pinned_message = build("PinnedMessage", lambda _, m: bool(m.pinned_message))
pinned_message = create("PinnedMessage", lambda _, m: bool(m.pinned_message))
"""Filter service messages for pinned messages."""
reply_keyboard = build("ReplyKeyboard", lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup))
reply_keyboard = create("ReplyKeyboard", lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup))
"""Filter messages containing reply keyboard markups"""
inline_keyboard = build("InlineKeyboard", lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup))
inline_keyboard = create("InlineKeyboard", lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup))
"""Filter messages containing inline keyboard markups"""
mentioned = create("Mentioned", lambda _, m: bool(m.mentioned))
"""Filter messages containing mentions"""
via_bot = create("ViaBot", lambda _, m: bool(m.via_bot))
"""Filter messages sent via inline bots"""
service = create("Service", lambda _, m: bool(m.service))
"""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"""
media = create("Media", lambda _, m: bool(m.media))
"""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"""
@staticmethod
def command(command: str or list,
prefix: str = "/",
prefix: str or list = "/",
separator: str = " ",
case_sensitive: bool = False):
"""Filter commands, i.e.: text messages starting with "/" or any other custom prefix.
@ -152,9 +221,10 @@ class Filters:
a command arrives, the command itself and its arguments will be stored in the *command*
field of the :class:`Message <pyrogram.Message>`.
prefix (``str``, *optional*):
The command prefix. Defaults to "/" (slash).
Examples: /start, .help, !settings.
prefix (``str`` | ``list``, *optional*):
A prefix or a list of prefixes as string the filter should look for.
Defaults to "/" (slash). Examples: ".", "!", ["/", "!", "."].
Can be None or "" (empty string) to allow commands with no prefix at all.
separator (``str``, *optional*):
The command arguments separator. Defaults to " " (white space).
@ -166,15 +236,18 @@ class Filters:
"""
def f(_, m):
if m.text and m.text.startswith(_.p):
if m.text:
for i in _.p:
if m.text.startswith(i):
t = m.text.split(_.s)
c, a = t[0][len(_.p):], t[1:]
c, a = t[0][len(i):], t[1:]
c = c if _.cs else c.lower()
m.command = ([c] + a) if c in _.c else None
break
return bool(m.command)
return build(
return create(
"Command",
f,
c={command if case_sensitive
@ -183,7 +256,7 @@ class Filters:
else {c if case_sensitive
else c.lower()
for c in command},
p=prefix,
p=set(prefix) if prefix else {""},
s=separator,
cs=case_sensitive
)
@ -206,82 +279,71 @@ class Filters:
m.matches = [i for i in _.p.finditer(m.text or "")]
return bool(m.matches)
return build("Regex", f, p=re.compile(pattern, flags))
return create("Regex", f, p=re.compile(pattern, flags))
@staticmethod
def user(user: int or str or list):
"""Filter messages coming from specific users.
# noinspection PyPep8Naming
class user(Filter, set):
"""Filter messages coming from one or more users.
You can use `set bound methods <https://docs.python.org/3/library/stdtypes.html#set>`_ to manipulate the
users container.
Args:
user (``int`` | ``str`` | ``list``):
The user or list of user IDs (int) or usernames (str) the filter should look for.
users (``int`` | ``str`` | ``list``):
Pass one or more user ids/usernames to filter users.
For you yourself, "me" or "self" can be used as well.
Defaults to None (no users).
"""
return build(
"User",
lambda _, m: bool(m.from_user
and (m.from_user.id in _.u
or (m.from_user.username
and m.from_user.username.lower() in _.u))),
u=(
{user.lower().strip("@") if type(user) is str else user}
if not isinstance(user, list)
else {i.lower().strip("@") if type(i) is str else i for i in user}
)
def __init__(self, users: int or str or list = None):
users = [] if users is None else users if type(users) is list else [users]
super().__init__(
{"me" if i in ["me", "self"] else i.lower().strip("@") if type(i) is str else i for i in users}
if type(users) is list else
{"me" if users in ["me", "self"] else users.lower().strip("@") if type(users) is str else users}
)
@staticmethod
def chat(chat: int or str or list):
"""Filter messages coming from specific chats.
def __call__(self, message):
return bool(
message.from_user
and (message.from_user.id in self
or (message.from_user.username
and message.from_user.username.lower() in self)
or ("me" in self
and message.from_user.is_self))
)
# noinspection PyPep8Naming
class chat(Filter, set):
"""Filter messages coming from one or more chats.
You can use `set bound methods <https://docs.python.org/3/library/stdtypes.html#set>`_ to manipulate the
chats container.
Args:
chat (``int`` | ``str`` | ``list``):
The chat or list of chat IDs (int) or usernames (str) the filter should look for.
chats (``int`` | ``str`` | ``list``):
Pass one or more chat ids/usernames to filter chats.
For your personal cloud (Saved Messages) you can simply use "me" or "self".
Defaults to None (no chats).
"""
return build(
"Chat",
lambda _, m: bool(m.chat
and (m.chat.id in _.c
or (m.chat.username
and m.chat.username.lower() in _.c))),
c=(
{chat.lower().strip("@") if type(chat) is str else chat}
if not isinstance(chat, list)
else {i.lower().strip("@") if type(i) is str else i for i in chat}
)
def __init__(self, chats: int or str or list = None):
chats = [] if chats is None else chats if type(chats) is list else [chats]
super().__init__(
{"me" if i in ["me", "self"] else i.lower().strip("@") if type(i) is str else i for i in chats}
if type(chats) is list else
{"me" if chats in ["me", "self"] else chats.lower().strip("@") if type(chats) is str else chats}
)
service = build(
"Service",
lambda _, m: bool(
Filters.new_chat_members(m)
or Filters.left_chat_member(m)
or Filters.new_chat_title(m)
or Filters.new_chat_photo(m)
or Filters.delete_chat_photo(m)
or Filters.group_chat_created(m)
or Filters.supergroup_chat_created(m)
or Filters.channel_chat_created(m)
or Filters.migrate_to_chat_id(m)
or Filters.migrate_from_chat_id(m)
or Filters.pinned_message(m)
def __call__(self, message):
return bool(
message.chat
and (message.chat.id in self
or (message.chat.username
and message.chat.username.lower() in self)
or ("me" in self and message.from_user
and message.from_user.is_self
and not message.outgoing))
)
)
"""Filter all service messages."""
media = build(
"Media",
lambda _, m: bool(
Filters.audio(m)
or Filters.document(m)
or Filters.photo(m)
or Filters.sticker(m)
or Filters.video(m)
or Filters.gif(m)
or Filters.voice(m)
or Filters.video_note(m)
or Filters.contact(m)
or Filters.location(m)
or Filters.venue(m)
)
)
"""Filter all media messages."""
dan = create("Dan", lambda _, m: bool(m.from_user and m.from_user.id == 23122162))

View File

@ -17,7 +17,8 @@
# along with Pyrogram. If not, see <http://www.gnu.org/licenses/>.
from .callback_query_handler import CallbackQueryHandler
from .deleted_messages_handler import DeletedMessagesHandler
from .disconnect_handler import DisconnectHandler
from .message_handler import MessageHandler
from .deleted_messages_handler import DeletedMessagesHandler
from .raw_update_handler import RawUpdateHandler
from .user_status_handler import UserStatusHandler

View File

@ -49,6 +49,6 @@ class CallbackQueryHandler(Handler):
def check(self, callback_query):
return (
self.filters(callback_query)
if self.filters
if callable(self.filters)
else True
)

View File

@ -50,6 +50,6 @@ class DeletedMessagesHandler(Handler):
def check(self, messages):
return (
self.filters(messages.messages[0])
if self.filters
if callable(self.filters)
else True
)

View File

@ -50,6 +50,6 @@ class MessageHandler(Handler):
def check(self, message):
return (
self.filters(message)
if self.filters
if callable(self.filters)
else True
)

View File

@ -0,0 +1,54 @@
# Pyrogram - Telegram MTProto API Client Library for Python
# Copyright (C) 2017-2018 Dan Tès <https://github.com/delivrance>
#
# 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 <http://www.gnu.org/licenses/>.
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:`add_handler() <pyrogram.Client.add_handler>`
For a nicer way to register this handler, have a look at the
:meth:`on_user_status() <pyrogram.Client.on_user_status>` decorator.
Args:
callback (``callable``):
Pass a function that will be called when a new UserStatus update arrives. It takes *(client, user_status)*
as positional arguments (look at the section below for a detailed description).
filters (:obj:`Filters <pyrogram.Filters>`):
Pass one or more filters to allow only a subset of messages to be passed
in your callback function.
Other parameters:
client (:obj:`Client <pyrogram.Client>`):
The Client itself, useful when you want to call other API methods inside the user status handler.
user_status (:obj:`UserStatus <pyrogram.UserStatus>`):
The received UserStatus update.
"""
def __init__(self, callback: callable, filters=None):
super().__init__(callback, filters)
def check(self, user_status):
return (
self.filters(user_status)
if callable(self.filters)
else True
)

View File

@ -20,10 +20,10 @@ from .bots import Bots
from .chats import Chats
from .contacts import Contacts
from .decorators import Decorators
from .download_media import DownloadMedia
from .messages import Messages
from .password import Password
from .users import Users
from .utilities import Utilities
class Methods(
@ -32,7 +32,7 @@ class Methods(
Password,
Chats,
Users,
DownloadMedia,
Utilities,
Messages,
Decorators
):

View File

@ -50,6 +50,12 @@ class AnswerCallbackQuery(BaseClient):
cache_time (``int``):
The maximum amount of time in seconds that the result of the callback query may be cached client-side.
Telegram apps will support caching starting in version 3.14. Defaults to 0.
Returns:
True, on success.
Raises:
:class:`Error <pyrogram.Error>` in case of a Telegram RPC error.
"""
return self.send(
functions.messages.SetBotCallbackAnswer(

Some files were not shown because too many files have changed in this diff Show More