Killed fread with fire, improved peer handling, written basic update handling features, written static class for serializing and deserializing MadelineProto easily, fixed lots of bugs, more stuff kek

This commit is contained in:
danogentili 2016-12-26 20:23:46 +03:00
parent 7515530b37
commit 84abb27f18
19 changed files with 537 additions and 188 deletions

1
.gitignore vendored
View File

@ -71,3 +71,4 @@ session.mad
*.madeline
enc.tar.xz
a
web_data.php

View File

@ -215,14 +215,15 @@ var_dump($authorization);
var_dump($MadelineProto->resolve_username('@Palmas2012')); // Always use this method to resolve usernames, but you won't need to call this to get info about peers, as get_peer and get_input_peer will call it for you if needed
$mention = $MadelineProto->get_peer('@veetaw'); // Returns an object of type User or Chat
$mention = $MadelineProto->constructor2inputpeer($mention); // Converts an object of type User or Chat to an object of type inputPeer
$message = "I've installed MadelineProto!";
$mention = $MadelineProto->get_info('@@NonSonoGioTech'); // Returns the following array: ['constructor' => $constructor, 'inputPeer' => $inputPeer, 'inputType' => $inputType, 'Peer' => $Peer, 'id' => $id, 'botApiId' => $bot_api_id]
$mention = $mention['inputType']; // Selects only the inputType object
foreach (['@pwrtelegramgroup', '@pwrtelegramgroupita'] as $peer) {
$peer = $MadelineProto->get_input_peer($peer);
$peer = $MadelineProto->get_info($peer)['inputPeer']; // Select the inputPeerType (alias inputPeer) object
$sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message, 'entities' => [['_' => 'inputMessageEntityMentionName', 'offset' => 0, 'length' => strlen($message), 'user_id' => $mention]]]);
var_dump($sentMessage);
\danog\MadelineProto\Logger::log($sentMessage);
}
// The above works with bots too

View File

@ -219,14 +219,15 @@ var_dump($authorization);
var_dump($MadelineProto->resolve_username('@Palmas2012')); // Always use this method to resolve usernames, but you won't need to call this to get info about peers, as get_peer and get_input_peer will call it for you if needed
$mention = $MadelineProto->get_peer('@veetaw'); // Returns an object of type User or Chat
$mention = $MadelineProto->constructor2inputpeer($mention); // Converts an object of type User or Chat to an object of type inputPeer
$message = "I've installed MadelineProto!";
$mention = $MadelineProto->get_info('@@NonSonoGioTech'); // Returns the following array: ['constructor' => $constructor, 'inputPeer' => $inputPeer, 'inputType' => $inputType, 'Peer' => $Peer, 'id' => $id, 'botApiId' => $bot_api_id]
$mention = $mention['inputType']; // Selects only the inputType object
foreach (['@pwrtelegramgroup', '@pwrtelegramgroupita'] as $peer) {
$peer = $MadelineProto->get_input_peer($peer);
$peer = $MadelineProto->get_info($peer)['inputPeer']; // Select the inputPeerType (alias inputPeer) object
$sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message, 'entities' => [['_' => 'inputMessageEntityMentionName', 'offset' => 0, 'length' => strlen($message), 'user_id' => $mention]]]);
var_dump($sentMessage);
\danog\MadelineProto\Logger::log($sentMessage);
}
// The above works with bots too

Binary file not shown.

View File

