This commit is contained in:
Daniil Gentili 2019-08-29 18:34:33 +02:00
commit d2010cc259
33 changed files with 1110 additions and 585 deletions

273
README.md
View File

@ -175,279 +175,6 @@ Tip: if you receive an error (or nothing), [send us](https://t.me/pwrtelegramgro
* [Upload or download files up to 1.5 GB](https://docs.madelineproto.xyz/docs/FILES.html) * [Upload or download files up to 1.5 GB](https://docs.madelineproto.xyz/docs/FILES.html)
* [Make a phone call and play a song](https://docs.madelineproto.xyz/docs/CALLS.html) * [Make a phone call and play a song](https://docs.madelineproto.xyz/docs/CALLS.html)
* [Create a secret chat bot](https://docs.madelineproto.xyz/docs/SECRET_CHATS.html) * [Create a secret chat bot](https://docs.madelineproto.xyz/docs/SECRET_CHATS.html)
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_acceptUrlAuth.html" name="messages_acceptUrlAuth">Accept URL authorization: messages.acceptUrlAuth</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_acceptContact.html" name="contacts_acceptContact">Accept contact: contacts.acceptContact</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_acceptAuthorization.html" name="account_acceptAuthorization">Accept telegram passport authorization: account.acceptAuthorization</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_acceptTermsOfService.html" name="help_acceptTermsOfService">Accept telegram's TOS: help.acceptTermsOfService</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_installStickerSet.html" name="messages_installStickerSet">Add a sticker set: messages.installStickerSet</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_faveSticker.html" name="messages_faveSticker">Add a sticker to favorites: messages.faveSticker</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_saveRecentSticker.html" name="messages_saveRecentSticker">Add a sticker to recent stickers: messages.saveRecentSticker</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_addChatUser.html" name="messages_addChatUser">Add a user to a normal chat (use channels->inviteToChannel for supergroups): messages.addChatUser</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_addContact.html" name="contacts_addContact">Add contact: contacts.addContact</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_importContacts.html" name="contacts_importContacts">Add phone number as contact: contacts.importContacts</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/stickers_addStickerToSet.html" name="stickers_addStickerToSet">Add sticker to stickerset: stickers.addStickerToSet</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_inviteToChannel.html" name="channels_inviteToChannel">Add users to channel/supergroup: channels.inviteToChannel</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_block.html" name="contacts_block">Block a user: contacts.block</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_getAuthorizationForm.html" name="account_getAuthorizationForm">Bots only: get telegram passport authorization form: account.getAuthorizationForm</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/payments_sendPaymentForm.html" name="payments_sendPaymentForm">Bots only: send payment form: payments.sendPaymentForm</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_setBotPrecheckoutResults.html" name="messages_setBotPrecheckoutResults">Bots only: set precheckout results: messages.setBotPrecheckoutResults</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_setBotShippingResults.html" name="messages_setBotShippingResults">Bots only: set shipping results: messages.setBotShippingResults</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_setBotCallbackAnswer.html" name="messages_setBotCallbackAnswer">Bots only: set the callback answer (after a button was clicked): messages.setBotCallbackAnswer</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_setInlineBotResults.html" name="messages_setInlineBotResults">Bots only: set the results of an inline query: messages.setInlineBotResults</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getInlineBotResults.html" name="messages_getInlineBotResults">Call inline bot: messages.getInlineBotResults</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_cancelPasswordEmail.html" name="account_cancelPasswordEmail">Cancel password recovery email: account.cancelPasswordEmail</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_updateNotifySettings.html" name="account_updateNotifySettings">Change notification settings: account.updateNotifySettings</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/stickers_changeStickerPosition.html" name="stickers_changeStickerPosition">Change sticker position in photo: stickers.changeStickerPosition</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_changePhone.html" name="account_changePhone">Change the phone number associated to this account: account.changePhone</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_sendChangePhoneCode.html" name="account_sendChangePhoneCode">Change the phone number: account.sendChangePhoneCode</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/photos_updateProfilePhoto.html" name="photos_updateProfilePhoto">Change the profile photo: photos.updateProfilePhoto</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_setTyping.html" name="messages_setTyping">Change typing status: messages.setTyping</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getMessageEditData.html" name="messages_getMessageEditData">Check if about to edit a message or a media caption: messages.getMessageEditData</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_checkChatInvite.html" name="messages_checkChatInvite">Check if an invitation link is valid: messages.checkChatInvite</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_checkUsername.html" name="account_checkUsername">Check if this username is available: account.checkUsername</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_checkUsername.html" name="channels_checkUsername">Check if this username is free and can be assigned to a channel/supergroup: channels.checkUsername</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_clearAllDrafts.html" name="messages_clearAllDrafts">Clear all drafts: messages.clearAllDrafts</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_clearRecentStickers.html" name="messages_clearRecentStickers">Clear all recent stickers: messages.clearRecentStickers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/payments_clearSavedInfo.html" name="payments_clearSavedInfo">Clear saved payments info: payments.clearSavedInfo</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_confirmPasswordEmail.html" name="account_confirmPasswordEmail">Confirm password recovery using email: account.confirmPasswordEmail</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_confirmPhone.html" name="account_confirmPhone">Confirm this phone number is associated to this account, obtain phone_code_hash from sendConfirmPhoneCode: account.confirmPhone</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_getContactSignUpNotification.html" name="account_getContactSignUpNotification">Contact signup notification setting value: account.getContactSignUpNotification</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_migrateChat.html" name="messages_migrateChat">Convert chat to supergroup: messages.migrateChat</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_createChat.html" name="messages_createChat">Create a chat (not supergroup): messages.createChat</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_createChannel.html" name="channels_createChannel">Create channel/supergroup: channels.createChannel</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/stickers_createStickerSet.html" name="stickers_createStickerSet">Create stickerset: stickers.createStickerSet</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_resetAuthorization.html" name="account_resetAuthorization">Delete a certain session: account.resetAuthorization</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_resetWebAuthorization.html" name="account_resetWebAuthorization">Delete a certain telegram web login authorization: account.resetWebAuthorization</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_deleteChannel.html" name="channels_deleteChannel">Delete a channel/supergroup: channels.deleteChannel</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_deleteChatUser.html" name="messages_deleteChatUser">Delete a user from a chat (not supergroup): messages.deleteChatUser</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/auth_resetAuthorizations.html" name="auth_resetAuthorizations">Delete all logged-in sessions.: auth.resetAuthorizations</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_deleteUserHistory.html" name="channels_deleteUserHistory">Delete all messages of a user in a channel/supergroup: channels.deleteUserHistory</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/auth_dropTempAuthKeys.html" name="auth_dropTempAuthKeys">Delete all temporary authorization keys except the ones provided: auth.dropTempAuthKeys</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_deleteMessages.html" name="channels_deleteMessages">Delete channel/supergroup messages: channels.deleteMessages</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_deleteHistory.html" name="messages_deleteHistory">Delete chat history: messages.deleteHistory</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_deleteByPhones.html" name="contacts_deleteByPhones">Delete contacts by phones: contacts.deleteByPhones</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/folders_deleteFolder.html" name="folders_deleteFolder">Delete folder: folders.deleteFolder</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_deleteMessages.html" name="messages_deleteMessages">Delete messages: messages.deleteMessages</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_deleteContacts.html" name="contacts_deleteContacts">Delete multiple contacts: contacts.deleteContacts</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/photos_deletePhotos.html" name="photos_deletePhotos">Delete profile photos: photos.deletePhotos</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_deleteSecureValue.html" name="account_deleteSecureValue">Delete secure telegram passport value: account.deleteSecureValue</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_deleteHistory.html" name="channels_deleteHistory">Delete the history of a supergroup/channel: channels.deleteHistory</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_deleteAccount.html" name="account_deleteAccount">Delete this account: account.deleteAccount</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_updateDeviceLocked.html" name="account_updateDeviceLocked">Disable all notifications for a certain period: account.updateDeviceLocked</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/upload_getWebFile.html" name="upload_getWebFile">Download a file through telegram: upload.getWebFile</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_editMessage.html" name="messages_editMessage">Edit a message: messages.editMessage</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_editInlineBotMessage.html" name="messages_editInlineBotMessage">Edit a sent inline message: messages.editInlineBotMessage</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_editAdmin.html" name="channels_editAdmin">Edit admin permissions of a user in a channel/supergroup: channels.editAdmin</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_editChatAdmin.html" name="messages_editChatAdmin">Edit admin permissions: messages.editChatAdmin</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_editChatAbout.html" name="messages_editChatAbout">Edit chat info: messages.editChatAbout</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_editCreator.html" name="channels_editCreator">Edit creator of channel: channels.editCreator</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_editChatDefaultBannedRights.html" name="messages_editChatDefaultBannedRights">Edit default rights of chat: messages.editChatDefaultBannedRights</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/folders_editPeerFolders.html" name="folders_editPeerFolders">Edit folder: folders.editPeerFolders</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_editLocation.html" name="channels_editLocation">Edit location (geochats): channels.editLocation</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_editChatPhoto.html" name="messages_editChatPhoto">Edit the photo of a normal chat (not supergroup): messages.editChatPhoto</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_editPhoto.html" name="channels_editPhoto">Edit the photo of a supergroup/channel: channels.editPhoto</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_editChatTitle.html" name="messages_editChatTitle">Edit the title of a normal chat (not supergroup): messages.editChatTitle</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_editTitle.html" name="channels_editTitle">Edit the title of a supergroup/channel: channels.editTitle</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_editUserInfo.html" name="help_editUserInfo">Edit user info: help.editUserInfo</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_togglePreHistoryHidden.html" name="channels_togglePreHistoryHidden">Enable or disable hidden history for new channel/supergroup users: channels.togglePreHistoryHidden</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_exportChatInvite.html" name="messages_exportChatInvite">Export chat invite : messages.exportChatInvite</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_searchStickerSets.html" name="messages_searchStickerSets">Find a sticker set: messages.searchStickerSets</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_finishTakeoutSession.html" name="account_finishTakeoutSession">Finish account exporting session: account.finishTakeoutSession</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_forwardMessages.html" name="messages_forwardMessages">Forward messages: messages.forwardMessages</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_getCdnConfig.html" name="help_getCdnConfig">Get CDN configuration: help.getCdnConfig</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getStickerSet.html" name="messages_getStickerSet">Get a stickerset: messages.getStickerSet</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_getAccountTTL.html" name="account_getAccountTTL">Get account TTL: account.getAccountTTL</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_getAdminLog.html" name="channels_getAdminLog">Get admin log of a channel/supergroup: channels.getAdminLog</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getArchivedStickers.html" name="messages_getArchivedStickers">Get all archived stickers: messages.getArchivedStickers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_getLeftChannels.html" name="channels_getLeftChannels">Get all channels you left: channels.getLeftChannels</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getAllChats.html" name="messages_getAllChats">Get all chats (not supergroups or channels): messages.getAllChats</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_getContacts.html" name="contacts_getContacts">Get all contacts: contacts.getContacts</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_getAuthorizations.html" name="account_getAuthorizations">Get all logged-in authorizations: account.getAuthorizations</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getAllDrafts.html" name="messages_getAllDrafts">Get all message drafts: messages.getAllDrafts</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_getAllSecureValues.html" name="account_getAllSecureValues">Get all secure telegram passport values: account.getAllSecureValues</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getAllStickers.html" name="messages_getAllStickers">Get all stickerpacks: messages.getAllStickers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_getAdminedPublicChannels.html" name="channels_getAdminedPublicChannels">Get all supergroups/channels where you're admin: channels.getAdminedPublicChannels</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getMessagesViews.html" name="messages_getMessagesViews">Get and increase message views: messages.getMessagesViews</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_getAppConfig.html" name="help_getAppConfig">Get app config: help.getAppConfig</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_getAutoDownloadSettings.html" name="account_getAutoDownloadSettings">Get autodownload settings: account.getAutoDownloadSettings</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/langpack_getLanguages.html" name="langpack_getLanguages">Get available languages: langpack.getLanguages</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_getBlocked.html" name="contacts_getBlocked">Get blocked users: contacts.getBlocked</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/phone_getCallConfig.html" name="phone_getCallConfig">Get call configuration: phone.getCallConfig</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_getMessages.html" name="channels_getMessages">Get channel/supergroup messages: channels.getMessages</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_getParticipants.html" name="channels_getParticipants">Get channel/supergroup participants (you should use `$MadelineProto->get_pwr_chat($id)` instead): channels.getParticipants</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getCommonChats.html" name="messages_getCommonChats">Get chats in common with a user: messages.getCommonChats</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_getContactIDs.html" name="contacts_getContactIDs">Get contacts by IDs: contacts.getContactIDs</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_getDeepLinkInfo.html" name="help_getDeepLinkInfo">Get deep link info: help.getDeepLinkInfo</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getPeerDialogs.html" name="messages_getPeerDialogs">Get dialog info of peers: messages.getPeerDialogs</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getDialogUnreadMarks.html" name="messages_getDialogUnreadMarks">Get dialogs marked as unread manually: messages.getDialogUnreadMarks</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getDocumentByHash.html" name="messages_getDocumentByHash">Get document by SHA256 hash: messages.getDocumentByHash</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getEmojiURL.html" name="messages_getEmojiURL">Get emoji URL: messages.getEmojiURL</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getEmojiKeywordsDifference.html" name="messages_getEmojiKeywordsDifference">Get emoji keyword difference: messages.getEmojiKeywordsDifference</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getEmojiKeywordsLanguages.html" name="messages_getEmojiKeywordsLanguages">Get emoji keyword languages: messages.getEmojiKeywordsLanguages</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getEmojiKeywords.html" name="messages_getEmojiKeywords">Get emoji keywords: messages.getEmojiKeywords</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getFavedStickers.html" name="messages_getFavedStickers">Get favorite stickers: messages.getFavedStickers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getFeaturedStickers.html" name="messages_getFeaturedStickers">Get featured stickers: messages.getFeaturedStickers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_getGroupsForDiscussion.html" name="channels_getGroupsForDiscussion">Get groups for discussion: channels.getGroupsForDiscussion</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getInlineGameHighScores.html" name="messages_getInlineGameHighScores">Get high scores of a game sent in an inline message: messages.getInlineGameHighScores</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getGameHighScores.html" name="messages_getGameHighScores">Get high scores of a game: messages.getGameHighScores</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_getParticipant.html" name="channels_getParticipant">Get info about a certain channel/supergroup participant: channels.getParticipant</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_getAppUpdate.html" name="help_getAppUpdate">Get info about app updates: help.getAppUpdate</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getChats.html" name="messages_getChats">Get info about chats: messages.getChats</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_getChannels.html" name="channels_getChannels">Get info about multiple channels/supergroups: channels.getChannels</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/users_getUsers.html" name="users_getUsers">Get info about users: users.getUsers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_getSupport.html" name="help_getSupport">Get info of support user: help.getSupport</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_getProxyData.html" name="help_getProxyData">Get information about the current proxy: help.getProxyData</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_getInviteText.html" name="help_getInviteText">Get invitation text: help.getInviteText</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/langpack_getStrings.html" name="langpack_getStrings">Get language pack strings: langpack.getStrings</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/langpack_getDifference.html" name="langpack_getDifference">Get language pack updates: langpack.getDifference</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/langpack_getLangPack.html" name="langpack_getLangPack">Get language pack: langpack.getLangPack</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/langpack_getLanguage.html" name="langpack_getLanguage">Get language: langpack.getLanguage</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getMaskStickers.html" name="messages_getMaskStickers">Get masks: messages.getMaskStickers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getSplitRanges.html" name="messages_getSplitRanges">Get message ranges to fetch: messages.getSplitRanges</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getMessages.html" name="messages_getMessages">Get messages: messages.getMessages</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_getTopPeers.html" name="contacts_getTopPeers">Get most used chats: contacts.getTopPeers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_getNearestDc.html" name="help_getNearestDc">Get nearest datacenter: help.getNearestDc</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_getNotifyExceptions.html" name="account_getNotifyExceptions">Get notification exceptions: account.getNotifyExceptions</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_getNotifySettings.html" name="account_getNotifySettings">Get notification settings: account.getNotifySettings</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_getStatuses.html" name="contacts_getStatuses">Get online status of all users: contacts.getStatuses</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getOnlines.html" name="messages_getOnlines">Get online users: messages.getOnlines</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_getPassportConfig.html" name="help_getPassportConfig">Get passport config: help.getPassportConfig</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/payments_getPaymentForm.html" name="payments_getPaymentForm">Get payment form: payments.getPaymentForm</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/payments_getPaymentReceipt.html" name="payments_getPaymentReceipt">Get payment receipt: payments.getPaymentReceipt</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_getLocated.html" name="contacts_getLocated">Get people nearby (geochats): contacts.getLocated</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getPinnedDialogs.html" name="messages_getPinnedDialogs">Get pinned dialogs: messages.getPinnedDialogs</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getPollResults.html" name="messages_getPollResults">Get poll results: messages.getPollResults</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getHistory.html" name="messages_getHistory">Get previous messages of a group: messages.getHistory</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_getPrivacy.html" name="account_getPrivacy">Get privacy settings: account.getPrivacy</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getRecentLocations.html" name="messages_getRecentLocations">Get recent locations: messages.getRecentLocations</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getRecentStickers.html" name="messages_getRecentStickers">Get recent stickers: messages.getRecentStickers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_getRecentMeUrls.html" name="help_getRecentMeUrls">Get recent t.me URLs: help.getRecentMeUrls</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_getSaved.html" name="contacts_getSaved">Get saved contacts: contacts.getSaved</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getSavedGifs.html" name="messages_getSavedGifs">Get saved gifs: messages.getSavedGifs</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/payments_getSavedInfo.html" name="payments_getSavedInfo">Get saved payments info: payments.getSavedInfo</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getSearchCounters.html" name="messages_getSearchCounters">Get search counter: messages.getSearchCounters</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_getSecureValue.html" name="account_getSecureValue">Get secure value for telegram passport: account.getSecureValue</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_getConfig.html" name="help_getConfig">Get server configuration: help.getConfig</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getStatsURL.html" name="messages_getStatsURL">Get stats URL: messages.getStatsURL</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getAttachedStickers.html" name="messages_getAttachedStickers">Get stickers attachable to images: messages.getAttachedStickers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getStickers.html" name="messages_getStickers">Get stickers: messages.getStickers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_getSupportName.html" name="help_getSupportName">Get support name: help.getSupportName</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_getWebAuthorizations.html" name="account_getWebAuthorizations">Get telegram web login authorizations: account.getWebAuthorizations</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_getTmpPassword.html" name="account_getTmpPassword">Get temporary password for buying products through bots: account.getTmpPassword</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getBotCallbackAnswer.html" name="messages_getBotCallbackAnswer">Get the callback answer of a bot (after clicking a button): messages.getBotCallbackAnswer</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_getAppChangelog.html" name="help_getAppChangelog">Get the changelog of this app: help.getAppChangelog</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_getPassword.html" name="account_getPassword">Get the current password: account.getPassword</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_exportMessageLink.html" name="channels_exportMessageLink">Get the link of a message in a channel: channels.exportMessageLink</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/photos_getUserPhotos.html" name="photos_getUserPhotos">Get the profile photos of a user: photos.getUserPhotos</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getPeerSettings.html" name="messages_getPeerSettings">Get the settings of apeer: messages.getPeerSettings</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getUnreadMentions.html" name="messages_getUnreadMentions">Get unread mentions: messages.getUnreadMentions</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_getTermsOfServiceUpdate.html" name="help_getTermsOfServiceUpdate">Get updated TOS: help.getTermsOfServiceUpdate</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_getUserInfo.html" name="help_getUserInfo">Get user info: help.getUserInfo</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_getWallPaper.html" name="account_getWallPaper">Get wallpaper info: account.getWallPaper</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getWebPage.html" name="messages_getWebPage">Get webpage preview: messages.getWebPage</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getWebPagePreview.html" name="messages_getWebPagePreview">Get webpage preview: messages.getWebPagePreview</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_getDialogs.html" name="messages_getDialogs">Gets list of chats: you should use $MadelineProto->get_dialogs() instead: https://docs.madelineproto.xyz/docs/DIALOGS.html: messages.getDialogs</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_searchGlobal.html" name="messages_searchGlobal">Global message search: messages.searchGlobal</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_hidePeerSettingsBar.html" name="messages_hidePeerSettingsBar">Hide peer settings bar: messages.hidePeerSettingsBar</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_importChatInvite.html" name="messages_importChatInvite">Import chat invite: messages.importChatInvite</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/initConnection.html" name="initConnection">Initializes connection and save information on the user's device and application.: initConnection</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_installWallPaper.html" name="account_installWallPaper">Install wallpaper: account.installWallPaper</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/auth_cancelCode.html" name="auth_cancelCode">Invalidate sent phone code: auth.cancelCode</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/invokeWithTakeout.html" name="invokeWithTakeout">Invoke method from takeout session: invokeWithTakeout</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/invokeWithLayer.html" name="invokeWithLayer">Invoke this method with layer X: invokeWithLayer</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/invokeWithMessagesRange.html" name="invokeWithMessagesRange">Invoke with messages range: invokeWithMessagesRange</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/invokeWithoutUpdates.html" name="invokeWithoutUpdates">Invoke with method without returning updates in the socket: invokeWithoutUpdates</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/invokeAfterMsg.html" name="invokeAfterMsg">Invokes a query after successfull completion of one of the previous queries.: invokeAfterMsg</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_joinChannel.html" name="channels_joinChannel">Join a channel/supergroup: channels.joinChannel</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_editBanned.html" name="channels_editBanned">Kick or ban a user from a channel/supergroup: channels.editBanned</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_leaveChannel.html" name="channels_leaveChannel">Leave a channel/supergroup: channels.leaveChannel</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_saveAppLog.html" name="help_saveAppLog">Log data for developer of this app: help.saveAppLog</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_readHistory.html" name="channels_readHistory">Mark channel/supergroup history as read: channels.readHistory</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_readMessageContents.html" name="channels_readMessageContents">Mark channel/supergroup messages as read: channels.readMessageContents</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_markDialogUnread.html" name="messages_markDialogUnread">Mark dialog as unread : messages.markDialogUnread</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_readMentions.html" name="messages_readMentions">Mark mentions as read: messages.readMentions</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_readMessageContents.html" name="messages_readMessageContents">Mark message as read: messages.readMessageContents</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_readEncryptedHistory.html" name="messages_readEncryptedHistory">Mark messages as read in secret chats: messages.readEncryptedHistory</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_readHistory.html" name="messages_readHistory">Mark messages as read: messages.readHistory</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_receivedMessages.html" name="messages_receivedMessages">Mark messages as read: messages.receivedMessages</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_readFeaturedStickers.html" name="messages_readFeaturedStickers">Mark new featured stickers as read: messages.readFeaturedStickers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/phone_receivedCall.html" name="phone_receivedCall">Notify server that you received a call (server will refuse all incoming calls until the current call is over): phone.receivedCall</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_toggleDialogPin.html" name="messages_toggleDialogPin">Pin or unpin dialog: messages.toggleDialogPin</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_registerDevice.html" name="account_registerDevice">Register device for push notifications: account.registerDevice</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_uninstallStickerSet.html" name="messages_uninstallStickerSet">Remove a sticker set: messages.uninstallStickerSet</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/stickers_removeStickerFromSet.html" name="stickers_removeStickerFromSet">Remove sticker from stickerset: stickers.removeStickerFromSet</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_reorderPinnedDialogs.html" name="messages_reorderPinnedDialogs">Reorder pinned dialogs: messages.reorderPinnedDialogs</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_reorderStickerSets.html" name="messages_reorderStickerSets">Reorder sticker sets: messages.reorderStickerSets</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_reportSpam.html" name="channels_reportSpam">Report a message in a supergroup/channel for spam: channels.reportSpam</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_report.html" name="messages_report">Report a message: messages.report</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_reportSpam.html" name="messages_reportSpam">Report a peer for spam: messages.reportSpam</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_reportEncryptedSpam.html" name="messages_reportEncryptedSpam">Report for spam a secret chat: messages.reportEncryptedSpam</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_reportPeer.html" name="account_reportPeer">Report for spam: account.reportPeer</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_requestUrlAuth.html" name="messages_requestUrlAuth">Request URL authorization: messages.requestUrlAuth</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_resendPasswordEmail.html" name="account_resendPasswordEmail">Resend password recovery email: account.resendPasswordEmail</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/auth_resendCode.html" name="auth_resendCode">Resend the SMS verification code: auth.resendCode</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_resetNotifySettings.html" name="account_resetNotifySettings">Reset all notification settings: account.resetNotifySettings</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_resetWebAuthorizations.html" name="account_resetWebAuthorizations">Reset all telegram web login authorizations: account.resetWebAuthorizations</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_resetSaved.html" name="contacts_resetSaved">Reset saved contacts: contacts.resetSaved</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_resetTopPeerRating.html" name="contacts_resetTopPeerRating">Reset top peer rating for a certain category/peer: contacts.resetTopPeerRating</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_resetWallPapers.html" name="account_resetWallPapers">Reset wallpapers: account.resetWallPapers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/invokeAfterMsgs.html" name="invokeAfterMsgs">Result type returned by a current query.: invokeAfterMsgs</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_getWallPapers.html" name="account_getWallPapers">Returns a list of available wallpapers.: account.getWallPapers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_saveGif.html" name="messages_saveGif">Save a GIF: messages.saveGif</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_saveDraft.html" name="messages_saveDraft">Save a message draft: messages.saveDraft</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_saveAutoDownloadSettings.html" name="account_saveAutoDownloadSettings">Save autodownload settings: account.saveAutoDownloadSettings</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/phone_saveCallDebug.html" name="phone_saveCallDebug">Save call debugging info: phone.saveCallDebug</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_saveSecureValue.html" name="account_saveSecureValue">Save telegram passport secure value: account.saveSecureValue</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_saveWallPaper.html" name="account_saveWallPaper">Save wallpaper: account.saveWallPaper</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_search.html" name="contacts_search">Search contacts: contacts.search</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_searchGifs.html" name="messages_searchGifs">Search gifs: messages.searchGifs</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_search.html" name="messages_search">Search peers or messages: messages.search</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/bots_sendCustomRequest.html" name="bots_sendCustomRequest">Send a custom request to the bot API: bots.sendCustomRequest</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_sendEncryptedFile.html" name="messages_sendEncryptedFile">Send a file to a secret chat: messages.sendEncryptedFile</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_sendMedia.html" name="messages_sendMedia">Send a media: messages.sendMedia</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_sendMessage.html" name="messages_sendMessage">Send a message: messages.sendMessage</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_sendEncryptedService.html" name="messages_sendEncryptedService">Send a service message to a secret chat: messages.sendEncryptedService</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_sendMultiMedia.html" name="messages_sendMultiMedia">Send an album: messages.sendMultiMedia</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/auth_requestPasswordRecovery.html" name="auth_requestPasswordRecovery">Send an email to recover the 2FA password: auth.requestPasswordRecovery</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_sendConfirmPhoneCode.html" name="account_sendConfirmPhoneCode">Send confirmation phone code: account.sendConfirmPhoneCode</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_sendVerifyEmailCode.html" name="account_sendVerifyEmailCode">Send email verification code: account.sendVerifyEmailCode</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_sendInlineBotResult.html" name="messages_sendInlineBotResult">Send inline bot result obtained with messages.getInlineBotResults to the chat: messages.sendInlineBotResult</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_sendEncrypted.html" name="messages_sendEncrypted">Send message to secret chat: messages.sendEncrypted</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_sendVerifyPhoneCode.html" name="account_sendVerifyPhoneCode">Send phone verification code: account.sendVerifyPhoneCode</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_sendScreenshotNotification.html" name="messages_sendScreenshotNotification">Send screenshot notification: messages.sendScreenshotNotification</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_setEncryptedTyping.html" name="messages_setEncryptedTyping">Send typing notification to secret chat: messages.setEncryptedTyping</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_sendVote.html" name="messages_sendVote">Send vote: messages.sendVote</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/bots_answerWebhookJSONQuery.html" name="bots_answerWebhookJSONQuery">Send webhook request via bot API: bots.answerWebhookJSONQuery</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_setAccountTTL.html" name="account_setAccountTTL">Set account TTL: account.setAccountTTL</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_setContactSignUpNotification.html" name="account_setContactSignUpNotification">Set contact sign up notification: account.setContactSignUpNotification</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_setDiscussionGroup.html" name="channels_setDiscussionGroup">Set discussion group of channel: channels.setDiscussionGroup</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/phone_setCallRating.html" name="phone_setCallRating">Set phone call rating: phone.setCallRating</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_setPrivacy.html" name="account_setPrivacy">Set privacy settings: account.setPrivacy</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/users_setSecureValueErrors.html" name="users_setSecureValueErrors">Set secure value error for telegram passport: users.setSecureValueErrors</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_setInlineGameScore.html" name="messages_setInlineGameScore">Set the game score of an inline message: messages.setInlineGameScore</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_setGameScore.html" name="messages_setGameScore">Set the game score: messages.setGameScore</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_setStickers.html" name="channels_setStickers">Set the supergroup/channel stickerpack: channels.setStickers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/help_setBotUpdatesStatus.html" name="help_setBotUpdatesStatus">Set the update status of webhook: help.setBotUpdatesStatus</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_startBot.html" name="messages_startBot">Start a bot: messages.startBot</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_initTakeoutSession.html" name="account_initTakeoutSession">Start account exporting session: account.initTakeoutSession</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_unregisterDevice.html" name="account_unregisterDevice">Stop sending PUSH notifications to app: account.unregisterDevice</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_toggleSignatures.html" name="channels_toggleSignatures">Toggle channel signatures: channels.toggleSignatures</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_toggleSlowMode.html" name="channels_toggleSlowMode">Toggle slow mode: channels.toggleSlowMode</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_toggleTopPeers.html" name="contacts_toggleTopPeers">Toggle top peers: contacts.toggleTopPeers</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/contacts_unblock.html" name="contacts_unblock">Unblock a user: contacts.unblock</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_updateStatus.html" name="account_updateStatus">Update online status: account.updateStatus</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_updatePinnedMessage.html" name="messages_updatePinnedMessage">Update pinned message: messages.updatePinnedMessage</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_updateProfile.html" name="account_updateProfile">Update profile info: account.updateProfile</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/channels_updateUsername.html" name="channels_updateUsername">Update the username of a supergroup/channel: channels.updateUsername</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_updateUsername.html" name="account_updateUsername">Update this user's username: account.updateUsername</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_uploadMedia.html" name="messages_uploadMedia">Upload a file without sending it to anyone: messages.uploadMedia</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/messages_uploadEncryptedFile.html" name="messages_uploadEncryptedFile">Upload a secret chat file without sending it to anyone: messages.uploadEncryptedFile</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/photos_uploadProfilePhoto.html" name="photos_uploadProfilePhoto">Upload profile photo: photos.uploadProfilePhoto</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_uploadWallPaper.html" name="account_uploadWallPaper">Upload wallpaper: account.uploadWallPaper</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/auth_recoverPassword.html" name="auth_recoverPassword">Use the code that was emailed to you after running $MadelineProto->auth->requestPasswordRecovery to login to your account: auth.recoverPassword</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/payments_validateRequestedInfo.html" name="payments_validateRequestedInfo">Validate requested payment info: payments.validateRequestedInfo</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_verifyEmail.html" name="account_verifyEmail">Verify email address: account.verifyEmail</a>
* <a href="https://docs.madelineproto.xyz/API_docs/methods/account_verifyPhone.html" name="account_verifyPhone">Verify phone number: account.verifyPhone</a>
* [Peers](https://docs.madelineproto.xyz/docs/USING_METHODS.html#peers) * [Peers](https://docs.madelineproto.xyz/docs/USING_METHODS.html#peers)
* [Files](https://docs.madelineproto.xyz/docs/FILES.html) * [Files](https://docs.madelineproto.xyz/docs/FILES.html)
* [Secret chats](https://docs.madelineproto.xyz/docs/USING_METHODS.html#secret-chats) * [Secret chats](https://docs.madelineproto.xyz/docs/USING_METHODS.html#secret-chats)

2
docs

@ -1 +1 @@
Subproject commit 1c91f7808bc95c909b6ba8502b68b5d4fe9c9a41 Subproject commit 11f1a298b7d50a1ee6a71bd5bbd61f40fc288100

View File

@ -61,10 +61,10 @@ function ___install_madeline()
$release_branch = ''; $release_branch = '';
} }
$release_fallback_branch = ''; $release_fallback_branch = '';
if (isset($_SERVER['SERVER_ADMIN']) && strpos($_SERVER['SERVER_ADMIN'], '000webhost.io') && $custom_branch === null) { /*if (isset($_SERVER['SERVER_ADMIN']) && strpos($_SERVER['SERVER_ADMIN'], '000webhost.io') && $custom_branch === null) {
$release_branch = '-deprecated'; $release_branch = '-deprecated';
$release_fallback_branch = '-deprecated'; $release_fallback_branch = '-deprecated';
} }*/
if (PHP_MAJOR_VERSION <= 5) { if (PHP_MAJOR_VERSION <= 5) {
$release_branch = '5'.$release_branch; $release_branch = '5'.$release_branch;

View File

@ -20,6 +20,10 @@
namespace danog\MadelineProto; namespace danog\MadelineProto;
use Amp\Deferred; use Amp\Deferred;
use function Amp\File\put;
use function Amp\File\rename;
use function Amp\File\get;
use function Amp\File\exists;
class API extends APIFactory class API extends APIFactory
{ {
@ -62,21 +66,15 @@ class API extends APIFactory
$realpaths = Serialization::realpaths($params); $realpaths = Serialization::realpaths($params);
$this->session = $realpaths['file']; $this->session = $realpaths['file'];
if (file_exists($realpaths['file'])) { if (yield exists($realpaths['file'])) {
if (!file_exists($realpaths['lockfile'])) { Logger::log('Waiting for shared lock of serialization lockfile...');
touch($realpaths['lockfile']); $unlock = yield Tools::flock($realpaths['lockfile'], LOCK_SH);
clearstatcache(); Logger::log('Shared lock acquired, deserializing...');
}
$realpaths['lockfile'] = fopen($realpaths['lockfile'], 'r');
\danog\MadelineProto\Logger::log('Waiting for shared lock of serialization lockfile...');
flock($realpaths['lockfile'], LOCK_SH);
\danog\MadelineProto\Logger::log('Shared lock acquired, deserializing...');
try { try {
$tounserialize = file_get_contents($realpaths['file']); $tounserialize = yield get($realpaths['file']);
} finally { } finally {
flock($realpaths['lockfile'], LOCK_UN); $unlock();
fclose($realpaths['lockfile']);
} }
\danog\MadelineProto\Magic::class_exists(); \danog\MadelineProto\Magic::class_exists();
@ -142,7 +140,6 @@ class API extends APIFactory
//$pong = yield $this->ping(['ping_id' => 3], ['async' => true]); //$pong = yield $this->ping(['ping_id' => 3], ['async' => true]);
//\danog\MadelineProto\Logger::log('Pong: '.$pong['ping_id'], Logger::ULTRA_VERBOSE); //\danog\MadelineProto\Logger::log('Pong: '.$pong['ping_id'], Logger::ULTRA_VERBOSE);
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['madelineproto_ready'], Logger::NOTICE); \danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
return; return;
} }
} }
@ -158,7 +155,7 @@ class API extends APIFactory
$this->API = new MTProto($params); $this->API = new MTProto($params);
$this->APIFactory(); $this->APIFactory();
$deferred->resolve(); $deferred->resolve();
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['apifactory_start'], Logger::VERBOSE); Logger::log(\danog\MadelineProto\Lang::$current_lang['apifactory_start'], Logger::VERBOSE);
yield $this->API->initAsync(); yield $this->API->initAsync();
$this->APIFactory(); $this->APIFactory();
$this->asyncInitPromise = null; $this->asyncInitPromise = null;
@ -312,14 +309,11 @@ class API extends APIFactory
} }
$this->serialized = time(); $this->serialized = time();
$realpaths = Serialization::realpaths($filename); $realpaths = Serialization::realpaths($filename);
if (!file_exists($realpaths['lockfile'])) { Logger::log('Waiting for exclusive lock of serialization lockfile...');
touch($realpaths['lockfile']);
clearstatcache(); $unlock = yield Tools::flock($realpaths['lockfile'], LOCK_EX);
}
$realpaths['lockfile'] = fopen($realpaths['lockfile'], 'w'); Logger::log('Lock acquired, serializing');
\danog\MadelineProto\Logger::log('Waiting for exclusive lock of serialization lockfile...');
flock($realpaths['lockfile'], LOCK_EX);
\danog\MadelineProto\Logger::log('Lock acquired, serializing');
try { try {
if (!$this->getting_api_id) { if (!$this->getting_api_id) {
@ -332,17 +326,16 @@ class API extends APIFactory
$this->API->settings['logger']['logger_param'] = [$this->API, 'noop']; $this->API->settings['logger']['logger_param'] = [$this->API, 'noop'];
} }
} }
$wrote = file_put_contents($realpaths['tempfile'], serialize($this)); $wrote = yield put($realpaths['tempfile'], serialize($this));
rename($realpaths['tempfile'], $realpaths['file']); yield rename($realpaths['tempfile'], $realpaths['file']);
} finally { } finally {
if (!$this->getting_api_id) { if (!$this->getting_api_id) {
$this->API->settings['updates']['callback'] = $update_closure; $this->API->settings['updates']['callback'] = $update_closure;
$this->API->settings['logger']['logger_param'] = $logger_closure; $this->API->settings['logger']['logger_param'] = $logger_closure;
} }
flock($realpaths['lockfile'], LOCK_UN); $unlock();
fclose($realpaths['lockfile']);
} }
\danog\MadelineProto\Logger::log('Done serializing'); Logger::log('Done serializing');
return $wrote; return $wrote;
})()); })());

