diff --git a/bot.php b/bot.php index 33cbc70b..d7c5f869 100755 --- a/bot.php +++ b/bot.php @@ -39,7 +39,6 @@ class EventHandler extends \danog\MadelineProto\EventHandler if (isset($update['message']['_']) && $update['message']['_'] === 'messageEmpty') { return; } - $res = json_encode($update, JSON_PRETTY_PRINT); try { 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/Connection.php b/src/danog/MadelineProto/Connection.php index 9caa7706..e9a578b5 100644 --- a/src/danog/MadelineProto/Connection.php +++ b/src/danog/MadelineProto/Connection.php @@ -199,7 +199,13 @@ class Connection $this->disconnect(); yield $this->API->datacenter->dcConnectAsync($this->ctx->getDc()); if ($this->API->hasAllAuth() && !$this->hasPendingCalls()) { - $this->callFork($this->API->method_call_async_read('ping', ['ping_id' => $this->random_int()], ['datacenter' => $this->datacenter])); + $this->callFork((function () { + try { + $this->API->method_call_async_read('ping', ['ping_id' => $this->random_int()], ['datacenter' => $this->datacenter]); + } catch (\Throwable $e) { + $this->API->logger("Got an error while pinging on reconnect: $e", Logger::FATAL_ERROR); + } + })()); } } diff --git a/src/danog/MadelineProto/DataCenter.php b/src/danog/MadelineProto/DataCenter.php index dc6e7e4d..723bd650 100644 --- a/src/danog/MadelineProto/DataCenter.php +++ b/src/danog/MadelineProto/DataCenter.php @@ -471,18 +471,10 @@ class DataCenter } break; case 'wss': - if ($this->settings[$dc_config_number]['obfuscated']) { - $default = [[DefaultStream::getName(), []], [WssStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], end($default)]; - } else { - $default = [[DefaultStream::getName(), []], [WssStream::getName(), []], [BufferedRawStream::getName(), []], end($default)]; - } + $default = [[DefaultStream::getName(), []], [WssStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], end($default)]; break; case 'ws': - if ($this->settings[$dc_config_number]['obfuscated']) { - $default = [[DefaultStream::getName(), []], [WsStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], end($default)]; - } else { - $default = [[DefaultStream::getName(), []], [WsStream::getName(), []], [BufferedRawStream::getName(), []], end($default)]; - } + $default = [[DefaultStream::getName(), []], [WsStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], end($default)]; break; } } diff --git a/src/danog/MadelineProto/Logger.php b/src/danog/MadelineProto/Logger.php index a1609c05..c8a8cf8f 100644 --- a/src/danog/MadelineProto/Logger.php +++ b/src/danog/MadelineProto/Logger.php @@ -101,7 +101,9 @@ class Logger Exception::$rollbar = false; RPCErrorException::$rollbar = false; } - + if (!isset($settings['logger']['logger_param']) && isset($settings['logger']['param'])) { + $settings['logger']['logger_param'] = $settings['logger']['param']; + } if (php_sapi_name() !== 'cli') { if (isset($settings['logger']['logger_param']) && basename($settings['logger']['logger_param']) === 'MadelineProto.log') { $settings['logger']['logger_param'] = Magic::$script_cwd.'/MadelineProto.log'; @@ -179,6 +181,8 @@ class Logger { if (!is_null(self::$default)) { self::$default->logger($param, $level, basename(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php')); + } else { + echo $param.PHP_EOL; } } diff --git a/src/danog/MadelineProto/Loop/Connection/ReadLoop.php b/src/danog/MadelineProto/Loop/Connection/ReadLoop.php index 21160737..a1e3cb36 100644 --- a/src/danog/MadelineProto/Loop/Connection/ReadLoop.php +++ b/src/danog/MadelineProto/Loop/Connection/ReadLoop.php @@ -18,6 +18,8 @@ namespace danog\MadelineProto\Loop\Connection; +use Amp\ByteStream\PendingReadError; +use Amp\ByteStream\StreamException; use Amp\Loop; use Amp\Websocket\ClosedException; use danog\MadelineProto\Logger; @@ -58,7 +60,7 @@ class ReadLoop extends SignalLoop while (true) { try { $error = yield $this->waitSignal($this->readMessage()); - } catch (NothingInTheSocketException|StreamException|PendingReadError $e) { + } catch (NothingInTheSocketException | StreamException | PendingReadError | \Error $e) { if (isset($connection->old)) { return; } diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php index 8010e342..0822f321 100644 --- a/src/danog/MadelineProto/MTProto.php +++ b/src/danog/MadelineProto/MTProto.php @@ -287,9 +287,6 @@ class MTProto extends AsyncConstruct implements TLCallback public function __wakeup_async($backtrace) { set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']); - //set_exception_handler(['\\danog\\MadelineProto\\Serialization', 'serialize_all']); - Magic::class_exists(); - $this->setup_logger(); if (\danog\MadelineProto\Magic::$has_thread && is_object(\Thread::getCurrentThread())) { return; diff --git a/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php b/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php index 23147109..db16c816 100644 --- a/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php @@ -391,6 +391,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/Files.php b/src/danog/MadelineProto/MTProtoTools/Files.php index 4e9aa90d..88fdb337 100644 --- a/src/danog/MadelineProto/MTProtoTools/Files.php +++ b/src/danog/MadelineProto/MTProtoTools/Files.php @@ -41,6 +41,7 @@ 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 function Amp\Promise\all; use Amp\File\BlockingHandle; use Amp\Artax\Client; @@ -566,7 +567,7 @@ trait Files $res['mime'] = $this->get_mime_from_extension($res['ext'], 'image/jpeg'); } if (!isset($res['name']) || $res['name'] === '') { - $res['name'] = $message_media['file']['access_hash']; + $res['name'] = Tools::unpack_signed_long_string($message_media['file']['access_hash']); } return $res; @@ -636,7 +637,7 @@ trait Files $res['thumb_size'] = $message_media['type']; if ($message_media['location']['_'] === 'fileLocationUnavailable') { - $res['name'] = $message_media['volume_id'].'_'.$message_media['local_id']; + $res['name'] = Tools::unpack_signed_long_string($message_media['volume_id']).'_'.$message_media['local_id']; $res['mime'] = $this->get_mime_from_buffer($res['data']); $res['ext'] = $this->get_extension_from_mime($res['mime']); } else { @@ -658,7 +659,7 @@ trait Files case 'fileLocationUnavailable': throw new \danog\MadelineProto\Exception('File location unavailable'); case 'fileLocation': - $res['name'] = $message_media['volume_id'].'_'.$message_media['local_id']; + $res['name'] = Tools::unpack_signed_long_string($message_media['volume_id']).'_'.$message_media['local_id']; $res['InputFileLocation'] = [ '_' => 'inputFileLocation', 'volume_id' => $message_media['volume_id'], @@ -675,7 +676,7 @@ trait Files return $res; case 'fileLocationToBeDeprecated': - $res['name'] = $message_media['volume_id'].'_'.$message_media['local_id']; + $res['name'] = Tools::unpack_signed_long_string($message_media['volume_id']).'_'.$message_media['local_id']; $res['ext'] = '.jpg'; $res['mime'] = $this->get_mime_from_extension($res['ext'], 'image/jpeg'); $res['InputFileLocation'] = [ @@ -730,7 +731,7 @@ trait Files $res['ext'] = $this->get_extension_from_location($res['InputFileLocation'], $this->get_extension_from_mime($message_media['document']['mime_type'])); } if (!isset($res['name']) || $res['name'] === '') { - $res['name'] = $message_media['document']['access_hash']; + $res['name'] = Tools::unpack_signed_long_string($message_media['document']['access_hash']); } if (isset($message_media['document']['size'])) { $res['size'] = $message_media['document']['size']; diff --git a/src/danog/MadelineProto/MTProtoTools/PasswordCalculator.php b/src/danog/MadelineProto/MTProtoTools/PasswordCalculator.php index 43f75255..36c03727 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']; @@ -131,6 +213,7 @@ class PasswordCalculator $gForHash = $this->current_algo['gForHash']; $p = $this->current_algo['p']; $pForHash = $this->current_algo['pForHash']; + $B = $this->srp_B; $BForHash = $this->srp_BForHash; $id = $this->srp_id; @@ -166,9 +249,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 +277,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/MTProtoTools/ResponseHandler.php b/src/danog/MadelineProto/MTProtoTools/ResponseHandler.php index 9f0b7728..3558d43b 100644 --- a/src/danog/MadelineProto/MTProtoTools/ResponseHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/ResponseHandler.php @@ -312,7 +312,15 @@ trait ResponseHandler if (isset($request['promise'])) { $promise = $request['promise']; unset($request['promise']); - $promise->fail($data); + try { + $promise->fail($data); + } catch (\Error $e) { + if (strpos($e->getMessage(), "Promise has already been resolved") !== 0) { + throw $e; + } + $this->logger->logger("Got promise already resolved error", \danog\MadelineProto\Logger::FATAL_ERROR); + } + } else { $this->logger->logger('Rejecting: already got response for '.(isset($request['_']) ? $request['_'] : '-')); $this->logger->logger("Rejecting: $data"); @@ -580,7 +588,14 @@ trait ResponseHandler if (isset($this->datacenter->sockets[$datacenter]->outgoing_messages[$request_id]['promise'])) { // This should not happen but happens, should debug $promise = $this->datacenter->sockets[$datacenter]->outgoing_messages[$request_id]['promise']; unset($this->datacenter->sockets[$datacenter]->outgoing_messages[$request_id]['promise']); - $promise->resolve($response); + try { + $promise->resolve($response); + } catch (\Error $e) { + if (strpos($e->getMessage(), "Promise has already been resolved") !== 0) { + throw $e; + } + $this->logger->logger("Got promise already resolved error", \danog\MadelineProto\Logger::FATAL_ERROR); + } } } )()); diff --git a/src/danog/MadelineProto/Magic.php b/src/danog/MadelineProto/Magic.php index 36411c35..cf3ad811 100644 --- a/src/danog/MadelineProto/Magic.php +++ b/src/danog/MadelineProto/Magic.php @@ -66,7 +66,7 @@ class Magic public static function class_exists() { 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 (!defined('\\phpseclib\\Crypt\\Common\\SymmetricKey::MODE_IGE') || \phpseclib\Crypt\Common\SymmetricKey::MODE_IGE !== 7) { throw new Exception(\danog\MadelineProto\Lang::$current_lang['phpseclib_fork']); @@ -183,6 +183,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..d7f27a12 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='.$method.'&code='.$code.'&error='.$error), 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/Serialization.php b/src/danog/MadelineProto/Serialization.php index 2346814b..54fba515 100644 --- a/src/danog/MadelineProto/Serialization.php +++ b/src/danog/MadelineProto/Serialization.php @@ -24,11 +24,6 @@ namespace danog\MadelineProto; */ class Serialization { - public static function serialize_all($exception) - { - echo $exception.PHP_EOL; - } - public static function realpaths($file) { $file = Absolute::absolute($file); diff --git a/src/danog/MadelineProto/Stream/MTProtoTransport/ObfuscatedStream.php b/src/danog/MadelineProto/Stream/MTProtoTransport/ObfuscatedStream.php index 63dbfd4a..8043158e 100644 --- a/src/danog/MadelineProto/Stream/MTProtoTransport/ObfuscatedStream.php +++ b/src/danog/MadelineProto/Stream/MTProtoTransport/ObfuscatedStream.php @@ -194,6 +194,7 @@ class ObfuscatedStream implements BufferedProxyStreamInterface $extra['secret'] = substr($extra['secret'], 1, 16); } } + $this->extra = $extra; } diff --git a/src/danog/MadelineProto/Stream/Transport/WsStream.php b/src/danog/MadelineProto/Stream/Transport/WsStream.php index 6d70f5c1..6e01385e 100644 --- a/src/danog/MadelineProto/Stream/Transport/WsStream.php +++ b/src/danog/MadelineProto/Stream/Transport/WsStream.php @@ -84,6 +84,7 @@ class WsStream implements RawStreamInterface break; } } + if (!$this->stream) { throw new ConnectionException('Failed to read response from server'); } diff --git a/src/danog/MadelineProto/TL_telegram_v102.tl b/src/danog/MadelineProto/TL_telegram_v102.tl index 8d69f8e5..97fb9d97 100644 --- a/src/danog/MadelineProto/TL_telegram_v102.tl +++ b/src/danog/MadelineProto/TL_telegram_v102.tl @@ -1050,17 +1050,20 @@ invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X; auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode; auth.signUp#1b067634 phone_number:string phone_code_hash:string phone_code:string first_name:string last_name:string = auth.Authorization; auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization; +auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentCode; +auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool; + auth.logOut#5717da40 = Bool; + auth.resetAuthorizations#9fab0d1a = Bool; auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization; auth.importAuthorization#e3ef9613 id:int bytes:bytes = auth.Authorization; + auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool; auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_auth_token:string = auth.Authorization; auth.checkPassword#d18b4d16 password:InputCheckPasswordSRP = auth.Authorization; auth.requestPasswordRecovery#d897bc66 = auth.PasswordRecovery; auth.recoverPassword#4ea56e92 code:string = auth.Authorization; -auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentCode; -auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool; auth.dropTempAuthKeys#8e48a188 except_auth_keys:Vector = Bool; account.registerDevice#5cbea590 token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector = Bool; diff --git a/src/danog/MadelineProto/Tools.php b/src/danog/MadelineProto/Tools.php index 43132140..42ca113b 100644 --- a/src/danog/MadelineProto/Tools.php +++ b/src/danog/MadelineProto/Tools.php @@ -23,6 +23,9 @@ use Amp\Failure; use Amp\Loop; use Amp\Promise; use Amp\Success; +use function Amp\ByteStream\getOutputBufferStream; +use function Amp\ByteStream\getStdin; +use function Amp\ByteStream\getStdout; use function Amp\Promise\all; use function Amp\Promise\any; use function Amp\Promise\first; @@ -35,6 +38,7 @@ use function Amp\ByteStream\getOutputBufferStream; use function Amp\File\exists; use function Amp\File\touch; use Amp\File\StatCache; +use phpseclib\Math\BigInteger; /** * Some tools. @@ -122,6 +126,16 @@ trait Tools return unpack('q', \danog\MadelineProto\Magic::$BIG_ENDIAN ? strrev($value) : $value)[1]; } + public static function unpack_signed_long_string($value) + { + if (strlen($value) !== 8) { + throw new TL\Exception(\danog\MadelineProto\Lang::$current_lang['length_not_8']); + } + + $big = new BigInteger($value, -256); + return (string) $big; + } + public static function pack_signed_int($value) { if ($value > 2147483647) { @@ -194,6 +208,7 @@ trait Tools try { Loop::run(function () use (&$resolved, &$value, &$exception, $promise) { $promise->onResolve(function ($e, $v) use (&$resolved, &$value, &$exception) { + Loop::stop(); $resolved = true; $exception = $e; @@ -308,12 +323,21 @@ trait Tools if ($file) { $file = " started @ $file"; } - if ($logger) $logger->logger("Got the following exception within a forked strand$file, trying to rethrow"); + if ($logger) { + $logger->logger("Got the following exception within a forked strand$file, trying to rethrow"); + } + if ($e->getMessage() === "Cannot get return value of a generator that hasn't returned") { $logger->logger("Well you know, this might actually not be the actual exception, scroll up in the logs to see the actual exception"); - if (!$zis || !$zis->destructing) Promise\rethrow(new Failure($e)); + if (!$zis || !$zis->destructing) { + Promise\rethrow(new Failure($e)); + } + } else { - if ($logger) $logger->logger($e); + if ($logger) { + $logger->logger($e); + } + Promise\rethrow(new Failure($e)); } } @@ -332,7 +356,7 @@ trait Tools return; } $b = self::call($b()); - $b->onResolve(static function ($e, $res) use ($deferred) { + $b->onResolve(function ($e, $res) use ($deferred) { if ($e) { if (isset($this)) { $this->rethrow($e, $file); @@ -415,8 +439,7 @@ trait Tools return array_shift($lines); } - public static function echo($string) - { + public static function echo ($string) { return getOutputBufferStream()->write($string); } public static function is_array_or_alike($var) 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/src/danog/MadelineProto/Wrappers/Loop.php b/src/danog/MadelineProto/Wrappers/Loop.php index ce71e383..7f83c44c 100644 --- a/src/danog/MadelineProto/Wrappers/Loop.php +++ b/src/danog/MadelineProto/Wrappers/Loop.php @@ -68,6 +68,9 @@ trait Loop } catch (\danog\MadelineProto\Exception $e) { $needs_restart = true; } + if (isset($_REQUEST['MadelineSelfRestart'])) { + $this->logger->logger("Self-restarted, restart token ".$_REQUEST['MadelineSelfRestart']); + } $this->logger->logger($needs_restart ? 'Will self-restart' : 'Will not self-restart'); $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); @@ -105,9 +108,31 @@ trait Loop if ($needs_restart) { $logger = &$this->logger; Shutdown::addCallback(static function () use (&$logger) { - $a = fsockopen((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] ? 'tls' : 'tcp').'://'.$_SERVER['SERVER_NAME'], $_SERVER['SERVER_PORT']); - fwrite($a, $_SERVER['REQUEST_METHOD'].' '.$_SERVER['REQUEST_URI'].' '.$_SERVER['SERVER_PROTOCOL']."\r\n".'Host: '.$_SERVER['SERVER_NAME']."\r\n\r\n"); - $logger->logger('Self-restarted'); + $address = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] ? 'tls' : 'tcp').'://'.$_SERVER['SERVER_NAME']; + $port = $_SERVER['SERVER_PORT']; + + $uri = $_SERVER['REQUEST_URI']; + + $params = $_GET; + $params['MadelineSelfRestart'] = $this->random_int(); + + list($url, $query) = explode($uri, '?', 2); + $query = http_build_query($params); + $uri = implode('?', [$url, $query]); + + $payload = $_SERVER['REQUEST_METHOD'].' '.$uri.' '.$_SERVER['SERVER_PROTOCOL']."\r\n".'Host: '.$_SERVER['SERVER_NAME']."\r\n\r\n"; + + $logger->logger("Connecting to $address:$port"); + $a = fsockopen($address, $port); + + $logger->logger("Sending self-restart payload"); + $logger->logger($payload); + fwrite($a, $payload); + + $logger->logger("Payload sent with token {$params['MadelineSelfRestart']}, waiting for self-restart"); + + sleep(10); + fclose($a); }, 'restarter'); } @@ -164,5 +189,8 @@ trait Loop ob_end_flush(); flush(); $GLOBALS['exited'] = true; + if (function_exists('fastcgi_finish_request')) { + \fastcgi_finish_request(); + } } } diff --git a/tests/makephar.sh b/tests/makephar.sh index c3326e32..3ee7a360 100755 --- a/tests/makephar.sh +++ b/tests/makephar.sh @@ -52,7 +52,7 @@ cd .. sudo apt-get update -q sudo apt-get install php7.3-cli php7.3-json php7.3-mbstring php7.3-curl php7.3-xml php7.3-json -y - composer global require spatie/7to5 dev-master#5c65f68 + composer global require spatie/7to5 dev-master#d4be6d0 [ -f $HOME/.composer/vendor/bin/php7to5 ] && php7to5=$HOME/.composer/vendor/bin/php7to5 [ -f $HOME/.config/composer/vendor/bin/php7to5 ] && php7to5=$HOME/.config/composer/vendor/bin/php7to5 diff --git a/tests/testing.php b/tests/testing.php index 7469075c..dce3c12b 100755 --- a/tests/testing.php +++ b/tests/testing.php @@ -37,7 +37,7 @@ if (file_exists('.env')) { $dotenv->load(); } if (getenv('TEST_SECRET_CHAT') == '') { - echo('TEST_SECRET_CHAT is not defined in .env, please define it (copy .env.example).'.PHP_EOL); + echo ('TEST_SECRET_CHAT is not defined in .env, please define it (copy .env.example).'.PHP_EOL); die(1); } echo 'Loading settings...'.PHP_EOL;