Added documentation, simplified code, organized exceptions, added some more examples in testing.php, decided to unset flags in deserialized responses, moved message id arrays to Connection classes, added wrappers for logging in to telegram as a bot or as a user and for logging out, fixed deserializing of gzip packed objects, added more logging, fixed bugs, added methods to get and parse configuration, saved some fairies, fixed exporting/importing of authorization, added some wakeup methods to prevent problems during serialization, added support for ipv6 and automagical detection too. I think we can safely say this is now a beta.

This commit is contained in:
Daniil Gentili 2016-11-24 23:15:22 +00:00
parent c168890384
commit 7be648fb20
19 changed files with 551 additions and 215 deletions

1
.gitignore vendored
View File

@ -65,3 +65,4 @@ vendor
*bak *bak
number.php number.php
token.php token.php
*~uploading*

214
README.md
View File

@ -2,15 +2,211 @@
[![StyleCI](https://styleci.io/repos/61838413/shield)](https://styleci.io/repos/61838413) [![StyleCI](https://styleci.io/repos/61838413/shield)](https://styleci.io/repos/61838413)
[![Build Status](https://travis-ci.org/danog/MadelineProto.svg?branch=master)](https://travis-ci.org/danog/MadelineProto) [![Build Status](https://travis-ci.org/danog/MadelineProto.svg?branch=master)](https://travis-ci.org/danog/MadelineProto)
Licensed under AGPLv3. Created by [Daniil Gentili](https://daniil.it), licensed under AGPLv3.
PHP implementation of MTProto, based on [telepy](https://github.com/griganton/telepy_old). PHP implementation of MTProto, based on [telepy](https://github.com/griganton/telepy_old).
This project can run on PHP 7, PHP 5.6 and HHVM. This project can run on PHP 7, PHP 5.6 and HHVM.
This is a WIP. This project is in beta state.
Structure of this project:
## Usage
### Instantiation
```
$madeline = 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:
```
$settings = [
'authorization' => [ // Authorization settings
'default_temp_auth_key_expires_in' => 31557600, // validity of temporary keys and the binding of the temporary and permanent keys
'rsa_key' => '-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw
Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+
8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n
Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
-----END RSA PUBLIC KEY-----', // RSA public key
],
'connection' => [ // List of datacenters/subdomains where to connect
'ssl_subdomains' => [ // Subdomains of web.telegram.org for https protocol
1 => 'pluto',
2 => 'venus',
3 => 'aurora',
4 => 'vesta',
5 => 'flora', // musa oh wait no :(
],
'test' => [ // Test datacenters
'ipv4' => [ // ipv4 addresses
2 => [ // The rest will be fetched using help.getConfig
'ip_address' => '149.154.167.40',
'port' => 443,
'media_only' => false,
'tcpo_only' => false
]
],
'ipv6' => [ // ipv6 addresses
2 => [ // The rest will be fetched using help.getConfig
'ip_address' => '2001:067c:04e8:f002:0000:0000:0000:000e',
'port' => 443,
'media_only' => false,
'tcpo_only' => false
]
]
],
'main' => [ // Main datacenters
'ipv4' => [ // ipv4 addresses
2 => [ // The rest will be fetched using help.getConfig
'ip_address' => '149.154.167.51',
'port' => 443,
'media_only' => false,
'tcpo_only' => false
]
],
'ipv6' => [ // ipv6 addresses
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,
],
];
```
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.
Example:
```
$settings = [
'authorization' => [ // Authorization settings
'default_temp_auth_key_expires_in' => 86400, // a day
]
]
```
Becomes:
```
$settings = [
'authorization' => [ // Authorization settings
'default_temp_auth_key_expires_in' => 86400,
'rsa_key' => '-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw
Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+
8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n
Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
-----END RSA PUBLIC KEY-----',
]
// The remaining subsetting arrays are the set to the default ones
]
```
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.
The settings array can be accessed in the instantiated class like this:
```
$MadelineProto = new \danog\MadelineProto\API();
$generated_settings = $MadelineProto->API->settings;
```
### Calling mtproto methods and available wrappers
A list of mtproto methods can be found [here](https://tjhorner.com/tl-schema/#functions).
To call an MTProto method simply call it as if it is a method of the API class, substitute namespace sepators (.) with -> if needed:
```
$MadelineProto = new \danog\MadelineProto\API();
$checkedPhone = $MadelineProto->auth->checkPhone( // auth.checkPhone becomes auth->checkPhone
[
'phone_number' => '3993838383', // Random invalid number, note that there should be no +
]
);
$ping = $MadelineProto->ping([3]); // parameter names can be omitted as long as the order specified by the TL scheme is respected
```
The API class also provides some wrapper methods for logging in as a bot or as a normal user:
```
$sentCode = $MadelineProto->phone_login($number); // Send code
var_dump($sentCode);
echo 'Enter the code you received: ';
$code = '';
for ($x = 0; $x < $sentCode['type']['length']; $x++) {
$code .= fgetc(STDIN);
}
$authorization = $MadelineProto->complete_phone_login($code); // Complete authorization
var_dump($authorization);
$authorization = $MadelineProto->bot_login($token); // Note that every time you login as a bot or as a user MadelineProto will logout first, so now MadelineProto is logged in as the bot with the token $token, not with the number $number
var_dump($authorization);
```
### Exceptions
MadelineProto can throw three different exceptions:
* \danog\MadelineProto\Exception - Default exception, thrown when a php error occures and in a lot of other cases
* \danog\MadelineProto\RPCErrorException - Thrown when an RPC error occurres (an error received via the mtproto API)
* \danog\MadelineProto\TL/Exception - Thrown on TL serialization/deserialization errors
## Contributing
[Here](https://github.com/danog/MadelineProto/projects/1) you can find this project's roadmap.
You can use this scheme of the structure of this project to help yourself:
``` ```
src/danog/MadelineProto/ src/danog/MadelineProto/
MTProtoTools/ MTProtoTools/
@ -18,7 +214,6 @@ src/danog/MadelineProto/
AuthKeyHandler - Handles generation of the temporary and permanent authorization keys AuthKeyHandler - Handles generation of the temporary and permanent authorization keys
CallHandler - Handles synchronous calls to mtproto methods or objects, also basic response management (waits until the socket receives a response) CallHandler - Handles synchronous calls to mtproto methods or objects, also basic response management (waits until the socket receives a response)
Crypt - Handles ige and aes encryption Crypt - Handles ige and aes encryption
Exception - Handles exceptions in the MTProtoTools namespace
MessageHandler - Handles sending and receiving of mtproto messages (packs TL serialized data with message id, auth key id and encrypts it with Crypt if needed, adds them to the arrays of incoming and outgoing messages) MessageHandler - Handles sending and receiving of mtproto messages (packs TL serialized data with message id, auth key id and encrypts it with Crypt if needed, adds them to the arrays of incoming and outgoing messages)
MsgIdHandler - Handles message ids (checks if they are valid, adds them to the arrays of incoming and outgoing messages) MsgIdHandler - Handles message ids (checks if they are valid, adds them to the arrays of incoming and outgoing messages)
ResponseHandler - Handles the content of responses received, service messages, rpc results, errors, and stores them into the response section of the outgoing messages array) ResponseHandler - Handles the content of responses received, service messages, rpc results, errors, and stores them into the response section of the outgoing messages array)
@ -29,22 +224,21 @@ src/danog/MadelineProto/
TL - Handles TL serialization and deserialization TL - Handles TL serialization and deserialization
TLConstructor - Represents a TL Constructor TLConstructor - Represents a TL Constructor
TLMethod - Represents a TL method TLMethod - Represents a TL method
API - Wrapper class that istantiates the MTProto class, sets the error handler, and provides a wrapper for calling mtproto methods directly as class submethods 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
APIFactory - Provides a wrapper for calling namespaced mtproto methods directly as class submethods 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 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) DataCenter - Handles mtproto datacenters (is a wrapper for Connection classes)
DebugTools - Various debugging tools DebugTools - Various debugging tools
Exception - Handles exceptions in the main namespace 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, istantiation of classes, writing of client info
MTProtoTools - Extends all of the classes in MTProtoTools/ 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 prime.py and getpq.py - prime module (python) for p and q generation
PrimeModule.php - prime module (php) for p and q generation by wrapping the python module, using wolfram alpha or a built in PHP engine PrimeModule.php - prime module (php) for p and q generation by wrapping the python module, using wolfram alpha or a built in PHP engine
RSA - Handles RSA public keys and signatures RSA - Handles RSA public keys and signatures
Tools - Various tools (positive modulus, string2bin, python-like range) Tools - Various tools (positive modulus, string2bin, python-like range)
``` ```
Check out the [Contribution guide](https://github.com/danog/MadelineProto/blob/master/CONTRIBUTING.md) before contributing.
This project adheres to the [Hacktoberfest](https://hacktoberfest.digitalocean.com/) event by DigitalOcean.
Browse the issues of this project and help close at least four of them with a pull request to get a free t-shirt!
Check out the [Contribution guide first though](https://github.com/danog/MadelineProto/blob/master/CONTRIBUTING.md).

View File

@ -22,10 +22,8 @@ class API extends APIFactory
{ {
set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']); set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']);
$this->API = new MTProto($params); $this->API = new MTProto($params);
\danog\MadelineProto\Logger::log('Running APIFactory...');
foreach ($this->API->tl->methods->method_namespace as $namespace => $method) { $this->APIFactory();
$this->{$method} = new APIFactory($method, $this->API);
}
\danog\MadelineProto\Logger::log('Ping...'); \danog\MadelineProto\Logger::log('Ping...');
$pong = $this->ping([3]); $pong = $this->ping([3]);
@ -37,6 +35,82 @@ class API extends APIFactory
\danog\MadelineProto\Logger::log('MadelineProto is ready!'); \danog\MadelineProto\Logger::log('MadelineProto is ready!');
restore_error_handler(); restore_error_handler();
} }
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);
}
}
public function logout() {
$this->API->datacenter->authorized = false;
$this->API->datacenter->authorization = null;
if (!$this->API->method_call('auth.logOut')) {
throw new Exception('An error occurred while logging out!');
}
\danog\MadelineProto\Logger::log('Logged out successfully!');
return true;
}
public function bot_login($token) {
if ($this->API->datacenter->authorized) {
\danog\MadelineProto\Logger::log('This instance of MadelineProto is already logged in. Logging out first...');
$this->logout();
}
\danog\MadelineProto\Logger::log('Logging in as a bot...');
$this->API->datacenter->authorization = $this->API->method_call(
'auth.importBotAuthorization',
[
'bot_auth_token' => $token,
'api_id' => $this->API->settings['app_info']['api_id'],
'api_hash' => $this->API->settings['app_info']['api_hash'],
]
);
$this->API->datacenter->authorized = true;
\danog\MadelineProto\Logger::log('Logged in successfully!');
return $this->API->datacenter->authorization;
}
public function phone_login($number, $sms_type = 5) {
if ($this->API->datacenter->authorized) {
\danog\MadelineProto\Logger::log('This instance of MadelineProto is already logged in. Logging out first...');
$this->logout();
}
\danog\MadelineProto\Logger::log('Sending code...');
$this->API->datacenter->authorization = $this->API->method_call(
'auth.sendCode',
[
'phone_number' => $number,
'sms_type' => $sms_type,
'api_id' => $this->API->settings['app_info']['api_id'],
'api_hash' => $this->API->settings['app_info']['api_hash'],
'lang_code' => $this->API->settings['app_info']['lang_code'],
]
);
$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.');
return $this->API->datacenter->authorization;
}
public function complete_phone_login($code) {
if (!$this->API->datacenter->waiting_code) {
throw new Exception("I'm not waiting for the code! Please call the phone_login method first");
}
\danog\MadelineProto\Logger::log('Logging in as a normal user...');
$this->API->datacenter->authorization = $this->API->method_call(
'auth.signIn',
[
'phone_number' => $this->API->datacenter->authorization['phone_number'],
'phone_code_hash' => $this->API->datacenter->authorization['phone_code_hash'],
'phone_code' => $code,
]
);
$this->API->datacenter->waiting_code = false;
$this->API->datacenter->authorized = true;
\danog\MadelineProto\Logger::log('Logged in successfully!');
return $this->API->datacenter->authorization;
}
public function __wakeup() {
$this->APIFactory();
}
public function __destruct() public function __destruct()
{ {

View File

@ -26,7 +26,8 @@ class APIFactory
public function __call($name, $arguments) public function __call($name, $arguments)
{ {
set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']); set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']);
return $this->API->method_call($this->namespace.$name, $arguments[0]); $this->API->get_config();
return $this->API->method_call($this->namespace.$name, is_array($arguments[0]) ? $arguments[0] : []);
restore_error_handler(); restore_error_handler();
} }
} }

View File

@ -29,6 +29,13 @@ class Connection extends Tools
public $auth_key; public $auth_key;
public $session_id; public $session_id;
public $seq_no = 0; public $seq_no = 0;
public $authorized = false;
public $authorization = null;
public $waiting_code = false;
public $incoming_messages = [];
public $outgoing_messages = [];
public function __construct($ip, $port, $protocol, $timeout) public function __construct($ip, $port, $protocol, $timeout)
{ {
@ -104,7 +111,9 @@ class Connection extends Tools
$this->__destruct(); $this->__destruct();
$this->__construct($this->ip, $this->port, $this->protocol, $this->timeout); $this->__construct($this->ip, $this->port, $this->protocol, $this->timeout);
} }
public function __wakeup() {
$this->close_and_reopen();
}
/** /**
* Function to get hex crc32. * Function to get hex crc32.
* *

View File

@ -17,36 +17,22 @@ namespace danog\MadelineProto;
*/ */
class DataCenter extends Tools class DataCenter extends Tools
{ {
public $referenced_variables = ['time_delta', 'temp_auth_key', 'auth_key', 'session_id', 'seq_no'];
public $sockets; public $sockets;
public $curdc = 0; public $curdc = 0;
public $dclist = [];
public $settings = [];
public function __construct($dclist, $settings) public function __construct(&$dclist, &$settings)
{ {
$this->dclist = $dclist; $this->dclist = &$dclist;
$this->settings = $settings; $this->settings = &$settings;
if (isset($this->settings['all'])) {
foreach ($this->range(1, 6) as $n) {
$this->settings[$n] = $this->settings['all'];
}
unset($this->settings['all']);
}
foreach ($this->range(1, 6) as $n) {
if (!isset($this->settings[$n])) {
$this->settings[$n] = [
'protocol' => 'tcp_full',
'port' => '443',
'test_mode' => true,
'timeout' => 10,
];
}
}
} }
public function dc_disconnect($dc_number) public function dc_disconnect($dc_number)
{ {
if ($this->curdc == $dc_number) { if ($this->curdc == $dc_number) {
$this->unset_curdc(); $this->curdc = 0;
} }
if (isset($this->sockets[$dc_number])) { if (isset($this->sockets[$dc_number])) {
\danog\MadelineProto\Logger::log('Disconnecting from DC '.$dc_number.'...'); \danog\MadelineProto\Logger::log('Disconnecting from DC '.$dc_number.'...');
@ -56,53 +42,40 @@ class DataCenter extends Tools
public function dc_connect($dc_number, $settings = []) public function dc_connect($dc_number, $settings = [])
{ {
$this->curdc = $dc_number;
if (isset($this->sockets[$dc_number])) { if (isset($this->sockets[$dc_number])) {
$this->set_curdc($dc_number);
return false; return false;
} }
\danog\MadelineProto\Logger::log('Connecting to DC '.$dc_number.'...');
if ($settings == []) { if ($settings == []) {
$settings = $this->settings[$dc_number]; $settings = $this->settings[$dc_number];
} }
$address = $settings['test_mode'] ? $this->dclist['test'][$dc_number] : $this->dclist['main'][$dc_number]; $test = $settings['test_mode'] ? 'test' : 'main';
$ipv6 = $settings['ipv6'] ? 'ipv6' : 'ipv4';
$address = $this->dclist[$test][$ipv6][$dc_number]['ip_address'];
$port = $this->dclist[$test][$ipv6][$dc_number]['port'];
if ($settings['protocol'] == 'https') { if ($settings['protocol'] == 'https') {
$subdomain = $this->dclist['ssl_subdomains'][$dc_number].($settings['upload'] ? '-1' : ''); $subdomain = $this->dclist['ssl_subdomains'][$dc_number].($settings['upload'] ? '-1' : '');
$path = $settings['test_mode'] ? 'apiw_test1' : 'apiw1'; $path = $settings['test_mode'] ? 'apiw_test1' : 'apiw1';
$address = 'https://'.$subdomain.'.web.telegram.org/'.$path; $address = 'https://'.$subdomain.'.web.telegram.org/'.$path;
} }
$this->sockets[$dc_number] = new Connection($address, $settings['port'], $settings['protocol'], $settings['timeout']); \danog\MadelineProto\Logger::log('Connecting to DC '.$dc_number.' ('.$test.' server, '.$ipv6.')...');
$this->set_curdc($dc_number);
$this->sockets[$dc_number] = new Connection($address, $port, $settings['protocol'], $settings['timeout']);
return true; return true;
} }
public function set_curdc($dc_number) public function &__get($name)
{ {
$this->curdc = $dc_number; return $this->sockets[$this->curdc]->{$name};
foreach ($this->referenced_variables as $key) {
$this->{$key} = &$this->sockets[$dc_number]->{$key};
} }
} public function __set($name, $value)
public function unset_curdc()
{ {
$this->curdc = 0; $this->sockets[$this->curdc]->{$name} =& $value;
foreach ($this->referenced_variables as $key) {
unset($this->{$key});
}
} }
public function __call($name, $arguments) public function __call($name, $arguments)
{ {
return $this->sockets[$this->curdc]->{$name}(...$arguments); return $this->sockets[$this->curdc]->{$name}(...$arguments);
} }
public function __destroy()
{
$this->unset_curdc();
foreach ($this->sockets as $n => $socket) {
unset($this->sockets[$n]);
}
}
} }

View File

@ -29,13 +29,13 @@ class Logger
* 2 - Log to file defined in second parameter * 2 - Log to file defined in second parameter
* 3 - Echo logs * 3 - Echo logs
*/ */
public static function constructor($mode, $optional = null) public static function constructor(&$mode, &$optional = null)
{ {
if ($mode == null) { if ($mode == null) {
throw new Exception('No mode was specified!'); throw new Exception('No mode was specified!');
} }
self::$mode = (string) $mode; self::$mode =& $mode;
self::$optional = $optional; self::$optional =& $optional;
self::$constructed = true; self::$constructed = true;
} }
@ -50,13 +50,13 @@ class Logger
} }
$param = str_pad(basename(debug_backtrace()[0]['file'], '.php').': ', 16).((self::$mode == 3) ? "\t" : '').$param; $param = str_pad(basename(debug_backtrace()[0]['file'], '.php').': ', 16).((self::$mode == 3) ? "\t" : '').$param;
switch (self::$mode) { switch (self::$mode) {
case '1': case 1:
error_log($param); error_log($param);
break; break;
case '2': case 2:
error_log($param, 3, self::$optional); error_log($param, 3, self::$optional);
break; break;
case '3': case 3:
echo $param.PHP_EOL; echo $param.PHP_EOL;
break; break;
default: default:

View File

@ -18,13 +18,23 @@ namespace danog\MadelineProto;
class MTProto extends MTProtoTools class MTProto extends MTProtoTools
{ {
public $settings = []; public $settings = [];
public $authorized = false;
public $waiting_code = false;
public $config = ['expires' => -1];
public $ipv6 = false;
public function __construct($settings = []) public function __construct($settings = [])
{ {
$google = '';
try {
$google = file_get_contents('https://ipv6.google.com');
} catch (Exception $e) { ; };
$this->ipv6 = strlen($google) > 0;
// Set default settings // Set default settings
$default_settings = [ $default_settings = [
'authorization' => [ 'authorization' => [ // Authorization settings
'default_temp_auth_key_expires_in' => 31557600, 'default_temp_auth_key_expires_in' => 31557600, // validity of temporary keys and the binding of the temporary and permanent keys
'rsa_key' => '-----BEGIN RSA PUBLIC KEY----- 'rsa_key' => '-----BEGIN RSA PUBLIC KEY-----
MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6 MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6
lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS
@ -32,64 +42,94 @@ an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw
Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+ Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+
8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n 8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n
Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
-----END RSA PUBLIC KEY-----', -----END RSA PUBLIC KEY-----', // RSA public key
], ],
'connection' => [ 'connection' => [ // List of datacenters/subdomains where to connect
'ssl_subdomains' => [ 'ssl_subdomains' => [ // Subdomains of web.telegram.org for https protocol
1 => 'pluto', 1 => 'pluto',
2 => 'venus', 2 => 'venus',
3 => 'aurora', 3 => 'aurora',
4 => 'vesta', 4 => 'vesta',
5 => 'flora', 5 => 'flora', // musa oh wait no :(
], ],
'test' => [ 'test' => [ // Test datacenters
1 => '149.154.175.10', 'ipv4' => [ // ipv4 addresses
2 => '149.154.167.40', 2 => [ // The rest will be fetched using help.getConfig
3 => '149.154.175.117', 'ip_address' => '149.154.167.40',
'port' => 443,
'media_only' => false,
'tcpo_only' => false
]
], ],
'main' => [ 'ipv6' => [ // ipv6 addresses
1 => '149.154.175.50', 2 => [ // The rest will be fetched using help.getConfig
2 => '149.154.167.51', 'ip_address' => '2001:067c:04e8:f002:0000:0000:0000:000e',
3 => '149.154.175.100', 'port' => 443,
4 => '149.154.167.91', 'media_only' => false,
5 => '149.154.171.5', 'tcpo_only' => false
]
]
],
'main' => [ // Main datacenters
'ipv4' => [ // ipv4 addresses
2 => [ // The rest will be fetched using help.getConfig
'ip_address' => '149.154.167.51',
'port' => 443,
'media_only' => false,
'tcpo_only' => false
]
],
'ipv6' => [ // ipv6 addresses
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' => [ // connection settings
'all' => [ 'all' => [ // These settings will be applied on every datacenter that hasn't a custom settings subarray...
'protocol' => 'tcp_full', 'protocol' => 'tcp_full', // can be tcp_full, tcp_abridged, tcp_intermediate, http (unsupported), https (unsupported), udp (unsupported)
'test_mode' => false, 'test_mode' => false, // decides whether to connect to the main telegram servers or to the testing servers (deep telegram)
'port' => '443', 'ipv6' => $this->ipv6, // decides whether to use ipv6, ipv6 attribute of API attribute of API class contains autodetected boolean
'timeout' => 10 'timeout' => 10 // timeout for sockets
], ],
'default_dc' => 2,
], ],
'app_info' => [ 'app_info' => [ // obtained in https://my.telegram.org
'api_id' => 25628, 'api_id' => 25628,
'api_hash' => '1fe17cda7d355166cdaa71f04122873c', 'api_hash' => '1fe17cda7d355166cdaa71f04122873c',
'device_model' => php_uname('s'), 'device_model' => php_uname('s'),
'system_version' => php_uname('r'), 'system_version' => php_uname('r'),
'app_version' => 'Unicorn', 'app_version' => 'Unicorn', // 🌚
'lang_code' => 'en', 'lang_code' => 'en',
], ],
'tl_schema' => [ 'tl_schema' => [ // TL scheme files
'layer' => 57, 'layer' => 57, // layer version
'src' => [ 'src' => [
'mtproto' => __DIR__.'/TL_mtproto_v1.json', 'mtproto' => __DIR__.'/TL_mtproto_v1.json', // mtproto TL scheme
'telegram' => __DIR__.'/TL_telegram_v57.json', 'telegram' => __DIR__.'/TL_telegram_v57.json', // telegram TL scheme
], ],
], ],
'logger' => [ 'logger' => [ // Logger settings
'logger' => 1, /*
* 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_param' => '/tmp/MadelineProto.log',
'logger' => 3, 'logger' => 3, // overwrite previous setting and echo logs
], ],
'max_tries' => [ 'max_tries' => [
'query' => 5, 'query' => 5, // How many times should I try to call a method or send an object before throwing an exception
'authorization' => 5, 'authorization' => 5, // How many times should I try to generate an authorization key before throwing an exception
'response' => 5, 'response' => 5,// How many times should I try to get a response of a query before throwing an exception
], ],
'msg_array_limit' => [ 'msg_array_limit' => [ // How big should be the arrays containing the incoming and outgoing messages?
'incoming' => 30, 'incoming' => 30,
'outgoing' => 30, 'outgoing' => 30,
], ],
@ -104,14 +144,21 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
} }
} }
} }
if (isset($settings['connection_settings']['all'])) {
foreach ($this->range(1, 6) as $n) {
if (!isset($settings['connection_settings'][$n])) {
$settings['connection_settings'][$n] = $settings['connection_settings']['all'];
}
}
unset($settings['connection_settings']['all']);
}
$this->settings = $settings; $this->settings = $settings;
// Setup logger // Setup logger
$this->setup_logger(); $this->setup_logger();
// Connect to servers // Connect to servers
\danog\MadelineProto\Logger::log('Istantiating DataCenter...'); $this->mk_datacenter();
$this->datacenter = new DataCenter($this->settings['connection'], $this->settings['connection_settings']);
// Load rsa key // Load rsa key
\danog\MadelineProto\Logger::log('Loading RSA key...'); \danog\MadelineProto\Logger::log('Loading RSA key...');
@ -121,11 +168,17 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
\danog\MadelineProto\Logger::log('Translating tl schemas...'); \danog\MadelineProto\Logger::log('Translating tl schemas...');
$this->tl = new TL\TL($this->settings['tl_schema']['src']); $this->tl = new TL\TL($this->settings['tl_schema']['src']);
$this->incoming_messages = []; $this->switch_dc(2, true);
$this->outgoing_messages = []; $this->get_config();
$this->future_salts = []; }
public function __wakeup() {
$this->switch_dc($this->settings['connection_settings']['default_dc'], true); $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']);
} }
public function setup_logger() public function setup_logger()
@ -140,15 +193,18 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
public function switch_dc($new_dc, $allow_nearest_dc_switch = false) public function switch_dc($new_dc, $allow_nearest_dc_switch = false)
{ {
\danog\MadelineProto\Logger::log('Switching to DC '.$new_dc.'...'); \danog\MadelineProto\Logger::log('Switching to DC '.$new_dc.'...');
/* if ($this->datacenter->curdc !== 0) { if ($this->datacenter->curdc !== 0 && $this->datacenter->authorized) {
try {
$exported_authorization = $this->method_call('auth.exportAuthorization', ['dc_id' => $new_dc]); $exported_authorization = $this->method_call('auth.exportAuthorization', ['dc_id' => $new_dc]);
} catch (\danog\MadelineProto\RPCErrorException $e) { ; } }
}*/
if ($this->datacenter->dc_connect($new_dc)) { if ($this->datacenter->dc_connect($new_dc)) {
$this->init_authorization(); $this->init_authorization();
$this->write_client_info($allow_nearest_dc_switch); $this->config = $this->write_client_info('help.getConfig');
// if (isset($exported_authorization)) $this->method_call('auth.importAuthorization', $exported_authorization); $this->parse_config();
if (isset($exported_authorization)) {
$this->datacenter->authorization = $this->method_call('auth.importAuthorization', $exported_authorization);
$this->datacenter->authorized = true;
}
$this->get_nearest_dc($allow_nearest_dc_switch);
} }
} }
@ -169,21 +225,24 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
} }
} }
public function write_client_info($allow_switch) public function write_client_info($method, $arguments = [])
{ {
\danog\MadelineProto\Logger::log('Writing client info...'); \danog\MadelineProto\Logger::log('Writing client info (also executing '.$method.')...');
$nearest_dc = $this->method_call( return $this->method_call(
'invokeWithLayer', 'invokeWithLayer',
[ [
'layer' => $this->settings['tl_schema']['layer'], 'layer' => $this->settings['tl_schema']['layer'],
'query' => $this->tl->serialize_method('initConnection', 'query' => $this->tl->serialize_method('initConnection',
array_merge( array_merge(
$this->settings['app_info'], $this->settings['app_info'],
['query' => $this->tl->serialize_method('help.getNearestDc', [])] ['query' => $this->tl->serialize_method($method, $arguments)]
) )
), ),
] ]
); );
}
public function get_nearest_dc($allow_switch) {
$nearest_dc = $this->method_call('help.getNearestDc');
\danog\MadelineProto\Logger::log("We're in ".$nearest_dc['country'].', current dc is '.$nearest_dc['this_dc'].', nearest dc is '.$nearest_dc['nearest_dc'].'.'); \danog\MadelineProto\Logger::log("We're in ".$nearest_dc['country'].', current dc is '.$nearest_dc['this_dc'].', nearest dc is '.$nearest_dc['nearest_dc'].'.');
if ($nearest_dc['nearest_dc'] != $nearest_dc['this_dc'] && $allow_switch) { if ($nearest_dc['nearest_dc'] != $nearest_dc['this_dc'] && $allow_switch) {
@ -191,4 +250,23 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
$this->settings['connection_settings']['default_dc'] = $nearest_dc['nearest_dc']; $this->settings['connection_settings']['default_dc'] = $nearest_dc['nearest_dc'];
} }
} }
public function get_config() {
if ($this->config['expires'] > time()) {
return;
}
$this->config = $this->method_call('help.getConfig');
$this->parse_config();
}
public function parse_config() {
\danog\MadelineProto\Logger::log('Received config!', $this->config);
foreach ($this->config["dc_options"] as $dc) {
$test = $this->config['test_mode'] ? 'test' : 'main';
$ipv6 = ($dc['ipv6'] ? 'ipv6' : 'ipv4');
$id = $dc['id'];
$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;
}
}
} }

View File

@ -20,22 +20,23 @@ class AckHandler extends \danog\MadelineProto\PrimeModule
public function ack_outgoing_message_id($message_id) public function ack_outgoing_message_id($message_id)
{ {
// The server acknowledges that it received my message // The server acknowledges that it received my message
if (!isset($this->outgoing_messages[$message_id])) { if (!isset($this->datacenter->outgoing_messages[$message_id])) {
throw new \danog\MadelineProto\Exception("Couldn't find message id ".$message_id.' in the array of outgoing messages. Maybe try to increase its size?'); throw new \danog\MadelineProto\Exception("Couldn't find message id ".$message_id.' in the array of outgoing messages. Maybe try to increase its size?');
} }
$this->outgoing_messages[$message_id]['ack'] = true; $this->datacenter->outgoing_messages[$message_id]['ack'] = true;
} }
public function ack_incoming_message_id($message_id) public function ack_incoming_message_id($message_id)
{ {
if ($this->datacenter->temp_auth_key['id'] === null || $this->datacenter->temp_auth_key['id'] == $this->string2bin('\x00\x00\x00\x00\x00\x00\x00\x00')) {
return;
}
// I let the server know that I received its message // I let the server know that I received its message
if (!isset($this->incoming_messages[$message_id])) { if (!isset($this->datacenter->incoming_messages[$message_id])) {
throw new \danog\MadelineProto\Exception("Couldn't find message id ".$message_id.' in the array of incoming message ids. Maybe try to increase its size?'); throw new \danog\MadelineProto\Exception("Couldn't find message id ".$message_id.' in the array of incoming message ids. Maybe try to increase its size?');
} }
if ($this->datacenter->temp_auth_key['id'] === null || $this->datacenter->temp_auth_key['id'] == $this->string2bin('\x00\x00\x00\x00\x00\x00\x00\x00') || (isset($this->datacenter->incoming_messages[$message_id]['ack']) && $this->datacenter->incoming_messages[$message_id]['ack'])) {
return;
}
$this->object_call('msgs_ack', ['msg_ids' => [$message_id]]); $this->object_call('msgs_ack', ['msg_ids' => [$message_id]]);
$this->incoming_messages[$message_id]['ack'] = true; $this->datacenter->incoming_messages[$message_id]['ack'] = true;
} }
} }

View File

@ -318,7 +318,9 @@ class AuthKeyHandler extends AckHandler
} }
foreach ($this->range(0, $this->settings['max_tries']['authorization']) as $retry_id) { foreach ($this->range(0, $this->settings['max_tries']['authorization']) as $retry_id) {
\danog\MadelineProto\Logger::log('Generating b...');
$b = new \phpseclib\Math\BigInteger(\phpseclib\Crypt\Random::string(256), 256); $b = new \phpseclib\Math\BigInteger(\phpseclib\Crypt\Random::string(256), 256);
\danog\MadelineProto\Logger::log('Generating g_b...');
$g_b = $g->powMod($b, $dh_prime); $g_b = $g->powMod($b, $dh_prime);
/* /*
@ -467,8 +469,10 @@ class AuthKeyHandler extends AckHandler
break; break;
} }
} }
} catch (Exception $e) { } catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log('An exception occurred while generating the authorization key. Retrying...'); \danog\MadelineProto\Logger::log('An exception occurred while generating the authorization key. Retrying...');
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log('An RPCErrorExceptiom occurred while generating the authorization key. Retrying...');
} }
} }

View File

@ -28,8 +28,8 @@ class CallHandler extends AuthKeyHandler
\danog\MadelineProto\Logger::log('Getting response (try number '.$count.' for '.$optional_name.')...'); \danog\MadelineProto\Logger::log('Getting response (try number '.$count.' for '.$optional_name.')...');
$last_received = $this->recv_message(); $last_received = $this->recv_message();
$this->handle_message($last_sent, $last_received); $this->handle_message($last_sent, $last_received);
if (isset($this->outgoing_messages[$last_sent]['response']) && isset($this->incoming_messages[$this->outgoing_messages[$last_sent]['response']]['content'])) { if (isset($this->datacenter->outgoing_messages[$last_sent]['response']) && isset($this->datacenter->incoming_messages[$this->datacenter->outgoing_messages[$last_sent]['response']]['content'])) {
$response = $this->incoming_messages[$this->outgoing_messages[$last_sent]['response']]['content']; $response = $this->datacenter->incoming_messages[$this->datacenter->outgoing_messages[$last_sent]['response']]['content'];
} }
} }
if ($response == null) { if ($response == null) {
@ -43,7 +43,7 @@ class CallHandler extends AuthKeyHandler
\danog\MadelineProto\Logger::log('Received request to switch to DC '.$dc); \danog\MadelineProto\Logger::log('Received request to switch to DC '.$dc);
$this->switch_dc($dc); $this->switch_dc($dc);
return $this->method_call($this->outgoing_messages[$last_sent]['content']['method'], $this->outgoing_messages[$last_sent]['content']['args']); return $this->method_call($this->datacenter->outgoing_messages[$last_sent]['content']['method'], $this->datacenter->outgoing_messages[$last_sent]['content']['args']);
break; break;
default: default:
@ -57,29 +57,22 @@ class CallHandler extends AuthKeyHandler
} }
} }
public function method_call($method, $args, $message_id = null) public function method_call($method, $args = [], $message_id = null)
{ {
if (!is_array($args)) { if (!is_array($args)) {
throw new \danog\MadelineProto\Exception("Arguments aren't an array."); throw new \danog\MadelineProto\Exception("Arguments aren't an array.");
} }
foreach (range(1, $this->settings['max_tries']['query']) as $i) { foreach (range(1, $this->settings['max_tries']['query']) as $count) {
try { try {
\danog\MadelineProto\Logger::log('Calling method (try number '.$count.' for '.$method.')...');
$args = $this->tl->get_named_method_args($method, $args); $args = $this->tl->get_named_method_args($method, $args);
$int_message_id = $this->send_message($this->tl->serialize_method($method, $args), $this->tl->content_related($method), $message_id); $int_message_id = $this->send_message($this->tl->serialize_method($method, $args), $this->tl->content_related($method), $message_id);
$this->outgoing_messages[$int_message_id]['content'] = ['method' => $method, 'args' => $args]; $this->datacenter->outgoing_messages[$int_message_id]['content'] = ['method' => $method, 'args' => $args];
$server_answer = $this->wait_for_response($int_message_id, $method); $server_answer = $this->wait_for_response($int_message_id, $method);
} catch (\danog\MadelineProto\Exception $e) { } catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log('An error occurred while calling method '.$method.': '.$e->getMessage().' in '.basename($e->getFile(), '.php').' on line '.$e->getLine().'. Recreating connection and retrying to call method...'); \danog\MadelineProto\Logger::log('An error occurred while calling method '.$method.': '.$e->getMessage().' in '.basename($e->getFile(), '.php').' on line '.$e->getLine().'. Recreating connection and retrying to call method...');
$this->datacenter->close_and_reopen(); $this->datacenter->close_and_reopen();
continue; continue;
} catch (\danog\MadelineProto\RPCErrorException $e) {
if ($e->getCode() == -404) {
\danog\MadelineProto\Logger::log('Temporary authorization key expired. Regenerating and recalling method...');
$this->datacenter->temp_auth_key = null;
$this->init_authorization();
continue;
}
throw $e;
} }
if ($server_answer == null) { if ($server_answer == null) {
throw new \danog\MadelineProto\Exception('An error occurred while calling method '.$method.'.'); throw new \danog\MadelineProto\Exception('An error occurred while calling method '.$method.'.');
@ -90,16 +83,17 @@ class CallHandler extends AuthKeyHandler
throw new \danog\MadelineProto\Exception('An error occurred while calling method '.$method.'.'); throw new \danog\MadelineProto\Exception('An error occurred while calling method '.$method.'.');
} }
public function object_call($object, $args) public function object_call($object, $args = [])
{ {
if (!is_array($args)) { if (!is_array($args)) {
throw new \danog\MadelineProto\Exception("Arguments aren't an array."); throw new \danog\MadelineProto\Exception("Arguments aren't an array.");
} }
foreach (range(1, $this->settings['max_tries']['query']) as $i) { foreach (range(1, $this->settings['max_tries']['query']) as $count) {
try { try {
\danog\MadelineProto\Logger::log('Sending object (try number '.$count.' for '.$object.')...');
$int_message_id = $this->send_message($this->tl->serialize_obj($object, $args), $this->tl->content_related($object)); $int_message_id = $this->send_message($this->tl->serialize_obj($object, $args), $this->tl->content_related($object));
$this->outgoing_messages[$int_message_id]['content'] = ['object' => $object, 'args' => $args]; $this->datacenter->outgoing_messages[$int_message_id]['content'] = ['object' => $object, 'args' => $args];
// $server_answer = $this->wait_for_response($int_message_id); // $server_answer = $this->wait_for_response($int_message_id);
} catch (Exception $e) { } catch (Exception $e) {
\danog\MadelineProto\Logger::log('An error occurred while calling object '.$object.': '.$e->getMessage().' in '.$e->getFile().':'.$e->getLine().'. Recreating connection and retrying to call object...'); \danog\MadelineProto\Logger::log('An error occurred while calling object '.$object.': '.$e->getMessage().' in '.$e->getFile().':'.$e->getLine().'. Recreating connection and retrying to call object...');

View File

@ -40,7 +40,7 @@ class MessageHandler extends Crypt
$padding = \phpseclib\Crypt\Random::string($this->posmod(-strlen($encrypted_data), 16)); $padding = \phpseclib\Crypt\Random::string($this->posmod(-strlen($encrypted_data), 16));
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->temp_auth_key['auth_key']); list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->temp_auth_key['auth_key']);
$message = $this->datacenter->temp_auth_key['id'].$message_key.$this->ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv); $message = $this->datacenter->temp_auth_key['id'].$message_key.$this->ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv);
$this->outgoing_messages[$int_message_id]['seq_no'] = $seq_no; $this->datacenter->outgoing_messages[$int_message_id]['seq_no'] = $seq_no;
} }
$this->datacenter->send_message($message); $this->datacenter->send_message($message);
@ -105,12 +105,12 @@ class MessageHandler extends Crypt
if ($message_key != substr(sha1(substr($decrypted_data, 0, 32 + $message_data_length), true), -16)) { if ($message_key != substr(sha1(substr($decrypted_data, 0, 32 + $message_data_length), true), -16)) {
throw new \danog\MadelineProto\Exception('msg_key mismatch'); throw new \danog\MadelineProto\Exception('msg_key mismatch');
} }
$this->incoming_messages[$message_id]['seq_no'] = $seq_no; $this->datacenter->incoming_messages[$message_id]['seq_no'] = $seq_no;
} else { } else {
throw new \danog\MadelineProto\Exception('Got unknown auth_key id'); throw new \danog\MadelineProto\Exception('Got unknown auth_key id');
} }
$deserialized = $this->tl->deserialize($this->fopen_and_write('php://memory', 'rw+b', $message_data)); $deserialized = $this->tl->deserialize($this->fopen_and_write('php://memory', 'rw+b', $message_data));
$this->incoming_messages[$message_id]['content'] = $deserialized; $this->datacenter->incoming_messages[$message_id]['content'] = $deserialized;
return $message_id; return $message_id;
} }

View File

@ -29,20 +29,20 @@ class MsgIdHandler extends MessageHandler
if ($new_message_id % 4 != 0) { if ($new_message_id % 4 != 0) {
throw new \danog\MadelineProto\Exception('Given message id ('.$new_message_id.') is not divisible by 4.'); throw new \danog\MadelineProto\Exception('Given message id ('.$new_message_id.') is not divisible by 4.');
} }
$keys = array_keys($this->outgoing_messages); $keys = array_keys($this->datacenter->outgoing_messages);
asort($keys); asort($keys);
if ($new_message_id <= end($keys)) { if ($new_message_id <= end($keys)) {
throw new \danog\MadelineProto\Exception('Given message id ('.$new_message_id.') is lower than or equal than the current limit ('.end($keys).').', 1); throw new \danog\MadelineProto\Exception('Given message id ('.$new_message_id.') is lower than or equal than the current limit ('.end($keys).').', 1);
} }
$this->outgoing_messages[$new_message_id] = []; $this->datacenter->outgoing_messages[$new_message_id] = [];
if (count($this->outgoing_messages) > $this->settings['msg_array_limit']['outgoing']) { if (count($this->datacenter->outgoing_messages) > $this->settings['msg_array_limit']['outgoing']) {
array_shift($this->outgoing_messages); array_shift($this->datacenter->outgoing_messages);
} }
} else { } else {
if ($new_message_id % 4 != 1 && $new_message_id % 4 != 3) { if ($new_message_id % 4 != 1 && $new_message_id % 4 != 3) {
throw new \danog\MadelineProto\Exception('message id mod 4 != 1 or 3'); throw new \danog\MadelineProto\Exception('message id mod 4 != 1 or 3');
} }
$keys = array_keys($this->incoming_messages); $keys = array_keys($this->datacenter->incoming_messages);
if ($container) { if ($container) {
asort($keys); asort($keys);
if ($new_message_id >= end($keys)) { if ($new_message_id >= end($keys)) {
@ -56,11 +56,11 @@ class MsgIdHandler extends MessageHandler
} }
} }
} }
$this->incoming_messages[$new_message_id] = []; $this->datacenter->incoming_messages[$new_message_id] = [];
if (count($this->incoming_messages) > $this->settings['msg_array_limit']['incoming']) { if (count($this->datacenter->incoming_messages) > $this->settings['msg_array_limit']['incoming']) {
array_shift($this->incoming_messages); array_shift($this->datacenter->incoming_messages);
} }
ksort($this->incoming_messages); ksort($this->datacenter->incoming_messages);
} }
} }
@ -75,7 +75,7 @@ class MsgIdHandler extends MessageHandler
); );
*/ */
$keys = array_keys($this->outgoing_messages); $keys = array_keys($this->datacenter->outgoing_messages);
asort($keys); asort($keys);
$keys = end($keys); $keys = end($keys);
if ($int_message_id <= $keys) { if ($int_message_id <= $keys) {

View File

@ -19,7 +19,8 @@ class ResponseHandler extends MsgIdHandler
{ {
public function handle_message($last_sent, $last_received) public function handle_message($last_sent, $last_received)
{ {
$response = $this->incoming_messages[$last_received]['content']; $response = $this->datacenter->incoming_messages[$last_received]['content'];
switch ($response['_']) { switch ($response['_']) {
case 'msgs_ack': case 'msgs_ack':
foreach ($response['msg_ids'] as $msg_id) { foreach ($response['msg_ids'] as $msg_id) {
@ -30,14 +31,15 @@ class ResponseHandler extends MsgIdHandler
case 'rpc_result': case 'rpc_result':
$this->ack_incoming_message_id($last_received); // Acknowledge that I received the server's response $this->ack_incoming_message_id($last_received); // Acknowledge that I received the server's response
$this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received my request $this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received my request
$this->outgoing_messages[$response['req_msg_id']]['response'] = $last_received; $this->datacenter->outgoing_messages[$response['req_msg_id']]['response'] = $last_received;
$this->incoming_messages[$last_received]['content'] = $response['result']; $this->datacenter->incoming_messages[$last_received]['content'] = $response['result'];
return $this->handle_message($last_sent, $last_received);
break; break;
case 'future_salts': case 'future_salts':
$this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received my request $this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received my request
$this->outgoing_messages[$response['req_msg_id']]['response'] = $last_received; $this->datacenter->outgoing_messages[$response['req_msg_id']]['response'] = $last_received;
$this->incoming_messages[$last_received]['content'] = $response; $this->datacenter->incoming_messages[$last_received]['content'] = $response;
break; break;
case 'bad_msg_notification': case 'bad_msg_notification':
@ -54,20 +56,20 @@ class ResponseHandler extends MsgIdHandler
48 => 'incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it)', 48 => 'incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it)',
64 => 'invalid container.', 64 => 'invalid container.',
]; ];
throw new \danog\MadelineProto\Exception('Received bad_msg_notification for '.$response['bad_msg_id'].': '.$error_codes[$response['error_code']]); throw new \danog\MadelineProto\RPCErrorException('Received bad_msg_notification for '.$response['bad_msg_id'].': '.$error_codes[$response['error_code']]);
break; break;
case 'bad_server_salt': case 'bad_server_salt':
$this->datacenter->temp_auth_key['server_salt'] = $response['new_server_salt']; $this->datacenter->temp_auth_key['server_salt'] = $response['new_server_salt'];
$this->ack_outgoing_message_id($response['bad_msg_id']); // Acknowledge that the server received my request $this->ack_outgoing_message_id($response['bad_msg_id']); // Acknowledge that the server received my request
$this->outgoing_messages[$response['bad_msg_id']]['response'] = $last_received; $this->datacenter->outgoing_messages[$response['bad_msg_id']]['response'] = $last_received;
$this->incoming_messages[$last_received]['content'] = $response; $this->datacenter->incoming_messages[$last_received]['content'] = $response;
break; break;
case 'pong': case 'pong':
foreach ($this->outgoing_messages as $msg_id => &$omessage) { foreach ($this->datacenter->outgoing_messages as $msg_id => &$omessage) {
if (isset($omessage['content']['args']['ping_id']) && $omessage['content']['args']['ping_id'] == $response['ping_id']) { if (isset($omessage['content']['args']['ping_id']) && $omessage['content']['args']['ping_id'] == $response['ping_id']) {
$omessage['response'] = $response['msg_id']; $omessage['response'] = $response['msg_id'];
$this->incoming_messages[$response['msg_id']]['content'] = $response; $this->datacenter->incoming_messages[$response['msg_id']]['content'] = $response;
$this->ack_outgoing_message_id($msg_id); $this->ack_outgoing_message_id($msg_id);
} }
} }
@ -84,7 +86,7 @@ class ResponseHandler extends MsgIdHandler
\danog\MadelineProto\Logger::log($response['messages']); \danog\MadelineProto\Logger::log($response['messages']);
foreach ($response['messages'] as $message) { foreach ($response['messages'] as $message) {
$this->check_message_id($message['msg_id'], false, true); $this->check_message_id($message['msg_id'], false, true);
$this->incoming_messages[$message['msg_id']] = ['seq_no' => $message['seqno'], 'content' => $message['body']]; $this->datacenter->incoming_messages[$message['msg_id']] = ['seq_no' => $message['seqno'], 'content' => $message['body']];
$responses[] = $this->handle_message($last_sent, $message['msg_id']); $responses[] = $this->handle_message($last_sent, $message['msg_id']);
} }
foreach ($responses as $key => $response) { foreach ($responses as $key => $response) {
@ -109,11 +111,11 @@ class ResponseHandler extends MsgIdHandler
break; break;
case 'msg_copy': case 'msg_copy':
$this->ack_incoming_message_id($last_received); // Acknowledge that I received the server's response $this->ack_incoming_message_id($last_received); // Acknowledge that I received the server's response
if (isset($this->incoming_messages[$response['orig_message']['msg_id']])) { if (isset($this->datacenter->incoming_messages[$response['orig_message']['msg_id']])) {
$this->ack_incoming_message_id($response['orig_message']['msg_id']); // Acknowledge that I received the server's response $this->ack_incoming_message_id($response['orig_message']['msg_id']); // Acknowledge that I received the server's response
} else { } else {
$this->check_message_id($message['orig_message']['msg_id'], false, true); $this->check_message_id($message['orig_message']['msg_id'], false, true);
$this->incoming_messages[$message['orig_message']['msg_id']] = ['content' => $response['orig_message']]; $this->datacenter->incoming_messages[$message['orig_message']['msg_id']] = ['content' => $response['orig_message']];
return $this->handle_message($last_sent, $message['orig_message']['msg_id']); return $this->handle_message($last_sent, $message['orig_message']['msg_id']);
} }
@ -123,8 +125,7 @@ class ResponseHandler extends MsgIdHandler
\danog\MadelineProto\Logger::log($response); \danog\MadelineProto\Logger::log($response);
break; break;
case 'gzip_packed': case 'gzip_packed':
$this->incoming_messages[$last_received]['content'] = gzdecode($response); $this->datacenter->incoming_messages[$last_received]['content'] = $this->tl->deserialize($this->fopen_and_write('php://memory', 'rw+b', gzdecode($response['packed_data'])));
return $this->handle_message($last_sent, $last_received); return $this->handle_message($last_sent, $last_received);
break; break;
case 'rpc_answer_dropped_running': case 'rpc_answer_dropped_running':
@ -132,8 +133,8 @@ class ResponseHandler extends MsgIdHandler
$this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received the original query (the same one, the response to which we wish to forget) $this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received the original query (the same one, the response to which we wish to forget)
default: default:
$this->ack_incoming_message_id($last_received); // Acknowledge that I received the server's response $this->ack_incoming_message_id($last_received); // Acknowledge that I received the server's response
$this->outgoing_messages[$last_sent]['response'] = $last_received; $this->datacenter->outgoing_messages[$last_sent]['response'] = $last_received;
$this->incoming_messages[$last_received]['content'] = $response; $this->datacenter->incoming_messages[$last_received]['content'] = $response;
break; break;
} }
} }

View File

@ -0,0 +1,17 @@
<?php
/*
Copyright 2016 Daniil Gentili
(https://daniil.it)
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/>.
*/
namespace danog\MadelineProto;
class RPCErrorException extends \Exception
{
}

View File

@ -79,7 +79,7 @@ class TL extends \danog\MadelineProto\Tools
$serialized = \danog\PHP\Struct::pack('<i', $tl['id']); $serialized = \danog\PHP\Struct::pack('<i', $tl['id']);
$flags = 0; $flags = 0;
foreach ($tl['params'] as $cur_flag) { foreach ($tl['params'] as $cur_flag) {
if ($cur_flag['opt']) { if ($cur_flag['flag']) {
$flag_pow = pow(2, $cur_flag['pow']); $flag_pow = pow(2, $cur_flag['pow']);
switch ($cur_flag['type']) { switch ($cur_flag['type']) {
case 'true': case 'true':
@ -102,7 +102,7 @@ class TL extends \danog\MadelineProto\Tools
$arguments['flags'] = $flags; $arguments['flags'] = $flags;
foreach ($tl['params'] as $current_argument) { foreach ($tl['params'] as $current_argument) {
if (!isset($arguments[$current_argument['name']])) { if (!isset($arguments[$current_argument['name']])) {
if ($current_argument['opt'] && (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 & pow(2, $current_argument['pow'])) == 0)) {
//\danog\MadelineProto\Logger::log('Skipping '.$current_argument['name'].' of type '.$current_argument['type'].'/'.$current_argument['subtype']); //\danog\MadelineProto\Logger::log('Skipping '.$current_argument['name'].' of type '.$current_argument['type'].'/'.$current_argument['subtype']);
continue; continue;
} }
@ -284,9 +284,8 @@ class TL extends \danog\MadelineProto\Tools
$x = $this->deserialize($bytes_io, $tl_elem['predicate'], $subtype); $x = $this->deserialize($bytes_io, $tl_elem['predicate'], $subtype);
} else { } else {
$x = ['_' => $tl_elem['predicate']]; $x = ['_' => $tl_elem['predicate']];
$done_opt = false;
foreach ($tl_elem['params'] as $arg) { foreach ($tl_elem['params'] as $arg) {
if ($arg['opt']) { if ($arg['flag']) {
$flag_pow = pow(2, $arg['pow']); $flag_pow = pow(2, $arg['pow']);
switch ($arg['type']) { switch ($arg['type']) {
case 'true': case 'true':
@ -309,6 +308,9 @@ class TL extends \danog\MadelineProto\Tools
} }
$x[$arg['name']] = $this->deserialize($bytes_io, $arg['type'], $arg['subtype']); $x[$arg['name']] = $this->deserialize($bytes_io, $arg['type'], $arg['subtype']);
} }
if (isset($x['flags'])) { // I don't think we need this anymore
unset($x['flags']);
}
} }
break; break;
} }

View File

@ -27,10 +27,10 @@ class TLConstructor
$this->type[$this->key] = $json_dict['type']; $this->type[$this->key] = $json_dict['type'];
$this->params[$this->key] = $json_dict['params']; $this->params[$this->key] = $json_dict['params'];
foreach ($this->params[$this->key] as &$param) { foreach ($this->params[$this->key] as &$param) {
$param['opt'] = false; $param['flag'] = false;
$param['subtype'] = null; $param['subtype'] = null;
if (preg_match('/^flags\.\d*\?/', $param['type'])) { if (preg_match('/^flags\.\d*\?/', $param['type'])) {
$param['opt'] = true; $param['flag'] = true;
$param['pow'] = preg_replace(['/^flags\./', '/\?.*/'], '', $param['type']); $param['pow'] = preg_replace(['/^flags\./', '/\?.*/'], '', $param['type']);
$param['type'] = preg_replace('/^flags\.\d*\?/', '', $param['type']); $param['type'] = preg_replace('/^flags\.\d*\?/', '', $param['type']);
} }

View File

@ -33,10 +33,10 @@ class TLMethod
} }
foreach ($this->params[$this->key] as &$param) { foreach ($this->params[$this->key] as &$param) {
$param['opt'] = false; $param['flag'] = false;
$param['subtype'] = null; $param['subtype'] = null;
if (preg_match('/^flags\.\d*\?/', $param['type'])) { if (preg_match('/^flags\.\d*\?/', $param['type'])) {
$param['opt'] = true; $param['flag'] = true;
$param['pow'] = preg_replace(['/^flags\./', '/\?.*/'], '', $param['type']); $param['pow'] = preg_replace(['/^flags\./', '/\?.*/'], '', $param['type']);
$param['type'] = preg_replace('/^flags\.\d*\?/', '', $param['type']); $param['type'] = preg_replace('/^flags\.\d*\?/', '', $param['type']);
} }

View File

@ -14,41 +14,28 @@ If not, see <http://www.gnu.org/licenses/>.
require_once 'vendor/autoload.php'; require_once 'vendor/autoload.php';
$MadelineProto = new \danog\MadelineProto\API(); $MadelineProto = new \danog\MadelineProto\API();
/*if (file_exists('number.php')) {
if (file_exists('number.php')) {
include_once 'number.php'; include_once 'number.php';
$sentCode = $MadelineProto->auth->sendCode(
$checkedPhone = $MadelineProto->auth->checkPhone( // auth.checkPhone becomes auth->checkPhone
[ [
'phone_number' => $number, 'phone_number' => $number
'sms_type' => 5,
'api_id' => $MadelineProto->API->settings['app_info']['api_id'],
'api_hash' => $MadelineProto->API->settings['app_info']['api_hash'],
'lang_code' => $MadelineProto->API->settings['app_info']['lang_code'],
] ]
); );
var_dump($checkedPhone);
$sentCode = $MadelineProto->phone_login($number);
var_dump($sentCode); var_dump($sentCode);
echo 'Enter the code you received: '; echo 'Enter the code you received: ';
$code = ''; $code = '';
for ($x = 0; $x < $sentCode['type']['length']; $x++) { for ($x = 0; $x < $sentCode['type']['length']; $x++) {
$code .= fgetc(STDIN); $code .= fgetc(STDIN);
} }
$authorization = $MadelineProto->auth->signIn( $authorization = $MadelineProto->complete_phone_login($code);
[
'phone_number' => $number,
'phone_code_hash' => $sentCode['phone_code_hash'],
'phone_code' => $code,
]
);
var_dump($authorization); var_dump($authorization);
}*/ }
if (file_exists('token.php')) { if (file_exists('token.php')) {
include_once 'token.php'; include_once 'token.php';
$botauthorization = $MadelineProto->auth->importBotAuthorization( $MadelineProto->bot_login($token);
[
'bot_auth_token' => $token,
'api_id' => $MadelineProto->API->settings['app_info']['api_id'],
'api_hash' => $MadelineProto->API->settings['app_info']['api_hash'],
]
);
var_dump($botauthorization);
} }
echo 'Size of MadelineProto instance is '.strlen(var_export($MadelineProto, true)).' bytes'.PHP_EOL; echo 'Size of MadelineProto instance is '.strlen(var_export($MadelineProto, true)).' bytes'.PHP_EOL;