View File

@ -20,7 +20,6 @@ namespace danog\MadelineProto;
use Amp\ByteStream\ClosedException; use Amp\ByteStream\ClosedException;
use Amp\Deferred; use Amp\Deferred;
use Amp\Promise;
use danog\MadelineProto\Loop\Connection\CheckLoop; use danog\MadelineProto\Loop\Connection\CheckLoop;
use danog\MadelineProto\Loop\Connection\HttpWaitLoop; use danog\MadelineProto\Loop\Connection\HttpWaitLoop;
use danog\MadelineProto\Loop\Connection\ReadLoop; use danog\MadelineProto\Loop\Connection\ReadLoop;
@ -42,6 +41,7 @@ class Connection
use Crypt; use Crypt;
use MsgIdHandler; use MsgIdHandler;
use SeqNoHandler; use SeqNoHandler;
use \danog\Serializable; use \danog\Serializable;
use Tools; use Tools;
@ -55,39 +55,23 @@ class Connection
public $stream; public $stream;
public $time_delta = 0;
public $type = 0; public $type = 0;
public $peer_tag; public $peer_tag;
public $temp_auth_key; public $temp_auth_key;
public $auth_key; public $auth_key;
public $session_id;
public $session_out_seq_no = 0;
public $session_in_seq_no = 0;
public $incoming_messages = [];
public $outgoing_messages = [];
public $new_incoming = [];
public $new_outgoing = [];
public $pending_outgoing = []; public $pending_outgoing = [];
public $pending_outgoing_key = 0; public $pending_outgoing_key = 0;
public $pending_outgoing_unencrypted = [];
public $pending_outgoing_unencrypted_key = 0;
public $max_incoming_id;
public $max_outgoing_id;
public $authorized = false; public $authorized = false;
public $call_queue = [];
public $ack_queue = [];
public $i = [];
public $last_recv = 0;
public $last_http_wait = 0;
public $datacenter; public $datacenter;
public $API; public $API;
public $resumeWriterDeferred;
public $ctx;
public $pendingCheckWatcherId;
public $http_req_count = 0; public $ctx;
public $http_res_count = 0;
public function getCtx() public function getCtx()
{ {
@ -294,14 +278,4 @@ class Connection
{ {
return ['peer_tag', 'temp_auth_key', 'auth_key', 'session_id', 'session_out_seq_no', 'session_in_seq_no', 'max_incoming_id', 'max_outgoing_id', 'authorized', 'ack_queue']; return ['peer_tag', 'temp_auth_key', 'auth_key', 'session_id', 'session_out_seq_no', 'session_in_seq_no', 'max_incoming_id', 'max_outgoing_id', 'authorized', 'ack_queue'];
} }
public function __wakeup()
{
$this->time_delta = 0;
$this->pending_outgoing = [];
$this->new_outgoing = [];
$this->new_incoming = [];
$this->outgoing_messages = [];
$this->incoming_messages = [];
}
} }

