Rewrote TL class using webogram, fixed bug in recognition of system version, improved response handling, improved tests, improved documentation

This commit is contained in:
Daniil Gentili 2016-11-29 00:47:59 +00:00
parent 1d9ea6ad0c
commit 8bba87c353
16 changed files with 303 additions and 228 deletions

View File

@ -16,5 +16,7 @@ before_script:
script:
- "./testing.php"
before_install:
- openssl aes-256-cbc -K $encrypted_f108bed02ef2_key -iv $encrypted_f108bed02ef2_iv
- openssl aes-256-cbc -K $encrypted_0b05103af24b_key -iv $encrypted_0b05103af24b_iv
-in session.madeline.enc -out session.madeline -d
- openssl aes-256-cbc -K $encrypted_0b05103af24b_key -iv $encrypted_0b05103af24b_iv
-in token.php.enc -out token.php -d

View File

@ -2,4 +2,6 @@
To contribute to this project simply fork it, make all improvements, commit changes (squashing is recommended for multiple commits) and submit a pull request. That's it!
If you have an issue, please *always* upload MadelineProto logs from when the error occurred to hastebin or similar websites.
Bye :)

View File

@ -181,6 +181,12 @@ $checkedPhone = $MadelineProto->auth->checkPhone( // auth.checkPhone becomes aut
]
);
$ping = $MadelineProto->ping([3]); // parameter names can be omitted as long as the order specified by the TL scheme is respected
$message = "Hey! I'm sending this message with MadelineProto!";
$username = $MadelineProto->contacts->resolveUsername(['username' => 'pwrtelegramgroup']);
var_dump($username);
$peer = ['_' => 'inputPeerChannel', 'channel_id' => $username['peer']['channel_id'], 'access_hash' => $username['chats'][0]['access_hash']];
$sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message, 'random_id' => \danog\PHP\Struct::unpack('<q', \phpseclib\Crypt\Random::string(8))[0]]);
var_dump($sentMessage);
```
The API class also provides some wrapper methods for logging in as a bot or as a normal user:
@ -199,6 +205,8 @@ $authorization = $MadelineProto->bot_login($token); // Note that every time you
var_dump($authorization);
```
See testing.php for more examples.
### Storing sessions
An istance of MadelineProto can be safely serialized or unserialized.

View File

@ -25,6 +25,7 @@ class MTProto extends MTProtoTools
public function __construct($settings = [])
{
// Detect ipv6
$google = '';
try {
$google = file_get_contents('https://ipv6.google.com');
@ -32,6 +33,22 @@ class MTProto extends MTProtoTools
}
$this->ipv6 = strlen($google) > 0;
// Detect device model
$device_model = 'Web server';
try {
$device_model = php_uname('s');
} catch (Exception $e) {
}
// Detect system version
$system_version = phpversion();
try {
$system_version = php_uname('r');
} catch (Exception $e) {
}
// Set default settings
$default_settings = [
'authorization' => [ // Authorization settings
@ -101,8 +118,8 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
'app_info' => [ // obtained in https://my.telegram.org
'api_id' => 25628,
'api_hash' => '1fe17cda7d355166cdaa71f04122873c',
'device_model' => php_uname('s'),
'system_version' => php_uname('r'),
'device_model' => $device_model,
'system_version' => $system_version,
'app_version' => 'Unicorn', // 🌚
'lang_code' => 'en',
],

View File

@ -21,9 +21,10 @@ class AckHandler extends \danog\MadelineProto\PrimeModule
{
// The server acknowledges that it received my message
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?');
\danog\MadelineProto\Logger::log("Couldn't find message id ".$message_id.' in the array of outgoing messages. Maybe try to increase its size?');
return false;
}
$this->datacenter->outgoing_messages[$message_id]['ack'] = true;
return $this->datacenter->outgoing_messages[$message_id]['ack'] = true;
}
public function ack_incoming_message_id($message_id)
@ -37,6 +38,6 @@ class AckHandler extends \danog\MadelineProto\PrimeModule
}
$this->object_call('msgs_ack', ['msg_ids' => [$message_id]]);
$this->datacenter->incoming_messages[$message_id]['ack'] = true;
return $this->datacenter->incoming_messages[$message_id]['ack'] = true;
}
}

View File

