From c6f16422644be465b4ee9d4d2740a95d86ef5b8e Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Fri, 25 Nov 2016 20:52:56 +0000 Subject: [PATCH] Fixed bugs, updated docs, fixed serialization and deserialization of class, improved TL class --- .gitignore | 2 + README.md | 124 +++++++++---------- src/danog/MadelineProto/API.php | 22 +++- src/danog/MadelineProto/Connection.php | 2 +- src/danog/MadelineProto/MTProto.php | 13 +- src/danog/MadelineProto/TL/TL.php | 23 ++-- src/danog/MadelineProto/TL/TLConstructor.php | 27 +--- src/danog/MadelineProto/TL/TLMethod.php | 25 +--- testing.php | 13 +- 9 files changed, 108 insertions(+), 143 deletions(-) diff --git a/.gitignore b/.gitignore index 662a2b608..ab58b6d9f 100644 --- a/.gitignore +++ b/.gitignore @@ -66,3 +66,5 @@ vendor number.php token.php *~uploading* +session.mad +*.madeline \ No newline at end of file diff --git a/README.md b/README.md index 509c5d590..9effbcd8d 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ This project is in beta state. This project depends on [PHPStruct](https://github.com/danog/PHPStruct), [phpseclib](https://github.com/phpseclib/phpseclib), https://packagist.org/packages/paragonie/constant_time_encoding and https://packagist.org/packages/paragonie/random_compat -To install them all simply run: +To install dependencies install composer and run: ``` composer update ``` @@ -27,13 +27,13 @@ In the cloned repo. ### Instantiation ``` -$madeline = new \danog\MadelineProto\API(); +$MadelineProto = new \danog\MadelineProto\API(); ``` ### Settings -The constructor accepts an optional parameter, which is the settings array. -Here you can see its default value and explanations for every setting: +The constructor accepts an optional parameter, which is the settings array. This array contains some other arrays, which are the settings for a specific MadelineProto function. +Here you can see the default values for the settings\ arrays and explanations for every setting: ``` $settings = [ 'authorization' => [ // Authorization settings @@ -86,60 +86,53 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB 2 => [ // The rest will be fetched using help.getConfig 'ip_address' => '2001:067c:04e8:f002:0000:0000:0000:000a', 'port' => 443, - 'media_only' => false, - 'tcpo_only' => false - ] - ] - ], - ], - 'connection_settings' => [ // connection settings - 'all' => [ // Connection settings will be applied on datacenter ids matching the key of these settings subarrays, if the key is equal to all like in this case that will match all datacenters that haven't a custom settings subarray... - 'protocol' => 'tcp_full', // can be tcp_full, tcp_abridged, tcp_intermediate, http (unsupported), https (unsupported), udp (unsupported) - 'test_mode' => false, // decides whether to connect to the main telegram servers or to the testing servers (deep telegram) - 'ipv6' => $this->ipv6, // decides whether to use ipv6, ipv6 attribute of API attribute of API class contains autodetected boolean - 'timeout' => 10 // timeout for sockets - ], - ], - 'app_info' => [ // obtained in https://my.telegram.org - 'api_id' => 25628, - 'api_hash' => '1fe17cda7d355166cdaa71f04122873c', - 'device_model' => php_uname('s'), - 'system_version' => php_uname('r'), - 'app_version' => 'Unicorn', // 🌚 - 'lang_code' => 'en', - ], - 'tl_schema' => [ // TL scheme files - 'layer' => 57, // layer version - 'src' => [ - 'mtproto' => __DIR__.'/TL_mtproto_v1.json', // mtproto TL scheme - 'telegram' => __DIR__.'/TL_telegram_v57.json', // telegram TL scheme - ], - ], - 'logger' => [ // Logger settings - /* - * logger modes: - * 0 - No logger - * 1 - Log to the default logger destination - * 2 - Log to file defined in second parameter - * 3 - Echo logs - */ - 'logger' => 1, // write to - 'logger_param' => '/tmp/MadelineProto.log', - 'logger' => 3, // overwrite previous setting and echo logs - ], - 'max_tries' => [ - 'query' => 5, // How many times should I try to call a method or send an object before throwing an exception - 'authorization' => 5, // How many times should I try to generate an authorization key before throwing an exception - 'response' => 5,// How many times should I try to get a response of a query before throwing an exception - ], - 'msg_array_limit' => [ // How big should be the arrays containing the incoming and outgoing messages? - 'incoming' => 30, - 'outgoing' => 30, - ], - ]; + 'media_only' => false, + 'tcpo_only' => false + ] + ] + ], + ], + 'connection_settings' => [ // connection settings + 'all' => [ // Connection settings will be applied on datacenter ids matching the key of these settings subarrays, if the key is equal to all like in this case that will match all datacenters that haven't a custom settings subarray... + 'protocol' => 'tcp_full', // can be tcp_full, tcp_abridged, tcp_intermediate, http (unsupported), https (unsupported), udp (unsupported) + 'test_mode' => false, // decides whether to connect to the main telegram servers or to the testing servers (deep telegram) + 'ipv6' => $this->ipv6, // decides whether to use ipv6, ipv6 attribute of API attribute of API class contains autodetected boolean + 'timeout' => 10 // timeout for sockets + ], + ], + 'app_info' => [ // obtained in https://my.telegram.org + 'api_id' => 25628, + 'api_hash' => '1fe17cda7d355166cdaa71f04122873c', + 'device_model' => php_uname('s'), + 'system_version' => php_uname('r'), + 'app_version' => 'Unicorn', // 🌚 + 'lang_code' => 'en', + ], + 'tl_schema' => [ // TL scheme files + 'layer' => 57, // layer version + 'src' => [ + 'mtproto' => __DIR__.'/TL_mtproto_v1.json', // mtproto TL scheme + 'telegram' => __DIR__.'/TL_telegram_v57.json', // telegram TL scheme + ], + ], + 'logger' => [ // Logger settings + 'logger' => 1, // 0 - No logger, 1 - Log to the default logger destination, 2 - Log to file defined in logger_param, 3 - Echo logs + 'logger_param' => '/tmp/MadelineProto.log', + 'logger' => 3, // overwrite previous setting and echo logs + ], + 'max_tries' => [ + 'query' => 5, // How many times should I try to call a method or send an object before throwing an exception + 'authorization' => 5, // How many times should I try to generate an authorization key before throwing an exception + 'response' => 5,// How many times should I try to get a response of a query before throwing an exception + ], + 'msg_array_limit' => [ // How big should be the arrays containing the incoming and outgoing messages? + 'incoming' => 30, + 'outgoing' => 30, + ], +]; ``` -You can provide only part of any of the subsettings array, the rest will be automagically set to the default values of the specified subsettings array. +You can provide part of any subsetting array, that way the remaining arrays will be automagically set to default and undefined values of specified subsetting arrays will be set to the default values. Example: ``` $settings = [ @@ -162,17 +155,16 @@ Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB -----END RSA PUBLIC KEY-----', ] - // The remaining subsetting arrays are the set to the default ones + // The remaining subsetting arrays are the set to default ] ``` -The rest of the settings array and of the authorization array will be set to the default values specified in the default array. -Note that only subsetting arrays or values of a subsetting array will be set to default. +Note that only settings arrays or values of a settings array will be set to default. The settings array can be accessed in the instantiated class like this: ``` $MadelineProto = new \danog\MadelineProto\API(); -$generated_settings = $MadelineProto->API->settings; +var_dump($MadelineProto->API->settings); ``` ### Calling mtproto methods and available wrappers @@ -205,6 +197,10 @@ $authorization = $MadelineProto->bot_login($token); // Note that every time you var_dump($authorization); ``` +### Storing sessions + +An istance of MadelineProto can be safely serialized or unserialized. + ### Exceptions MadelineProto can throw three different exceptions: @@ -233,16 +229,16 @@ src/danog/MadelineProto/ TL/ Exception - Handles exceptions in the TL namespace TL - Handles TL serialization and deserialization - TLConstructor - Represents a TL Constructor - TLMethod - Represents a TL method - API - Wrapper class that istantiates the MTProto class, sets the error handler, provides a wrapper for calling mtproto methods directly as class submethods, and provides some simplified wrappers for logging in to telegram + TLConstructor - Stores TL constructors + TLMethod - Stores TL methods + API - Wrapper class that instantiates the MTProto class, sets the error handler, provides a wrapper for calling mtproto methods directly as class submethods, and provides some simplified wrappers for logging in to telegram APIFactory - Provides a wrapper for calling namespaced mtproto methods directly as class submethods Connection - Handles tcp/udp/http connections and wrapping payloads generated by MTProtoTools/MessageHandler into the right message according to the protocol, stores authorization keys, session id and sequence number DataCenter - Handles mtproto datacenters (is a wrapper for Connection classes) DebugTools - Various debugging tools Exception - Handles exceptions and PHP errors RPCErrorException - Handles RPC errors - MTProto - Extends MTProtoTools, handles initial connection, generation of authorization keys, istantiation of classes, writing of client info + MTProto - Extends MTProtoTools, handles initial connection, generation of authorization keys, instantiation of classes, writing of client info MTProtoTools - Extends all of the classes in MTProtoTools/ Logger - Static logging class prime.py and getpq.py - prime module (python) for p and q generation diff --git a/src/danog/MadelineProto/API.php b/src/danog/MadelineProto/API.php index def88885e..efbd740f4 100644 --- a/src/danog/MadelineProto/API.php +++ b/src/danog/MadelineProto/API.php @@ -23,6 +23,7 @@ class API extends APIFactory set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']); $this->API = new MTProto($params); + \danog\MadelineProto\Logger::log('Running APIFactory...'); $this->APIFactory(); \danog\MadelineProto\Logger::log('Ping...'); @@ -38,26 +39,28 @@ class API extends APIFactory public function APIFactory() { - \danog\MadelineProto\Logger::log('Running APIFactory...'); - foreach ($this->API->tl->methods->method_namespace as $namespace => $method) { - $this->{$method} = new APIFactory($method, $this->API); + foreach ($this->API->tl->methods->method_namespace as $namespace) { + $this->{$namespace} = new APIFactory($namespace, $this->API); } } public function logout() { - $this->API->datacenter->authorized = false; - $this->API->datacenter->authorization = null; + set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']); if (!$this->API->method_call('auth.logOut')) { throw new Exception('An error occurred while logging out!'); } + $this->API->datacenter->authorized = false; + $this->API->datacenter->authorization = null; \danog\MadelineProto\Logger::log('Logged out successfully!'); + restore_error_handler(); return true; } public function bot_login($token) { + set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']); if ($this->API->datacenter->authorized) { \danog\MadelineProto\Logger::log('This instance of MadelineProto is already logged in. Logging out first...'); $this->logout(); @@ -73,12 +76,14 @@ class API extends APIFactory ); $this->API->datacenter->authorized = true; \danog\MadelineProto\Logger::log('Logged in successfully!'); + restore_error_handler(); return $this->API->datacenter->authorization; } public function phone_login($number, $sms_type = 5) { + set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']); if ($this->API->datacenter->authorized) { \danog\MadelineProto\Logger::log('This instance of MadelineProto is already logged in. Logging out first...'); $this->logout(); @@ -97,12 +102,14 @@ class API extends APIFactory $this->API->datacenter->authorization['phone_number'] = $number; $this->API->datacenter->waiting_code = true; \danog\MadelineProto\Logger::log('Code sent successfully! Once you receive the code you should use the complete_phone_login function.'); + restore_error_handler(); return $this->API->datacenter->authorization; } public function complete_phone_login($code) { + set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']); if (!$this->API->datacenter->waiting_code) { throw new Exception("I'm not waiting for the code! Please call the phone_login method first"); } @@ -118,10 +125,15 @@ class API extends APIFactory $this->API->datacenter->waiting_code = false; $this->API->datacenter->authorized = true; \danog\MadelineProto\Logger::log('Logged in successfully!'); + restore_error_handler(); return $this->API->datacenter->authorization; } + public function __sleep() + { + return ["API"]; + } public function __wakeup() { $this->APIFactory(); diff --git a/src/danog/MadelineProto/Connection.php b/src/danog/MadelineProto/Connection.php index 6b0a0d376..29fdb84e7 100644 --- a/src/danog/MadelineProto/Connection.php +++ b/src/danog/MadelineProto/Connection.php @@ -114,7 +114,7 @@ class Connection extends Tools public function __wakeup() { - $this->close_and_reopen(); + $this->__construct($this->ip, $this->port, $this->protocol, $this->timeout); } /** diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php index 692fd3714..1c8ef307e 100644 --- a/src/danog/MadelineProto/MTProto.php +++ b/src/danog/MadelineProto/MTProto.php @@ -159,7 +159,8 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB $this->setup_logger(); // Connect to servers - $this->mk_datacenter(); + \danog\MadelineProto\Logger::log('Istantiating DataCenter...'); + $this->datacenter = new DataCenter($this->settings['connection'], $this->settings['connection_settings']); // Load rsa key \danog\MadelineProto\Logger::log('Loading RSA key...'); @@ -176,14 +177,7 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB public function __wakeup() { $this->setup_logger(); - $this->mk_datacenter(); - } - - public function mk_datacenter() - { - // Connect to servers - \danog\MadelineProto\Logger::log('Istantiating DataCenter...'); - $this->datacenter = new DataCenter($this->settings['connection'], $this->settings['connection_settings']); + $this->datacenter->__construct($this->settings['connection'], $this->settings['connection_settings']); } public function setup_logger() @@ -278,5 +272,6 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB $test .= (isset($this->settings['connection'][$test][$ipv6][$id]) && $this->settings['connection'][$test][$ipv6][$id]['ip_address'] != $dc['ip_address']) ? '_bk' : ''; $this->settings['connection'][$test][$ipv6][$id] = $dc; } + unset($this->config['dc_options']); } } diff --git a/src/danog/MadelineProto/TL/TL.php b/src/danog/MadelineProto/TL/TL.php index bb9056a33..3dfb0904f 100644 --- a/src/danog/MadelineProto/TL/TL.php +++ b/src/danog/MadelineProto/TL/TL.php @@ -80,21 +80,20 @@ class TL extends \danog\MadelineProto\Tools $flags = 0; foreach ($tl['params'] as $cur_flag) { if ($cur_flag['flag']) { - $flag_pow = pow(2, $cur_flag['pow']); switch ($cur_flag['type']) { case 'true': case 'false': - $flags = (isset($arguments[$cur_flag['name']]) && $arguments[$cur_flag['name']]) ? ($flags | $flag_pow) : ($flags & ~$flag_pow); + $flags = (isset($arguments[$cur_flag['name']]) && $arguments[$cur_flag['name']]) ? ($flags | $cur_flag['pow']) : ($flags & ~$cur_flag['pow']); unset($arguments[$cur_flag['name']]); break; case 'Bool': - $arguments[$cur_flag['name']] = (isset($arguments[$cur_flag['name']]) && $arguments[$cur_flag['name']]) && (($flags & $flag_pow) != 0); - if (($flags & $flag_pow) == 0) { + $arguments[$cur_flag['name']] = (isset($arguments[$cur_flag['name']]) && $arguments[$cur_flag['name']]) && (($flags & $cur_flag['pow']) != 0); + if (($flags & $cur_flag['pow']) == 0) { unset($arguments[$cur_flag['name']]); } break; default: - $flags = (isset($arguments[$cur_flag['name']]) && $arguments[$cur_flag['name']] !== null) ? ($flags | $flag_pow) : ($flags & ~$flag_pow); + $flags = (isset($arguments[$cur_flag['name']]) && $arguments[$cur_flag['name']] !== null) ? ($flags | $cur_flag['pow']) : ($flags & ~$cur_flag['pow']); break; } } @@ -102,7 +101,7 @@ class TL extends \danog\MadelineProto\Tools $arguments['flags'] = $flags; foreach ($tl['params'] as $current_argument) { if (!isset($arguments[$current_argument['name']])) { - if ($current_argument['flag'] && (in_array($current_argument['type'], ['true', 'false']) || ($flags & pow(2, $current_argument['pow'])) == 0)) { + if ($current_argument['flag'] && (in_array($current_argument['type'], ['true', 'false']) || ($flags & $current_argument['pow']) == 0)) { //\danog\MadelineProto\Logger::log('Skipping '.$current_argument['name'].' of type '.$current_argument['type'].'/'.$current_argument['subtype']); continue; } @@ -182,12 +181,7 @@ class TL extends \danog\MadelineProto\Tools return $concat; default: - $tl_elem = $this->constructors->find_by_predicate($type); - if ($tl_elem === false) { - throw new Exception('Could not serialize type: '.$type); - } - - return \danog\PHP\Struct::pack('serialize_generic($type, $value); break; } } @@ -286,19 +280,18 @@ class TL extends \danog\MadelineProto\Tools $x = ['_' => $tl_elem['predicate']]; foreach ($tl_elem['params'] as $arg) { if ($arg['flag']) { - $flag_pow = pow(2, $arg['pow']); switch ($arg['type']) { case 'true': case 'false': - $x[$arg['name']] = ($x['flags'] & $flag_pow) == 1; + $x[$arg['name']] = ($x['flags'] & $arg['pow']) == 1; continue 2; break; case 'Bool': $default = false; default: $default = null; - if (($x['flags'] & $flag_pow) == 0) { + if (($x['flags'] & $arg['pow']) == 0) { $x[$arg['name']] = $default; //\danog\MadelineProto\Logger::log('Skipping '.$arg['name'].' of type '.$arg['type'].'/'.$arg['subtype']); continue 2; diff --git a/src/danog/MadelineProto/TL/TLConstructor.php b/src/danog/MadelineProto/TL/TLConstructor.php index 40cff6d32..bc0e4dcd6 100644 --- a/src/danog/MadelineProto/TL/TLConstructor.php +++ b/src/danog/MadelineProto/TL/TLConstructor.php @@ -12,7 +12,7 @@ If not, see . namespace danog\MadelineProto\TL; -class TLConstructor +class TLConstructor extends TLParams { public $id = []; public $predicate = []; @@ -20,35 +20,14 @@ class TLConstructor public $params = []; public $key = 0; + public function add($json_dict, $mtproto) { $this->id[$this->key] = (int) $json_dict['id']; $this->predicate[$this->key] = (string) ((($mtproto && $json_dict['predicate'] == 'message') ? 'MT' : '').$json_dict['predicate']); $this->type[$this->key] = $json_dict['type']; $this->params[$this->key] = $json_dict['params']; - foreach ($this->params[$this->key] as &$param) { - $param['flag'] = false; - $param['subtype'] = null; - if (preg_match('/^flags\.\d*\?/', $param['type'])) { - $param['flag'] = true; - $param['pow'] = preg_replace(['/^flags\./', '/\?.*/'], '', $param['type']); - $param['type'] = preg_replace('/^flags\.\d*\?/', '', $param['type']); - } - if (preg_match('/vector<.*>/i', $param['type'])) { - if (preg_match('/vector/', $param['type'])) { - $param['subtype'] = preg_replace(['/.*$/'], '', $param['type']); - $param['type'] = 'vector'; - } - if (preg_match('/Vector/', $param['type'])) { - $param['subtype'] = preg_replace(['/.*$/'], '', $param['type']); - $param['type'] = 'Vector t'; - } - if (preg_match('/^\%/', $param['subtype'])) { - $param['subtype'] = lcfirst(preg_replace('/^\%/', '', $param['subtype'])); - } - $param['subtype'] = (($mtproto && $param['subtype'] == 'message') ? 'MT' : '').$param['subtype']; - } - } + $this->parse_params($this->key, $mtproto); $this->key++; } diff --git a/src/danog/MadelineProto/TL/TLMethod.php b/src/danog/MadelineProto/TL/TLMethod.php index 266d94f4f..28cc03f1f 100644 --- a/src/danog/MadelineProto/TL/TLMethod.php +++ b/src/danog/MadelineProto/TL/TLMethod.php @@ -12,7 +12,7 @@ If not, see . namespace danog\MadelineProto\TL; -class TLMethod +class TLMethod extends TLParams { public $id = []; public $method = []; @@ -32,28 +32,7 @@ class TLMethod $this->method_namespace[$namespace[0]] = $namespace[0]; } - foreach ($this->params[$this->key] as &$param) { - $param['flag'] = false; - $param['subtype'] = null; - if (preg_match('/^flags\.\d*\?/', $param['type'])) { - $param['flag'] = true; - $param['pow'] = preg_replace(['/^flags\./', '/\?.*/'], '', $param['type']); - $param['type'] = preg_replace('/^flags\.\d*\?/', '', $param['type']); - } - if (preg_match('/vector<.*>/i', $param['type'])) { - if (preg_match('/vector/', $param['type'])) { - $param['subtype'] = preg_replace(['/.*$/'], '', $param['type']); - $param['type'] = 'vector'; - } - if (preg_match('/Vector/', $param['type'])) { - $param['subtype'] = preg_replace(['/.*$/'], '', $param['type']); - $param['type'] = 'Vector t'; - } - if (preg_match('/^\%/', $param['subtype'])) { - $param['subtype'] = lcfirst(preg_replace('/^\%/', '', $param['subtype'])); - } - } - } + $this->parse_params($this->key); $this->key++; } diff --git a/testing.php b/testing.php index c043a6743..f03564126 100755 --- a/testing.php +++ b/testing.php @@ -34,8 +34,17 @@ if (file_exists('number.php')) { $authorization = $MadelineProto->complete_phone_login($code); var_dump($authorization); } + +echo 'Serializing MadelineProto to session.madeline...'.PHP_EOL; +echo 'Wrote '.file_put_contents('session.madeline', serialize($MadelineProto)).' bytes'.PHP_EOL; + +echo 'Deserializing MadelineProto from session.madeline...'.PHP_EOL; +$unserialized = unserialize(file_get_contents('session.madeline')); + if (file_exists('token.php')) { include_once 'token.php'; - $MadelineProto->bot_login($token); + $authorization = $unserialized->bot_login($token); + var_dump($authorization); } -echo 'Size of MadelineProto instance is '.strlen(var_export($MadelineProto, true)).' bytes'.PHP_EOL; +echo 'Size of MadelineProto instance is '.strlen(serialize($unserialized)).' bytes'.PHP_EOL; +