View File

@ -74,7 +74,7 @@ class Conversion
{ {
set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']); set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
if (!extension_loaded('sqlite3')) { if (!extension_loaded('sqlite3')) {
throw new Exception(['extension', 'sqlite3']); throw Exception::extension('sqlite3');
} }
if (!isset(pathinfo($session)['extension'])) { if (!isset(pathinfo($session)['extension'])) {
$session .= '.session'; $session .= '.session';

View File

@ -21,6 +21,7 @@ namespace danog\MadelineProto;
use Amp\Artax\Client; use Amp\Artax\Client;
use Amp\Artax\Cookie\ArrayCookieJar; use Amp\Artax\Cookie\ArrayCookieJar;
use Amp\Artax\Cookie\CookieJar;
use Amp\Artax\DefaultClient; use Amp\Artax\DefaultClient;
use Amp\Artax\HttpSocketPool; use Amp\Artax\HttpSocketPool;
use Amp\CancellationToken; use Amp\CancellationToken;
@ -38,6 +39,7 @@ use Amp\Socket\ClientConnectContext;
use Amp\Socket\ClientSocket; use Amp\Socket\ClientSocket;
use Amp\Socket\ClientTlsContext; use Amp\Socket\ClientTlsContext;
use Amp\Socket\ConnectException; use Amp\Socket\ConnectException;
use Amp\Socket\Socket;
use Amp\TimeoutException; use Amp\TimeoutException;
use danog\MadelineProto\Stream\Common\BufferedRawStream; use danog\MadelineProto\Stream\Common\BufferedRawStream;
use danog\MadelineProto\Stream\ConnectionContext; use danog\MadelineProto\Stream\ConnectionContext;
@ -55,7 +57,6 @@ use danog\MadelineProto\Stream\Transport\WssStream;
use danog\MadelineProto\Stream\Transport\WsStream; use danog\MadelineProto\Stream\Transport\WsStream;
use function Amp\call; use function Amp\call;
use function Amp\Socket\Internal\parseUri; use function Amp\Socket\Internal\parseUri;
use Amp\Artax\Cookie\CookieJar;
/** /**
* Manages datacenters. * Manages datacenters.
@ -98,7 +99,7 @@ class DataCenter
$this->HTTPClient = new DefaultClient($this->CookieJar, new HttpSocketPool(new ProxySocketPool([$this, 'rawConnectAsync']))); $this->HTTPClient = new DefaultClient($this->CookieJar, new HttpSocketPool(new ProxySocketPool([$this, 'rawConnectAsync'])));
$DoHHTTPClient = new DefaultClient( $DoHHTTPClient = new DefaultClient(
$this->CookieJar, $this->CookieJar,
new HttpSocketPool( new HttpSocketPool(
new ProxySocketPool( new ProxySocketPool(
function (string $uri, CancellationToken $token = null, ClientConnectContext $ctx = null) { function (string $uri, CancellationToken $token = null, ClientConnectContext $ctx = null) {
@ -327,8 +328,34 @@ class DataCenter
continue; // Could not connect to host, try next host in the list. continue; // Could not connect to host, try next host in the list.
} }
if ($dc = $ctx->getDc()) {
$callback = [$this->sockets[$dc], 'haveRead'];
$socket = new class($socket) extends ClientSocket
{
private $callback;
public function setReadCallback($callback)
{
$this->callback = $callback;
}
return new ClientSocket($socket); /** @inheritdoc */
public function read(): Promise
{
$promise = parent::read();
$promise->onResolve(function ($e, $res) {
if ($res) {
($this->callback)();
}
});
return $promise;
}
};
$socket->setReadCallback($callback);
} else {
$socket = new ClientSocket($socket);
}
return $socket;
} }
// This is reached if either all URIs failed or the maximum number of attempts is reached. // This is reached if either all URIs failed or the maximum number of attempts is reached.

View File