@ -64,7 +64,6 @@ class AuthKeyHandler extends AckHandler
*/
foreach ($ResPQ['server_public_key_fingerprints'] as $curfp) {
$curfp_biginteger = new \phpseclib\Math\BigInteger($curfp);
if ($this->key->fp->equals($curfp_biginteger)) {
$public_key_fingerprint = $curfp;
break;
@ -115,7 +114,7 @@ class AuthKeyHandler extends AckHandler
'new_nonce' => $new_nonce,
'expires_in' => $expires_in,
];
$p_q_inner_data = $this->tl->serialize_obj('p_q_inner_data'.(($expires_in < 0) ? '' : '_temp'), $data_unserialized);
$p_q_inner_data = $this->tl->serialize_object(['type' => 'p_q_inner_data'.(($expires_in < 0) ? '' : '_temp')], $data_unserialized);
/*
* ***********************************************************************
@ -350,8 +349,8 @@ class AuthKeyHandler extends AckHandler
* string $g_b : g^b mod dh_prime
* ]
*/
$data = $this->tl->serialize_obj(
'client_DH_inner_data',
$data = $this->tl->serialize_object(
['type' => 'client_DH_inner_data'],
[
'nonce' => $nonce,
'server_nonce' => $server_nonce,
@ -470,9 +469,9 @@ class AuthKeyHandler extends AckHandler
}
}
} 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: '.$e->getMessage().' Retrying...');
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log('An RPCErrorExceptiom occurred while generating the authorization key. Retrying...');
\danog\MadelineProto\Logger::log('An RPCErrorException occurred while generating the authorization key: '.$e->getMessage().' Retrying...');
}
}
@ -487,7 +486,7 @@ class AuthKeyHandler extends AckHandler
$temp_auth_key_id = \danog\PHP\Struct::unpack('<q', $this->datacenter->temp_auth_key['id'])[0];
$perm_auth_key_id = \danog\PHP\Struct::unpack('<q', $this->datacenter->auth_key['id'])[0];
$temp_session_id = \danog\PHP\Struct::unpack('<q', $this->datacenter->session_id)[0];
$message_data = $this->tl->serialize_obj('bind_auth_key_inner',
$message_data = $this->tl->serialize_object(['type' => 'bind_auth_key_inner'],
[
'nonce' => $nonce,
'temp_auth_key_id' => $temp_auth_key_id,

View File

@ -17,17 +17,14 @@ namespace danog\MadelineProto\MTProtoTools;
*/
class CallHandler extends AuthKeyHandler
{
public function wait_for_response($last_sent, $optional_name = null)
public function wait_for_response($last_sent, $optional_name, $response_type)
{
if ($optional_name == null) {
$optional_name = $last_sent;
}
$response = null;
$count = 0;
while ($response == null && $count++ < $this->settings['max_tries']['response']) {
\danog\MadelineProto\Logger::log('Getting response (try number '.$count.' for '.$optional_name.')...');
$last_received = $this->recv_message();
$this->handle_message($last_sent, $last_received);
$this->handle_message($last_sent, $last_received, $response_type);
if (isset($this->datacenter->outgoing_messages[$last_sent]['response']) && isset($this->datacenter->incoming_messages[$this->datacenter->outgoing_messages[$last_sent]['response']]['content'])) {
$response = $this->datacenter->incoming_messages[$this->datacenter->outgoing_messages[$last_sent]['response']]['content'];
}
@ -67,7 +64,7 @@ class CallHandler extends AuthKeyHandler
$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);
$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, $this->tl->methods->find_by_method($method)['type']);
} 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...');
$this->datacenter->close_and_reopen();
@ -91,7 +88,7 @@ class CallHandler extends AuthKeyHandler
foreach (range(1, $this->settings['max_tries']['query']) as $count) {
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_object(['type' => $object], $args), $this->tl->content_related($object));
$this->datacenter->outgoing_messages[$int_message_id]['content'] = ['object' => $object, 'args' => $args];
// $server_answer = $this->wait_for_response($int_message_id);
} catch (Exception $e) {

View File

@ -54,7 +54,8 @@ class MessageHandler extends Crypt
{
$payload = $this->datacenter->read_message();
if (fstat($payload)['size'] == 4) {
throw new \danog\MadelineProto\RPCErrorException('Generic', \danog\PHP\Struct::unpack('<i', fread($payload, 4))[0]);
$error = \danog\PHP\Struct::unpack('<i', fread($payload, 4))[0];
throw new \danog\MadelineProto\RPCErrorException($error, $error);
}
$auth_key_id = fread($payload, 8);
if ($auth_key_id == $this->string2bin('\x00\x00\x00\x00\x00\x00\x00\x00')) {

View File

@ -19,10 +19,10 @@ class MsgIdHandler extends MessageHandler
{
public function check_message_id($new_message_id, $outgoing, $container = false)
{
if (((int) ((time() + $this->datacenter->time_delta - 300) * pow(2, 30)) * 4) > $new_message_id) {
if (((int) ((time() + $this->datacenter->time_delta - 300) << 32)) > $new_message_id) {
throw new \danog\MadelineProto\Exception('Given message id ('.$new_message_id.') is too old.');
}
if (((int) ((time() + $this->datacenter->time_delta + 30) * pow(2, 30)) * 4) < $new_message_id) {
if (((int) ((time() + $this->datacenter->time_delta + 30) << 32)) < $new_message_id) {
throw new \danog\MadelineProto\Exception('Given message id ('.$new_message_id.') is too new.');
}
if ($outgoing) {

View File

@ -17,7 +17,7 @@ namespace danog\MadelineProto\MTProtoTools;
*/
class ResponseHandler extends MsgIdHandler
{
public function handle_message($last_sent, $last_received)
public function handle_message($last_sent, $last_received, $expected_type)
{
$response = $this->datacenter->incoming_messages[$last_received]['content'];
@ -30,17 +30,11 @@ class ResponseHandler extends MsgIdHandler
case 'rpc_result':
$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->datacenter->outgoing_messages[$response['req_msg_id']]['response'] = $last_received;
$this->datacenter->incoming_messages[$last_received]['content'] = $response['result'];
return $this->handle_message($last_sent, $last_received);
break;
case 'future_salts':
$this->ack_outgoing_message_id($response['req_msg_id']); // Acknowledge that the server received my request
$this->datacenter->outgoing_messages[$response['req_msg_id']]['response'] = $last_received;
$this->datacenter->incoming_messages[$last_received]['content'] = $response;
$this->try_store_response($response['req_msg_id'], $last_received, $expected_type, true);
break;
case 'bad_server_salt':
@ -67,7 +61,6 @@ class ResponseHandler extends MsgIdHandler
}
throw new \danog\MadelineProto\RPCErrorException('Received bad_msg_notification for '.$response['bad_msg_id'].': '.$error_codes[$response['error_code']]);
break;
break;
case 'pong':
foreach ($this->datacenter->outgoing_messages as $msg_id => &$omessage) {
@ -91,7 +84,7 @@ class ResponseHandler extends MsgIdHandler
foreach ($response['messages'] as $message) {
$this->check_message_id($message['msg_id'], false, true);
$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'], $expected_type);
}
foreach ($responses as $key => $response) {
if ($response == null) {
@ -121,26 +114,28 @@ class ResponseHandler extends MsgIdHandler
$this->check_message_id($message['orig_message']['msg_id'], false, true);
$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'], $expected_type);
}
break;
case 'http_wait':
\danog\MadelineProto\Logger::log('Received http wait.');
\danog\MadelineProto\Logger::log($response);
break;
case 'gzip_packed':
$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);
break;
case 'rpc_answer_dropped_running':
case 'rpc_answer_dropped':
$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:
$this->ack_incoming_message_id($last_received); // Acknowledge that I received the server's response
$this->datacenter->outgoing_messages[$last_sent]['response'] = $last_received;
$this->datacenter->incoming_messages[$last_received]['content'] = $response;
$this->try_store_response($last_sent, $last_received, $expected_type);
break;
}
}
public function try_store_response($request, $response, $type, $force = true) {
if ($force) {
return $this->datacenter->outgoing_messages[$request]['response'] = $response;
}
if ($this->tl->constructors->find_by_predicate($this->datacenter->incoming_messages[$response]['content']['_'])['type'] == $type) {
$this->datacenter->outgoing_messages[$request]['response'] = $response;
}
}
}

View File

@ -28,20 +28,17 @@ class RSA extends TL\TL
$key->loadKey($rsa_key);
$this->n = $key->modulus;
$this->e = $key->exponent;
unset($key);
\danog\MadelineProto\Logger::log('Computing fingerprint...');
$this->fp_bytes = substr(
sha1(
$this->serialize_param(
'bytes',
null,
$this->serialize_object(
['type' => 'bytes'],
$this->n->toBytes()
)
.
$this->serialize_param(
'bytes',
null,
$this->serialize_object(
['type' => 'bytes'],
$this->e->toBytes()
),
true
@ -55,6 +52,7 @@ class RSA extends TL\TL
public function encrypt($data)
{
\danog\MadelineProto\Logger::log('Encrypting with rsa key...');
$bigintdata = new \phpseclib\Math\BigInteger($data, 256);
return $bigintdata->powMod($this->e, $this->n)->toBytes();

View File

@ -54,29 +54,122 @@ class TL extends \danog\MadelineProto\Tools
return $arguments;
}
public function serialize_obj($object, $arguments)
public function serialize_bool($bool) {
return \danog\PHP\Struct::pack('<i', $this->constructors->find_by_predicate('bool'.($bool ? 'True' : 'False'))['id']);
}
public function deserialize_bool($data) {
$id = \danog\PHP\Struct::unpack('<i', $data) [0];
$tl_elem = $this->constructors->find_by_id($id);
if ($tl_elem === false) {
throw new Exception('Could not extract boolean');
}
return $tl_elem['predicate'] === 'boolTrue';
}
public function serialize_object($type, $object)
{
$tl_constructor = $this->constructors->find_by_predicate($object);
if ($tl_constructor === false) {
switch ($type['type']) {
case 'int':
if (!is_numeric($object)) {
throw new Exception("given value isn't numeric");
}
return \danog\PHP\Struct::pack('<i', $object);
case '#':
if (!is_numeric($object)) {
throw new Exception("given value isn't numeric");
}
return \danog\PHP\Struct::pack('<I', $object);
case 'long':
if (!is_numeric($object)) {
var_dump($object);
throw new Exception("given value isn't numeric");
}
return \danog\PHP\Struct::pack('<q', $object);
case 'int128':
case 'int256':
case 'int512':
return (string) $object;
case 'double':
return \danog\PHP\Struct::pack('<d', $object);
case 'string':
case 'bytes':
$l = strlen($object);
$concat = '';
if ($l <= 253) {
$concat .= \danog\PHP\Struct::pack('<b', $l);
$concat .= $object;
$concat .= pack('@'.$this->posmod((-$l - 1), 4));
} else {
$concat .= $this->string2bin('\xfe');
$concat .= substr(\danog\PHP\Struct::pack('<i', $l), 0, 3);
$concat .= $object;
$concat .= pack('@'.$this->posmod(-$l, 4));
}
return $concat;
case 'Bool':
if (!is_bool($object)) {
throw new Exception("given value isn't a boolean");
}
return $this->serialize_bool($object);
case 'true':
return;
case '!X':
return $object;
case 'Vector t':
$concat = \danog\PHP\Struct::pack('<i', $this->constructors->find_by_predicate('vector')['id']);
$concat .= \danog\PHP\Struct::pack('<i', count($object));
foreach ($object as $current_object) {
$concat .= $this->serialize_object(['type' => $type['subtype']], $current_object);
}
return $concat;
}
$auto = false;
if (!isset($object['_'])) {
$constructorData = $this->constructors->find_by_predicate($type['type']);
if ($constructorData === false) {
throw new Exception('Predicate was not set!');
}
$auto = true;
$object['_'] = $constructorData['predicate'];
}
$predicate = $object['_'];
$constructorData = $this->constructors->find_by_predicate($predicate);
if ($constructorData === false) {
throw new Exception('Could not extract type: '.$object);
}
return $this->serialize_generic($tl_constructor, $arguments);
}
public function serialize_method($method, $arguments)
{
$tl_method = $this->methods->find_by_method($method);
if ($tl_method === false) {
throw new Exception('Could not extract type: '.$method);
if ($bare = ($type['type'] != '' && $type['type'][0] == "%")) {
$type['type'] = substr($type['type'], 1);
}
return $this->serialize_generic($tl_method, $arguments);
}
if ($predicate == $type['type'] && !$auto) {
$bare = true;
}
public function serialize_generic($tl, $arguments)
$concat = '';
if (!$bare) {
$concat .= \danog\PHP\Struct::pack('<i', $constructorData['id']);
}
return $concat.$this->serialize_params($constructorData, $object);
}
public function serialize_method($method, $arguments)
{
$serialized = \danog\PHP\Struct::pack('<i', $tl['id']);
$tl = $this->methods->find_by_method($method);
if ($tl === false) {
throw new Exception('Could not extract type: '.$method);
}
return \danog\PHP\Struct::pack('<i', $tl['id']).$this->serialize_params($tl, $arguments);
}
public function serialize_params($tl, $arguments)
{
$serialized = '';
$flags = 0;
foreach ($tl['params'] as $cur_flag) {
if ($cur_flag['flag']) {
@ -108,87 +201,15 @@ class TL extends \danog\MadelineProto\Tools
throw new Exception('Missing required parameter ('.$current_argument['name'].')');
}
//\danog\MadelineProto\Logger::log('Serializing '.$current_argument['name'].' of type '.$current_argument['type'].'/'.$current_argument['subtype']);
$serialized .= $this->serialize_param($current_argument['type'], $current_argument['subtype'], $arguments[$current_argument['name']]);
$serialized .= $this->serialize_object($current_argument, $arguments[$current_argument['name']]);
}
return $serialized;
}
public function serialize_param($type, $subtype = null, $value = null)
public function get_length($bytes_io, $type = ['type' => ''])
{
switch ($type) {
case 'Bool':
if (!is_bool($value)) {
throw new Exception("serialize_param: given value isn't a boolean");
}
return $this->serialize_param('bool'.($value ? 'True' : 'False'));
break;
case 'int':
if (!is_numeric($value)) {
throw new Exception("serialize_param: given value isn't numeric");
}
return \danog\PHP\Struct::pack('<i', $value);
break;
case '#':
if (!is_numeric($value)) {
throw new Exception("serialize_param: given value isn't numeric");
}
return \danog\PHP\Struct::pack('<I', $value);
break;
case 'long':
if (!is_numeric($value)) {
throw new Exception("serialize_param: given value isn't numeric");
}
return \danog\PHP\Struct::pack('<q', $value);
break;
case 'int128':
case 'int256':
return (string) $value;
break;
case 'double':
return \danog\PHP\Struct::pack('<d', $value);
break;
case 'string':
case 'bytes':
$l = strlen($value);
$concat = '';
if ($l <= 253) {
$concat .= \danog\PHP\Struct::pack('<b', $l);
$concat .= $value;
$concat .= pack('@'.$this->posmod((-$l - 1), 4));
} else {
$concat .= $this->string2bin('\xfe');
$concat .= substr(\danog\PHP\Struct::pack('<i', $l), 0, 3);
$concat .= $value;
$concat .= pack('@'.$this->posmod(-$l, 4));
}
return $concat;
break;
case '!X':
return $value;
case 'Vector t':
$concat = \danog\PHP\Struct::pack('<i', $this->constructors->find_by_predicate('vector')['id']);
$concat .= \danog\PHP\Struct::pack('<l', count($value));
foreach ($value as $curv) {
$concat .= $this->serialize_param($subtype, null, $curv);
}
return $concat;
default:
return $this->serialize_generic($type, $value);
break;
}
}
public function get_length($bytes_io, $type = null, $subtype = null)
{
$this->deserialize($bytes_io, $type, $subtype);
$this->deserialize($bytes_io, $type);
return ftell($bytes_io);
}
@ -196,39 +217,33 @@ class TL extends \danog\MadelineProto\Tools
/**
* :type bytes_io: io.BytesIO object.
*/
public function deserialize($bytes_io, $type = null, $subtype = null)
public function deserialize($bytes_io, $type = ['type' => ''])
{
if (!(get_resource_type($bytes_io) == 'file' || get_resource_type($bytes_io) == 'stream')) {
throw new Exception('An invalid bytes_io handle was provided.');
if (!(!is_string($bytes_io) && (get_resource_type($bytes_io) == 'file' || get_resource_type($bytes_io) == 'stream'))) {
if (is_string($bytes_io)) {
$bytes_io = $this->fopen_and_write('php://memory', 'rw+b', $bytes_io);
} else {
throw new Exception('An invalid bytes_io handle was provided.');
}
}
//\danog\MadelineProto\Logger::log('Deserializing '.$type.'/'.$subtype.' at byte '.ftell($bytes_io));
switch ($type) {
//\danog\MadelineProto\Logger::log('Deserializing '.$type['type'].'/'.$subtype.' at byte '.ftell($bytes_io));
switch ($type['type']) {
case 'Bool':
$id = \danog\PHP\Struct::unpack('<i', fread($bytes_io, 4)) [0];
$tl_elem = $this->constructors->find_by_id($id);
if ($tl_elem === false) {
throw new Exception('Could not extract type: '.$type.' with id '.$id);
}
$x = $tl_elem['predicate'] === 'boolTrue';
break;
return $this->deserialize_bool(fread($bytes_io, 4));
case 'int':
$x = \danog\PHP\Struct::unpack('<i', fread($bytes_io, 4)) [0];
break;
return \danog\PHP\Struct::unpack('<i', fread($bytes_io, 4)) [0];
case '#':
$x = \danog\PHP\Struct::unpack('<I', fread($bytes_io, 4)) [0];
break;
return \danog\PHP\Struct::unpack('<I', fread($bytes_io, 4)) [0];
case 'long':
$x = \danog\PHP\Struct::unpack('<q', fread($bytes_io, 8)) [0];
break;
return \danog\PHP\Struct::unpack('<q', fread($bytes_io, 8)) [0];
case 'double':
$x = \danog\PHP\Struct::unpack('<d', fread($bytes_io, 8)) [0];
break;
return \danog\PHP\Struct::unpack('<d', fread($bytes_io, 8)) [0];
case 'int128':
$x = fread($bytes_io, 16);
break;
return fread($bytes_io, 16);
case 'int256':
$x = fread($bytes_io, 32);
break;
return fread($bytes_io, 32);
case 'int512':
return fread($bytes_io, 32);
case 'string':
case 'bytes':
$l = \danog\PHP\Struct::unpack('<B', fread($bytes_io, 1)) [0];
@ -252,62 +267,76 @@ class TL extends \danog\MadelineProto\Tools
if (!is_string($x)) {
throw new Exception("deserialize: generated value isn't a string");
}
break;
return $x;
case 'true':
return true;
case 'Vector t':
$id = \danog\PHP\Struct::unpack('<i', fread($bytes_io, 4)) [0];
$constructorData = $this->constructors->find_by_id($id);
if ($constructorData === false) {
throw new Exception('Could not extract type: '.$type['type'].' with id '.$id);
}
switch ($constructorData['predicate']) {
case 'gzip_packed':
return $this->deserialize(gzdecode($this->deserialize($bytes_io, ['type' => 'string'])));
case 'Vector t':
case 'vector':
break;
default:
throw new Exception('Invalid vector constructor: '.$constructorData['predicate']);
}
case 'vector':
if ($subtype == null) {
throw new Exception('deserialize: subtype is null');
}
$count = \danog\PHP\Struct::unpack('<l', fread($bytes_io, 4)) [0];
$x = [];
$count = \danog\PHP\Struct::unpack('<i', fread($bytes_io, 4)) [0];
$result = [];
for ($i = 0; $i < $count; $i++) {
$x[] = $this->deserialize($bytes_io, $subtype);
$result[] = $this->deserialize($bytes_io, ['type' => $type['subtype']]);
}
break;
default:
$tl_elem = $this->constructors->find_by_predicate($type);
if ($tl_elem === false) {
$id = \danog\PHP\Struct::unpack('<i', fread($bytes_io, 4)) [0];
$tl_elem = $this->constructors->find_by_id($id);
if ($tl_elem === false) {
throw new Exception('Could not extract type: '.$type.' with id '.$id);
}
}
$base_boxed_types = ['Vector t', 'Int', 'Long', 'Double', 'String', 'Int128', 'Int256'];
if (in_array($tl_elem['type'], $base_boxed_types)) {
$x = $this->deserialize($bytes_io, $tl_elem['predicate'], $subtype);
} else {
$x = ['_' => $tl_elem['predicate']];
foreach ($tl_elem['params'] as $arg) {
if ($arg['flag']) {
switch ($arg['type']) {
case 'true':
case 'false':
$x[$arg['name']] = ($x['flags'] & $arg['pow']) == 1;
continue 2;
break;
case 'Bool':
$default = false;
default:
$default = null;
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;
}
break;
}
}
$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;
return $result;
}
if ($type['type'] != '' && $type['type'][0] == '%') {
$checkType = substr($type['type'], 1);
$constructorData = $this->constructors->find_by_type($checkType);
if ($constructorData === false) {
throw new Exception('Constructor not found for type: '. $checkType);
}
} else {
$constructorData = $this->constructors->find_by_predicate($type['type']);
if ($constructorData === false) {
$id = \danog\PHP\Struct::unpack('<i', fread($bytes_io, 4)) [0];
$constructorData = $this->constructors->find_by_id($id);
if ($constructorData === false) {
throw new Exception('Could not extract type: '.$type['type'].' with id '.$id);
}
}
}
if ($constructorData['predicate'] == 'gzip_packed') {
return $this->deserialize(gzdecode($this->deserialize($bytes_io, ['type' => 'string'])));
}
$x = ['_' => $constructorData['predicate']];
foreach ($constructorData['params'] as $arg) {
if ($arg['flag']) {
switch ($arg['type']) {
case 'true':
case 'false':
$x[$arg['name']] = ($x['flags'] & $arg['pow']) == 1;
continue 2;
break;
case 'Bool':
$default = false;
default:
$default = null;
if (($x['flags'] & $arg['pow']) == 0) {
$x[$arg['name']] = $default;
continue 2;
}
break;
}
}
$x[$arg['name']] = $this->deserialize($bytes_io, $arg);
}
if (isset($x['flags'])) { // I don't think we need this anymore
unset($x['flags']);
}
return $x;
}

View File

@ -30,6 +30,18 @@ class TLConstructor extends TLParams
$this->key++;
}
public function find_by_type($type)
{
$key = array_search($type, $this->type);
return ($key === false) ? false : [
'id' => $this->id[$key],
'predicate' => $this->predicate[$key],
'type' => $this->type[$key],
'params' => $this->params[$key],
];
}
public function find_by_predicate($predicate)
{
$key = array_search($predicate, $this->predicate);

View File

@ -18,7 +18,6 @@ class TLParams
{
foreach ($this->params[$key] as &$param) {
$param['flag'] = false;
$param['subtype'] = null;
if (preg_match('/^flags\.\d*\?/', $param['type'])) {
$param['flag'] = true;
$flag = explode('?', explode('.', $param['type'])[1]);
@ -34,11 +33,9 @@ class TLParams
$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'];
$param['subtype'] = (($mtproto && $param['subtype'] == 'Message') ? 'MT' : '').$param['subtype'];
}
$param['type'] = (($mtproto && $param['type'] == 'Message') ? 'MT' : '').$param['type'];
}
}
}

View File

@ -13,10 +13,10 @@ If not, see <http://www.gnu.org/licenses/>.
require_once 'vendor/autoload.php';
$MadelineProto = new \danog\MadelineProto\API();
if (file_exists('number.php')) {
if (file_exists('number.php') && !file_exists('session.madeline')) {
include_once 'number.php';
$MadelineProto = new \danog\MadelineProto\API();
$checkedPhone = $MadelineProto->auth->checkPhone(// auth.checkPhone becomes auth->checkPhone
[
@ -33,17 +33,34 @@ 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;
$MadelineProto = unserialize(file_get_contents('session.madeline'));
$message = 'Travis ci tests in progress...';
$peers = [];
foreach (['pwrtelegramgroup', 'pwrtelegramgroupita'] as $user) {
$username = $MadelineProto->contacts->resolveUsername(['username' => $user]);
var_dump($username);
$peers[$user] = ['_' => 'inputPeerChannel', 'channel_id' => $username['peer']['channel_id'], 'access_hash' => $username['chats'][0]['access_hash']];
}
echo 'Serializing MadelineProto to session.madeline...'.PHP_EOL;
echo 'Wrote '.file_put_contents('session.madeline', serialize($MadelineProto)).' bytes'.PHP_EOL;
foreach ($peers as $peer) {
$sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message, 'random_id' => \danog\PHP\Struct::unpack('<q', \phpseclib\Crypt\Random::string(8))[0]]);
var_dump($sentMessage);
}
echo 'Deserializing MadelineProto from session.madeline...'.PHP_EOL;
$unserialized = unserialize(file_get_contents('session.madeline'));
echo 'Size of MadelineProto instance is '.strlen(serialize($MadelineProto)).' bytes'.PHP_EOL;
if (file_exists('token.php')) {
include_once 'token.php';
$authorization = $unserialized->bot_login($token);
$MadelineProto = new \danog\MadelineProto\API();
$authorization = $MadelineProto->bot_login($token);
var_dump($authorization);
}
echo 'Size of MadelineProto instance is '.strlen(serialize($unserialized)).' bytes'.PHP_EOL;
foreach ($peers as $peer) {
$sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message, 'random_id' => \danog\PHP\Struct::unpack('<q', \phpseclib\Crypt\Random::string(8))[0]]);
var_dump($sentMessage);
}

Binary file not shown.