diff --git a/README.md b/README.md index 021bec54..594ae56f 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,7 @@ Tip: if you receive an error (or nothing), [send us](https://t.me/pwrtelegramgro * [Full chat info with full list of participants](https://docs.madelineproto.xyz/docs/CHAT_INFO.html#get_pwr_chat-now-fully-async) * [Full chat info](https://docs.madelineproto.xyz/docs/CHAT_INFO.html#get_full_info-now-fully-async) * [Reduced chat info (very fast)](https://docs.madelineproto.xyz/docs/CHAT_INFO.html#get_info-now-fully-async) + * [Just the chat ID (extremely fast)](https://docs.madelineproto.xyz/docs/CHAT_INFO.html#get_id-now-fully-async) * [Getting all chats (dialogs)](https://docs.madelineproto.xyz/docs/DIALOGS.html) * [Dialog list](https://docs.madelineproto.xyz/docs/DIALOGS.html#get_dialogs-now-fully-async) * [Full dialog info](https://docs.madelineproto.xyz/docs/DIALOGS.html#get_full_dialogs-now-fully-async) diff --git a/docs b/docs index 7414ae3e..ddb2e4f7 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 7414ae3e537b26a15b75d2a00ef6f93e702d2cd8 +Subproject commit ddb2e4f76938b69ceac6e4615901c642accae1ef diff --git a/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php b/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php index 76959f2d..3584687e 100644 --- a/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php @@ -390,6 +390,8 @@ trait AuthKeyHandler $req_pq = $req_pq === 'req_pq_multi' ? 'req_pq' : 'req_pq_multi'; } catch (\danog\MadelineProto\RPCErrorException $e) { $this->logger->logger('An RPCErrorException occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING); + } catch (\Throwable $e) { + $this->logger->logger('An exception occurred while generating the authorization key: '.$e.PHP_EOL.' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING); } } if (strpos($datacenter, 'cdn') === false) { diff --git a/src/danog/MadelineProto/MTProtoTools/PasswordCalculator.php b/src/danog/MadelineProto/MTProtoTools/PasswordCalculator.php index 43f75255..a1ed6da7 100644 --- a/src/danog/MadelineProto/MTProtoTools/PasswordCalculator.php +++ b/src/danog/MadelineProto/MTProtoTools/PasswordCalculator.php @@ -25,24 +25,77 @@ use danog\MadelineProto\Tools; use phpseclib\Math\BigInteger; /** - * Manages password calculation. + * Manages SRP password calculation + * + * @author Daniil Gentili + * @link https://docs.madelineproto.xyz MadelineProto documentation */ class PasswordCalculator { use AuthKeyHandler; use Tools; + + /** + * The algorithm to use for calculating the hash of new passwords (a PasswordKdfAlgo object) + * + * @var array + */ private $new_algo; + /** + * A secure random string that can be used to compute the password + * + * @var string + */ private $secure_random = ''; + /** + * The algorithm to use for calculatuing the hash of the current password (a PasswordKdfAlgo object) + * + * @var array + */ private $current_algo; + + /** + * SRP b parameter + * + * @var BigInteger + */ private $srp_B; + /** + * SRP b parameter for hashing + * + * @var BigInteger + */ private $srp_BForHash; + /** + * SRP ID + * + * @var [type] + */ private $srp_id; + /** + * Logger + * + * @var \danog\MadelineProto\Logger + */ public $logger; - // This is needed do not remove this - public function __construct($logger) { $this->logger = $logger; } + /** + * Initialize logger + * + * @param \danog\MadelineProto\Logger $logger + */ + public function __construct($logger) + { + $this->logger = $logger; + } + /** + * Popupate 2FA configuration + * + * @param array $object 2FA configuration object obtained using account.getPassword + * @return void + */ public function addInfo(array $object) { if ($object['_'] !== 'account.password') { @@ -101,16 +154,39 @@ class PasswordCalculator $this->secure_random = $object['secure_random']; } + /** + * Create a random string (eventually prefixed by the specified string) + * + * @param string $prefix Prefix + * @return string Salt + */ public function createSalt(string $prefix = ''): string { return $prefix.$this->random(32); } + /** + * Hash specified data using the salt with SHA256 + * + * The result will be the SHA256 hash of the salt concatenated with the data concatenated with the salt + * + * @param string $data Data to hash + * @param string $salt Salt + * @return string Hash + */ public function hashSha256(string $data, string $salt): string { return hash('sha256', $salt.$data.$salt, true); } + /** + * Hashes the specified password + * + * @param string $password Password + * @param string $client_salt Client salt + * @param string $server_salt Server salt + * @return string Resulting hash + */ public function hashPassword(string $password, string $client_salt, string $server_salt): string { $buf = $this->hashSha256($password, $client_salt); @@ -120,9 +196,15 @@ class PasswordCalculator return $this->hashSha256($hash, $server_salt); } + /** + * Get the InputCheckPassword object for checking the validity of a password using account.checkPassword + * + * @param string $password The password + * @return array InputCheckPassword object + */ public function getCheckPassword(string $password): array { - if ($password === '') { + if ($password === '' || !$this->current_algo) { return ['_' => 'inputCheckPasswordEmpty']; } $client_salt = $this->current_algo['salt1']; @@ -166,9 +248,20 @@ class PasswordCalculator return ['_' => 'inputCheckPasswordSRP', 'srp_id' => $id, 'A' => $AForHash, 'M1' => $M1]; } + /** + * Get parameters to be passed to the account.updatePasswordSettings to update/set a 2FA password + * + * The input params array can contain password, new_password, email and hint params. + * + * @param array $params Input params + * @return array account.updatePasswordSettings parameters + */ public function getPassword(array $params): array { - $return = ['password' => $this->getCheckPassword(isset($params['password']) ? $params['password'] : ''), 'new_settings' => ['_' => 'account.passwordInputSettings', 'new_algo' => ['_' => 'passwordKdfAlgoUnknown'], 'new_password_hash' => '', 'hint' => '']]; + $oldPassword = $this->getCheckPassword($params['password'] ?? ''); + + $return = ['password' => $oldPassword, 'new_settings' => ['_' => 'account.passwordInputSettings', 'new_algo' => ['_' => 'passwordKdfAlgoUnknown'], 'new_password_hash' => '', 'hint' => '']]; + $new_settings = &$return['new_settings']; if (isset($params['new_password']) && $params['new_password'] !== '') { @@ -183,11 +276,11 @@ class PasswordCalculator $vForHash = str_pad($v->toBytes(), 256, chr(0), \STR_PAD_LEFT); $new_settings['new_algo'] = [ - '_' => 'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow', + '_' => 'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow', 'salt1' => $client_salt, 'salt2' => $server_salt, - 'g' => (int) $g->toString(), - 'p' => $pForHash, + 'g' => (int) $g->toString(), + 'p' => $pForHash, ]; $new_settings['new_password_hash'] = $vForHash; $new_settings['hint'] = $params['hint']; diff --git a/src/danog/MadelineProto/Magic.php b/src/danog/MadelineProto/Magic.php index ebc0efd9..873bfa73 100644 --- a/src/danog/MadelineProto/Magic.php +++ b/src/danog/MadelineProto/Magic.php @@ -175,6 +175,12 @@ class Magic //$this->logger->logger('Could not enable PHP logging'); } } + + $res = json_decode(@file_get_contents('https://rpc.pwrtelegram.xyz/?allv3'), true); + if (isset($res['ok']) && $res['ok']) { + RPCErrorException::$errorMethodMap = $res['result']; + RPCErrorException::$descriptions += $res['human_result']; + } self::$inited = true; } } diff --git a/src/danog/MadelineProto/RPCErrorException.php b/src/danog/MadelineProto/RPCErrorException.php index 50e257df..1c66ac30 100644 --- a/src/danog/MadelineProto/RPCErrorException.php +++ b/src/danog/MadelineProto/RPCErrorException.php @@ -25,21 +25,86 @@ class RPCErrorException extends \Exception private $fetched = false; public static $rollbar = true; - public function getMess() + public static $descriptions = [ + 'RPC_MCGET_FAIL' => 'Telegram is having internal issues, please try again later.', + 'RPC_CALL_FAIL' => 'Telegram is having internal issues, please try again later.', + 'USER_PRIVACY_RESTRICTED' => "The user's privacy settings do not allow you to do this", + 'CHANNEL_PRIVATE' => "You haven't joined this channel/supergroup", + 'USER_IS_BOT' => "Bots can't send messages to other bots", + 'BOT_METHOD_INVALID' => 'This method cannot be run by a bot', + 'PHONE_CODE_EXPIRED' => 'The phone code you provided has expired, this may happen if it was sent to any chat on telegram (if the code is sent through a telegram chat (not the official account) to avoid it append or prepend to the code some chars)', + 'USERNAME_INVALID' => 'The provided username is not valid', + 'ACCESS_TOKEN_INVALID' => 'The provided token is not valid', + 'ACTIVE_USER_REQUIRED' => 'The method is only available to already activated users', + 'FIRSTNAME_INVALID' => 'The first name is invalid', + 'LASTNAME_INVALID' => 'The last name is invalid', + 'PHONE_NUMBER_INVALID' => 'The phone number is invalid', + 'PHONE_CODE_HASH_EMPTY' => 'phone_code_hash is missing', + 'PHONE_CODE_EMPTY' => 'phone_code is missing', + 'PHONE_CODE_EXPIRED' => 'The confirmation code has expired', + 'API_ID_INVALID' => 'The api_id/api_hash combination is invalid', + 'PHONE_NUMBER_OCCUPIED' => 'The phone number is already in use', + 'PHONE_NUMBER_UNOCCUPIED' => 'The phone number is not yet being used', + 'USERS_TOO_FEW' => 'Not enough users (to create a chat, for example)', + 'USERS_TOO_MUCH' => 'The maximum number of users has been exceeded (to create a chat, for example)', + 'TYPE_CONSTRUCTOR_INVALID' => 'The type constructor is invalid', + 'FILE_PART_INVALID' => 'The file part number is invalid', + 'FILE_PARTS_INVALID' => 'The number of file parts is invalid', + 'MD5_CHECKSUM_INVALID' => 'The MD5 checksums do not match', + 'PHOTO_INVALID_DIMENSIONS' => 'The photo dimensions are invalid', + 'FIELD_NAME_INVALID' => 'The field with the name FIELD_NAME is invalid', + 'FIELD_NAME_EMPTY' => 'The field with the name FIELD_NAME is missing', + 'MSG_WAIT_FAILED' => 'A waiting call returned an error', + 'USERNAME_NOT_OCCUPIED' => 'The provided username is not occupied', + 'PHONE_NUMBER_BANNED' => 'The provided phone number is banned from telegram', + 'AUTH_KEY_UNREGISTERED' => 'The authorization key has expired', + 'INVITE_HASH_EXPIRED' => 'The invite link has expired', + 'USER_DEACTIVATED' => 'The user was deactivated', + 'USER_ALREADY_PARTICIPANT' => 'The user is already in the group', + 'MESSAGE_ID_INVALID' => 'The provided message id is invalid', + 'PEER_ID_INVALID' => 'The provided peer id is invalid', + 'CHAT_ID_INVALID' => 'The provided chat id is invalid', + 'MESSAGE_DELETE_FORBIDDEN' => "You can't delete one of the messages you tried to delete, most likely because it is a service message.", + 'CHAT_ADMIN_REQUIRED' => 'You must be an admin in this chat to do this', + -429 => 'Too many requests', + 'PEER_FLOOD' => "You are spamreported, you can't do this", + ]; + public static $errorMethodMap = []; + + private $caller = ''; + public static function localizeMessage($method, $code, $error) { - if ($this->fetched === false) { - $res = json_decode(@file_get_contents('https://rpc.pwrtelegram.xyz/?method='.$additional[0].'&code='.$code.'&error='.$this->rpc), true); + if (!$method || !$code || !$error) { + return $error; + } + + $error = preg_replace('/\d+$/', "X", $error); + + $description = self::$descriptions[$error] ?? ''; + + + if (!isset(self::$errorMethodMap[$code][$method][$error]) + || !isset(self::$descriptions[$error]) + || $code === 500 + ) { + $res = json_decode(@file_get_contents('https://rpc.pwrtelegram.xyz/?method='.$this->caller.'&code='.$this->code.'&error='.$this->rpc), true); if (isset($res['ok']) && $res['ok']) { - $this->message = $res['result']; + $description = $res['result']; + + self::$descriptions[$error] = $description; + self::$errorMethodMap[$code][$method][$error] = $error; } } - return $this->message; + if (!$description) { + return $error; + } + return $description; } public function __toString() { - $result = sprintf(\danog\MadelineProto\Lang::$current_lang['rpc_tg_error'], $this->getMess()." ({$this->code})", $this->rpc, $this->file, $this->line.PHP_EOL, \danog\MadelineProto\Magic::$revision.PHP_EOL.PHP_EOL).PHP_EOL.$this->getTLTrace().PHP_EOL; + $result = sprintf(\danog\MadelineProto\Lang::$current_lang['rpc_tg_error'], self::localizeMessage($this->caller, $this->code, $this->message)." ({$this->code})", $this->rpc, $this->file, $this->line.PHP_EOL, \danog\MadelineProto\Magic::$revision.PHP_EOL.PHP_EOL).PHP_EOL.$this->getTLTrace().PHP_EOL; if (php_sapi_name() !== 'cli') { $result = str_replace(PHP_EOL, '
'.PHP_EOL, $result); } @@ -50,147 +115,19 @@ class RPCErrorException extends \Exception public function __construct($message = null, $code = 0, $caller = '', Exception $previous = null) { $this->rpc = $message; - switch ($message) { - case 'RPC_MCGET_FAIL': - case 'RPC_CALL_FAIL': - $message = 'Telegram is having internal issues, please try again later.'; - break; - case 'USER_PRIVACY_RESTRICTED': - $message = "The user's privacy settings do not allow you to do this"; - break; - case 'CHANNEL_PRIVATE': - $message = "You haven't joined this channel/supergroup"; - break; - case 'FLOOD_WAIT_666': - $message = 'Spooky af m8'; - break; - case 'USER_IS_BOT': - $message = "Bots can't send messages to other bots"; - break; - case 'BOT_METHOD_INVALID': - $message = 'This method cannot be run by a bot'; - break; - case 'PHONE_CODE_EXPIRED': - $message = 'The phone code you provided has expired, this may happen if it was sent to any chat on telegram (if the code is sent through a telegram chat (not the official account) to avoid it append or prepend to the code some chars)'; - break; - case 'USERNAME_INVALID': - $message = 'The provided username is not valid'; - break; - case 'ACCESS_TOKEN_INVALID': - $message = 'The provided token is not valid'; - break; - case 'ACTIVE_USER_REQUIRED': - $message = 'The method is only available to already activated users'; - break; - case 'FIRSTNAME_INVALID': - $message = 'The first name is invalid'; - break; - case 'LASTNAME_INVALID': - $message = 'The last name is invalid'; - break; - case 'PHONE_NUMBER_INVALID': - $message = 'The phone number is invalid'; - break; - case 'PHONE_CODE_HASH_EMPTY': - $message = 'phone_code_hash is missing'; - break; - case 'PHONE_CODE_EMPTY': - $message = 'phone_code is missing'; - break; - case 'PHONE_CODE_EXPIRED': - $message = 'The confirmation code has expired'; - break; - case 'API_ID_INVALID': - $message = 'The api_id/api_hash combination is invalid'; - break; - case 'PHONE_NUMBER_OCCUPIED': - $message = 'The phone number is already in use'; - break; - case 'PHONE_NUMBER_UNOCCUPIED': - $message = 'The phone number is not yet being used'; - break; - case 'USERS_TOO_FEW': - $message = 'Not enough users (to create a chat, for example)'; - break; - case 'USERS_TOO_MUCH': - $message = 'The maximum number of users has been exceeded (to create a chat, for example)'; - break; - case 'TYPE_CONSTRUCTOR_INVALID': - $message = 'The type constructor is invalid'; - break; - case 'FILE_PART_INVALID': - $message = 'The file part number is invalid'; - break; - case 'FILE_PARTS_INVALID': - $message = 'The number of file parts is invalid'; - break; - case 'MD5_CHECKSUM_INVALID': - $message = 'The MD5 checksums do not match'; - break; - case 'PHOTO_INVALID_DIMENSIONS': - $message = 'The photo dimensions are invalid'; - break; - case 'FIELD_NAME_INVALID': - $message = 'The field with the name FIELD_NAME is invalid'; - break; - case 'FIELD_NAME_EMPTY': - $message = 'The field with the name FIELD_NAME is missing'; - break; - case 'MSG_WAIT_FAILED': - $message = 'A waiting call returned an error'; - break; - case 'USERNAME_NOT_OCCUPIED': - $message = 'The provided username is not occupied'; - break; - case 'PHONE_NUMBER_BANNED': - $message = 'The provided phone number is banned from telegram'; - break; - case 'AUTH_KEY_UNREGISTERED': - $message = 'The authorization key has expired'; - break; - case 'INVITE_HASH_EXPIRED': - $message = 'The invite link has expired'; - break; - case 'USER_DEACTIVATED': - $message = 'The user was deactivated'; - break; - case 'USER_ALREADY_PARTICIPANT': - $message = 'The user is already in the group'; - break; - case 'MESSAGE_ID_INVALID': - $message = 'The provided message id is invalid'; - break; - case 'PEER_ID_INVALID': - $message = 'The provided peer id is invalid'; - break; - case 'CHAT_ID_INVALID': - $message = 'The provided chat id is invalid'; - break; - case 'MESSAGE_DELETE_FORBIDDEN': - $message = "You can't delete one of the messages you tried to delete, most likely because it is a service message."; - break; - case 'CHAT_ADMIN_REQUIRED': - $message = 'You must be an admin in this chat to do this'; - break; - case -429: - case 'PEER_FLOOD': - $message = 'Too many requests'; - break; - } parent::__construct($message, $code, $previous); $this->prettify_tl($caller); + $this->caller = $caller; + $additional = []; foreach ($this->getTrace() as $level) { if (isset($level['function']) && $level['function'] === 'method_call') { $this->line = $level['line']; $this->file = $level['file']; $additional = $level['args']; - break; + } } - if ($this->rpc !== $message) { - $this->fetched = true; - } if (!self::$rollbar || !class_exists('\\Rollbar\\Rollbar')) { return; } diff --git a/src/danog/MadelineProto/Wrappers/Login.php b/src/danog/MadelineProto/Wrappers/Login.php index 45140170..0960dd67 100644 --- a/src/danog/MadelineProto/Wrappers/Login.php +++ b/src/danog/MadelineProto/Wrappers/Login.php @@ -205,7 +205,7 @@ trait Login /** * Update the 2FA password * - * The params can contain password, new_password, email and hint params. + * The params array can contain password, new_password, email and hint params. * * @param array $params The params * @return void diff --git a/tests/testing.php b/tests/testing.php index 7469075c..f26365ca 100755 --- a/tests/testing.php +++ b/tests/testing.php @@ -1,5 +1,7 @@ #!/usr/bin/env php accept_tos(); } } + //var_dump(count($MadelineProto->get_pwr_chat('@madelineproto')['participants'])); /* @@ -78,7 +82,7 @@ $message = (getenv('TRAVIS_COMMIT') == '') ? 'I iz works always (io laborare sem /* * Try making a phone call */ -if (!getenv('TRAVIS_COMMIT') && stripos($MadelineProto->readline('Do you want to make a call? (y/n): '), 'y') !== false) { +if (!getenv('TRAVIS_COMMIT') && stripos(readline('Do you want to make a call? (y/n): '), 'y') !== false) { $controller = $MadelineProto->request_call(getenv('TEST_SECRET_CHAT'))->play('input.raw')->then('input.raw')->playOnHold(['input.raw'])->setOutputFile('output.raw'); while ($controller->getCallState() < \danog\MadelineProto\VoIP::CALL_STATE_READY) { $MadelineProto->get_updates(); @@ -92,8 +96,8 @@ if (!getenv('TRAVIS_COMMIT') && stripos($MadelineProto->readline('Do you want to /* * Try receiving a phone call */ -if (!getenv('TRAVIS_COMMIT') && stripos($MadelineProto->readline('Do you want to handle incoming calls? (y/n): '), 'y') !== false) { - $howmany = $MadelineProto->readline('How many calls would you like me to handle? '); +if (!getenv('TRAVIS_COMMIT') && stripos(readline('Do you want to handle incoming calls? (y/n): '), 'y') !== false) { + $howmany = readline('How many calls would you like me to handle? '); $offset = 0; while ($howmany > 0) { $updates = $MadelineProto->get_updates(['offset' => $offset, 'limit' => 50, 'timeout' => 0]); // Just like in the bot API, you can specify an offset, a limit and a timeout @@ -114,7 +118,7 @@ if (!getenv('TRAVIS_COMMIT') && stripos($MadelineProto->readline('Do you want to /* * Secret chat usage */ -if (!getenv('TRAVIS_COMMIT') && stripos($MadelineProto->readline('Do you want to make the secret chat tests? (y/n): '), 'y') !== false) { +if (!getenv('TRAVIS_COMMIT') && stripos(readline('Do you want to make the secret chat tests? (y/n): '), 'y') !== false) { /** * Request a secret chat. */