@ -19,6 +19,7 @@
namespace danog\MadelineProto; namespace danog\MadelineProto;
// This code was written a few years ago: it is garbage, and has to be rewritten
class DocsBuilder class DocsBuilder
{ {
use \danog\MadelineProto\TL\TL; use \danog\MadelineProto\TL\TL;

View File

@ -23,9 +23,21 @@ trait Methods
{ {
public function mk_methods() public function mk_methods()
{ {
$bots = json_decode(file_get_contents('https://rpc.madelineproto.xyz/bot.json'), true)['result']; static $bots;
$errors = json_decode(file_get_contents('https://rpc.madelineproto.xyz/v1.json'), true); if (!$bots) $bots = json_decode(file_get_contents('https://rpc.madelineproto.xyz/bot.json'), true)['result'];
$errors['result'] = array_merge_recursive(...$errors['result']); static $errors;
if (!$errors) $errors = json_decode(file_get_contents('https://rpc.madelineproto.xyz/v1.json'), true);
$new = ['result' => []];
foreach ($errors['result'] as $code => $suberrors) {
foreach ($suberrors as $method => $suberrors) {
if (!isset($new[$method])) {
$new[$method] = [];
}
foreach ($suberrors as $error) {
$new['result'][$method][] = [$error, $code];
}
}
}
foreach (glob('methods/'.$this->any) as $unlink) { foreach (glob('methods/'.$this->any) as $unlink) {
unlink($unlink); unlink($unlink);
} }
@ -76,11 +88,12 @@ trait Methods
$this->docs_methods[$method] = '$MadelineProto->'.$md_method.'(\\['.$params.'\\]) === [$'.str_replace('_', '\\_', $type).'](../types/'.$php_type.'.md)<a name="'.$method.'"></a> $this->docs_methods[$method] = '$MadelineProto->'.$md_method.'(\\['.$params.'\\]) === [$'.str_replace('_', '\\_', $type).'](../types/'.$php_type.'.md)<a name="'.$method.'"></a>
'; ';
/*
if (!isset(\danog\MadelineProto\MTProto::DISALLOWED_METHODS[$data['method']]) && isset($this->td_descriptions['methods'][$data['method']])) { if (!isset(\danog\MadelineProto\MTProto::DISALLOWED_METHODS[$data['method']]) && isset($this->td_descriptions['methods'][$data['method']])) {
$this->human_docs_methods[$this->td_descriptions['methods'][$data['method']]['description'].': '.$data['method']] = '* <a href="'.$method.'.html" name="'.$method.'">'.$this->td_descriptions['methods'][$data['method']]['description'].': '.$data['method'].'</a> $this->human_docs_methods[$this->td_descriptions['methods'][$data['method']]['description'].': '.$data['method']] = '* <a href="'.$method.'.html" name="'.$method.'">'.$this->td_descriptions['methods'][$data['method']]['description'].': '.$data['method'].'</a>
'; ';
} }*/
$params = ''; $params = '';
$lua_params = ''; $lua_params = '';
$pwr_params = ''; $pwr_params = '';
@ -192,11 +205,12 @@ image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png
'; ';
/*
if (isset(\danog\MadelineProto\MTProto::DISALLOWED_METHODS[$data['method']])) { if (isset(\danog\MadelineProto\MTProto::DISALLOWED_METHODS[$data['method']])) {
$header .= '**'.\danog\MadelineProto\MTProto::DISALLOWED_METHODS[$data['method']]."**\n\n\n\n\n"; $header .= '**'.\danog\MadelineProto\MTProto::DISALLOWED_METHODS[$data['method']]."**\n\n\n\n\n";
file_put_contents('methods/'.$method.'.md', $header); file_put_contents('methods/'.$method.'.md', $header);
continue; continue;
} }*/
if ($this->td) { if ($this->td) {
$header .= 'YOU CANNOT USE THIS METHOD IN MADELINEPROTO $header .= 'YOU CANNOT USE THIS METHOD IN MADELINEPROTO
@ -293,14 +307,15 @@ You can also use normal markdown, note that to create mentions you must use the
MadelineProto supports all html entities supported by [html_entity_decode](http://php.net/manual/en/function.html-entity-decode.php). MadelineProto supports all html entities supported by [html_entity_decode](http://php.net/manual/en/function.html-entity-decode.php).
'; ';
} }
if (isset($errors['result'][$data['method']])) { if (isset($new['result'][$data['method']])) {
$example .= '### Errors this method can return: $example .= '### Errors
| Error | Description | | Code | Type | Description |
|----------|---------------| |------|----------|---------------|
'; ';
foreach ($errors['result'][$data['method']] as $error) { foreach ($new['result'][$data['method']] as $error) {
$example .= '|'.$error.'|'.$errors['human_result'][$error][0].'|'."\n"; [$error, $code] = $error;
$example .= "|$code|$error|".$errors['human_result'][$error][0].'|'."\n";
} }
$example .= "\n\n"; $example .= "\n\n";
} }

View File

@ -31,27 +31,8 @@ class Exception extends \Exception
public function __construct($message = null, $code = 0, self $previous = null, $file = null, $line = null) public function __construct($message = null, $code = 0, self $previous = null, $file = null, $line = null)
{ {
if (is_array($message) && $message[0] === 'extension') {
if ($message[1] === 'libtgvoip') {
$additional = 'Follow the instructions @ https://voip.madelineproto.xyz to install it.';
} elseif ($message[1] === 'prime') {
$additional = 'Follow the instructions @ https://prime.madelineproto.xyz to install it.';
} else {
$additional = 'Try running sudo apt-get install php'.PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION.'-'.$message[1].'.';
}
$message = 'MadelineProto requires the '.$message[1].' extension to run. '.$additional;
if (php_sapi_name() !== 'cli') {
echo $message.'<br>';
}
$file = 'MadelineProto';
$line = 1;
}
$this->prettify_tl(); $this->prettify_tl();
if ($file !== null) { if ($file !== null) {
if (basename($file) === 'Threaded.php') {
$line = debug_backtrace(0)[2]['line'];
$file = debug_backtrace(0)[2]['file'];
}
$this->file = $file; $this->file = $file;
} }
if ($line !== null) { if ($line !== null) {
@ -61,6 +42,7 @@ class Exception extends \Exception
if (strpos($message, 'socket_accept') === false) { if (strpos($message, 'socket_accept') === false) {
\danog\MadelineProto\Logger::log($message.' in '.basename($this->file).':'.$this->line, \danog\MadelineProto\Logger::FATAL_ERROR); \danog\MadelineProto\Logger::log($message.' in '.basename($this->file).':'.$this->line, \danog\MadelineProto\Logger::FATAL_ERROR);
} }
if (in_array($message, ['The session is corrupted!', 'Re-executing query...', 'I had to recreate the temporary authorization key', 'This peer is not present in the internal peer database', "Couldn't get response", 'Chat forbidden', 'The php-libtgvoip extension is required to accept and manage calls. See daniil.it/MadelineProto for more info.', 'File does not exist', 'Please install this fork of phpseclib: https://github.com/danog/phpseclib'])) { if (in_array($message, ['The session is corrupted!', 'Re-executing query...', 'I had to recreate the temporary authorization key', 'This peer is not present in the internal peer database', "Couldn't get response", 'Chat forbidden', 'The php-libtgvoip extension is required to accept and manage calls. See daniil.it/MadelineProto for more info.', 'File does not exist', 'Please install this fork of phpseclib: https://github.com/danog/phpseclib'])) {
return; return;
} }
@ -72,6 +54,22 @@ class Exception extends \Exception
} }
} }
public static function extension(string $extensionName)
{
$additional = 'Try running sudo apt-get install php'.PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION.'-'.$extensionName.'.';
if ($extensionName === 'libtgvoip') {
$additional = 'Follow the instructions @ https://voip.madelineproto.xyz to install it.';
} elseif ($extensionName === 'prime') {
$additional = 'Follow the instructions @ https://prime.madelineproto.xyz to install it.';
}
$message = 'MadelineProto requires the '.$extensionName.' extension to run. '.$additional;
if (php_sapi_name() !== 'cli') {
echo $message.'<br>';
}
$file = 'MadelineProto';
$line = 1;
return new self($message, 0, null, $file, $line);
}
/** /**
* ExceptionErrorHandler. * ExceptionErrorHandler.
* *

View File

@ -55,7 +55,9 @@ class CheckLoop extends ResumableSignalLoop
} }
if ($connection->hasPendingCalls()) { if ($connection->hasPendingCalls()) {
$last_recv = $connection->get_max_id(true); $last_msgid = $connection->get_max_id(true);
$last_chunk = $connection->getLastChunk();
if ($connection->temp_auth_key !== null) { if ($connection->temp_auth_key !== null) {
$full_message_ids = $connection->getPendingCalls(); //array_values($connection->new_outgoing); $full_message_ids = $connection->getPendingCalls(); //array_values($connection->new_outgoing);
foreach (array_chunk($full_message_ids, 8192) as $message_ids) { foreach (array_chunk($full_message_ids, 8192) as $message_ids) {
@ -139,7 +141,7 @@ class CheckLoop extends ResumableSignalLoop
return; return;
} }
if ($connection->get_max_id(true) === $last_recv) { if ($connection->get_max_id(true) === $last_msgid && $connection->getLastChunk() === $last_chunk) {
$API->logger->logger("We did not receive a response for $timeout seconds: reconnecting and exiting check loop on DC $datacenter"); $API->logger->logger("We did not receive a response for $timeout seconds: reconnecting and exiting check loop on DC $datacenter");
$this->exitedLoop(); $this->exitedLoop();
yield $connection->reconnect(); yield $connection->reconnect();

View File

@ -214,7 +214,6 @@ class ReadLoop extends SignalLoop
$connection->incoming_messages[$message_id]['content'] = $deserialized; $connection->incoming_messages[$message_id]['content'] = $deserialized;
$connection->incoming_messages[$message_id]['response'] = -1; $connection->incoming_messages[$message_id]['response'] = -1;
$connection->new_incoming[$message_id] = $message_id; $connection->new_incoming[$message_id] = $message_id;
$connection->last_recv = time();
$connection->last_http_wait = 0; $connection->last_http_wait = 0;
$API->logger->logger('Received payload from DC '.$datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE); $API->logger->logger('Received payload from DC '.$datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE);

View File

@ -187,7 +187,7 @@ class WriteLoop extends ResumableSignalLoop
$body_length = strlen($message['serialized_body']); $body_length = strlen($message['serialized_body']);
$actual_length = $body_length + 32; $actual_length = $body_length + 32;
if ($total_length && $total_length + $actual_length > 32760 || $count >= 1020) { if ($total_length && $total_length + $actual_length > 32760 || $count >= 1020) {
$API->logger->logger('Length overflow, postponing part of payload', \danog\MadelineProto\Logger::NOTICE); $API->logger->logger('Length overflow, postponing part of payload', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
break; break;
} }
@ -215,6 +215,7 @@ class WriteLoop extends ResumableSignalLoop
'system_lang_code' => $API->settings['app_info']['lang_code'], 'system_lang_code' => $API->settings['app_info']['lang_code'],
'lang_code' => $API->settings['app_info']['lang_code'], 'lang_code' => $API->settings['app_info']['lang_code'],
'lang_pack' => $API->settings['app_info']['lang_pack'], 'lang_pack' => $API->settings['app_info']['lang_pack'],
'proxy' => $connection->getCtx()->getInputClientProxy(),
'query' => $MTmessage['body'], 'query' => $MTmessage['body'],
] ]
), ),
@ -247,7 +248,7 @@ class WriteLoop extends ResumableSignalLoop
$body_length = strlen($MTmessage['body']); $body_length = strlen($MTmessage['body']);
$actual_length = $body_length + 32; $actual_length = $body_length + 32;
if ($total_length && $total_length + $actual_length > 32760) { if ($total_length && $total_length + $actual_length > 32760) {
$API->logger->logger('Length overflow, postponing part of payload', \danog\MadelineProto\Logger::NOTICE); $API->logger->logger('Length overflow, postponing part of payload', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
break; break;
} }
$count++; $count++;

View File

@ -159,6 +159,9 @@ class FeedLoop extends ResumableSignalLoop
case 'updateNewChannelMessage': case 'updateNewChannelMessage':
case 'updateEditChannelMessage': case 'updateEditChannelMessage':
$channelId = isset($update['message']['to_id']['channel_id']) ? $update['message']['to_id']['channel_id'] : false; $channelId = isset($update['message']['to_id']['channel_id']) ? $update['message']['to_id']['channel_id'] : false;
if (!$channelId) {
return false;
}
break; break;
case 'updateChannelWebPage': case 'updateChannelWebPage':
case 'updateDeleteChannelMessages': case 'updateDeleteChannelMessages':

View File

@ -31,8 +31,6 @@ use danog\MadelineProto\MTProtoTools\UpdatesState;
use danog\MadelineProto\Stream\MTProtoTransport\HttpsStream; use danog\MadelineProto\Stream\MTProtoTransport\HttpsStream;
use danog\MadelineProto\Stream\MTProtoTransport\HttpStream; use danog\MadelineProto\Stream\MTProtoTransport\HttpStream;
use danog\MadelineProto\TL\TLCallback; use danog\MadelineProto\TL\TLCallback;
use function Amp\ByteStream\getStdin;
use function Amp\ByteStream\getInputBufferStream;
/** /**
* Manages all of the mtproto stuff. * Manages all of the mtproto stuff.
@ -136,6 +134,7 @@ class MTProto extends AsyncConstruct implements TLCallback
public $authorized = 0; public $authorized = 0;
public $authorized_dc = -1; public $authorized_dc = -1;
private $rsa_keys = []; private $rsa_keys = [];
private $cdn_rsa_keys = [];
private $dh_config = ['version' => 0]; private $dh_config = ['version' => 0];
public $chats = []; public $chats = [];
public $channel_participants = []; public $channel_participants = [];
@ -164,21 +163,6 @@ class MTProto extends AsyncConstruct implements TLCallback
\danog\MadelineProto\Magic::class_exists(); \danog\MadelineProto\Magic::class_exists();
// Parse settings // Parse settings
$this->parse_settings($settings); $this->parse_settings($settings);
if (!defined('\\phpseclib\\Crypt\\Common\\SymmetricKey::MODE_IGE') || \phpseclib\Crypt\Common\SymmetricKey::MODE_IGE !== 7) {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['phpseclib_fork']);
}
if (!extension_loaded('xml')) {
throw new Exception(['extension', 'xml']);
}
if (!extension_loaded('fileinfo')) {
throw new Exception(['extension', 'fileinfo']);
}
if (!extension_loaded('json')) {
throw new Exception(['extension', 'json']);
}
if (!extension_loaded('mbstring')) {
throw new Exception(['extension', 'mbstring']);
}
// Connect to servers // Connect to servers
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['inst_dc'], Logger::ULTRA_VERBOSE); $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['inst_dc'], Logger::ULTRA_VERBOSE);
if (!($this->channels_state instanceof CombinedUpdatesState)) { if (!($this->channels_state instanceof CombinedUpdatesState)) {
@ -199,6 +183,7 @@ class MTProto extends AsyncConstruct implements TLCallback
} }
// Load rsa keys // Load rsa keys
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['load_rsa'], Logger::ULTRA_VERBOSE); $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['load_rsa'], Logger::ULTRA_VERBOSE);
$this->rsa_keys = [];
foreach ($this->settings['authorization']['rsa_keys'] as $key) { foreach ($this->settings['authorization']['rsa_keys'] as $key) {
$key = yield (new RSA())->load($key); $key = yield (new RSA())->load($key);
$this->rsa_keys[$key->fp] = $key; $this->rsa_keys[$key->fp] = $key;
@ -327,22 +312,6 @@ class MTProto extends AsyncConstruct implements TLCallback
if (isset($this->settings['app_info']['lang_code']) && isset(Lang::$lang[$this->settings['app_info']['lang_code']])) { if (isset($this->settings['app_info']['lang_code']) && isset(Lang::$lang[$this->settings['app_info']['lang_code']])) {
Lang::$current_lang = &Lang::$lang[$this->settings['app_info']['lang_code']]; Lang::$current_lang = &Lang::$lang[$this->settings['app_info']['lang_code']];
} }
if (!defined('\\phpseclib\\Crypt\\AES::MODE_IGE')) {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['phpseclib_fork']);
}
if (!extension_loaded('xml')) {
throw new Exception(['extension', 'xml']);
}
if (!extension_loaded('fileinfo')) {
throw new Exception(['extension', 'fileinfo']);
}
if (!extension_loaded('mbstring')) {
throw new Exception(['extension', 'mbstring']);
}
if (!extension_loaded('json')) {
throw new Exception(['extension', 'json']);
}
if (!isset($this->referenceDatabase)) { if (!isset($this->referenceDatabase)) {
$this->referenceDatabase = new ReferenceDatabase($this); $this->referenceDatabase = new ReferenceDatabase($this);
} }
@ -471,9 +440,9 @@ class MTProto extends AsyncConstruct implements TLCallback
} }
/*if (!$this->settings['updates']['handle_old_updates']) { /*if (!$this->settings['updates']['handle_old_updates']) {
$this->channels_state = new CombinedUpdatesState(); $this->channels_state = new CombinedUpdatesState();
$this->msg_ids = []; $this->msg_ids = [];
$this->got_state = false; $this->got_state = false;
}*/ }*/
yield $this->connect_to_all_dcs_async(); yield $this->connect_to_all_dcs_async();
foreach ($this->calls as $id => $controller) { foreach ($this->calls as $id => $controller) {
@ -819,9 +788,11 @@ class MTProto extends AsyncConstruct implements TLCallback
], 'upload' => [ ], 'upload' => [
'allow_automatic_upload' => true, 'allow_automatic_upload' => true,
'part_size' => 512 * 1024, 'part_size' => 512 * 1024,
'parallel_chunks' => 20,
], 'download' => [ ], 'download' => [
'report_broken_media' => true, 'report_broken_media' => true,
'part_size' => 1024 * 1024, 'part_size' => 1024 * 1024,
'parallel_chunks' => 20,
], 'pwr' => [ ], 'pwr' => [
'pwr' => false, 'pwr' => false,
// Need info ? // Need info ?
@ -986,7 +957,6 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->dialog_params = ['_' => 'MadelineProto.dialogParams', 'limit' => 0, 'offset_date' => 0, 'offset_id' => 0, 'offset_peer' => ['_' => 'inputPeerEmpty'], 'count' => 0]; $this->dialog_params = ['_' => 'MadelineProto.dialogParams', 'limit' => 0, 'offset_date' => 0, 'offset_id' => 0, 'offset_peer' => ['_' => 'inputPeerEmpty'], 'count' => 0];
$this->full_chats = []; $this->full_chats = [];
} }
public function resetUpdateState() public function resetUpdateState()
{ {
@ -1082,7 +1052,7 @@ class MTProto extends AsyncConstruct implements TLCallback
try { try {
foreach ((yield $this->method_call_async_read('help.getCdnConfig', [], ['datacenter' => $datacenter]))['public_keys'] as $curkey) { foreach ((yield $this->method_call_async_read('help.getCdnConfig', [], ['datacenter' => $datacenter]))['public_keys'] as $curkey) {
$tempkey = new \danog\MadelineProto\RSA($curkey['public_key']); $tempkey = new \danog\MadelineProto\RSA($curkey['public_key']);
$this->rsa_keys[$tempkey->fp] = $tempkey; $this->cdn_rsa_keys[$tempkey->fp] = $tempkey;
} }
} catch (\danog\MadelineProto\TL\Exception $e) { } catch (\danog\MadelineProto\TL\Exception $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::FATAL_ERROR); $this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::FATAL_ERROR);

View File

@ -35,7 +35,8 @@ trait AuthKeyHandler
public function create_auth_key_async($expires_in, $datacenter): \Generator public function create_auth_key_async($expires_in, $datacenter): \Generator
{ {
$req_pq = strpos($datacenter, 'cdn') ? 'req_pq' : 'req_pq_multi'; $cdn = strpos($datacenter, 'cdn');
$req_pq = $cdn ? 'req_pq' : 'req_pq_multi';
for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) { for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) {
try { try {
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['req_pq'], \danog\MadelineProto\Logger::VERBOSE); $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['req_pq'], \danog\MadelineProto\Logger::VERBOSE);
@ -69,7 +70,7 @@ trait AuthKeyHandler
* *********************************************************************** * ***********************************************************************
* Find our key in the server_public_key_fingerprints vector * Find our key in the server_public_key_fingerprints vector
*/ */
foreach ($this->rsa_keys as $curkey) { foreach ($cdn ? array_merge($this->cdn_rsa_keys, $this->rsa_keys) : $this->rsa_keys as $curkey) {
if (in_array($curkey->fp, $ResPQ['server_public_key_fingerprints'])) { if (in_array($curkey->fp, $ResPQ['server_public_key_fingerprints'])) {
$key = $curkey; $key = $curkey;
} }

View File

@ -19,62 +19,198 @@
namespace danog\MadelineProto\MTProtoTools; namespace danog\MadelineProto\MTProtoTools;
use Amp\ByteStream\InputStream;
use Amp\ByteStream\OutputStream;
use Amp\ByteStream\ResourceInputStream;
use Amp\ByteStream\ResourceOutputStream;
use Amp\ByteStream\StreamException;
use Amp\Deferred;
use Amp\File\Handle;
use Amp\File\StatCache;
use Amp\Promise;
use Amp\Success;
use danog\MadelineProto\Async\AsyncParameters; use danog\MadelineProto\Async\AsyncParameters;
use danog\MadelineProto\Exception; use danog\MadelineProto\Exception;
use danog\MadelineProto\FileCallbackInterface;
use danog\MadelineProto\Logger; use danog\MadelineProto\Logger;
use danog\MadelineProto\RPCErrorException; use danog\MadelineProto\RPCErrorException;
use danog\MadelineProto\Stream\Common\SimpleBufferedRawStream;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\Transport\PremadeStream;
use function Amp\File\exists;
use function Amp\File\open;
use function Amp\File\stat;
use function Amp\File\touch;
use danog\MadelineProto\Tools; use danog\MadelineProto\Tools;
use function Amp\Promise\all; use function Amp\Promise\all;
use Amp\File\BlockingHandle;
use Amp\Artax\Client;
/** /**
* Manages upload and download of files. * Manages upload and download of files.
*/ */
trait Files trait Files
{ {
public function upload_async($file, $file_name = '', $cb = null, $encrypted = false): \Generator public function upload_async($file, $file_name = '', $cb = null, $encrypted = false)
{ {
if (is_object($file)) { if (is_object($file) && $file instanceof FileCallbackInterface) {
if (!isset(class_implements($file)['danog\MadelineProto\FileCallbackInterface'])) {
throw new \danog\MadelineProto\Exception('Provided object does not implement FileCallbackInterface');
}
$cb = $file; $cb = $file;
$file = $file->getFile(); $file = $file->getFile();
} }
if (is_string($file) || (is_object($file) && method_exists($file, '__toString'))) {
$t = microtime(true); if (filter_var($file, FILTER_VALIDATE_URL)) {
return yield $this->upload_from_url_async($file);
}
} else if (is_array($file)) {
return yield $this->upload_from_tgfile_async($file, $cb, $encrypted);
}
$file = \danog\MadelineProto\Absolute::absolute($file); $file = \danog\MadelineProto\Absolute::absolute($file);
if (!file_exists($file)) { if (!yield exists($file)) {
throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['file_not_exist']); throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['file_not_exist']);
} }
if (empty($file_name)) { if (empty($file_name)) {
$file_name = basename($file); $file_name = basename($file);
} }
$datacenter = $this->settings['connection_settings']['default_dc'];
if (isset($this->datacenter->sockets[$datacenter.'_media'])) { StatCache::clear($file);
$datacenter .= '_media';
} $size = (yield stat($file))['size'];
$file_size = filesize($file); if ($size > 512 * 1024 * 3000) {
if ($file_size > 512 * 1024 * 3000) {
throw new \danog\MadelineProto\Exception('Given file is too big!'); throw new \danog\MadelineProto\Exception('Given file is too big!');
} }
$stream = yield open($file, 'rb');
$mime = $this->get_mime_from_file($file);
try {
return yield $this->upload_from_stream_async($stream, $size, $stream, $mime, $cb, $encrypted);
} finally {
yield $stream->close();
}
}
public function upload_from_url_async($url, int $size = 0, string $file_name = '', $cb = null, bool $encrypted = false)
{
if (is_object($url) && $url instanceof FileCallbackInterface) {
$cb = $url;
$url = $url->getFile();
}
/** @var $response \Amp\Artax\Response */
$response = yield $this->datacenter->getHTTPClient()->request($url, [Client::OP_MAX_BODY_BYTES => 512 * 1024 * 3000, Client::OP_TRANSFER_TIMEOUT => 10*1000*3600]);
if (200 !== $status = $response->getStatus()) {
throw new Exception("Wrong status code: $status ".$response->getReason());
}
$mime = trim(explode(';', $response->getHeader('content-type') ?? 'application/octet-stream')[0]);
$size = $response->getHeader('content-length') ?? $size;
$stream = $response->getBody();
if (!$size) {
$this->logger->logger("No content length for $url, caching first");
$body = $stream;
$stream = new BlockingHandle(fopen('php://temp', 'r+b'), 'php://temp', 'r+b');
while (null !== $chunk = yield $body->read()) {
yield $stream->write($chunk);
}
$size = $stream->tell();
if (!$size) {
throw new Exception('Wrong size!');
}
yield $stream->seek(0);
}
return yield $this->upload_from_stream_async($stream, $size, $mime, $file_name, $cb, $encrypted);
}
public function upload_from_stream_async($stream, int $size, string $mime, string $file_name = '', $cb = null, bool $encrypted = false)
{
if (is_object($stream) && $stream instanceof FileCallbackInterface) {
$cb = $stream;
$stream = $stream->getFile();
}
/** @var $stream \Amp\ByteStream\OutputStream */
if (!is_object($stream)) {
$stream = new ResourceOutputStream($stream);
}
if (!$stream instanceof InputStream) {
throw new Exception("Invalid stream provided");
}
$seekable = false;
if (method_exists($stream, 'seek')) {
try {
yield $stream->seek(0);
$seekable = true;
} catch (StreamException $e) {
}
}
$created = false;
if ($stream instanceof Handle) {
$callable = static function (int $offset, int $size) use ($stream, $seekable) {
if ($seekable) {
while ($stream->tell() !== $offset) {
yield $stream->seek($offset);
}
}
return yield $stream->read($size);
};
} else {
if (!$stream instanceof BufferedRawStream) {
$ctx = (new ConnectionContext)
->addStream(PremadeStream::getName(), $stream)
->addStream(SimpleBufferedRawStream::getName());
$stream = yield $ctx->getStream();
$created = true;
}
$callable = static function (int $offset, int $size) use ($stream) {
$reader = yield $stream->getReadBuffer($l);
try {
return yield $reader->bufferRead($size);
} catch (\danog\MadelineProto\NothingInTheSocketException $e) {
$reader = yield $stream->getReadBuffer($size);
return yield $reader->bufferRead($size);
}
};
$seekable = false;
}
$res = yield $this->upload_from_callable_async($callable, $size, $mime, $file_name, $cb, $seekable, $encrypted);
if ($created) {
$stream->disconnect();
}
return $res;
}
public function upload_from_callable_async($callable, int $size, string $mime, string $file_name = '', $cb = null, bool $refetchable = true, bool $encrypted = false)
{
if (is_object($callable) && $callable instanceof FileCallbackInterface) {
$cb = $callable;
$callable = $callable->getFile();
}
if (!is_callable($callable)) {
throw new Exception('Invalid callable provided');
}
if ($cb === null) { if ($cb === null) {
$cb = function ($percent) { $cb = function ($percent) {
$this->logger->logger('Upload status: '.$percent.'%', \danog\MadelineProto\Logger::NOTICE); $this->logger->logger('Upload status: '.$percent.'%', \danog\MadelineProto\Logger::NOTICE);
}; };
} }
$part_size = $this->settings['upload']['part_size'];
$part_total_num = (int) ceil($file_size / $part_size);
$part_num = 0;
$method = $file_size > 10 * 1024 * 1024 ? 'upload.saveBigFilePart' : 'upload.saveFilePart';
$constructor = 'input'.($encrypted === true ? 'Encrypted' : '').($file_size > 10 * 1024 * 1024 ? 'FileBig' : 'File').($encrypted === true ? 'Uploaded' : '');
$file_id = $this->random(8);
$f = fopen($file, 'r');
$seekable = stream_get_meta_data($f)['seekable']; $datacenter = $this->settings['connection_settings']['default_dc'];
if ($seekable) { if (isset($this->datacenter->sockets[$datacenter.'_media'])) {
fseek($f, 0); $datacenter .= '_media';
} }
$part_size = $this->settings['upload']['part_size'];
$parallel_chunks = $this->settings['upload']['parallel_chunks'] ? $this->settings['upload']['parallel_chunks'] : 3000;
$part_total_num = (int) ceil($size / $part_size);
$part_num = 0;
$method = $size > 10 * 1024 * 1024 ? 'upload.saveBigFilePart' : 'upload.saveFilePart';
$constructor = 'input'.($encrypted === true ? 'Encrypted' : '').($size > 10 * 1024 * 1024 ? 'FileBig' : 'File').($encrypted === true ? 'Uploaded' : '');
$file_id = $this->random(8);
$ige = null; $ige = null;
if ($encrypted === true) { if ($encrypted === true) {
$key = $this->random(32); $key = $this->random(32);
@ -85,39 +221,65 @@ trait Files
$ige->setIV($iv); $ige->setIV($iv);
$ige->setKey($key); $ige->setKey($key);
$ige->enableContinuousBuffer(); $ige->enableContinuousBuffer();
$refetchable = false;
} }
$ctx = hash_init('md5'); $ctx = hash_init('md5');
$promises = []; $promises = [];
$cur_part_num = 0;
$cb = function () use ($cb, $part_total_num) {
static $cur = 0;
$cur++;
$this->callFork($cb($cur * 100 / $part_total_num));
};
$start = microtime(true);
while ($part_num < $part_total_num) { while ($part_num < $part_total_num) {
$t = microtime(true);
$read_deferred = yield $this->method_call_async_write( $read_deferred = yield $this->method_call_async_write(
$method, $method,
new AsyncParameters( new AsyncParameters(
static function () use ($file_id, $part_num, $part_total_num, $part_size, $f, $ctx, $ige, $seekable) { static function () use ($file_id, $part_num, $part_total_num, $part_size, $callable, $ctx, $ige) {
if ($seekable) { static $fetched = false;
fseek($f, $part_num * $part_size); $already_fetched = $fetched;
} elseif (ftell($f) !== $part_num * $part_size) { $fetched = true;
throw new \danog\MadelineProto\Exception('Wrong position!');
}
$bytes = stream_get_contents($f, $part_size); $bytes = yield $callable($part_num * $part_size, $part_size);
if (!$already_fetched) {
hash_update($ctx, $bytes);
}
if ($ige) { if ($ige) {
$bytes = $ige->encrypt(str_pad($bytes, $part_size, chr(0))); $bytes = $ige->encrypt(str_pad($bytes, $part_size, chr(0)));
} }
hash_update($ctx, $bytes);
return ['file_id' => $file_id, 'file_part' => $part_num, 'file_total_parts' => $part_total_num, 'bytes' => $bytes]; return ['file_id' => $file_id, 'file_part' => $part_num, 'file_total_parts' => $part_total_num, 'bytes' => $bytes];
}, },
$seekable $refetchable
), ),
['heavy' => true, 'file' => true, 'datacenter' => $datacenter] ['heavy' => true, 'file' => true, 'datacenter' => &$datacenter]
); );
$this->callFork($cb(ftell($f) * 100 / $file_size)); $read_deferred->promise()->onResolve(static function ($e, $res) use ($cb) {
$this->logger->logger('Speed for chunk: '.(($part_size * 8 / 1000000) / (microtime(true) - $t))); if ($res) {
$cb();
}
});
$part_num++; $part_num++;
$promises[] = $read_deferred->promise(); $promises[] = $read_deferred->promise();
if (!($part_num % $parallel_chunks)) { // 20 mb at a time, for a typical bandwidth of 1gbps (run the code in this if every second)
$result = yield $this->all($promises);
foreach ($result as $kkey => $result) {
if (!$result) {
throw new \danog\MadelineProto\Exception('Upload of part '.$kkey.' failed');
}
}
$promises = [];
$time = microtime(true) - $start;
$speed = (int) (($size * 8) / $time) / 1000000;
$this->logger->logger("Partial upload time: $time");
$this->logger->logger("Partial upload speed: $speed mbps");
}
} }
$result = yield all($promises); $result = yield all($promises);
@ -126,18 +288,18 @@ trait Files
throw new \danog\MadelineProto\Exception('Upload of part '.$kkey.' failed'); throw new \danog\MadelineProto\Exception('Upload of part '.$kkey.' failed');
} }
} }
$time = microtime(true) - $start;
$speed = (int) (($size * 8) / $time) / 1000000;
$this->logger->logger("Total upload time: $time");
$this->logger->logger("Total upload speed: $speed mbps");
$constructor = ['_' => $constructor, 'id' => $file_id, 'parts' => $part_total_num, 'name' => $file_name, 'mime_type' => $this->get_mime_from_file($file)]; $constructor = ['_' => $constructor, 'id' => $file_id, 'parts' => $part_total_num, 'name' => $file_name, 'mime_type' => $mime];
if ($encrypted === true) { if ($encrypted === true) {
$constructor['key_fingerprint'] = $fingerprint; $constructor['key_fingerprint'] = $fingerprint;
$constructor['key'] = $key; $constructor['key'] = $key;
$constructor['iv'] = $iv; $constructor['iv'] = $iv;
} }
$constructor['md5_checksum'] = hash_final($ctx);
fclose($f);
clearstatcache();
$this->logger->logger('Speed: '.(($file_size * 8) / (microtime(true) - $t) / 1000000));
return $constructor; return $constructor;
} }
@ -147,7 +309,80 @@ trait Files
return $this->upload_async($file, $file_name, $cb, true); return $this->upload_async($file, $file_name, $cb, true);
} }
public function gen_all_file_async($media, $regenerate) public function upload_from_tgfile_async($media, $cb = null, $encrypted = false)
{
if (is_object($media) && $media instanceof FileCallbackInterface) {
$cb = $media;
$media = $media->getFile();
}
$media = yield $this->get_download_info_async($media);
if (!isset($media['size'], $media['mime'])) {
throw new Exception('Wrong file provided!');
}
$size = $media['size'];
$mime = $media['mime'];
$chunk_size = $this->settings['upload']['part_size'];
$bridge = new class
{
private $done = [];
private $pending = [];
public $nextRead;
public $size;
public $part_size;
public function read(int $offset, int $size)
{
$nextRead = $this->nextRead;
$this->nextRead = new Deferred;
if ($nextRead) {
$nextRead->resolve(true);
}
if (isset($this->done[$offset])) {
if (strlen($this->done[$offset]) > $size) {
throw new Exception('Wrong size!');
}
$result = $this->done[$offset];
unset($this->done[$offset]);
return $result;
}
$this->pending[$offset] = new Deferred;
return $this->pending[$offset]->promise();
}
public function write(string $data, int $offset)
{
if (isset($this->pending[$offset])) {
$promise = $this->pending[$offset];
unset($this->pending[$offset]);
$promise->resolve($data);
} else {
$this->done[$offset] = $data;
}
$length = strlen($data);
if ($offset + $length === $this->size || $length < $this->part_size) {
return;
}
return $this->nextRead->promise();
}
};
$bridge->size = $size;
$bridge->part_size = $chunk_size;
$reader = [$bridge, 'read'];
$writer = [$bridge, 'write'];
$read = $this->upload_from_callable_async($reader, $size, $mime, '', $cb, false, $encrypted);
$write = $this->download_to_callable_async($media, $writer, null, true, 0, -1, $chunk_size);
list($res) = yield $this->all([$read, $write]);
return $res;
}
public function gen_all_file_async($media)
{ {
$res = [$this->constructors->find_by_predicate($media['_'])['type'] => $media]; $res = [$this->constructors->find_by_predicate($media['_'])['type'] => $media];
switch ($media['_']) { switch ($media['_']) {
@ -240,7 +475,7 @@ trait Files
return $res; return $res;
} }
public function get_file_info_async($constructor, $regenerate = false) public function get_file_info_async($constructor)
{ {
if (is_string($constructor)) { if (is_string($constructor)) {
$constructor = $this->unpack_file_id($constructor)['MessageMedia']; $constructor = $this->unpack_file_id($constructor)['MessageMedia'];
@ -256,7 +491,7 @@ trait Files
$constructor = $constructor['media']; $constructor = $constructor['media'];
} }
return yield $this->gen_all_file_async($constructor, $regenerate); return yield $this->gen_all_file_async($constructor);
} }
public function get_propic_info_async($data) public function get_propic_info_async($data)
{ {
@ -325,13 +560,13 @@ trait Files
$res['name'] .= ' - '.$audio['performer']; $res['name'] .= ' - '.$audio['performer'];
} }
} }
if (!isset($res['ext'])) { if (!isset($res['ext']) || $res['ext'] === '') {
$res['ext'] = $this->get_extension_from_location($res['InputFileLocation'], $this->get_extension_from_mime(isset($res['mime']) ? $res['mime'] : 'image/jpeg')); $res['ext'] = $this->get_extension_from_location($res['InputFileLocation'], $this->get_extension_from_mime($res['mime'] ?? 'image/jpeg'));
} }
if (!isset($res['mime'])) { if (!isset($res['mime']) || $res['mime'] === '') {
$res['mime'] = $this->get_mime_from_extension($res['ext'], 'image/jpeg'); $res['mime'] = $this->get_mime_from_extension($res['ext'], 'image/jpeg');
} }
if (!isset($res['name'])) { if (!isset($res['name']) || $res['name'] === '') {
$res['name'] = Tools::unpack_signed_long_string($message_media['file']['access_hash']); $res['name'] = Tools::unpack_signed_long_string($message_media['file']['access_hash']);
} }
@ -492,10 +727,10 @@ trait Files
), ),
]; ];
if (!isset($res['ext'])) { if (!isset($res['ext']) || $res['ext'] === '') {
$res['ext'] = $this->get_extension_from_location($res['InputFileLocation'], $this->get_extension_from_mime($message_media['document']['mime_type'])); $res['ext'] = $this->get_extension_from_location($res['InputFileLocation'], $this->get_extension_from_mime($message_media['document']['mime_type']));
} }
if (!isset($res['name'])) { if (!isset($res['name']) || $res['name'] === '') {
$res['name'] = Tools::unpack_signed_long_string($message_media['document']['access_hash']); $res['name'] = Tools::unpack_signed_long_string($message_media['document']['access_hash']);
} }
if (isset($message_media['document']['size'])) { if (isset($message_media['document']['size'])) {
@ -509,10 +744,105 @@ trait Files
throw new \danog\MadelineProto\Exception('Invalid constructor provided: '.$message_media['_']); throw new \danog\MadelineProto\Exception('Invalid constructor provided: '.$message_media['_']);
} }
} }
/*
public function download_to_browser_single_async($message_media, $cb = null)
{
if (php_sapi_name() === 'cli') {
throw new Exception('Cannot download file to browser from command line: start this script from a browser');
}
if (headers_sent()) {
throw new Exception('Headers already sent, cannot stream file to browser!');
}
if (is_object($message_media) && $message_media instanceof FileCallbackInterface) {
$cb = $message_media;
$message_media = $message_media->getFile();
}
$message_media = yield $this->get_download_info_async($message_media);
$servefile = $_SERVER['REQUEST_METHOD'] !== 'HEAD';
if (isset($_SERVER['HTTP_RANGE'])) {
$range = explode('=', $_SERVER['HTTP_RANGE'], 2);
if (count($range) == 1) {
$range[1] = '';
}
list($size_unit, $range_orig) = $range;
if ($size_unit == 'bytes') {
//multiple ranges could be specified at the same time, but for simplicity only serve the first range
//http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
$list = explode(',', $range_orig, 2);
if (count($list) == 1) {
$list[1] = '';
}
list($range, $extra_ranges) = $list;
} else {
$range = '';
return Tools::noCache(416, '<html><body><h1>416 Requested Range Not Satisfiable.</h1><br><p>Could not use selected range.</p></body></html>');
}
} else {
$range = '';
}
$listseek = explode('-', $range, 2);
if (count($listseek) == 1) {
$listseek[1] = '';
}
list($seek_start, $seek_end) = $listseek;
$seek_end = empty($seek_end) ? ($message_media['size'] - 1) : min(abs(intval($seek_end)), $message_media['size'] - 1);
if (!empty($seek_start) && $seek_end < abs(intval($seek_start))) {
return Tools::noCache(416, '<html><body><h1>416 Requested Range Not Satisfiable.</h1><br><p>Could not use selected range.</p></body></html>');
}
$seek_start = empty($seek_start) ? 0 : abs(intval($seek_start));
if ($servefile) {
if ($seek_start > 0 || $seek_end < $select['file_size'] - 1) {
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes '.$seek_start.'-'.$seek_end.'/'.$select['file_size']);
header('Content-Length: '.($seek_end - $seek_start + 1));
} else {
header('Content-Length: '.$select['file_size']);
}
header('Content-Type: '.$select['mime']);
header('Cache-Control: max-age=31556926;');
header('Content-Transfer-Encoding: Binary');
header('Accept-Ranges: bytes');
//header('Content-disposition: attachment: filename="'.basename($select['file_path']).'"');
$MadelineProto->download_to_stream($select['file_id'], fopen('php://output', 'w'), function ($percent) {
flush();
ob_flush();
\danog\MadelineProto\Logger::log('Download status: '.$percent.'%');
}, $seek_start, $seek_end + 1);
//analytics(true, $file_path, $MadelineProto->get_self()['id'], $dbuser, $dbpassword);
$MadelineProto->API->getting_state = false;
$MadelineProto->API->store_db([], true);
$MadelineProto->API->reset_session();
} else {
if ($seek_start > 0 || $seek_end < $select['file_size'] - 1) {
header('HTTP/1.1 206 Partial Content');
header('Content-Range: bytes '.$seek_start.'-'.$seek_end.'/'.$select['file_size']);
header('Content-Length: '.($seek_end - $seek_start + 1));
} else {
header('Content-Length: '.$select['file_size']);
}
header('Content-Type: '.$select['mime']);
header('Cache-Control: max-age=31556926;');
header('Content-Transfer-Encoding: Binary');
header('Accept-Ranges: bytes');
analytics(true, $file_path, null, $dbuser, $dbpassword);
//header('Content-disposition: attachment: filename="'.basename($select['file_path']).'"');
}
header('Content-Length: '.$info['size']);
header('Content-Type: '.$info['mime']);
}*/
public function extract_photosize($photo)
{
}
public function download_to_dir_async($message_media, $dir, $cb = null) public function download_to_dir_async($message_media, $dir, $cb = null)
{ {
if (is_object($dir) && class_implements($dir)['danog\MadelineProto\FileCallbackInterface']) { if (is_object($dir) && $dir instanceof FileCallbackInterface) {
$cb = $dir; $cb = $dir;
$dir = $dir->getFile(); $dir = $dir->getFile();
} }
@ -524,69 +854,100 @@ trait Files
public function download_to_file_async($message_media, $file, $cb = null) public function download_to_file_async($message_media, $file, $cb = null)
{ {
if (is_object($file) && class_implements($file)['danog\MadelineProto\FileCallbackInterface']) { if (is_object($file) && $file instanceof FileCallbackInterface) {
$cb = $file; $cb = $file;
$file = $file->getFile(); $file = $file->getFile();
} }
$file = \danog\MadelineProto\Absolute::absolute(preg_replace('|/+|', '/', $file)); $file = \danog\MadelineProto\Absolute::absolute(preg_replace('|/+|', '/', $file));
if (!file_exists($file)) { if (!yield exists($file)) {
touch($file); yield touch($file);
} }
$file = realpath($file); $file = realpath($file);
$message_media = yield $this->get_download_info_async($message_media); $message_media = yield $this->get_download_info_async($message_media);
$stream = fopen($file, 'r+b');
$size = fstat($stream)['size']; StatCache::clear($file);
$size = (yield stat($file))['size'];
$stream = yield open($file, 'cb');
$this->logger->logger('Waiting for lock of file to download...'); $this->logger->logger('Waiting for lock of file to download...');
do { $unlock = yield $this->flock($file, LOCK_EX);
$res = flock($stream, LOCK_EX | LOCK_NB);
if (!$res) {
yield $this->sleep(0.1);
}
} while (!$res);
try { try {
yield $this->download_to_stream_async($message_media, $stream, $cb, $size, -1); yield $this->download_to_stream_async($message_media, $stream, $cb, $size, -1);
} finally { } finally {
flock($stream, LOCK_UN); $unlock();
fclose($stream); yield $stream->close();
clearstatcache(); StatCache::clear($file);
} }
return $file; return $file;
} }
public function download_to_stream_async($message_media, $stream, $cb = null, $offset = 0, $end = -1) public function download_to_stream_async($message_media, $stream, $cb = null, $offset = 0, $end = -1)
{ {
if (is_object($stream) && class_implements($stream)['danog\MadelineProto\FileCallbackInterface']) { $message_media = yield $this->get_download_info_async($message_media);
if (is_object($stream) && $stream instanceof FileCallbackInterface) {
$cb = $stream; $cb = $stream;
$stream = $stream->getFile(); $stream = $stream->getFile();
} }
/** @var $stream \Amp\ByteStream\OutputStream */
if (!is_object($stream)) {
$stream = new ResourceOutputStream($stream);
}
if (!$stream instanceof OutputStream) {
throw new Exception("Invalid stream provided");
}
$seekable = false;
if (method_exists($stream, 'seek')) {
try {
yield $stream->seek($offset);
$seekable = true;
} catch (StreamException $e) {
}
}
$callable = static function (string $payload, int $offset) use ($stream, $seekable) {
if ($seekable) {
while ($stream->tell() !== $offset) {
yield $stream->seek($offset);
}
}
return yield $stream->write($payload);
};
return yield $this->download_to_callable_async($message_media, $callable, $cb, $seekable, $offset, $end);
}
public function download_to_callable_async($message_media, $callable, $cb = null, $parallelize = true, $offset = 0, $end = -1, int $part_size = null)
{
$message_media = yield $this->get_download_info_async($message_media);
if (is_object($callable) && $callable instanceof FileCallbackInterface) {
$cb = $callable;
$callable = $callable->getFile();
}
if (!is_callable($callable)) {
throw new Exception('Wrong callable provided');
}
if ($cb === null) { if ($cb === null) {
$cb = function ($percent) { $cb = function ($percent) {
$this->logger->logger('Download status: '.$percent.'%', \danog\MadelineProto\Logger::NOTICE); $this->logger->logger('Download status: '.$percent.'%', \danog\MadelineProto\Logger::NOTICE);
}; };
} }
$message_media = yield $this->get_download_info_async($message_media);
try {
if (stream_get_meta_data($stream)['seekable']) {
fseek($stream, $offset);
}
} catch (\danog\MadelineProto\Exception $e) {
}
$downloaded_size = 0;
if ($end === -1 && isset($message_media['size'])) { if ($end === -1 && isset($message_media['size'])) {
$end = $message_media['size']; $end = $message_media['size'];
} }
$size = $end - $offset;
$part_size = $this->settings['download']['part_size']; $part_size = $part_size ?? $this->settings['download']['part_size'];
$percent = 0; $parallel_chunks = $this->settings['download']['parallel_chunks'] ? $this->settings['download']['parallel_chunks'] : 3000;
$datacenter = isset($message_media['InputFileLocation']['dc_id']) ? $message_media['InputFileLocation']['dc_id'] : $this->settings['connection_settings']['default_dc']; $datacenter = isset($message_media['InputFileLocation']['dc_id']) ? $message_media['InputFileLocation']['dc_id'] : $this->settings['connection_settings']['default_dc'];
if (isset($this->datacenter->sockets[$datacenter.'_media'])) { if (isset($this->datacenter->sockets[$datacenter.'_media'])) {
$datacenter .= '_media'; $datacenter .= '_media';
} }
if (isset($message_media['key'])) { if (isset($message_media['key'])) {
$digest = hash('md5', $message_media['key'].$message_media['iv'], true); $digest = hash('md5', $message_media['key'].$message_media['iv'], true);
$fingerprint = $this->unpack_signed_int(substr($digest, 0, 4) ^ substr($digest, 4, 4)); $fingerprint = $this->unpack_signed_int(substr($digest, 0, 4) ^ substr($digest, 4, 4));
@ -597,43 +958,126 @@ trait Files
$ige->setIV($message_media['iv']); $ige->setIV($message_media['iv']);
$ige->setKey($message_media['key']); $ige->setKey($message_media['key']);
$ige->enableContinuousBuffer(); $ige->enableContinuousBuffer();
$parallelize = false;
} }
$theend = false;
if ($offset === $end) {
$cb(100);
return true;
}
$params = [];
$start_at = $offset % $part_size;
$probable_end = $end !== -1 ? $end : 512 * 1024 * 3000;
$breakOut = false;
for ($x = $offset - $start_at; $x < $probable_end; $x += $part_size) {
$end_at = $part_size;
if ($end !== -1 && $x + $part_size > $end) {
$end_at = $end % $part_size;
$breakOut = true;
}
$params[] = [
'offset' => $x,
'limit' => $part_size,
'part_start_at' => $start_at,
'part_end_at' => $end_at,
];
$start_at = 0;
if ($breakOut) {
break;
}
}
if (!$params) {
$cb(100);
return true;
}
$count = count($params);
$cb = function () use ($cb, $count) {
static $cur = 0;
$cur++;
$this->callFork($cb($cur * 100 / $count));
};
$cdn = false; $cdn = false;
while (true) { $params[0]['previous_promise'] = new Success(true);
if ($start_at = $offset % $part_size) {
$offset -= $start_at; $start = microtime(true);
$size = yield $this->download_part($message_media, $cdn, $datacenter, $old_dc, $ige, $cb, array_shift($params), $callable, $parallelize);
if ($params) {
$previous_promise = new Success(true);
$promises = [];
foreach ($params as $key => $param) {
$param['previous_promise'] = $previous_promise;
$previous_promise = $this->call($this->download_part($message_media, $cdn, $datacenter, $old_dc, $ige, $cb, $param, $callable, $parallelize));
$previous_promise->onResolve(static function ($e, $res) use (&$size) {
if ($res) {
$size += $res;
}
});
$promises[] = $previous_promise;
if (!($key % $parallel_chunks)) { // 20 mb at a time, for a typical bandwidth of 1gbps
yield $this->all($promises);
$promises = [];
$time = microtime(true) - $start;
$speed = (int) (($size * 8) / $time) / 1000000;
$this->logger->logger("Partial download time: $time");
$this->logger->logger("Partial download speed: $speed mbps");
}
}
if ($promises) {
yield $this->all($promises);
}
}
$time = microtime(true) - $start;
$speed = (int) (($size * 8) / $time) / 1000000;
$this->logger->logger("Total download time: $time");
$this->logger->logger("Total download speed: $speed mbps");
if ($cdn) {
$this->clear_cdn_hashes($message_media['file_token']);
}
return true;
}
private function download_part(&$message_media, &$cdn, &$datacenter, &$old_dc, &$ige, $cb, $offset, $callable, $seekable, $postpone = false)
{
static $method = [
false => 'upload.getFile', // non-cdn
true => 'upload.getCdnFile', // cdn
];
do {
if (!$cdn) {
$basic_param = [
'location' => $message_media['InputFileLocation'],
];
} else {
$basic_param = [
'file_token' => $message_media['file_token'],
];
} }
try { try {
$res = $cdn ? $res = yield $this->method_call_async_read(
yield $this->method_call_async_read( $method[$cdn],
'upload.getCdnFile', $basic_param + $offset,
[
'file_token' => $message_media['file_token'],
'offset' => $offset,
'limit' => $part_size,
],
[
'heavy' => true,
'file' => true,
'FloodWaitLimit' => 0,
'datacenter' => $datacenter,
]
) :
yield $this->method_call_async_read(
'upload.getFile',
[
'location' => $message_media['InputFileLocation'],
'offset' => $offset,
'limit' => $part_size,
],
[ [
'heavy' => true, 'heavy' => true,
'file' => true, 'file' => true,
'FloodWaitLimit' => 0, 'FloodWaitLimit' => 0,
'datacenter' => &$datacenter, 'datacenter' => &$datacenter,
'postpone' => $postpone,
] ]
); );
} catch (\danog\MadelineProto\RPCErrorException $e) { } catch (\danog\MadelineProto\RPCErrorException $e) {
@ -658,6 +1102,7 @@ trait Files
throw $e; throw $e;
} }
} }
if ($res['_'] === 'upload.fileCdnRedirect') { if ($res['_'] === 'upload.fileCdnRedirect') {
$cdn = true; $cdn = true;
$message_media['file_token'] = $res['file_token']; $message_media['file_token'] = $res['file_token'];
@ -670,9 +1115,7 @@ trait Files
yield $this->get_config_async([], ['datacenter' => $this->datacenter->curdc]); yield $this->get_config_async([], ['datacenter' => $this->datacenter->curdc]);
} }
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['stored_on_cdn'], \danog\MadelineProto\Logger::NOTICE); $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['stored_on_cdn'], \danog\MadelineProto\Logger::NOTICE);
continue; } else if ($res['_'] === 'upload.cdnFileReuploadNeeded') {
}
if ($res['_'] === 'upload.cdnFileReuploadNeeded') {
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['cdn_reupload'], \danog\MadelineProto\Logger::NOTICE); $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['cdn_reupload'], \danog\MadelineProto\Logger::NOTICE);
yield $this->get_config_async([], ['datacenter' => $this->datacenter->curdc]); yield $this->get_config_async([], ['datacenter' => $this->datacenter->curdc]);
@ -691,51 +1134,35 @@ trait Files
continue; continue;
} }
if ($cdn === false && $res['type']['_'] === 'storage.fileUnknown' && $res['bytes'] === '') { if ($cdn === false && $res['type']['_'] === 'storage.fileUnknown' && $res['bytes'] === '') {
$datacenter = 1; $datacenter = 0;
} }
while ($cdn === false && $res['type']['_'] === 'storage.fileUnknown' && $res['bytes'] === '') { while ($cdn === false &&
$res = yield $this->method_call_async_read('upload.getFile', ['location' => $message_media['InputFileLocation'], 'offset' => $offset, 'limit' => $part_size], ['heavy' => true, 'datacenter' => $datacenter]); $res['type']['_'] === 'storage.fileUnknown' &&
$datacenter++; $res['bytes'] === '' &&
if (!isset($this->datacenter->sockets[$datacenter])) { isset($this->datacenter->sockets[++$datacenter])
break; ) {
} $res = yield $this->method_call_async_read('upload.getFile', $basic_param + $offset, ['heavy' => true, 'file' => true, 'FloodWaitLimit' => 0, 'datacenter' => $datacenter]);
} }
if (isset($message_media['cdn_key'])) { if (isset($message_media['cdn_key'])) {
$ivec = substr($message_media['cdn_iv'], 0, 12).pack('N', $offset >> 4); $ivec = substr($message_media['cdn_iv'], 0, 12).pack('N', $offset['offset'] >> 4);
$res['bytes'] = $this->ctr_encrypt($res['bytes'], $message_media['cdn_key'], $ivec); $res['bytes'] = $this->ctr_encrypt($res['bytes'], $message_media['cdn_key'], $ivec);
$this->check_cdn_hash($message_media['file_token'], $offset, $res['bytes'], $old_dc); $this->check_cdn_hash($message_media['file_token'], $offset['offset'], $res['bytes'], $old_dc);
} }
if (isset($message_media['key'])) { if (isset($message_media['key'])) {
$res['bytes'] = $ige->decrypt($res['bytes']); $res['bytes'] = $ige->decrypt($res['bytes']);
} }
if ($start_at) { if ($offset['part_start_at'] || $offset['part_end_at'] !== $offset['limit']) {
$res['bytes'] = substr($res['bytes'], $start_at); $res['bytes'] = substr($res['bytes'], $offset['part_start_at'], $offset['part_end_at'] - $offset['part_start_at']);
} }
if ($end !== -1 && strlen($res['bytes']) + $downloaded_size >= $size) {
$res['bytes'] = substr($res['bytes'], 0, $size - $downloaded_size);
$theend = true;
}
if ($res['bytes'] === '') {
break;
}
$offset += strlen($res['bytes']);
$downloaded_size += strlen($res['bytes']);
$this->logger->logger(fwrite($stream, $res['bytes']), \danog\MadelineProto\Logger::ULTRA_VERBOSE);
if ($theend) {
break;
}
if ($end !== -1) {
$this->callFork($cb($percent = $downloaded_size * 100 / $size));
}
}
if ($end === -1) {
$this->callFork($cb(100));
}
if ($cdn) {
$this->clear_cdn_hashes($message_media['file_token']);
}
return true; if (!$seekable) {
yield $offset['previous_promise'];
}
$res = yield $callable((string) $res['bytes'], $offset['offset'] + $offset['part_start_at']);
$cb();
return $res;
} while (true);
} }
private $cdn_hashes = []; private $cdn_hashes = [];