@ -16,6 +16,7 @@ class API extends APIFactory
{
use \danog\MadelineProto\Wrappers\Login;
use \danog\MadelineProto\Wrappers\PeerHandler;
use \danog\MadelineProto\Wrappers\SettingsManager;
public $API;
public $settings;
@ -23,6 +24,11 @@ class API extends APIFactory
public function __construct($params = [])
{
// Detect 64 bit
if (PHP_INT_SIZE < 8) {
throw new Exception('MadelineProto supports only 64 bit systems ATM');
}
set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']);
$this->API = new MTProto($params);
@ -45,6 +51,7 @@ class API extends APIFactory
public function __wakeup()
{
set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']);
$this->APIFactory();
}

View File

@ -162,8 +162,12 @@ class Connection extends Tools
if (!(get_resource_type($this->sock) == 'file' || get_resource_type($this->sock) == 'stream')) {
throw new Exception("Connection: couldn't connect to socket.");
}
$packet = stream_get_contents($this->sock, $length);
if (strlen($packet) != $length) {
throw new \danog\MadelineProto\Exception("WARNING: Wrong length was read (should've read ".($length).", read ".strlen($packet).")!");
}
return fread($this->sock, $length);
return $packet;
break;
case 'http':
case 'https':
@ -193,19 +197,20 @@ class Connection extends Tools
if ($in_seq_no != $this->in_seq_no) {
throw new Exception('Incoming seq_no mismatch');
}
$payload = $this->fopen_and_write('php://memory', 'rw+b', substr($packet, 4, $packet_length - 12));
break;
case 'tcp_intermediate':
$packet_length_data = $this->sock->read(4);
$packet_length_data = $this->read(4);
if (strlen($packet_length_data) < 4) {
throw new Exception('Nothing in the socket!');
}
$packet_length = \danog\PHP\Struct::unpack('<I', $packet_length_data)[0];
$packet = $this->sock->read($packet_length);
$packet = $this->read($packet_length);
$payload = $this->fopen_and_write('php://memory', 'rw+b', $packet);
break;
case 'tcp_abridged':
$packet_length_data = $this->sock->read(1);
$packet_length_data = $this->read(1);
if (strlen($packet_length_data) < 1) {
throw new Exception('Nothing in the socket!');
}
@ -213,10 +218,10 @@ class Connection extends Tools
if ($packet_length < 127) {
$packet_length <<= 2;
} else {
$packet_length_data = $this->sock->read(3);
$packet_length_data = $this->read(3);
$packet_length = \danog\PHP\Struct::unpack('<I', $packet_length_data.pack('x'))[0] << 2;
}
$packet = $this->sock->read($packet_length);
$packet = $this->read($packet_length);
$payload = $this->fopen_and_write('php://memory', 'rw+b', $packet);
break;
case 'http':

View File

@ -32,14 +32,42 @@ class MTProto extends PrimeModule
public $settings = [];
public $config = ['expires' => -1];
public $ipv6 = false;
public $should_serialize = true;
public function __construct($settings = [])
{
// Detect 64 bit
if (PHP_INT_SIZE < 8) {
throw new Exception('MadelineProto supports only 64 bit systems ATM');
}
// Parse settings
$this->parse_settings($settings);
// Setup logger
$this->setup_logger();
// Connect to servers
\danog\MadelineProto\Logger::log('Istantiating DataCenter...');
$this->datacenter = new DataCenter($this->settings['connection'], $this->settings['connection_settings']);
// Load rsa key
\danog\MadelineProto\Logger::log('Loading RSA key...');
$this->key = new RSA($settings['authorization']['rsa_key']);
// Istantiate TL class
\danog\MadelineProto\Logger::log('Translating tl schemas...');
$this->tl = new TL\TL($this->settings['tl_schema']['src']);
$this->switch_dc(2, true);
$this->get_config();
}
public function __wakeup()
{
$this->setup_logger();
$this->datacenter->__construct($this->settings['connection'], $this->settings['connection_settings']);
$this->reset_session();
if ($this->datacenter->authorized) {
$this->get_updates_difference();
}
}
public function parse_settings($settings) {
// Detect ipv6
$google = '';
try {
@ -161,9 +189,13 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
'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,
'incoming' => 100,
'outgoing' => 100,
],
'updates' => [
'updates_array_limit' => 1000, // How big should be the array containing the updates processed with the default example_update_handler callback
'callback' => [$this, 'get_updates_update_handler'] // A callable function that will be called every time an update is received, must accept an array (for the update) as the only parameter
]
];
foreach ($default_settings as $key => $param) {
if (!isset($settings[$key])) {
@ -184,36 +216,7 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
unset($settings['connection_settings']['all']);
}
$this->settings = $settings;
// Setup logger
$this->setup_logger();
// Connect to servers
\danog\MadelineProto\Logger::log('Istantiating DataCenter...');
$this->datacenter = new DataCenter($this->settings['connection'], $this->settings['connection_settings']);
// Load rsa key
\danog\MadelineProto\Logger::log('Loading RSA key...');
$this->key = new RSA($settings['authorization']['rsa_key']);
// Istantiate TL class
\danog\MadelineProto\Logger::log('Translating tl schemas...');
$this->tl = new TL\TL($this->settings['tl_schema']['src']);
$this->switch_dc(2, true);
$this->get_config();
}
public function __wakeup()
{
$this->setup_logger();
$this->datacenter->__construct($this->settings['connection'], $this->settings['connection_settings']);
$this->reset_session();
if ($this->datacenter->authorized) {
$this->get_updates_state();
}
}
public function setup_logger()
{
if (!\danog\MadelineProto\Logger::$constructed) {
@ -243,15 +246,14 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
if (!isset($this->datacenter->sockets[$new_dc])) {
$this->datacenter->dc_connect($new_dc);
$this->init_authorization();
$this->config = $this->write_client_info('help.getConfig');
$this->parse_config();
$this->get_config($this->write_client_info('help.getConfig'));
$this->get_nearest_dc($allow_nearest_dc_switch);
}
if (
(isset($this->datacenter->sockets[$old_dc]->authorized) && $this->datacenter->sockets[$old_dc]->authorized) &&
!(isset($this->datacenter->sockets[$new_dc]->authorized) && $this->datacenter->sockets[$new_dc]->authorized && $this->datacenter->sockets[$new_dc]->authorization['user']['id'] === $this->datacenter->sockets[$old_dc]->authorization['user']['id'])
) {
$this->should_serialize = true;
$this->datacenter->curdc = $old_dc;
$exported_authorization = $this->method_call('auth.exportAuthorization', ['dc_id' => $new_dc]);
$this->datacenter->curdc = $new_dc;
@ -273,6 +275,7 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
if ($this->datacenter->auth_key == null) {
\danog\MadelineProto\Logger::log('Generating permanent authorization key...');
$this->datacenter->auth_key = $this->create_auth_key(-1);
$this->should_serialize = true;
}
\danog\MadelineProto\Logger::log('Generating temporary authorization key...');
$this->datacenter->temp_auth_key = $this->create_auth_key($this->settings['authorization']['default_temp_auth_key_expires_in']);
@ -306,21 +309,22 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
if ($nearest_dc['nearest_dc'] != $nearest_dc['this_dc'] && $allow_switch) {
$this->switch_dc($nearest_dc['nearest_dc']);
$this->settings['connection_settings']['default_dc'] = $nearest_dc['nearest_dc'];
$this->should_serialize = true;
}
}
public function get_config()
public function get_config($config = [])
{
if ($this->config['expires'] > time()) {
return;
}
$this->config = $this->method_call('help.getConfig');
$this->config = empty($config) ? $this->method_call('help.getConfig') : $config;
$this->should_serialize = true;
$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');
@ -329,5 +333,6 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
$this->settings['connection'][$test][$ipv6][$id] = $dc;
}
unset($this->config['dc_options']);
\danog\MadelineProto\Logger::log('Updated config!', $this->config);
}
}

View File

@ -25,6 +25,7 @@ trait CallHandler
foreach (range(1, $this->settings['max_tries']['query']) as $count) {
try {
\danog\MadelineProto\Logger::log('Calling method (try number '.$count.' for '.$method.')...');
$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];
@ -35,7 +36,7 @@ trait CallHandler
\danog\MadelineProto\Logger::log('Getting response (try number '.$res_count.' for '.$method.')...');
$this->recv_message();
$this->handle_messages();
if (!isset($this->datacenter->incoming_messages[$this->datacenter->outgoing_messages[$int_message_id]['response']]['content'])) {
if (!isset($this->datacenter->outgoing_messages[$int_message_id]['response']) || !isset($this->datacenter->incoming_messages[$this->datacenter->outgoing_messages[$int_message_id]['response']]['content'])) {
continue;
}
$server_answer = $this->datacenter->incoming_messages[$this->datacenter->outgoing_messages[$int_message_id]['response']]['content'];
@ -94,6 +95,7 @@ trait CallHandler
} 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();
sleep(1); // To avoid flooding
continue;
}
if ($server_answer == null) {
@ -113,7 +115,7 @@ trait CallHandler
foreach (range(1, $this->settings['max_tries']['query']) as $count) {
try {
\danog\MadelineProto\Logger::log('Sending object (try number '.$count.' for '.$object.')...');
\danog\MadelineProto\Logger::log($object == 'msgs_ack' ? 'ack '.$args['msg_ids'][0] : 'Sending object (try number '.$count.' for '.$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'] = ['method' => $object, 'args' => $args];
} catch (Exception $e) {

View File

@ -55,7 +55,7 @@ trait MessageHandler
{
$payload = $this->datacenter->read_message();
if (fstat($payload)['size'] == 4) {
$error = \danog\PHP\Struct::unpack('<i', fread($payload, 4))[0];
$error = \danog\PHP\Struct::unpack('<i', stream_get_contents($payload, 4))[0];
if ($error == -404) {
if ($this->datacenter->temp_auth_key != null) {
\danog\MadelineProto\Logger::log('WARNING: Resetting auth key...');
@ -68,13 +68,13 @@ trait MessageHandler
}
throw new \danog\MadelineProto\RPCErrorException($error, $error);
}
$auth_key_id = fread($payload, 8);
$auth_key_id = stream_get_contents($payload, 8);
if ($auth_key_id == $this->string2bin('\x00\x00\x00\x00\x00\x00\x00\x00')) {
list($message_id, $message_length) = \danog\PHP\Struct::unpack('<QI', fread($payload, 12));
list($message_id, $message_length) = \danog\PHP\Struct::unpack('<QI', stream_get_contents($payload, 12));
$this->check_message_id($message_id, false);
$message_data = fread($payload, $message_length);
$message_data = stream_get_contents($payload, $message_length);
} elseif ($auth_key_id == $this->datacenter->temp_auth_key['id']) {
$message_key = fread($payload, 16);
$message_key = stream_get_contents($payload, 16);
$encrypted_data = stream_get_contents($payload);
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->temp_auth_key['auth_key'], 'from server');
$decrypted_data = $this->ige_decrypt($encrypted_data, $aes_key, $aes_iv);

View File

@ -24,7 +24,10 @@ trait PeerHandler
foreach ($users as $key => $user) {
switch ($user['_']) {
case 'user':
$this->chats[$user['id']] = $user;
if (!isset($this->chats[$user['id']])) {
$this->chats[$user['id']] = $user;
$this->should_serialize = true;
}
case 'userEmpty':
break;
default:
@ -40,17 +43,134 @@ trait PeerHandler
switch ($chat['_']) {
case 'chat':
case 'chatEmpty':
$this->chats[-$chat['id']] = $chat;
if (!isset($this->chats[-$chat['id']])) {
$this->should_serialize = true;
$this->chats[-$chat['id']] = $chat;
}
case 'chatForbidden':
case 'channelEmpty':
break;
case 'channel':
$this->chats[(int) ('-100'.$chat['id'])] = $chat;
if (!isset($this->chats[(int) ('-100'.$chat['id'])])) {
$this->should_serialize = true;
$this->chats[(int) ('-100'.$chat['id'])] = $chat;
}
break;
default:
throw new \danog\MadelineProto\Exception('Invalid chat provided at key '.$key.': '.var_export($chat, true));
break;
}
}
$this->should_serialize = true;
}
public function get_info($id, $recursive = true)
{
if (is_array($id)) {
switch ($id['_']) {
case 'inputPeerSelf':
case 'inputPeerSelf':
$id = $this->datacenter->authorization['user']['id'];
break;
case 'user':
$id = $id['id'];
break;
case 'inputPeerUser':
case 'inputUser':
case 'peerUser':
$id = $id['user_id'];
break;
case 'chat':
$id = -$id['id'];
break;
case 'inputPeerChat':
case 'peerChat':
$id = -$id['chat_id'];
break;
case 'channel':
$id = '-100'.$id['id'];
break;
case 'inputPeerChannel':
case 'inputChannel':
case 'peerChannel':
$id = '-100'.$id['channel_id'];
break;
default:
throw new \danog\MadelineProto\Exception('Invalid constructor given ' . var_export($id, true));
break;
}
}
if (preg_match('/^channel#/', $id)) $id = str_replace('channel#', '-100', $id);
if (preg_match('/^chat#/', $id)) $id = str_replace('chat#', '-', $id);
if (preg_match('/^user#/', $id)) $id = str_replace('user#', '', $id);
if (is_numeric($id)) {
$id = (int)$id;
if (isset($this->chats[$id])) {
return $this->gen_all($this->chats[$id]);
}
debug_print_backtrace();
// if ($recursive) {
// }
throw new \danog\MadelineProto\Exception("Couldn't find peer by provided chat id ".$id);
}
$id = str_replace('@', '', $id);
foreach ($this->chats as $chat) {
if (isset($chat['username']) && $chat['username'] == $id) {
return $this->gen_all($chat);
}
}
if ($recursive) {
$this->resolve_username($id);
return $this->get_info($id, false);
}
throw new \danog\MadelineProto\Exception("Couldn't find peer by provided username ".$id);
}
public function gen_all($constructor) {
switch ($constructor['_']) {
case 'user':
$inputPeer = $constructor['self'] ? ['_' => 'inputPeerSelf'] : ['_' => 'inputPeerUser', 'user_id' => $constructor['id'], 'access_hash' => $constructor['access_hash']];
$inputType = $constructor['self'] ? ['_' => 'inputUserSelf'] : ['_' => 'inputUser', 'user_id' => $constructor['id'], 'access_hash' => $constructor['access_hash']];
$Peer = ['_' => 'peerUser', 'user_id' => $constructor['id']];
$id = $constructor['id'];
$bot_api_id = $constructor['id'];
break;
case 'chat':
$inputPeer = ['_' => 'inputPeerChat', 'chat_id' => $constructor['id']];
$inputType = [];
$Peer = ['_' => 'peerChat', 'chat_id' => $constructor['id']];
$id = $constructor['id'];
$bot_api_id = -$constructor['id'];
break;
case 'channel':
$inputPeer = ['_' => 'inputPeerChannel', 'channel_id' => $constructor['id'], 'access_hash' => $constructor['access_hash']];
$inputType = ['_' => 'inputChannel', 'channel_id' => $constructor['id'], 'access_hash' => $constructor['access_hash']];
$Peer = ['_' => 'peerChannel', 'channel_id' => $constructor['id']];
$id = $constructor['id'];
$bot_api_id = (int)('-100'.$constructor['id']);
break;
default:
throw new \danog\MadelineProto\Exception('Invalid constructor given ' . var_export($constructor, true));
break;
}
return ['constructor' => $constructor, 'inputPeer' => $inputPeer, 'inputType' => $inputType, 'Peer' => $Peer, 'id' => $id, 'botApiId' => $bot_api_id];
}
public function resolve_username($username)
{
$res = $this->method_call('contacts.resolveUsername', ['username' => str_replace('@', '', $username)]);
if ($res['_'] == 'contacts.resolvedPeer') {
return $res;
}
throw new \danog\MadelineProto\Exception('resolve_username returned an unexpected constructor: '.var_export($username, true));
}
}

View File

@ -125,15 +125,9 @@ trait ResponseHandler
$this->datacenter->temp_auth_key['server_salt'] = $response['server_salt'];
$this->ack_incoming_message_id($current_msg_id); // Acknowledge that I received the server's response
\danog\MadelineProto\Logger::log('new session created');
\danog\MadelineProto\Logger::log($response);
unset($this->datacenter->new_incoming[$current_msg_id]);
if ($this->datacenter->authorized) {
$this->get_updates_state();
}
break;
case 'msg_container':
\danog\MadelineProto\Logger::log($response['messages']);
unset($this->datacenter->new_incoming[$current_msg_id]);
foreach ($response['messages'] as $message) {
$this->check_message_id($message['msg_id'], false, true);
@ -191,8 +185,6 @@ trait ResponseHandler
if (isset($this->datacenter->incoming_messages[$response['answer_msg_id']])) {
$this->ack_incoming_message_id($response['answer_msg_id']);
} else {
$this->object_call('msg_resend_req', ['msg_ids' => [$response['answer_msg_id']]]);
}
break;
case 'msg_resend_req':

View File

@ -18,19 +18,82 @@ namespace danog\MadelineProto\MTProtoTools;
trait UpdateHandler
{
public $updates_state = [];
public $channels_state = [];
public function update_state($data, $chat_id = 0)
{
if (!isset($this->updates_state[$chat_id])) {
$this->updates_state[$chat_id] = ['date' => 0, 'pts' => 0, 'seq' => 0];
public $updates = [];
public function get_updates_update_handler($update) {
if (count($this->updates) > $this->settings['updates']['updates_array_limit']) {
array_shift($this->updates);
}
$this->updates_state[$chat_id]['pts'] = (!isset($data['pts']) || $data['pts'] == 0) ? $this->updates_state[$chat_id]['pts'] : $data['pts'];
$this->updates_state[$chat_id]['seq'] = (!isset($data['seq']) || $data['seq'] == 0) ? $this->updates_state[$chat_id]['seq'] : $data['seq'];
$this->updates_state[$chat_id]['date'] = (!isset($data['date']) || $data['date'] < $this->updates_state[$chat_id]['date']) ? $this->updates_state[$chat_id]['date'] : $data['date'];
$this->updates[] = $update;
\danog\MadelineProto\Logger::log('Stored ', $update);
}
public function get_updates_state()
public function get_updates($offset, $limit = null, $timeout = 0) {
sleep($timeout);
$this->get_updates_difference();
$result = array_slice($this->updates, $offset, $limit, true);
$updates = [];
foreach ($result as $key => $value) {
$updates[] = ['update_id' => $key, 'update' => $value];
unset($this->updates[$key]);
}
return $updates;
}
public function &get_channel_state($channel, $pts = 0) {
if (!isset($this->channels_state[$channel])) {
$this->channels_state[$channel] = ['pts' => $pts, 'pop_pts' => [], 'pending_seq_updates' =>[]];
}
return $this->channels_state[$channel];
}
public function update_channel_state($channel, $data)
{
$this->get_channel_state($channel);
$this->channels_state[$channel]['pts'] = (!isset($data['pts']) || $data['pts'] == 0) ? $this->get_channel_state($channel)['pts'] : $data['pts'];
}
public function get_channel_difference($channel)
{
$this->get_channel_state($channel);
$difference = $this->method_call('updates.getChannelDifference', ['channel' => $this->get_info('channel#'.$channel)['inputType'], 'filter' => ['_' => 'channelMessagesFilterEmpty'],'pts' => $this->get_channel_state($channel)['pts'], 'limit' => 30]);
switch ($difference['_']) {
case 'updates.channelDifferenceEmpty':
$this->update_channel_state($difference);
break;
case 'updates.difference':
$this->handle_update_messages($difference['new_messages'], $channel);
$this->handle_multiple_update($difference['other_updates']);
$this->update_channel_state($difference);
if (!$difference['final']) {
$this->get_channel_difference($channel);
}
break;
case 'updates.differenceTooLong':
unset($this->channels_state[$channel]);
\danog\MadelineProto\Logger::log('Got updates.differenceTooLong: ', $difference);
break;
default:
throw new \danog\MadelineProto\Exception('Unrecognized update difference received: '.var_export($difference));
break;
}
}
public function update_state($data)
{
if (empty($this->updates_state)) {
$this->updates_state = ['date' => 0, 'pts' => 0, 'seq' => 0, 'pending_seq_updates' => []];
}
$this->updates_state['pts'] = (!isset($data['pts']) || $data['pts'] == 0) ? $this->updates_state['pts'] : $data['pts'];
$this->updates_state['seq'] = (!isset($data['seq']) || $data['seq'] == 0) ? $this->updates_state['seq'] : $data['seq'];
$this->updates_state['date'] = (!isset($data['date']) || $data['date'] < $this->updates_state['date']) ? $this->updates_state['date'] : $data['date'];
}
public function get_updates_difference()
{
if (empty($this->updates_state)) {
return $this->update_state($this->method_call('updates.getState'));
@ -41,19 +104,15 @@ trait UpdateHandler
$this->update_state($difference);
break;
case 'updates.difference':
$this->add_users($difference['users']);
$this->add_chats($difference['chats']);
$this->handle_update_messages($difference['new_messages']);
$this->handle_multiple_update($difference['other_updates']);
$this->update_state($difference['state']);
break;
case 'updates.differenceSlice':
$this->add_users($difference['users']);
$this->add_chats($difference['chats']);
$this->handle_update_messages($difference['new_messages']);
$this->handle_multiple_update($difference['other_updates']);
$this->update_state($difference['intermediate_state']);
$this->get_updates_state();
$this->get_updates_difference();
break;
default:
throw new \danog\MadelineProto\Exception('Unrecognized update difference received: '.var_export($difference));
@ -65,13 +124,22 @@ trait UpdateHandler
{
switch ($updates['_']) {
case 'updatesTooLong':
$this->get_updates_state();
$this->get_updates_difference();
break;
case 'updateShortMessage':
case 'updateShortChatMessage':
case 'updateShortSentMessage':
$update = ['_' => 'updateNewMessage'];
$this->handle_update_messages($update, ['date' => $updates['date']]);
// case 'updateShortSentMessage':
$fromID = isset($updates['from_id']) ? $updates['from_id'] : ($updates['out'] ? $this->datacenter->authorization['user']['id'] : $updates['user_id']);
$toID = isset($updates['chat_id'])
? $updates['chat_id']
: ($updates['out'] ? $updates['user_id'] : $this->datacenter->authorization['user']['id']);
$message = $updates;
$message['_'] = 'message';
$message['to_id'] = $toID;
$message['from_id'] = $this->get_info($fromID)['Peer'];
$update = ['_' => 'updateNewMessage', 'message' => $message, 'pts' => $updates['pts'], 'pts_count' => $updates['pts_count']];
$this->handle_update($update, ['date' => $updates['date']]);
break;
case 'updateShort':
$this->handle_update($updates['update'], ['date' => $updates['date']]);
@ -79,12 +147,13 @@ trait UpdateHandler
case 'updatesCombined':
$this->add_users($updates['users']);
$this->add_chats($updates['chats']);
$this->handle_multiple_update($updates['updates']);
$this->handle_multiple_update($updates['updates'], ['date' => $updates['date'], 'seq' => $updates['seq'], 'seq_start' => $updates['seq_start']]);
break;
case 'updates':
$this->add_users($updates['users']);
$this->add_chats($updates['chats']);
$this->handle_multiple_update($updates['updates']);
$this->handle_multiple_update($updates['updates'], ['date' => $updates['date'], 'seq' => $updates['seq']]);
break;
default:
throw new \danog\MadelineProto\Exception('Unrecognized update received: '.var_export($updates));
@ -92,22 +161,135 @@ trait UpdateHandler
}
}
public function handle_update($update)
public function handle_update($update, $options = [])
{
var_dump($update);
$channel_id = false;
switch ($update['_']) {
case 'updateNewChannelMessage':
case 'updateEditChannelMessage':
$channel_id = $update['message']['to_id']['channel_id'];
break;
case 'updateDeleteChannelMessages':
$channel_id = $update['channel_id'];
break;
case 'updateChannelTooLong':
$channel_id = $update['channel_id'];
if (!isset($this->channels_state[$channel_id])) {
return false;
}
return $this->get_channel_difference($channel_id);
break;
}
$this->save_update($update);
/*
switch ($update['_']) {
case 'updateNewMessage':
case 'updateEditMessage':
case 'updateNewChannelMessage':
case 'updateEditChannelMessage':
$message = $update['message'];
if (isset($message['from_id']) && !isset($this->chats[$message['from_id']]) ||
isset($message['fwd_from']['from_id']) && !isset($this->chats[$message['fwd_from']['from_id']]) ||
isset($message['fwd_from']['channel_id']) && !isset($this->chats[(int)('-100'.$message['fwd_from']['channel_id'])]) ||
!isset($this->get_info($message['to_id'])['bot_api_info'])) {
\danog\MadelineProto\Logger::log('Not enough data for message update');
if ($channel_id !== false && isset($this->chats[$channel_id])) {
$this->get_channel_difference($channel_id);
} else {
$this->get_updates_difference();
}
return false;
}
break;
default:
if ($channel_id !== false && isset($this->chats[$channel_id])) {
return false;
}
break;
}
$pop_pts = false;
if ($update['pts']) {
$new_pts = $cur_state['pts'] + (isset($update['pts_count']) ? $update['pts_count'] : 0);
if ($new_pts < $update['pts']) {
\danog\MadelineProto\Logger::log('Pts hole', $cur_state, $update, $this->get_info($channel_id));
$this->cur_state['pop_pts'][] = $update;
if ($channel_id && isset($this->chats[$channel_id])) {
$this->get_channel_difference($channel_id);
} else {
$this->get_updates_difference();
}
return false;
}
if ($new_pts > $update['pts']) {
$cur_state['pts'] = $update['pts'];
$pop_pts = true;
} else if ($update['pts_count']) {
return false;
}
} else if (!$channel_id && isset($options['seq']) && $options['seq'] > 0) {
$seq = $options['seq'];
$seq_start = isset($options['seq_start']) ? $options['seq_start'] : $options['seq'];
if ($seq_start != $cur_state['seq'] + 1) {
if ($seq_start > $cur_state['seq']) {
\danog\MadelineProto\Logger::('Seq hole', $cur_state);
if (!isset($cur_state['pending_seq_updates'][$seq_start])) {
$cur_state['pending_seq_updates'][$seq_start] = ['seq' => $seq, 'date': $options['date'], 'updates' => []];
}
$cur_state['pending_seq_updates'][$seq_start][] = $update;
if (!$cur_state.syncPending.seqAwaiting ||
$cur_state.syncPending.seqAwaiting < $seq_start) {
$cur_state.syncPending.seqAwaiting = $seq_start
}
return false;
}
}
if (curState.seq != seq) {
curState.seq = seq
if (options.date && curState.date < options.date) {
curState.date = options.date
}
popSeq = true
}
}
saveUpdate(update)
if (popPts) {
popPendingPtsUpdate(channelID)
}
else if (popSeq) {
popPendingSeqUpdate()
}*/
}
public function handle_multiple_update($updates)
public function handle_multiple_update($updates, $options = [])
{
foreach ($updates as $update) {
$this->handle_update($update);
$this->handle_update($update, $options);
}
}
public function handle_update_messages($messages)
public function handle_update_messages($messages, $channel = false)
{
foreach ($messages as $message) {
$this->handle_update(['_' => 'updateNewMessage', 'message' => $message, 'pts' => $this->updates_state[0]['pts'], 'pts_count' => 0]);
$this->save_update(['_' => $channel == false ? 'updateNewMessage' : 'updateNewChannelMessage', 'message' => $message, 'pts' => $channel == false ? $this->updates_state['pts'] : $this->get_channel_state($channel)['pts'], 'pts_count' => 0]);
}
}
public function save_update($update) {
$this->settings['updates']['callback']($update);
}
}

View File

@ -0,0 +1,31 @@
<?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;
/**
* Manages serialization of the MadelineProto instance
*/
class Serialization {
public static function serialize($filename, $instance, $force = false) {
if ($instance->API->should_serialize || !(file_exists($filename) && !empty(file_get_contents($filename))) || $force) {
$instance->API->should_serialize = false;
return file_put_contents($filename, serialize($instance));
}
return false;
}
public static function deserialize($filename) {
return file_exists($filename) ? unserialize(file_get_contents($filename)) : false;
}
}

View File

@ -87,7 +87,6 @@ class TL extends \danog\MadelineProto\Tools
return \danog\PHP\Struct::pack('<I', $object);
case 'long':
if (!is_numeric($object)) {
var_dump($object);
throw new Exception("given value isn't numeric");
}
@ -252,42 +251,42 @@ class TL extends \danog\MadelineProto\Tools
throw new Exception('An invalid bytes_io handle was provided.');
}
}
//\danog\MadelineProto\Logger::log('Deserializing '.$type['type'].'/'.$subtype.' at byte '.ftell($bytes_io));
//\danog\MadelineProto\Logger::log('Deserializing '.$type['type'].' at byte '.ftell($bytes_io));
switch ($type['type']) {
case 'Bool':
return $this->deserialize_bool(fread($bytes_io, 4));
return $this->deserialize_bool(stream_get_contents($bytes_io, 4));
case 'int':
return \danog\PHP\Struct::unpack('<i', fread($bytes_io, 4))[0];
return \danog\PHP\Struct::unpack('<i', stream_get_contents($bytes_io, 4))[0];
case '#':
return \danog\PHP\Struct::unpack('<I', fread($bytes_io, 4))[0];
return \danog\PHP\Struct::unpack('<I', stream_get_contents($bytes_io, 4))[0];
case 'long':
return \danog\PHP\Struct::unpack('<q', fread($bytes_io, 8))[0];
return \danog\PHP\Struct::unpack('<q', stream_get_contents($bytes_io, 8))[0];
case 'double':
return \danog\PHP\Struct::unpack('<d', fread($bytes_io, 8))[0];
return \danog\PHP\Struct::unpack('<d', stream_get_contents($bytes_io, 8))[0];
case 'int128':
return fread($bytes_io, 16);
return stream_get_contents($bytes_io, 16);
case 'int256':
return fread($bytes_io, 32);
return stream_get_contents($bytes_io, 32);
case 'int512':
return fread($bytes_io, 32);
return stream_get_contents($bytes_io, 32);
case 'string':
case 'bytes':
$l = \danog\PHP\Struct::unpack('<B', fread($bytes_io, 1))[0];
$l = \danog\PHP\Struct::unpack('<B', stream_get_contents($bytes_io, 1))[0];
if ($l > 254) {
throw new Exception('Length is too big');
}
if ($l == 254) {
$long_len = \danog\PHP\Struct::unpack('<I', fread($bytes_io, 3).$this->string2bin('\x00'))[0];
$x = fread($bytes_io, $long_len);
$long_len = \danog\PHP\Struct::unpack('<I', stream_get_contents($bytes_io, 3).$this->string2bin('\x00'))[0];
$x = stream_get_contents($bytes_io, $long_len);
$resto = $this->posmod(-$long_len, 4);
if ($resto > 0) {
fread($bytes_io, $resto);
stream_get_contents($bytes_io, $resto);
}
} else {
$x = fread($bytes_io, $l);
$x = stream_get_contents($bytes_io, $l);
$resto = $this->posmod(-($l + 1), 4);
if ($resto > 0) {
fread($bytes_io, $resto);
stream_get_contents($bytes_io, $resto);
}
}
if (!is_string($x)) {
@ -298,14 +297,14 @@ class TL extends \danog\MadelineProto\Tools
case 'true':
return true;
case 'Vector t':
$id = \danog\PHP\Struct::unpack('<i', fread($bytes_io, 4))[0];
$id = \danog\PHP\Struct::unpack('<i', stream_get_contents($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'])));
return $this->deserialize($this->fopen_and_write('php://memory', 'rw+b', gzdecode($this->deserialize($bytes_io, ['type' => 'string']))));
case 'Vector t':
case 'vector':
break;
@ -313,7 +312,7 @@ class TL extends \danog\MadelineProto\Tools
throw new Exception('Invalid vector constructor: '.$constructorData['predicate']);
}
case 'vector':
$count = \danog\PHP\Struct::unpack('<i', fread($bytes_io, 4))[0];
$count = \danog\PHP\Struct::unpack('<i', stream_get_contents($bytes_io, 4))[0];
$result = [];
for ($i = 0; $i < $count; $i++) {
$result[] = $this->deserialize($bytes_io, ['type' => $type['subtype']]);
@ -330,7 +329,7 @@ class TL extends \danog\MadelineProto\Tools
} else {
$constructorData = $this->constructors->find_by_predicate($type['type']);
if ($constructorData === false) {
$id = \danog\PHP\Struct::unpack('<i', fread($bytes_io, 4))[0];
$id = \danog\PHP\Struct::unpack('<i', stream_get_contents($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);
@ -338,7 +337,7 @@ class TL extends \danog\MadelineProto\Tools
}
}
if ($constructorData['predicate'] == 'gzip_packed') {
return $this->deserialize(gzdecode($this->deserialize($bytes_io, ['type' => 'string'])));
return $this->deserialize($this->fopen_and_write('php://memory', 'rw+b', gzdecode($this->deserialize($bytes_io, ['type' => 'string']))));
}
$x = ['_' => $constructorData['predicate']];
foreach ($constructorData['params'] as $arg) {
@ -365,7 +364,6 @@ class TL extends \danog\MadelineProto\Tools
if (isset($x['flags'])) { // I don't think we need this anymore
unset($x['flags']);
}
return $x;
}

View File

@ -35,7 +35,7 @@ class Tools
{
$pos = ftell($handle);
fseek($handle, 0);
$content = fread($handle, fstat($handle)['size']);
$content = stream_get_contents($handle, fstat($handle)['size']);
fseek($handle, $pos);
return $content;

View File

@ -24,8 +24,11 @@ trait Login
}
$this->API->datacenter->authorized = false;
$this->API->datacenter->authorization = null;
$this->API->updates = [];
\danog\MadelineProto\Logger::log('Logged out successfully!');
$this->API->should_serialize = true;
return true;
}
@ -45,7 +48,12 @@ trait Login
]
);
$this->API->datacenter->authorized = true;
$this->API->get_updates_state();
$this->API->get_updates_difference();
$this->API->should_serialize = true;
$this->API->updates = [];
\danog\MadelineProto\Logger::log('Logged in successfully!');
return $this->API->datacenter->authorization;
@ -70,6 +78,9 @@ trait Login
);
$this->API->datacenter->authorization['phone_number'] = $number;
$this->API->datacenter->waiting_code = true;
$this->API->should_serialize = true;
$this->API->updates = [];
\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;
@ -91,7 +102,9 @@ trait Login
);
$this->API->datacenter->waiting_code = false;
$this->API->datacenter->authorized = true;
$this->API->get_updates_state();
$this->API->get_updates_difference();
$this->API->should_serialize = true;
\danog\MadelineProto\Logger::log('Logged in successfully!');
return $this->API->datacenter->authorization;

View File

@ -17,56 +17,15 @@ namespace danog\MadelineProto\Wrappers;
*/
trait PeerHandler
{
public function get_peer($id, $recursive = true)
{
if (is_numeric($id)) {
if (isset($this->API->chats[$id])) {
return $this->API->chats[$id];
}
// if ($recursive) {
// }
throw new \danog\MadelineProto\Exception("Couldn't find peer by provided chat id ".$id);
}
$id = str_replace('@', '', $id);
foreach ($this->API->chats as $chat) {
if (isset($chat['username']) && $chat['username'] == $id) {
return $chat;
}
}
if ($recursive) {
$this->resolve_username($id);
return $this->get_peer($id, false);
}
throw new \danog\MadelineProto\Exception("Couldn't find peer by provided username ".$id);
public function get_info($id, $recursive = true) {
return $this->API->get_info($id, $recursive);
}
public function get_input_peer($id)
{
return $this->constructor2inputpeer($this->get_peer($id));
public function gen_all($constructor) {
return $this->API->gen_all($constructor);
}
public function constructor2inputpeer($peer)
{
switch ($peer['_']) {
case 'user':
return $peer['self'] ? ['_' => 'inputPeerSelf'] : ['_' => 'inputPeerUser', 'user_id' => $peer['id'], 'access_hash' => $peer['access_hash']];
case 'chat':
case 'chatEmpty':
return ['_' => 'inputPeerChat', 'chat_id' => $peer['id']];
case 'channel':
return ['_' => 'inputPeerChannel', 'channel_id' => $peer['id'], 'access_hash' => $peer['access_hash']];
default:
throw new \danog\MadelineProto\Exception('Invalid constructor given');
}
}
public function resolve_username($username)
{
$res = $this->API->method_call('contacts.resolveUsername', ['username' => str_replace('@', '', $username)]);
if ($res['_'] == 'contacts.resolvedPeer') {
return $res;
}
throw new \danog\MadelineProto\Exception('resolve_username returned an unexpected constructor: '.var_export($username, true));
public function resolve_username($username) {
return $this->API->resolve_username($username);
}
}

View File

@ -0,0 +1,25 @@
<?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\Wrappers;
/**
* Manages changing API instance settings.
*/
trait SettingsManager {
public function get_settings() {
return $this->API->settings;
}
public function update_settings($settings) {
$this->API->parse_settings($settings);
}
}

View File

@ -12,10 +12,16 @@ If not, see <http://www.gnu.org/licenses/>.
*/
require_once 'vendor/autoload.php';
$settings = [];
if (file_exists('web_data.php')) {
require_once('web_data.php');
}
if (file_exists('number.php') && !file_exists('session.madeline')) {
$MadelineProto = \danog\MadelineProto\Serialization::deserialize('session.madeline');
if (file_exists('number.php') && $MadelineProto === false) {
include_once 'number.php';
$MadelineProto = new \danog\MadelineProto\API();
$MadelineProto = new \danog\MadelineProto\API($settings);
$checkedPhone = $MadelineProto->auth->checkPhone(// auth.checkPhone becomes auth->checkPhone
[
@ -33,35 +39,36 @@ if (file_exists('number.php') && !file_exists('session.madeline')) {
$authorization = $MadelineProto->complete_phone_login($code);
\danog\MadelineProto\Logger::log($authorization);
echo 'Serializing MadelineProto to session.madeline...'.PHP_EOL;
echo 'Wrote '.file_put_contents('session.madeline', serialize($MadelineProto)).' bytes'.PHP_EOL;
echo 'Wrote '.\danog\MadelineProto\Serialization::serialize('session.madeline', $MadelineProto).' bytes'.PHP_EOL;
}
echo 'Deserializing MadelineProto from session.madeline...'.PHP_EOL;
$MadelineProto = unserialize(file_get_contents('session.madeline'));
$MadelineProto = \danog\MadelineProto\Serialization::deserialize('session.madeline');
$message = (getenv('TRAVIS_COMMIT') == '') ? 'Message entities can be sent too (yay)' : ('Travis ci tests in progress: commit '.getenv('TRAVIS_COMMIT').', job '.getenv('TRAVIS_JOB_NUMBER').', PHP version: '.getenv('TRAVIS_PHP_VERSION'));
$message = (getenv('TRAVIS_COMMIT') == '') ? 'I iz works always (io laborare sembre) (yo lavorar siempre)' : ('Travis ci tests in progress: commit '.getenv('TRAVIS_COMMIT').', job '.getenv('TRAVIS_JOB_NUMBER').', PHP version: '.getenv('TRAVIS_PHP_VERSION'));
$flutter = 'https://storage.pwrtelegram.xyz/pwrtelegrambot/document/file_6570.mp4';
\danog\MadelineProto\Logger::log($MadelineProto->resolve_username('@Palmas2012')); // Always use this method to resolve usernames, but you won't need to call this to get info about peers, as get_peer and get_input_peer will call it for you if needed
$mention = $MadelineProto->get_peer('@veetaw'); // Returns an object of type User or Chat
$mention = $MadelineProto->constructor2inputpeer($mention); // Converts an object of type User or Chat to an object of type inputPeer
$mention = $MadelineProto->get_info('@giuseppe_la_gaipa_2'); // Returns the following array: ['constructor' => $constructor, 'inputPeer' => $inputPeer, 'inputType' => $inputType, 'Peer' => $Peer, 'id' => $id, 'botApiId' => $bot_api_id]
$mention = $mention['inputType']; // Selects only the inputType object
foreach (['@pwrtelegramgroup', '@pwrtelegramgroupita'] as $peer) {
$peer = $MadelineProto->get_input_peer($peer); // Returns directly an inputPeer object, basically does the same thing I've done manually above
$sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message.' & pony', 'entities' => [['_' => 'messageEntityUrl', 'offset' => strlen($message) + 1, 'length' => 6, 'url' => $flutter], ['_' => 'inputMessageEntityMentionName', 'offset' => 0, 'length' => strlen($message), 'user_id' => $mention]]]);
$peer = $MadelineProto->get_info($peer)['inputPeer']; // Select the inputPeerType (alias inputPeer) object
$sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message, 'entities' => [['_' => 'inputMessageEntityMentionName', 'offset' => 0, 'length' => strlen($message), 'user_id' => $mention]]]);
\danog\MadelineProto\Logger::log($sentMessage);
}
sleep(5);
$MadelineProto->API->get_updates_difference();
echo 'Size of MadelineProto instance is '.strlen(serialize($MadelineProto)).' bytes'.PHP_EOL;
if (file_exists('token.php')) {
include_once 'token.php';
$MadelineProto = new \danog\MadelineProto\API();
$MadelineProto = new \danog\MadelineProto\API($settings);
$authorization = $MadelineProto->bot_login($token);
\danog\MadelineProto\Logger::log($authorization);
}
foreach (['@pwrtelegramgroup', '@pwrtelegramgroupita'] as $peer) {
$peer = $MadelineProto->get_input_peer($peer);
$sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message.' & pony', 'entities' => [['_' => 'messageEntityUrl', 'offset' => strlen($message) + 1, 'length' => 6, 'url' => $flutter], ['_' => 'inputMessageEntityMentionName', 'offset' => 0, 'length' => strlen($message), 'user_id' => $mention]]]);
$peer = $MadelineProto->get_info($peer)['inputPeer']; // Select the inputPeerType (alias inputPeer) object
$sentMessage = $MadelineProto->messages->sendMessage(['peer' => $peer, 'message' => $message, 'entities' => [['_' => 'inputMessageEntityMentionName', 'offset' => 0, 'length' => strlen($message), 'user_id' => $mention]]]);
\danog\MadelineProto\Logger::log($sentMessage);
}