View File

@ -376,6 +376,10 @@ trait ResponseHandler
return; return;
} }
if (in_array($response['error_message'], ['MSGID_DECREASE_RETRY', 'RPC_CALL_FAIL', 'RPC_MCGET_FAIL', 'no workers running'])) {
Loop::delay(1 * 1000, [$this, 'method_recall'], ['message_id' => $request_id, 'datacenter' => $datacenter]);
return;
}
$this->got_response_for_outgoing_message_id($request_id, $datacenter); $this->got_response_for_outgoing_message_id($request_id, $datacenter);
$this->handle_reject($datacenter, $request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], isset($request['_']) ? $request['_'] : '')); $this->handle_reject($datacenter, $request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], isset($request['_']) ? $request['_'] : ''));

View File

@ -23,10 +23,10 @@ use Amp\DoH\DoHConfig;
use Amp\DoH\Nameserver; use Amp\DoH\Nameserver;
use Amp\DoH\Rfc8484StubResolver; use Amp\DoH\Rfc8484StubResolver;
use Amp\Loop; use Amp\Loop;
use function Amp\ByteStream\getInputBufferStream;
use function Amp\ByteStream\getStdin;
use function Amp\Dns\resolver; use function Amp\Dns\resolver;
use function Amp\Promise\wait; use function Amp\Promise\wait;
use function Amp\ByteStream\getStdin;
use function Amp\ByteStream\getInputBufferStream;
class Magic class Magic
{ {
@ -68,6 +68,14 @@ class Magic
set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']); set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
set_exception_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionHandler']); set_exception_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionHandler']);
if (!self::$inited) { if (!self::$inited) {
if (!defined('\\phpseclib\\Crypt\\Common\\SymmetricKey::MODE_IGE') || \phpseclib\Crypt\Common\SymmetricKey::MODE_IGE !== 7) {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['phpseclib_fork']);
}
foreach (['intl', 'xml', 'fileinfo', 'json', 'mbstring'] as $extension) {
if (!extension_loaded($extension)) {
throw Exception::extension($extension);
}
}
self::$has_thread = class_exists('\\Thread') && method_exists('\\Thread', 'getCurrentThread'); self::$has_thread = class_exists('\\Thread') && method_exists('\\Thread', 'getCurrentThread');
self::$BIG_ENDIAN = pack('L', 1) === pack('N', 1); self::$BIG_ENDIAN = pack('L', 1) === pack('N', 1);
self::$bigint = PHP_INT_SIZE < 8; self::$bigint = PHP_INT_SIZE < 8;
@ -147,14 +155,14 @@ class Magic
Loop::onSignal(SIGINT, static function () { Loop::onSignal(SIGINT, static function () {
getStdin()->unreference(); getStdin()->unreference();
getInputBufferStream()->unreference(); getInputBufferStream()->unreference();
Logger::log('Got sigint', Logger::FATAL_ERROR); Logger::log('Got sigint', Logger::FATAL_ERROR);
die(); die();
}); });
/*Loop::onSignal(SIGTERM, static function () { /*Loop::onSignal(SIGTERM, static function () {
Logger::log('Got sigterm', Logger::FATAL_ERROR); Logger::log('Got sigterm', Logger::FATAL_ERROR);
Loop::stop(); Loop::stop();
die(); die();
});*/ });*/
} }
if (!self::$altervista && !self::$zerowebhost) { if (!self::$altervista && !self::$zerowebhost) {
$DohConfig = new DoHConfig( $DohConfig = new DoHConfig(

View File

@ -24,6 +24,9 @@ use danog\MadelineProto\Exception;
use danog\MadelineProto\Stream\Async\RawStream; use danog\MadelineProto\Stream\Async\RawStream;
use danog\MadelineProto\Stream\ConnectionContext; use danog\MadelineProto\Stream\ConnectionContext;
use function Amp\Socket\connect; use function Amp\Socket\connect;
use danog\MadelineProto\Stream\BufferedStreamInterface;
use danog\MadelineProto\Stream\BufferInterface;
use danog\MadelineProto\Stream\RawStreamInterface;
use Amp\ByteStream\ClosedException; use Amp\ByteStream\ClosedException;
/** /**
@ -31,7 +34,7 @@ use Amp\ByteStream\ClosedException;
* *
* @author Daniil Gentili <daniil@daniil.it> * @author Daniil Gentili <daniil@daniil.it>
*/ */
class BufferedRawStream implements \danog\MadelineProto\Stream\BufferedStreamInterface, \danog\MadelineProto\Stream\BufferInterface, \danog\MadelineProto\Stream\RawStreamInterface class BufferedRawStream implements BufferedStreamInterface, BufferInterface, RawStreamInterface
{ {
use RawStream; use RawStream;

View File

@ -0,0 +1,77 @@
<?php
/**
* Buffered raw stream.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto 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 Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2019 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream\Common;
use Amp\Promise;
use Amp\Success;
use danog\MadelineProto\Exception;
use danog\MadelineProto\Stream\Async\RawStream;
use danog\MadelineProto\Stream\ConnectionContext;
use function Amp\Socket\connect;
use danog\MadelineProto\Stream\BufferedStreamInterface;
use danog\MadelineProto\Stream\BufferInterface;
use danog\MadelineProto\Stream\RawStreamInterface;
/**
* Buffered raw stream.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class SimpleBufferedRawStream extends BufferedRawStream implements BufferedStreamInterface, BufferInterface, RawStreamInterface
{
/**
* Read data asynchronously.
*
* @param int $length Amount of data to read
*
* @return \Generator
*/
public function bufferReadAsync(int $length): \Generator
{
$size = fstat($this->memory_stream)['size'];
$offset = ftell($this->memory_stream);
$buffer_length = $size - $offset;
if ($buffer_length < $length && $buffer_length) {
fseek($this->memory_stream, $offset + $buffer_length);
}
while ($buffer_length < $length) {
$chunk = yield $this->read();
if ($chunk === null) {
fseek($this->memory_stream, $offset);
break;
}
fwrite($this->memory_stream, $chunk);
$buffer_length += strlen($chunk);
}
fseek($this->memory_stream, $offset);
return fread($this->memory_stream, $length);
}
/**
* Get class name.
*
* @return string
*/
public static function getName(): string
{
return __CLASS__;
}
}

View File

@ -22,6 +22,8 @@ use Amp\CancellationToken;
use Amp\Socket\ClientConnectContext; use Amp\Socket\ClientConnectContext;
use Amp\Uri\Uri; use Amp\Uri\Uri;
use danog\MadelineProto\Stream\Transport\DefaultStream; use danog\MadelineProto\Stream\Transport\DefaultStream;
use danog\MadelineProto\Stream\MTProtoTransport\ObfuscatedStream;
use danog\MadelineProto\Exception;
/** /**
* Connection context class. * Connection context class.
@ -217,6 +219,7 @@ class ConnectionContext
{ {
return $this->isDns; return $this->isDns;
} }
/** /**
* Whether this connection context will only be used by the DNS client * Whether this connection context will only be used by the DNS client
* *
@ -261,6 +264,10 @@ class ConnectionContext
*/ */
public function setDc($dc): self public function setDc($dc): self
{ {
$int = intval($dc);
if (!(1 <= $int && $int <= 1000)) {
throw new Exception("Invalid DC id provided: $dc");
}
$this->dc = $dc; $this->dc = $dc;
return $this; return $this;
@ -363,6 +370,23 @@ class ConnectionContext
return $obj; return $obj;
} }
/**
* Get the inputClientProxy proxy MTProto object
*
* @return array
*/
public function getInputClientProxy(): ?array
{
foreach ($this->nextStreams as $couple) {
list($streamName, $extra) = $couple;
if ($streamName === ObfuscatedStream::getName() && isset($extra['address'])) {
$extra['_'] = 'inputClientProxy';
return $extra;
}
}
return null;
}
/** /**
* Get a description "name" of the context. * Get a description "name" of the context.
* *

View File

@ -24,6 +24,9 @@ namespace danog\MadelineProto\Stream\MTProtoTools;
*/ */
trait MsgIdHandler trait MsgIdHandler
{ {
public $max_incoming_id;
public $max_outgoing_id;
public function check_message_id($new_message_id, $aargs) public function check_message_id($new_message_id, $aargs)
{ {
if (!is_object($new_message_id)) { if (!is_object($new_message_id)) {

View File

@ -26,6 +26,11 @@ trait SeqNoHandler
{ {
use \danog\MadelineProto\MTProtoTools\SeqNoHandler; use \danog\MadelineProto\MTProtoTools\SeqNoHandler;
public $session_out_seq_no = 0;
public $session_in_seq_no = 0;
public $session_id;
public function generate_out_seq_no($content_related) public function generate_out_seq_no($content_related)
{ {
$in = $content_related ? 1 : 0; $in = $content_related ? 1 : 0;

View File

@ -0,0 +1,59 @@
<?php
/**
* Session module.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto 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 Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2019 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream\MTProtoTools;
/**
* Manages MTProto session-specific data
*/
class Session
{
use MsgIdHandler;
use SaltHandler;
use SeqNoHandler;
public $incoming_messages = [];
public $outgoing_messages = [];
public $new_incoming = [];
public $new_outgoing = [];
public $http_req_count = 0;
public $http_res_count = 0;
public $last_http_wait = 0;
private $last_chunk = 0;
public $time_delta = 0;
public $call_queue = [];
public $ack_queue = [];
public function haveRead()
{
$this->last_chunk = microtime(true);
}
/**
* Get the receive date of the latest chunk of data from the socket
*
* @return void
*/
public function getLastChunk()
{
return $this->last_chunk;
}
}

View File

@ -47,7 +47,7 @@ class DefaultStream extends Socket implements RawStreamInterface, ProxyStreamInt
public function enableCrypto(ClientTlsContext $tlsContext = null): \Amp\Promise public function enableCrypto(ClientTlsContext $tlsContext = null): \Amp\Promise
{ {
return $this->enableCrypto($tlsContext); return $this->stream->enableCrypto($tlsContext);
} }
public function getStream() public function getStream()

View File

@ -0,0 +1,136 @@
<?php
/**
* Premade stream wrapper.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto 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 Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2019 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream\Transport;
use Amp\Promise;
use Amp\Socket\Socket;
use danog\MadelineProto\Stream\Async\RawStream;
use danog\MadelineProto\Stream\RawStreamInterface;
use function Amp\Socket\connect;
use function Amp\Socket\cryptoConnect;
use danog\MadelineProto\Stream\ProxyStreamInterface;
use Amp\ByteStream\ClosedException;
use danog\MadelineProto\Stream\ConnectionContext;
/**
* Premade stream wrapper.
*
* Manages reading data in chunks
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class PremadeStream extends Socket implements RawStreamInterface, ProxyStreamInterface
{
use RawStream;
private $stream;
public function __construct()
{
}
public function enableCrypto(ClientTlsContext $tlsContext = null): \Amp\Promise
{
return $this->stream->enableCrypto($tlsContext);
}
public function getStream()
{
return $this->stream;
}
public function connectAsync(ConnectionContext $ctx, string $header = ''): \Generator
{
if ($header !== '') {
yield $this->stream->write($header);
}
}
/**
* Async chunked read.
*
* @return Promise
*/
public function read(): Promise
{
return $this->stream ? $this->stream->read() : new \Amp\Success(null);
}
/**
* Async write.
*
* @param string $data Data to write
*
* @return Promise
*/
public function write(string $data): Promise
{
if (!$this->stream) {
throw new ClosedException("MadelineProto stream was disconnected");
}
return $this->stream->write($data);
}
/**
* Async close.
*
* @return Generator
*/
public function disconnect()
{
try {
if ($this->stream) {
if (method_exists($this->stream, 'close')) {
$this->stream->close();
}
$this->stream = null;
}
} catch (\Throwable $e) {
\danog\MadelineProto\Logger::log('Got exception while closing stream: '.$e->getMessage());
} catch (\Exception $e) {
\danog\MadelineProto\Logger::log('Got exception while closing stream: '.$e->getMessage());
}
}
public function close()
{
$this->disconnect();
}
/**
* {@inheritdoc}
*
* @return \Amp\Socket\Socket
*/
public function getSocket(): \Amp\Socket\Socket
{
return $this->stream;
}
/**
* {@inheritdoc}
*/
public function setExtra($extra)
{
$this->stream = $extra;
}
public static function getName(): string
{
return __CLASS__;
}
}

View File

@ -536,9 +536,6 @@ trait BotAPI
$arguments['message'] = trim($this->html_fixtags($arguments['message'])); $arguments['message'] = trim($this->html_fixtags($arguments['message']));
$dom = new \DOMDocument(); $dom = new \DOMDocument();
if (!extension_loaded('mbstring')) {
throw new \danog\MadelineProto\Exception(['extension', 'mbstring']);
}
$dom->loadHTML(mb_convert_encoding($arguments['message'], 'HTML-ENTITIES', 'UTF-8')); $dom->loadHTML(mb_convert_encoding($arguments['message'], 'HTML-ENTITIES', 'UTF-8'));
if (!isset($arguments['entities'])) { if (!isset($arguments['entities'])) {
$arguments['entities'] = []; $arguments['entities'] = [];

View File

@ -463,7 +463,13 @@ trait TL
} }
} elseif ($method === 'messages.sendEncryptedFile') { } elseif ($method === 'messages.sendEncryptedFile') {
if (isset($arguments['file'])) { if (isset($arguments['file'])) {
if (!is_array($arguments['file']) && $this->settings['upload']['allow_automatic_upload']) { if (
(
!is_array($arguments['file']) ||
!(isset($arguments['file']['_']) && $this->constructors->find_by_predicate($arguments['file']['_']) === 'InputEncryptedFile')
) &&
$this->settings['upload']['allow_automatic_upload']
) {
$arguments['file'] = yield $this->upload_encrypted_async($arguments['file']); $arguments['file'] = yield $this->upload_encrypted_async($arguments['file']);
} }
if (isset($arguments['file']['key'])) { if (isset($arguments['file']['key'])) {
@ -622,7 +628,16 @@ trait TL
}); });
} }
if (!is_array($arguments[$current_argument['name']]) && $current_argument['type'] === 'InputFile' && $this->settings['upload']['allow_automatic_upload']) { if ($current_argument['type'] === 'InputFile'
&& (
!is_array($arguments[$current_argument['name']])
|| !(
isset($arguments[$current_argument['name']]['_'])
&& $this->constructors->find_by_predicate($arguments[$current_argument['name']]['_']) === 'InputFile'
)
)
&& $this->settings['upload']['allow_automatic_upload']
) {
$arguments[$current_argument['name']] = yield $this->upload_async($arguments[$current_argument['name']]); $arguments[$current_argument['name']] = yield $this->upload_async($arguments[$current_argument['name']]);
} }

View File

@ -23,7 +23,6 @@ use Amp\Failure;
use Amp\Loop; use Amp\Loop;
use Amp\Promise; use Amp\Promise;
use Amp\Success; use Amp\Success;
use function Amp\ByteStream\getOutputBufferStream;
use function Amp\ByteStream\getStdin; use function Amp\ByteStream\getStdin;
use function Amp\ByteStream\getStdout; use function Amp\ByteStream\getStdout;
use function Amp\Promise\all; use function Amp\Promise\all;
@ -32,6 +31,10 @@ use function Amp\Promise\first;
use function Amp\Promise\some; use function Amp\Promise\some;
use function Amp\Promise\timeout; use function Amp\Promise\timeout;
use function Amp\Promise\wait; use function Amp\Promise\wait;
use function Amp\ByteStream\getOutputBufferStream;
use function Amp\File\exists;
use function Amp\File\touch;
use Amp\File\StatCache;
use phpseclib\Math\BigInteger; use phpseclib\Math\BigInteger;
/** /**
@ -368,7 +371,50 @@ trait Tools
return $deferred->promise(); return $deferred->promise();
} }
/**
* Asynchronously lock a file
* Resolves with a callbable that MUST eventually be called in order to release the lock
*
* @param string $file File to lock
* @param integer $operation Locking mode (see flock)
* @param numeric $polling Polling interval for lock
* @return Promise
*/
public static function flock(string $file, int $operation, $polling = 0.1): Promise
{
return self::call(self::flockAsync($file, $operation, $polling));
}
public static function noCache(int $status, string $message)
{
header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0');
header('Cache-Control: post-check=0, pre-check=0', false);
header('Pragma: no-cache');
http_response_code($status);
return self::echo($message);
}
public static function flockAsync(string $file, int $operation, $polling)
{
if (!yield exists($file)) {
yield touch($file);
StatCache::clear($file);
}
$operation |= LOCK_NB;
$res = fopen($file, 'c');
do {
$result = flock($res, $operation);
if (!$result) {
yield self::sleep($polling);
}
} while (!$result);
return static function () use (&$res) {
if ($res) {
flock($res, LOCK_UN);
fclose($res);
$res = null;
}
};
}
public static function sleep($time) public static function sleep($time)
{ {
return new \Amp\Delayed($time * 1000); return new \Amp\Delayed($time * 1000);

View File

@ -47,7 +47,7 @@ trait AuthKeyHandler
public function request_call_async($user) public function request_call_async($user)
{ {
if (!class_exists('\\danog\\MadelineProto\\VoIP')) { if (!class_exists('\\danog\\MadelineProto\\VoIP')) {
throw new \danog\MadelineProto\Exception(['extension', 'libtgvoip']); throw \danog\MadelineProto\Exception::extension('libtgvoip');
} }
$user = yield $this->get_info_async($user); $user = yield $this->get_info_async($user);
if (!isset($user['InputUser']) || $user['InputUser']['_'] === 'inputUserSelf') { if (!isset($user['InputUser']) || $user['InputUser']['_'] === 'inputUserSelf') {
@ -114,7 +114,7 @@ trait AuthKeyHandler
public function confirm_call_async($params) public function confirm_call_async($params)
{ {
if (!class_exists('\\danog\\MadelineProto\\VoIP')) { if (!class_exists('\\danog\\MadelineProto\\VoIP')) {
throw new \danog\MadelineProto\Exception(['extension', 'libtgvoip']); throw \danog\MadelineProto\Exception::extension('libtgvoip');
} }
if ($this->call_status($params['id']) !== \danog\MadelineProto\VoIP::CALL_STATE_REQUESTED) { if ($this->call_status($params['id']) !== \danog\MadelineProto\VoIP::CALL_STATE_REQUESTED) {
$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['call_error_2'], $params['id'])); $this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['call_error_2'], $params['id']));
@ -163,7 +163,7 @@ trait AuthKeyHandler
public function complete_call_async($params) public function complete_call_async($params)
{ {
if (!class_exists('\\danog\\MadelineProto\\VoIP')) { if (!class_exists('\\danog\\MadelineProto\\VoIP')) {
throw new \danog\MadelineProto\Exception(['extension', 'libtgvoip']); throw \danog\MadelineProto\Exception::extension('libtgvoip');
} }
if ($this->call_status($params['id']) !== \danog\MadelineProto\VoIP::CALL_STATE_ACCEPTED || !isset($this->calls[$params['id']]->storage['b'])) { if ($this->call_status($params['id']) !== \danog\MadelineProto\VoIP::CALL_STATE_ACCEPTED || !isset($this->calls[$params['id']]->storage['b'])) {
$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['call_error_3'], $params['id'])); $this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['call_error_3'], $params['id']));
@ -198,7 +198,7 @@ trait AuthKeyHandler
public function call_status($id) public function call_status($id)
{ {
if (!class_exists('\\danog\\MadelineProto\\VoIP')) { if (!class_exists('\\danog\\MadelineProto\\VoIP')) {
throw new \danog\MadelineProto\Exception(['extension', 'libtgvoip']); throw \danog\MadelineProto\Exception::extension('libtgvoip');
} }
if (isset($this->calls[$id])) { if (isset($this->calls[$id])) {
return $this->calls[$id]->getCallState(); return $this->calls[$id]->getCallState();
@ -210,7 +210,7 @@ trait AuthKeyHandler
public function get_call($call) public function get_call($call)
{ {
if (!class_exists('\\danog\\MadelineProto\\VoIP')) { if (!class_exists('\\danog\\MadelineProto\\VoIP')) {
throw new \danog\MadelineProto\Exception(['extension', 'libtgvoip']); throw \danog\MadelineProto\Exception::extension('libtgvoip');
} }
return $this->calls[$call]; return $this->calls[$call];
@ -219,7 +219,7 @@ trait AuthKeyHandler
public function discard_call_async($call, $reason, $rating = [], $need_debug = true) public function discard_call_async($call, $reason, $rating = [], $need_debug = true)
{ {
if (!class_exists('\\danog\\MadelineProto\\VoIP')) { if (!class_exists('\\danog\\MadelineProto\\VoIP')) {
throw new \danog\MadelineProto\Exception(['extension', 'libtgvoip']); throw \danog\MadelineProto\Exception::extension('libtgvoip');
} }
if (!isset($this->calls[$call['id']])) { if (!isset($this->calls[$call['id']])) {
return; return;

View File

@ -175,12 +175,13 @@ trait Loop
return; return;
} }
$this->logger->logger($message); $this->logger->logger($message);
$buffer = @ob_get_contents();
@ob_end_clean(); @ob_end_clean();
header('Connection: close'); header('Connection: close');
ignore_user_abort(true); ignore_user_abort(true);
ob_start(); $buffer .= '<html><body><h1>'.htmlentities($message).'</h1></body></html>';
echo '<html><body><h1>'.$message.'</h1></body</html>'; echo $buffer;
$size = ob_get_length(); $size = max(ob_get_length(), strlen($buffer));
header("Content-Length: $size"); header("Content-Length: $size");
header('Content-Type: text/html'); header('Content-Type: text/html');
ob_end_flush(); ob_end_flush();

View File

@ -58,6 +58,15 @@ try {
$MadelineProto->accept_tos(); $MadelineProto->accept_tos();
} }
} }
$inputMediaUploadedPhoto1 = ['_' => 'inputMediaUploadedPhoto','file' => '1.jpg'];
$inputMediaUploadedPhoto2 = ['_' => 'inputMediaUploadedPhoto','file' => '2.jpg'];
$inputMediaUploadedPhoto3 = ['_' => 'inputMediaUploadedPhoto','file' => '3.jpg'];
$inputSingleMedia1 = ['_' => 'inputSingleMedia', 'media' => $inputMediaUploadedPhoto1, 'message' => 'str'];
$inputSingleMedia2 = ['_' => 'inputSingleMedia', 'media' => $inputMediaUploadedPhoto2, 'message' => 'str'];
$inputSingleMedia3 = ['_' => 'inputSingleMedia', 'media' => $inputMediaUploadedPhoto3, 'message' => 'str'];
$Updates = $this->messages->sendMultiMedia(['peer' => 'danogentili','multi_media' => [$inputSingleMedia3, $inputSingleMedia2,$inputSingleMedia1]]);
//var_dump(count($MadelineProto->get_pwr_chat('@madelineproto')['participants'])); //var_dump(count($MadelineProto->get_pwr_chat('@madelineproto')['participants']));
/* /*