Added support for 32 bit systems, imimplemented threading.

This commit is contained in:
Daniil Gentili 2017-03-24 21:01:08 +01:00
parent 289006323f
commit c235afb30f
37 changed files with 1072 additions and 172636 deletions

View File

@ -1,6 +1,2 @@
MTPROTO_NUMBER=+39373739
MTPROTO_SETTINGS={"app_info":{"api_id":6,"api_hash":"eb06d4abfb49dc3eeb1aeb98ae0f581e"}, "connection_settings":{"all":{"test_mode":true}}}
TEST_USERNAME=@danogentili
TEST_DESTINATION_GROUPS=["@danogentili"]
BOT_TOKEN=299050432:AAGMdcyfqGTpwCo0bJkmlomHwMUieXRs8GY
MTPROTO_NUMBER=+393924682693
MTPROTO_SETTINGS={"app_info":{"api_id":6,"api_hash":"eb06d4abfb49dc3eeb1aeb98ae0f581e"},"connection_settings":{"all":{"test_mode":true}}}

View File

@ -336,7 +336,7 @@ Before sending any message, you must check if the secret chat was accepted by th
```
$status = $MadelineProto->secret_chat_info($chat);
$status = $MadelineProto->secret_chat_status($chat);
```
Returns 0 if the chat cannot be found in the local database, 1 if the chat was requested but not yet accepted, and 2 if it is a valid accepted secret chat.
@ -455,7 +455,7 @@ The same operation should be done when serializing to another destination manual
### Exceptions
MadelineProto can throw three different exceptions:
MadelineProto can throw lots of 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)
@ -466,7 +466,7 @@ MadelineProto can throw three different exceptions:
* \danog\MadelineProto\SecurityException - Thrown on security problems (invalid params during generation of auth key or similar)
* \danog\MadelineProto\Conversion\Exception - Thrown if some param/object can't be converted to/from bot API/TD/TD-CLI format (this includes markdown/html parsing)
* \danog\MadelineProto\TL\Conversion\Exception - Thrown if some param/object can't be converted to/from bot API/TD/TD-CLI format (this includes markdown/html parsing)

View File

@ -22,6 +22,9 @@ try {
$MadelineProto = new \danog\MadelineProto\API($settings);
$authorization = $MadelineProto->bot_login($token);
\danog\MadelineProto\Logger::log([$authorization], \danog\MadelineProto\Logger::NOTICE);
} else {
echo 'token.php does not exist';
die;
}
}
$offset = 0;

171757
calls

File diff suppressed because it is too large Load Diff

View File

@ -68,4 +68,4 @@ for ($x = 0; $x < $argv[2]; $x++) {
$MadelineProto->get_updates_difference();
echo 'Wrote '.\danog\MadelineProto\Serialization::serialize('session.madeline', $MadelineProto).' bytes'.PHP_EOL;
}
$MadelineProto->messages->sendMessage(['peer' => $argv[1], 'message' => '[Powered by MadelineProto](https://github.com/danog/MadelineProto)', 'parse_mode' => 'markdown']);
$MadelineProto->messages->sendMessage(['peer' => $argv[1], 'message' => '<a href="https://github.com/danog/MadelineProto">Powered by MadelineProto</a>', 'parse_mode' => 'markdown']);

View File

@ -22,11 +22,6 @@ 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);
@ -41,18 +36,19 @@ class API extends APIFactory
$this->API->v = $this->API->getV();
\danog\MadelineProto\Logger::log(['MadelineProto is ready!'], Logger::NOTICE);
}
/*
public function __sleep()
{
//$this->API->reset_session(false);
return ['API'];
}
*/
public function __wakeup()
{
set_error_handler(['\danog\MadelineProto\Exception', 'ExceptionErrorHandler']);
$this->APIFactory();
//$this->APIFactory();
}
public function __destruct()

View File

@ -94,6 +94,6 @@ class APIFactory
{
$this->API->get_config();
return method_exists($this->API, $this->namespace.$name) ? $this->API->{$this->namespace.$name}(...$arguments) : $this->API->method_call($this->namespace.$name, (isset($arguments[0]) && is_array($arguments[0])) ? $arguments[0] : [], (isset($arguments[1]) && is_array($arguments[1])) ? $arguments[1] : []);
return method_exists($this->API, $this->namespace.$name) ? $this->API->{$this->namespace.$name}(...$arguments) : $this->API->method_call($this->namespace.$name, (isset($arguments[0]) && is_array($arguments[0])) ? $arguments[0] : [], (isset($arguments[1]) && is_array($arguments[1])) ? $arguments[1] : ['datacenter' => $this->API->datacenter->curdc]);
}
}

View File

@ -29,10 +29,8 @@ class Connection
public $temp_auth_key;
public $auth_key;
public $session_id;
public $seq_no = 0;
public $authorized = false;
public $authorization = null;
public $login_temp_status = 'none';
public $session_out_seq_no = 0;
public $session_in_seq_no = 0;
public $incoming_messages = [];
public $outgoing_messages = [];
@ -105,7 +103,9 @@ class Connection
case 'tcp_full':
case 'http':
case 'https':
fclose($this->sock);
try {
fclose($this->sock);
} catch (\danog\MadelineProto\Exception $e) { ; }
break;
case 'udp':
throw new Exception("Connection: This protocol wasn't implemented yet.");
@ -167,8 +167,8 @@ class Connection
throw new Exception("Connection: couldn't connect to socket.");
}
$packet = stream_get_contents($this->sock, $length);
if ($packet === false) {
throw new NothingInTheSocketException('Nothing in the socket!');
if ($packet === false || strlen($packet) === 0) {
throw new \danog\MadelineProto\NothingInTheSocketException('Nothing in the socket!');
}
if (strlen($packet) != $length) {
throw new \danog\MadelineProto\Exception("WARNING: Wrong length was read (should've read ".($length).', read '.strlen($packet).')!');

View File

@ -24,6 +24,9 @@ class DataCenter
public $dclist = [];
public $settings = [];
public function __sleep() {
return ['sockets', 'curdc', 'dclist', 'settings'];
}
public function __construct(&$dclist, &$settings)
{
$this->dclist = &$dclist;
@ -44,38 +47,37 @@ class DataCenter
}
}
public function dc_connect($dc_number, $settings = [])
public function dc_connect($dc_number)
{
$this->curdc = $dc_number;
if (isset($this->sockets[$dc_number])) {
return false;
}
if ($settings === []) {
$settings = $this->settings[$dc_number];
}
$test = $settings['test_mode'] ? 'test' : 'main';
$ipv6 = $settings['ipv6'] ? 'ipv6' : 'ipv4';
$test = $this->settings[$dc_number]['test_mode'] ? 'test' : 'main';
$ipv6 = $this->settings[$dc_number]['ipv6'] ? 'ipv6' : 'ipv4';
$address = $this->dclist[$test][$ipv6][$dc_number]['ip_address'];
$address = $settings['ipv6'] ? '['.$address.']' : $address;
$address = $this->settings[$dc_number]['ipv6'] ? '['.$address.']' : $address;
$port = $this->dclist[$test][$ipv6][$dc_number]['port'];
if ($settings['protocol'] === 'https') {
if ($this->settings[$dc_number]['protocol'] === 'https') {
$subdomain = $this->dclist['ssl_subdomains'][$dc_number];
$path = $settings['test_mode'] ? 'apiw_test1' : 'apiw1';
$address = $settings['protocol'].'://'.$subdomain.'.web.telegram.org/'.$path;
$path = $this->settings[$dc_number]['test_mode'] ? 'apiw_test1' : 'apiw1';
$address = $this->settings[$dc_number]['protocol'].'://'.$subdomain.'.web.telegram.org/'.$path;
}
if ($settings['protocol'] === 'http') {
$address = $settings['protocol'].'://'.$address.'/api';
if ($this->settings[$dc_number]['protocol'] === 'http') {
$address = $this->settings[$dc_number]['protocol'].'://'.$address.'/api';
$port = 80;
}
\danog\MadelineProto\Logger::log(['Connecting to DC '.$dc_number.' ('.$test.' server, '.$ipv6.', '.$settings['protocol'].')...'], \danog\MadelineProto\Logger::VERBOSE);
$this->sockets[$dc_number] = new Connection($address, $port, $settings['protocol'], $settings['timeout']);
\danog\MadelineProto\Logger::log(['Connecting to DC '.$dc_number.' ('.$test.' server, '.$ipv6.', '.$this->settings[$dc_number]['protocol'].')...'], \danog\MadelineProto\Logger::VERBOSE);
$this->sockets[$dc_number] = new Connection($address, $port, $this->settings[$dc_number]['protocol'], $this->settings[$dc_number]['timeout']);
return true;
}
public function get_dcs() {
$test = $this->settings[2]['test_mode'] ? 'test' : 'main';
$ipv6 = $this->settings[2]['ipv6'] ? 'ipv6' : 'ipv4';
return array_keys($this->dclist[$test][$ipv6]);
}
public function &__get($name)
{
return $this->sockets[$this->curdc]->{$name};

View File

@ -14,6 +14,13 @@ namespace danog\MadelineProto;
class Exception extends \Exception
{
public function __construct($message = null, $code = 0, Exception $previous = null) {
parent::__construct($message, $code, $previous);
if (\danog\MadelineProto\Logger::$constructed && $this->file !== __FILE__) {
\danog\MadelineProto\Logger::log([$message.' in '.basename($this->file).':'.$this->line], \danog\MadelineProto\Logger::FATAL_ERROR);
}
}
/**
* ExceptionErrorHandler.
*

View File

@ -22,25 +22,14 @@ class Logger
public static $constructed = false;
public static $prefix = '';
public static $level = 3;
const ULTRA_VERBOSE = 'ULTRA_VERBOSE';
const VERBOSE = 'VERBOSE';
const NOTICE = 'NOTICE';
const WARNING = 'WARNING';
const ERROR = 'ERROR';
const FATAL_ERROR = 'FATAL ERROR';
const ULTRA_VERBOSE = 5;
const VERBOSE = 4;
const NOTICE = 3;
const WARNING = 2;
const ERROR = 1;
const FATAL_ERROR = 0;
public static function level2num($level)
{
switch ($level) {
case self::ULTRA_VERBOSE: return 5;
case self::VERBOSE: return 4;
case self::NOTICE: return 3;
case self::WARNING: return 2;
case self::ERROR: return 1;
case self::FATAL_ERROR: return 0;
default: return false;
}
}
/*
* Constructor function
@ -59,23 +48,27 @@ class Logger
self::$optional = &$optional;
self::$constructed = true;
self::$prefix = $prefix === '' ? '' : ', '.$prefix;
self::$level = self::level2num($level);
self::$level = $level;
}
public static function log($params, $level = self::NOTICE)
{
if (!self::$constructed) {
throw new Exception("The constructor function wasn't called! Please call the constructor function before using this method.");
}
$level = self::level2num($level);
if ($level > self::$level) {
return false;
}
$prefix = self::$prefix;
if (class_exists('\Thread') && is_object(\Thread::getCurrentThread())) {
$prefix .= ' (t)';
}
foreach (is_array($params) ? $params : [$params] as $param) {
if (!is_string($param)) {
$param = var_export($param, true);
}
$param = str_pad(basename(debug_backtrace()[0]['file'], '.php').self::$prefix.': ', 16 + strlen(self::$prefix))."\t".$param;
$param = str_pad(basename(debug_backtrace()[0]['file'], '.php').$prefix.': ', 16 + strlen($prefix))."\t".$param;
switch (self::$mode) {
case 1:
error_log($param);

View File

@ -61,7 +61,7 @@ class Lua
if ($params === 0) {
return 0;
}
$result = $this->MadelineProto->API->method_call($params['_'], $params);
$result = $this->MadelineProto->API->method_call($params['_'], $params, ['datacenter' => $this->MadelineProto->API->datacenter->curdc]);
if (is_callable($cb)) {
$cb($this->MadelineProto->mtproto_to_td($result), $cb_extra);
}
@ -71,7 +71,7 @@ class Lua
public function madeline_function($params, $cb = null, $cb_extra = null)
{
$result = $this->MadelineProto->API->method_call($params['_'], $params);
$result = $this->MadelineProto->API->method_call($params['_'], $params, ['datacenter' => $this->MadelineProto->API->datacenter->curdc]);
if (is_callable($cb)) {
$cb($result, $cb_extra);
}

View File

@ -25,27 +25,37 @@ class MTProto
use \danog\MadelineProto\MTProtoTools\MsgIdHandler;
use \danog\MadelineProto\MTProtoTools\PeerHandler;
use \danog\MadelineProto\MTProtoTools\ResponseHandler;
use \danog\MadelineProto\MTProtoTools\SaltHandler;
//use \danog\MadelineProto\MTProtoTools\SaltHandler;
use \danog\MadelineProto\MTProtoTools\SeqNoHandler;
use \danog\MadelineProto\MTProtoTools\UpdateHandler;
use \danog\MadelineProto\MTProtoTools\Files;
use \danog\MadelineProto\SecretChats\AuthKeyHandler;
use \danog\MadelineProto\SecretChats\MessageHandler;
use \danog\MadelineProto\TL\TL;
use \danog\MadelineProto\Conversion\BotAPI;
use \danog\MadelineProto\Conversion\BotAPIFiles;
use \danog\MadelineProto\Conversion\Extension;
use \danog\MadelineProto\Conversion\TD;
use \danog\MadelineProto\TL\Conversion\BotAPI;
use \danog\MadelineProto\TL\Conversion\BotAPIFiles;
use \danog\MadelineProto\TL\Conversion\Extension;
use \danog\MadelineProto\TL\Conversion\TD;
use \danog\MadelineProto\Tools;
public $settings = [];
public $config = ['expires' => -1];
public $ipv6 = false;
public $should_serialize = true;
public $authorization = null;
public $authorized = false;
public $login_temp_status = 'none';
public $bigint = false;
public $run_workers = false;
public $threads = false;
public $readers = [];
public function __construct($settings = [])
{
$this->bigint = PHP_INT_SIZE < 8;
// Parse settings
$this->parse_settings($settings);
// Connect to servers
\danog\MadelineProto\Logger::log(['Istantiating DataCenter...'], Logger::ULTRA_VERBOSE);
if (isset($this->datacenter)) {
@ -60,37 +70,90 @@ class MTProto
// Istantiate TL class
\danog\MadelineProto\Logger::log(['Translating tl schemas...'], Logger::ULTRA_VERBOSE);
$this->construct_TL($this->settings['tl_schema']['src']);
/*
* ***********************************************************************
* Define some needed numbers for BigInteger
*/
/*
* ***********************************************************************
* Define some needed numbers for BigInteger
*/
\danog\MadelineProto\Logger::log(['Executing dh_prime checks (0/3)...'], \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->zero = new \phpseclib\Math\BigInteger(0);
$this->one = new \phpseclib\Math\BigInteger(1);
//$two = new \phpseclib\Math\BigInteger(2);
$this->three = new \phpseclib\Math\BigInteger(3);
$this->four = new \phpseclib\Math\BigInteger(4);
$this->twoe1984 = new \phpseclib\Math\BigInteger('1751908409537131537220509645351687597690304110853111572994449976845956819751541616602568796259317428464425605223064365804210081422215355425149431390635151955247955156636234741221447435733643262808668929902091770092492911737768377135426590363166295684370498604708288556044687341394398676292971255828404734517580702346564613427770683056761383955397564338690628093211465848244049196353703022640400205739093118270803778352768276670202698397214556629204420309965547056893233608758387329699097930255380715679250799950923553703740673620901978370802540218870279314810722790539899334271514365444369275682816');
$this->twoe2047 = new \phpseclib\Math\BigInteger('16158503035655503650357438344334975980222051334857742016065172713762327569433945446598600705761456731844358980460949009747059779575245460547544076193224141560315438683650498045875098875194826053398028819192033784138396109321309878080919047169238085235290822926018152521443787945770532904303776199561965192760957166694834171210342487393282284747428088017663161029038902829665513096354230157075129296432088558362971801859230928678799175576150822952201848806616643615613562842355410104862578550863465661734839271290328348967522998634176499319107762583194718667771801067716614802322659239302476074096777926805529798115328');
$this->twoe2048 = new \phpseclib\Math\BigInteger('32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230656');
$this->switch_dc(2, true);
$this->get_config();
}
$this->connect_to_all_dcs();
$this->datacenter->curdc = 2;
$nearest_dc = $this->method_call('help.getNearestDc', [], ['datacenter' => $this->datacenter->curdc]);
\danog\MadelineProto\Logger::log(["We're in ".$nearest_dc['country'].', current dc is '.$nearest_dc['this_dc'].', nearest dc is '.$nearest_dc['nearest_dc'].'.'], Logger::NOTICE);
if ($nearest_dc['nearest_dc'] != $nearest_dc['this_dc']) {
$this->datacenter->curdc = $nearest_dc['nearest_dc'];
$this->settings['connection_settings']['default_dc'] = $nearest_dc['nearest_dc'];
$this->should_serialize = true;
}
$this->get_config([], ['datacenter' => $this->datacenter->curdc]);
$this->v = $this->getV();
$this->should_serialize = true;
}
public function setup_threads() {
if ($this->threads = $this->run_workers = class_exists('\Pool') && php_sapi_name() == "cli" && $this->settings['threading']['allow_threading']) {
\danog\MadelineProto\Logger::log(['THREADING IS ENABLED'], \danog\MadelineProto\Logger::NOTICE);
$this->start_threads();
}
}
public function start_threads() {
if ($this->threads) {
$dcs = $this->datacenter->get_dcs();
if (!isset($this->reader_pool)) $this->reader_pool = new \Pool(count($dcs));
foreach ($dcs as $dc) {
if (!isset($this->readers[$dc])) {
$this->readers [$dc] = new \danog\MadelineProto\Threads\SocketReader($this, $dc);
}
if (!$this->readers[$dc]->isRunning()) {
$this->readers[$dc]->garbage = false;
$this->reader_pool->submit($this->readers[$dc]);
Logger::log(['Socket reader on DC '.$dc.': RESTARTED'], Logger::WARNING);
} else {
Logger::log(['Socket reader on DC '.$dc.': WORKING'], Logger::NOTICE);
}
}
}
}
public function __sleep() {
$t = get_object_vars($this);
if (isset($t['reader_pool'])) unset($t['reader_pool']);
return array_keys($t);
}
public function __wakeup()
{
if (debug_backtrace()[0]['file'] === __DIR__.'/Threads/SocketReader.php' || (debug_backtrace()[0]['file'] === __FILE__ && debug_backtrace()[0]['line'] === 117)) return;
$this->bigint = PHP_INT_SIZE < 8;
$this->setup_logger();
if (!isset($this->v) || $this->v !== $this->getV()) {
\danog\MadelineProto\Logger::log(['Serialization is out of date, reconstructing object!'], Logger::WARNING);
$this->__construct($this->settings);
$this->v = $this->getV();
}
$this->setup_threads();
$this->datacenter->__construct($this->settings['connection'], $this->settings['connection_settings']);
$this->reset_session();
if ($this->datacenter->authorized && $this->settings['updates']['handle_updates']) {
if ($this->authorized && $this->settings['updates']['handle_updates']) {
\danog\MadelineProto\Logger::log(['Getting updates after deserialization...'], Logger::NOTICE);
$this->get_updates_difference();
}
}
public function __destruct() {
if (isset($this->reader_pool)) {
$this->run_workers = false;
\danog\MadelineProto\Logger::log(['Shutting down reader pool...'], Logger::NOTICE);
$this->reader_pool->shutdown();
}
}
public function parse_settings($settings)
{
// Detect ipv6
@ -100,11 +163,11 @@ class MTProto
'timeout' => 1,
],
]);
$google = file_get_contents('https://ipv6.google.com', false, $ctx);
$google = file_get_contents('http://ipv6.test-ipv6.com/', false, $ctx);
} catch (Exception $e) {
}
$this->ipv6 = strlen($google) > 0;
// Detect device model
try {
$device_model = php_uname('s');
@ -182,7 +245,7 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
'protocol' => 'tcp_full', // can be tcp_full, tcp_abridged, tcp_intermediate, http, https, 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' => 3, // timeout for sockets
'timeout' => 2, // timeout for sockets
],
],
'app_info' => [ // obtained in https://my.telegram.org
@ -190,8 +253,8 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
'api_hash' => '4251a2777e179232705e2462706f4143',
'device_model' => $device_model,
'system_version' => $system_version,
// 'app_version' => 'Unicorn', // 🌚
'app_version' => $this->getV(),
'app_version' => 'Unicorn', // 🌚
// 'app_version' => $this->getV(),
'lang_code' => 'en',
],
'tl_schema' => [ // TL scheme files
@ -236,6 +299,13 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
'secret_chats' => [
'accept_chats' => true, // Should I accept secret chats? Can be true, false or on array of user ids from which to accept chats
],
'calls' => [
'accept_calls' => true, // Should I accept calls? Can be true, false or on array of user ids from which to accept calls
],
'threading' => [
'allow_threading' => false, // Should I use threading, if it is enabled?
'handler_workers' => 10 // How many workers should every message handler pool of each socket reader have
],
'pwr' => ['pwr' => false, 'db_token' => false, 'strict' => false],
];
$settings = array_replace_recursive($default_settings, $settings);
@ -247,15 +317,26 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
}
unset($settings['connection_settings']['all']);
}
switch ($settings['logger']['logger_level']) {
case 'ULTRA_VERBOSE': $settings['logger']['logger_level'] = 5; break;
case 'VERBOSE': $settings['logger']['logger_level'] = 4; break;
case 'NOTICE': $settings['logger']['logger_level'] = 3; break;
case 'WARNING': $settings['logger']['logger_level'] = 2; break;
case 'ERROR': $settings['logger']['logger_level'] = 1; break;
case 'FATAL ERROR': $settings['logger']['logger_level'] = 0; break;
}
$this->settings = $settings;
// Setup logger
$this->setup_logger();
$this->should_serialize = true;
}
public function setup_logger()
{
\danog\MadelineProto\Logger::constructor($this->settings['logger']['logger'], $this->settings['logger']['logger_param'], isset($this->datacenter->authorization['user']) ? (isset($this->datacenter->authorization['user']['username']) ? $this->datacenter->authorization['user']['username'] : $this->datacenter->authorization['user']['id']) : '', isset($this->settings['logger']['logger_level']) ? $this->settings['logger']['logger_level'] : Logger::VERBOSE);
\danog\MadelineProto\Logger::constructor($this->settings['logger']['logger'], $this->settings['logger']['logger_param'], isset($this->authorization['user']) ? (isset($this->authorization['user']['username']) ? $this->authorization['user']['username'] : $this->authorization['user']['id']) : '', isset($this->settings['logger']['logger_level']) ? $this->settings['logger']['logger_level'] : Logger::VERBOSE);
}
public function reset_session($de = true)
@ -264,67 +345,66 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
if ($de) {
\danog\MadelineProto\Logger::log(['Resetting session id and seq_no in DC '.$id.'...'], Logger::VERBOSE);
$socket->session_id = $this->random(8);
$socket->seq_no = 0;
$socket->session_in_seq_no = 0;
$socket->session_out_seq_no = 0;
}
$socket->incoming_messages = [];
$socket->outgoing_messages = [];
$socket->new_outgoing = [];
$socket->new_incoming = [];
$this->should_serialize = true;
}
}
// Switches to a new datacenter and if necessary creates authorization keys, binds them and writes client info
public function switch_dc($new_dc, $allow_nearest_dc_switch = false)
// Connects to all datacenters and if necessary creates authorization keys, binds them and writes client info
public function connect_to_all_dcs()
{
$old_dc = $this->datacenter->curdc;
\danog\MadelineProto\Logger::log(['Switching from DC '.$old_dc.' to DC '.$new_dc.'...'], Logger::NOTICE);
if (!isset($this->datacenter->sockets[$new_dc])) {
$this->datacenter->dc_connect($new_dc);
$this->init_authorization();
$this->get_config($this->write_client_info('help.getConfig'));
$this->get_nearest_dc($allow_nearest_dc_switch);
foreach ($old = $this->datacenter->get_dcs() as $new_dc) {
if (!isset($this->datacenter->sockets[$new_dc])) $this->datacenter->dc_connect($new_dc);
}
$this->datacenter->curdc = $new_dc;
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'])
) {
\danog\MadelineProto\Logger::log(['Copying authorization...'], Logger::VERBOSE);
$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;
if (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->method_call('auth.logOut');
}
$this->datacenter->authorization = $this->method_call('auth.importAuthorization', $exported_authorization);
$this->datacenter->authorized = true;
}
\danog\MadelineProto\Logger::log(['Done! Current DC is '.$this->datacenter->curdc], Logger::NOTICE);
$this->setup_threads();
$this->init_authorization();
if ($old !== $this->datacenter->get_dcs()) $this->connect_to_all_dcs();
}
// Creates authorization keys
public function init_authorization()
{
if ($this->datacenter->session_id === null) {
$this->datacenter->session_id = $this->random(8);
}
if ($this->datacenter->temp_auth_key === null || $this->datacenter->auth_key === null) {
if ($this->datacenter->auth_key === null) {
\danog\MadelineProto\Logger::log(['Generating permanent authorization key...'], Logger::NOTICE);
$this->datacenter->auth_key = $this->create_auth_key(-1);
foreach ($this->datacenter->sockets as $id => &$socket) {
if ($socket->session_id === null) {
$socket->session_id = $this->random(8);
$socket->session_in_seq_no = 0;
$socket->session_out_seq_no = 0;
$this->should_serialize = true;
}
\danog\MadelineProto\Logger::log(['Generating temporary authorization key...'], Logger::NOTICE);
$this->datacenter->temp_auth_key = $this->create_auth_key($this->settings['authorization']['default_temp_auth_key_expires_in']);
$this->bind_temp_auth_key($this->settings['authorization']['default_temp_auth_key_expires_in']);
if (in_array($this->datacenter->protocol, ['http', 'https'])) {
$this->method_call('http_wait', ['max_wait' => 0, 'wait_after' => 0, 'max_delay' => 0]);
if ($socket->temp_auth_key === null || $socket->auth_key === null) {
if ($socket->auth_key === null) {
\danog\MadelineProto\Logger::log(['Generating permanent authorization key for DC '.$id.'...'], Logger::NOTICE);
$socket->auth_key = $this->create_auth_key(-1, $id);
}
\danog\MadelineProto\Logger::log(['Generating temporary authorization key for DC '.$id.'...'], Logger::NOTICE);
$socket->temp_auth_key = $this->create_auth_key($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
$this->bind_temp_auth_key($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
$this->get_config($this->write_client_info('help.getConfig', [], ['datacenter' => $id]));
if (in_array($socket->protocol, ['http', 'https'])) {
$this->method_call('http_wait', ['max_wait' => 0, 'wait_after' => 0, 'max_delay' => 0], ['datacenter' => $id]);
}
$this->should_serialize = true;
}
}
}
public function sync_authorization($authorized_dc) {
foreach ($this->datacenter->sockets as $new_dc => &$socket) {
if ($new_dc === $authorized_dc) continue;
\danog\MadelineProto\Logger::log(['Copying authorization from dc '.$authorized_dc.' to dc '.$new_dc.'...'], Logger::VERBOSE);
$this->should_serialize = true;
$exported_authorization = $this->method_call('auth.exportAuthorization', ['dc_id' => $new_dc], ['datacenter' => $authorized_dc]);
$this->method_call('auth.logOut', [], ['datacenter' => $new_dc]);
$this->method_call('auth.importAuthorization', $exported_authorization, ['datacenter' => $new_dc]);
}
}
public function write_client_info($method, $arguments = [])
public function write_client_info($method, $arguments = [], $options = [])
{
\danog\MadelineProto\Logger::log(['Writing client info (also executing '.$method.')...'], Logger::NOTICE);
@ -338,28 +418,17 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
['query' => $this->serialize_method($method, $arguments)]
)
),
]
],
$options
);
}
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'].'.'], Logger::NOTICE);
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($config = [])
public function get_config($config = [], $options = [])
{
if ($this->config['expires'] > time()) {
return;
}
$this->config = empty($config) ? $this->method_call('help.getConfig') : $config;
$this->config = empty($config) ? $this->method_call('help.getConfig', $config, $options) : $config;
$this->should_serialize = true;
$this->parse_config();
}
@ -380,15 +449,16 @@ Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB
$test .= (isset($this->settings['connection'][$test][$ipv6][$id]) && $this->settings['connection'][$test][$ipv6][$id]['ip_address'] != $dc['ip_address']) ? '_bk' : '';
$this->settings['connection'][$test][$ipv6][$id] = $dc;
}
$this->should_serialize = true;
}
public function getV()
{
return 2;
return 4;
}
public function get_self()
{
return $this->datacenter->authorization['user'];
return $this->authorization['user'];
}
}

View File

@ -17,31 +17,32 @@ namespace danog\MadelineProto\MTProtoTools;
*/
trait AckHandler
{
public function ack_outgoing_message_id($message_id)
public function ack_outgoing_message_id($message_id, $datacenter)
{
// The server acknowledges that it received my message
if (!isset($this->datacenter->outgoing_messages[$message_id])) {
if (!isset($this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id])) {
\danog\MadelineProto\Logger::log(["WARNING: Couldn't find message id ".$message_id.' in the array of outgoing messages. Maybe try to increase its size?'], \danog\MadelineProto\Logger::WARNING);
var_dump($message_id);
var_dump(debug_backtrace()[0]['file'], debug_backtrace()[0]['line']);
return false;
}
return $this->datacenter->outgoing_messages[$message_id]['ack'] = true;
return $this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['ack'] = true;
}
public function ack_incoming_message_id($message_id)
public function ack_incoming_message_id($message_id, $datacenter)
{
// I let the server know that I received its message
if (!isset($this->datacenter->incoming_messages[$message_id])) {
if (!isset($this->datacenter->sockets[$datacenter]->incoming_messages[$message_id])) {
\danog\MadelineProto\Logger::log(["WARNING: Couldn't find message id ".$message_id.' in the array of incomgoing messages. Maybe try to increase its size?'], \danog\MadelineProto\Logger::WARNING);
//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'] === str_repeat(chr(0), 8) || (isset($this->datacenter->incoming_messages[$message_id]['ack']) && $this->datacenter->incoming_messages[$message_id]['ack'])) {
if ($this->datacenter->sockets[$datacenter]->temp_auth_key['id'] === null || $this->datacenter->sockets[$datacenter]->temp_auth_key['id'] === str_repeat(chr(0), 8) || (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$message_id]['ack']) && $this->datacenter->sockets[$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]], ['datacenter' => $datacenter]);
return $this->datacenter->incoming_messages[$message_id]['ack'] = true;
return $this->datacenter->sockets[$datacenter]->incoming_messages[$message_id]['ack'] = true;
}
}

View File

@ -22,7 +22,7 @@ trait AuthKeyHandler
{
public $dh_config = ['version' => 0];
public function create_auth_key($expires_in = -1)
public function create_auth_key($expires_in, $datacenter)
{
for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) {
try {
@ -49,7 +49,8 @@ trait AuthKeyHandler
$ResPQ = $this->method_call('req_pq',
[
'nonce' => $nonce,
]
],
['datacenter' => $datacenter]
);
/*
@ -67,16 +68,9 @@ trait AuthKeyHandler
if (!isset($this->key->keydata['fp'])) {
$this->key = new \danog\MadelineProto\RSA($this->settings['authorization']['rsa_key']);
}
foreach ($ResPQ['server_public_key_fingerprints'] as $curfp) {
if ($this->key->keydata['fp'] === $curfp) {
$public_key_fingerprint = $curfp;
break;
}
}
if (in_array($this->key->keydata['fp'], $ResPQ['server_public_key_fingerprints'])) throw new \danog\MadelineProto\SecurityException("Couldn't find our key in the server_public_key_fingerprints vector.");
if (!isset($public_key_fingerprint)) {
throw new \danog\MadelineProto\SecurityException("Couldn't find our key in the server_public_key_fingerprints vector.");
}
$pq_bytes = $ResPQ['pq'];
$server_nonce = $ResPQ['server_nonce'];
@ -85,14 +79,14 @@ trait AuthKeyHandler
* ***********************************************************************
* Compute p and q
*/
$pq = \danog\PHP\Struct::unpack('>Q', $pq_bytes)[0];
$p = \danog\PrimeModule::auto_single($pq);
$q = $pq / $p;
if ($p > $q) {
$pq = new \phpseclib\Math\BigInteger($pq_bytes, 256);
$p = new \phpseclib\Math\BigInteger(\danog\PrimeModule::auto_single($pq->__toString()));
$q = $pq->divide($p)[0];
if ($p->compare($q) > 0) {
list($p, $q) = [$q, $p];
}
if ($pq !== $p * $q) {
if (!$pq->equals($p->multiply($q))) {
throw new \danog\MadelineProto\SecurityException("couldn't compute p and q.");
}
@ -102,8 +96,8 @@ trait AuthKeyHandler
* ***********************************************************************
* Serialize object for req_DH_params
*/
$p_bytes = \danog\PHP\Struct::pack('>I', $p);
$q_bytes = \danog\PHP\Struct::pack('>I', $q);
$p_bytes = $p->toBytes();
$q_bytes = $q->toBytes();
$new_nonce = $this->random(32);
@ -154,9 +148,11 @@ trait AuthKeyHandler
'server_nonce' => $server_nonce,
'p' => $p_bytes,
'q' => $q_bytes,
'public_key_fingerprint' => $public_key_fingerprint,
'public_key_fingerprint' => $this->key->keydata['fp'],
'encrypted_data' => $encrypted_data,
]
],
['datacenter' => $datacenter]
);
/*
@ -213,7 +209,7 @@ trait AuthKeyHandler
* int $server_time
* ]
*/
$server_DH_inner_data = $this->deserialize($answer);
$server_DH_inner_data = $this->deserialize($answer, ['type' => '']);
/*
* ***********************************************************************
@ -241,9 +237,9 @@ trait AuthKeyHandler
* Time delta
*/
$server_time = $server_DH_inner_data['server_time'];
$this->datacenter->time_delta = $server_time - time();
$this->datacenter->sockets[$datacenter]->time_delta = $server_time - time();
\danog\MadelineProto\Logger::log([sprintf('Server-client time delta = %.1f s', $this->datacenter->time_delta)], \danog\MadelineProto\Logger::VERBOSE);
\danog\MadelineProto\Logger::log([sprintf('Server-client time delta = %.1f s', $this->datacenter->sockets[$datacenter]->time_delta)], \danog\MadelineProto\Logger::VERBOSE);
$this->check_p_g($dh_prime, $g);
$this->check_G($g_a, $dh_prime);
@ -324,7 +320,9 @@ trait AuthKeyHandler
'nonce' => $nonce,
'server_nonce' => $server_nonce,
'encrypted_data' => $encrypted_data,
]
],
['datacenter' => $datacenter]
);
/*
@ -368,7 +366,7 @@ trait AuthKeyHandler
\danog\MadelineProto\Logger::log(['Diffie Hellman key exchange processed successfully!'], \danog\MadelineProto\Logger::VERBOSE);
$res_authorization['server_salt'] = \danog\PHP\Struct::unpack('<q', substr($new_nonce, 0, 8 - 0) ^ substr($server_nonce, 0, 8 - 0))[0];
$res_authorization['server_salt'] = substr($new_nonce, 0, 8 - 0) ^ substr($server_nonce, 0, 8 - 0);
$res_authorization['auth_key'] = $auth_key_str;
$res_authorization['id'] = substr($auth_key_sha, -8);
@ -407,8 +405,8 @@ trait AuthKeyHandler
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log(['An RPCErrorException occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...'], \danog\MadelineProto\Logger::WARNING);
} finally {
$this->datacenter->new_outgoing = [];
$this->datacenter->new_incoming = [];
$this->datacenter->sockets[$datacenter]->new_outgoing = [];
$this->datacenter->sockets[$datacenter]->new_incoming = [];
}
}
@ -497,7 +495,7 @@ trait AuthKeyHandler
public function get_dh_config()
{
$this->getting_state = true;
$dh_config = $this->method_call('messages.getDhConfig', ['version' => $this->dh_config['version'], 'random_length' => 0]);
$dh_config = $this->method_call('messages.getDhConfig', ['version' => $this->dh_config['version'], 'random_length' => 0], ['datacenter' => $this->datacenter->curdc]);
$this->getting_state = false;
if ($dh_config['_'] === 'messages.dhConfigNotModified') {
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Logger::VERBOSE, ['DH configuration not modified']);
@ -511,13 +509,11 @@ trait AuthKeyHandler
return $this->dh_config = $dh_config;
}
private $temp_requested_secret_chats = [];
private $secret_chats = [];
private $temp_requested_calls = [];
private $calls = [];
public function accept_secret_chat($params)
public function accept_call($params)
{
$dh_config = $this->get_dh_config();
\danog\MadelineProto\Logger::log(['Generating b...'], \danog\MadelineProto\Logger::VERBOSE);
@ -525,34 +521,15 @@ trait AuthKeyHandler
$params['g_a'] = new \phpseclib\Math\BigInteger($params['g_a'], 256);
$this->check_G($params['g_a'], $dh_config['p']);
$key = ['auth_key' => str_pad($params['g_a']->powMod($b, $dh_config['p'])->toBytes(), 256, chr(0), \STR_PAD_LEFT)];
$key['fingerprint'] = \danog\PHP\Struct::unpack('<q', substr(sha1($key['auth_key'], true), -8))[0];
$key['fingerprint'] = substr(sha1($key['auth_key'], true), -8);
$key['visualization_orig'] = substr(sha1($key['auth_key'], true), 16);
$key['visualization_46'] = substr(hash('sha256', $key['auth_key'], true), 20);
$this->secret_chats[$params['id']] = ['key' => $key, 'admin' => false, 'user_id' => $params['admin_id'], 'InputEncryptedChat' => ['_' => 'inputEncryptedChat', 'chat_id' => $params['id'], 'access_hash' => $params['access_hash']], 'in_seq_no_x' => 1, 'out_seq_no_x' => 0, 'layer' => 8, 'ttl' => PHP_INT_MAX, 'ttr' => 100, 'updated' => time(), 'incoming' => [], 'outgoing' => [], 'created' => time(), 'rekeying' => [0]];
$this->secret_chats[$params['id']] = ['key' => $key, 'admin' => false, 'user_id' => $params['admin_id'], 'InputPhoneCall' => ['id' => $params['id'], 'access_hash' => $params['access_hash'], '_' => 'inputPhoneCall'], 'in_seq_no_x' => 0, 'out_seq_no_x' => 1, 'layer' => 65, 'ttr' => 100, 'updated' => time(), 'incoming' => [], 'outgoing' => [], 'created' => time(), 'rekeying' => [0], 'protocol' => $params['protocol'], 'connection' => $params['connection'], 'alternative_connections' => $params['alternative_connections']];
//$this->secret_chats[$params['id']] = ['key' => $key, 'admin' => false, 'user_id' => $params['admin_id'], 'InputEncryptedChat' => ['_' => 'inputEncryptedChat', 'chat_id' => $params['id'], 'access_hash' => $params['access_hash']], 'in_seq_no_x' => 1, 'out_seq_no_x' => 0, 'layer' => 8, 'ttl' => PHP_INT_MAX, 'ttr' => 100, 'updated' => time(), 'incoming' => [], 'outgoing' => [], 'created' => time(), 'rekeying' => [0]];
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
$this->check_G($g_b, $dh_config['p']);
$this->notify_layer($params['id']);
$this->handle_pending_updates();
}
public function request_secret_chat($user)
{
$user = $this->get_info($user)['InputUser'];
\danog\MadelineProto\Logger::log(['Creating secret chat with '.$user['user_id'].'...'], \danog\MadelineProto\Logger::VERBOSE);
$dh_config = $this->get_dh_config();
\danog\MadelineProto\Logger::log(['Generating a...'], \danog\MadelineProto\Logger::VERBOSE);
$a = new \phpseclib\Math\BigInteger($this->random(256), 256);
\danog\MadelineProto\Logger::log(['Generating g_a...'], \danog\MadelineProto\Logger::VERBOSE);
$g_a = $dh_config['g']->powMod($a, $dh_config['p']);
$this->check_G($g_a, $dh_config['p']);
$res = $this->method_call('messages.requestEncryption', ['user_id' => $user, 'g_a' => $g_a->toBytes()]);
$this->temp_requested_secret_chats[$res['id']] = $a;
$this->handle_pending_updates();
$this->get_updates_difference();
return $res['id'];
}
public function request_call($user)
{
$user = $this->get_info($user)['InputUser'];
@ -563,8 +540,8 @@ trait AuthKeyHandler
\danog\MadelineProto\Logger::log(['Generating g_a...'], \danog\MadelineProto\Logger::VERBOSE);
$g_a = $dh_config['g']->powMod($a, $dh_config['p']);
$this->check_G($g_a, $dh_config['p']);
// $res = $this->method_call('phone.requestCall', ['user_id' => $user, 'g_a' => $g_a->toBytes(), 'protocol' => ['_' => 'phoneCallProtocol', 'min_layer' => $this->settings['tl_schema']['layer'], 'max_layer' => $this->settings['tl_schema']['layer']]]);
$res = $this->method_call('phone.requestCall', ['user_id' => $user, 'g_a' => $g_a->toBytes(), 'protocol' => ['_' => 'phoneCallProtocol', 'min_layer' => 65, 'max_layer' => 65, 'udp_reflector' => true]]);
// $res = $this->method_call('phone.requestCall', ['user_id' => $user, 'g_a' => $g_a->toBytes(), 'protocol' => ['_' => 'phoneCallProtocol', 'min_layer' => $this->settings['tl_schema']['layer'], 'max_layer' => $this->settings['tl_schema']['layer']]], ['datacenter' => $this->datacenter->curdc]);
$res = $this->method_call('phone.requestCall', ['user_id' => $user, 'g_a' => $g_a->toBytes(), 'protocol' => ['_' => 'phoneCallProtocol', 'min_layer' => 65, 'max_layer' => 65, 'udp_reflector' => true]], ['datacenter' => $this->datacenter->curdc]);
$this->temp_requested_calls[$res['phone_call']['id']] = $a;
$this->handle_pending_updates();
$this->get_updates_difference();
@ -572,9 +549,11 @@ trait AuthKeyHandler
return $res['phone_call']['id'];
}
public function complete_secret_chat($params)
public function complete_call($params)
{
if ($this->secret_chat_status($params['id']) !== 1) {
if ($this->call_status($params['id']) !== 1) {
\danog\MadelineProto\Logger::log(['Could not find and complete secret chat '.$params['id']]);
return false;
@ -583,133 +562,24 @@ trait AuthKeyHandler
$params['g_a_or_b'] = new \phpseclib\Math\BigInteger($params['g_a_or_b'], 256);
$this->check_G($params['g_a_or_b'], $dh_config['p']);
$key = ['auth_key' => str_pad($params['g_a_or_b']->powMod($this->temp_requested_secret_chats[$params['id']], $dh_config['p'])->toBytes(), 256, chr(0), \STR_PAD_LEFT)];
unset($this->temp_requested_secret_chats[$params['id']]);
$key['fingerprint'] = \danog\PHP\Struct::unpack('<q', substr(sha1($key['auth_key'], true), -8))[0];
unset($this->temp_requested_calls[$params['id']]);
$key['fingerprint'] = substr(sha1($key['auth_key'], true), -8);
if ($key['fingerprint'] !== $params['key_fingerprint']) {
$this->method_call('messages.discardEncryption', ['chat_id' => $params['id']]);
throw new \danog\MadelineProto\SecurityException('Invalid key fingerprint!');
}
$key['visualization_orig'] = substr(sha1($key['auth_key'], true), 16);
$key['visualization_46'] = substr(hash('sha256', $key['auth_key'], true), 20);
$this->secret_chats[$params['id']] = ['key' => $key, 'admin' => true, 'user_id' => $params['participant_id'], 'InputEncryptedChat' => ['chat_id' => $params['id'], 'access_hash' => $params['access_hash'], '_' => 'inputEncryptedChat'], 'in_seq_no_x' => 0, 'out_seq_no_x' => 1, 'layer' => 8, 'ttl' => PHP_INT_MAX, 'ttr' => 100, 'updated' => time(), 'incoming' => [], 'outgoing' => [], 'created' => time(), 'rekeying' => [0]];
$this->notify_layer($params['id']);
$this->secret_chats[$params['id']] = ['key' => $key, 'admin' => true, 'user_id' => $params['participant_id'], 'InputPhoneCall' => ['id' => $params['id'], 'access_hash' => $params['access_hash'], '_' => 'inputPhoneCall'], 'in_seq_no_x' => 0, 'out_seq_no_x' => 1, 'layer' => 65, 'ttr' => 100, 'updated' => time(), 'incoming' => [], 'outgoing' => [], 'created' => time(), 'rekeying' => [0], 'protocol' => $params['protocol'], 'connection' => $params['connection'], 'alternative_connections' => $params['alternative_connections']];
$this->handle_pending_updates();
}
public function notify_layer($chat)
public function call_status($id)
{
$this->method_call('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionNotifyLayer', 'layer' => $this->encrypted_layer]]]);
}
private $temp_rekeyed_secret_chats = [];
public function rekey($chat)
{
if ($this->secret_chats[$chat]['rekeying'][0] !== 0) {
return;
}
\danog\MadelineProto\Logger::log(['Rekeying secret chat '.$chat.'...'], \danog\MadelineProto\Logger::VERBOSE);
$dh_config = $this->get_dh_config();
\danog\MadelineProto\Logger::log(['Generating a...'], \danog\MadelineProto\Logger::VERBOSE);
$a = new \phpseclib\Math\BigInteger($this->random(256), 256);
\danog\MadelineProto\Logger::log(['Generating g_a...'], \danog\MadelineProto\Logger::VERBOSE);
$g_a = $dh_config['g']->powMod($a, $dh_config['p']);
$this->check_G($g_a, $dh_config['p']);
$e = \danog\PHP\Struct::unpack('<q', $this->random(8))[0];
$this->method_call('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionRequestKey', 'g_a' => $g_a->toBytes(), 'exchange_id' => $e]]]);
$this->temp_rekeyed_secret_chats[$e] = $a;
$this->secret_chats[$chat]['rekeying'] = [1, $e];
$this->handle_pending_updates();
$this->get_updates_difference();
return $e;
}
public function accept_rekey($chat, $params)
{
if ($this->secret_chats[$chat]['rekeying'][0] !== 0) {
$my = $this->temp_rekeyed_secret_chats[$this->secret_chats[$chat]['rekeying'][1]];
if ($my['exchange_id'] > $params['exchange_id']) {
return;
}
if ($my['exchange_id'] === $params['exchange_id']) {
$this->secret_chats[$chat]['rekeying'] = [0];
$this->rekey($chat);
return;
}
}
\danog\MadelineProto\Logger::log(['Accepting rekeying of secret chat '.$chat.'...'], \danog\MadelineProto\Logger::VERBOSE);
$dh_config = $this->get_dh_config();
\danog\MadelineProto\Logger::log(['Generating b...'], \danog\MadelineProto\Logger::VERBOSE);
$b = new \phpseclib\Math\BigInteger($this->random(256), 256);
$params['g_a'] = new \phpseclib\Math\BigInteger($params['g_a'], 256);
$this->check_G($params['g_a'], $dh_config['p']);
$key = ['auth_key' => str_pad($params['g_a']->powMod($b, $dh_config['p'])->toBytes(), 256, chr(0), \STR_PAD_LEFT)];
$key['fingerprint'] = \danog\PHP\Struct::unpack('<q', substr(sha1($key['auth_key'], true), -8))[0];
$key['visualization_orig'] = $this->secret_chats[$chat]['key']['visualization_orig'];
$key['visualization_46'] = substr(hash('sha256', $key['auth_key'], true), 20);
$this->temp_rekeyed_secret_chats[$params['exchange_id']] = $key;
$this->secret_chats[$chat]['rekeying'] = [2, $params['exchange_id']];
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
$this->check_G($g_b, $dh_config['p']);
$this->method_call('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAcceptKey', 'g_b' => $g_b->toBytes(), 'exchange_id' => $params['exchange_id'], 'key_fingerprint' => $key['fingerprint']]]]);
$this->handle_pending_updates();
$this->get_updates_difference();
}
public function commit_rekey($chat, $params)
{
if ($this->secret_chats[$chat]['rekeying'][0] !== 1) {
return;
}
\danog\MadelineProto\Logger::log(['Committing rekeying of secret chat '.$chat.'...'], \danog\MadelineProto\Logger::VERBOSE);
$dh_config = $this->get_dh_config();
$params['g_b'] = new \phpseclib\Math\BigInteger($params['g_b'], 256);
$this->check_G($params['g_b'], $dh_config['p']);
$key = ['auth_key' => str_pad($params['g_b']->powMod($this->temp_rekeyed_secret_chats[$params['exchange_id']], $dh_config['p'])->toBytes(), 256, chr(0), \STR_PAD_LEFT)];
$key['fingerprint'] = \danog\PHP\Struct::unpack('<q', substr(sha1($key['auth_key'], true), -8))[0];
$key['visualization_orig'] = $this->secret_chats[$chat]['key']['visualization_orig'];
$key['visualization_46'] = substr(hash('sha256', $key['auth_key'], true), 20);
if ($key['fingerprint'] !== $params['key_fingerprint']) {
$this->method_call('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAbortKey', 'exchange_id' => $params['exchange_id']]]]);
throw new \danog\MadelineProto\SecurityException('Invalid key fingerprint!');
}
$this->method_call('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionCommitKey', 'exchange_id' => $params['exchange_id'], 'key_fingerprint' => $key['fingerprint']]]]);
unset($this->temp_rekeyed_secret_chats[$chat]);
$this->secret_chats[$chat]['rekeying'] = [0];
$this->secret_chats[$chat]['key'] = $key;
$this->secret_chats[$chat]['ttr'] = 100;
$this->secret_chats[$chat]['updated'] = time();
$this->handle_pending_updates();
$this->get_updates_difference();
}
public function complete_rekey($chat, $params)
{
if ($this->secret_chats[$chat]['rekeying'][0] !== 2) {
return;
}
if ($this->temp_rekeyed_secret_chats['fingerprint'] !== $params['key_fingerprint']) {
$this->method_call('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAbortKey', 'exchange_id' => $params['exchange_id']]]]);
throw new \danog\MadelineProto\SecurityException('Invalid key fingerprint!');
}
\danog\MadelineProto\Logger::log(['Completing rekeying of secret chat '.$chat.'...'], \danog\MadelineProto\Logger::VERBOSE);
$this->secret_chats[$chat]['rekeying'] = [0];
$this->secret_chats[$chat]['key'] = $this->temp_rekeyed_secret_chats;
$this->secret_chats[$chat]['ttr'] = 100;
$this->secret_chats[$chat]['updated'] = time();
unset($this->temp_rekeyed_secret_chats[$params['exchange_id']]);
$this->method_call('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionNoop']]]);
}
public function secret_chat_status($chat)
{
if (isset($this->secret_chats[$chat])) {
if (isset($this->calls[$id])) {
return 2;
}
if (isset($this->temp_requested_secret_chats[$chat])) {
if (isset($this->temp_requested_calls[$id])) {
return 1;
}
@ -721,16 +591,16 @@ trait AuthKeyHandler
return $this->secret_chats[$chat];
}
public function bind_temp_auth_key($expires_in)
public function bind_temp_auth_key($expires_in, $datacenter)
{
for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) {
try {
\danog\MadelineProto\Logger::log(['Binding authorization keys...'], \danog\MadelineProto\Logger::VERBOSE);
$nonce = \danog\PHP\Struct::unpack('<q', $this->random(8))[0];
$nonce = $this->random(8);
$expires_at = time() + $expires_in;
$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];
$temp_auth_key_id = $this->datacenter->sockets[$datacenter]->temp_auth_key['id'];
$perm_auth_key_id = $this->datacenter->sockets[$datacenter]->auth_key['id'];
$temp_session_id = $this->datacenter->sockets[$datacenter]->session_id;
$message_data = $this->serialize_object(['type' => 'bind_auth_key_inner'],
[
'nonce' => $nonce,
@ -740,16 +610,15 @@ trait AuthKeyHandler
'expires_at' => $expires_at,
]
);
$int_message_id = $this->generate_message_id();
$message_id = $this->generate_message_id($datacenter);
$message_id = \danog\PHP\Struct::pack('<Q', $int_message_id);
$seq_no = 0;
$encrypted_data = $this->random(16).$message_id.\danog\PHP\Struct::pack('<II', $seq_no, strlen($message_data)).$message_data;
$message_key = substr(sha1($encrypted_data, true), -16);
$padding = $this->random($this->posmod(-strlen($encrypted_data), 16));
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->auth_key['auth_key']);
$encrypted_message = $this->datacenter->auth_key['id'].$message_key.$this->ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv);
$res = $this->method_call('auth.bindTempAuthKey', ['perm_auth_key_id' => $perm_auth_key_id, 'nonce' => $nonce, 'expires_at' => $expires_at, 'encrypted_message' => $encrypted_message], ['message_id' => $int_message_id]);
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->sockets[$datacenter]->auth_key['auth_key']);
$encrypted_message = $this->datacenter->sockets[$datacenter]->auth_key['id'].$message_key.$this->ige_encrypt($encrypted_data.$padding, $aes_key, $aes_iv);
$res = $this->method_call('auth.bindTempAuthKey', ['perm_auth_key_id' => $perm_auth_key_id, 'nonce' => $nonce, 'expires_at' => $expires_at, 'encrypted_message' => $encrypted_message], ['message_id' => $message_id, 'datacenter' => $datacenter]);
if ($res === true) {
\danog\MadelineProto\Logger::log(['Successfully binded temporary and permanent authorization keys.'], \danog\MadelineProto\Logger::NOTICE);
@ -762,8 +631,8 @@ trait AuthKeyHandler
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log(['An RPCErrorException occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...'], \danog\MadelineProto\Logger::WARNING);
} finally {
$this->datacenter->new_outgoing = [];
$this->datacenter->new_incoming = [];
$this->datacenter->sockets[$datacenter]->new_outgoing = [];
$this->datacenter->sockets[$datacenter]->new_incoming = [];
}
}
throw new \danog\MadelineProto\SecurityException('An error occurred while binding temporary and permanent authorization keys.');

View File

@ -25,9 +25,11 @@ trait CallHandler
if (!is_array($aargs)) {
throw new \danog\MadelineProto\Exception("Additonal arguments aren't an array.");
}
if (!isset($aargs['datacenter'])) throw new \danog\MadelineProto\Exception("No datacenter provided");
$args = $this->botAPI_to_MTProto($args);
$serialized = $this->serialize_method($method, $args);
$content_related = $this->content_related($method);
$type = $this->methods->find_by_method($method)['type'];
for ($count = 1; $count <= $this->settings['max_tries']['query']; $count++) {
try {
\danog\MadelineProto\Logger::log(['Calling method (try number '.$count.' for '.$method.')...'], \danog\MadelineProto\Logger::VERBOSE);
@ -36,93 +38,73 @@ trait CallHandler
if ($method === 'http_wait') {
return true;
}
$this->datacenter->outgoing_messages[$int_message_id]['content'] = ['method' => $method, 'args' => $args];
$this->datacenter->new_outgoing[$int_message_id] = ['msg_id' => $int_message_id, 'method' => $method, 'type' => $this->methods->find_by_method($method)['type']];
$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$int_message_id]['content'] = ['method' => $method, 'args' => $args];
$this->datacenter->sockets[$aargs['datacenter']]->new_outgoing[$int_message_id] = ['msg_id' => $int_message_id, 'method' => $method, 'type' => $type];
$res_count = 0;
$server_answer = null;
$update_count = 0;
while ($server_answer === null && $res_count++ < $this->settings['max_tries']['response']) { // Loop until we get a response, loop for a max of $this->settings['max_tries']['response'] times
$only_updates = false;
while ($server_answer === null && $res_count++ < $this->settings['max_tries']['response']+1) { // Loop until we get a response, loop for a max of $this->settings['max_tries']['response'] times
try {
\danog\MadelineProto\Logger::log(['Getting response (try number '.$res_count.' for '.$method.')...'], \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->recv_message(); // This method receives data from the socket, and parses stuff
if (!isset($this->datacenter->outgoing_messages[$int_message_id]['response']) || !isset($this->datacenter->incoming_messages[$this->datacenter->outgoing_messages[$int_message_id]['response']]['content'])) { // Checks if I have received the response to the called method, if not continue looping
if ($this->only_updates) {
$this->start_threads();
if (!isset($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$int_message_id]['response']) || !isset($this->datacenter->sockets[$aargs['datacenter']]->incoming_messages[$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$int_message_id]['response']]['content'])) { // Checks if I have received the response to the called method, if not continue looping
if ($only_updates) {
if ($update_count > 50) {
$update_count = 0;
continue;
} else {
$res_count--;
$update_count++;
}
$res_count--;
$update_count++;
}
continue;
}
$server_answer = $this->datacenter->incoming_messages[$this->datacenter->outgoing_messages[$int_message_id]['response']]['content']; // continue was not called, so I got a response
if (isset($aargs['heavy']) && $aargs['heavy']) {
$this->datacenter->incoming_messages[$this->datacenter->outgoing_messages[$int_message_id]['response']]['content'] = [];
} else {
$server_answer = $this->datacenter->sockets[$aargs['datacenter']]->incoming_messages[$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$int_message_id]['response']]['content'];
$this->datacenter->sockets[$aargs['datacenter']]->incoming_messages[$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$int_message_id]['response']]['content'] = [];
break;
}
//if (!$this->threads && !$this->run_workers) {
$this->recv_message($aargs['datacenter']); // This method receives data from the socket, and parses stuff
$only_updates = $this->handle_messages($aargs['datacenter']); // This method receives data from the socket, and parses stuff
//}
} catch (\danog\MadelineProto\Exception $e) {
if ($e->getMessage() === 'I had to recreate the temporary authorization key') {
continue 2;
}
\danog\MadelineProto\Logger::log(['An error getting response of method '.$method.': '.$e->getMessage().' in '.basename($e->getFile(), '.php').' on line '.$e->getLine().'. Retrying...'], \danog\MadelineProto\Logger::WARNING);
continue;
} catch (\danog\MadelineProto\NothingInTheSocketException $e) {
\danog\MadelineProto\Logger::log(['An error getting response of method '.$method.': '.$e->getMessage().' in '.basename($e->getFile(), '.php').' on line '.$e->getLine().'. Retrying...'], \danog\MadelineProto\Logger::WARNING);
continue;
}
}
if ($server_answer === null) {
throw new \danog\MadelineProto\Exception("Couldn't get response");
}
if (!isset($server_answer['_'])) {
\danog\MadelineProto\Logger::log(['Response does not have a type!', $server_answer], \danog\MadelineProto\Logger::FATAL_ERROR);
}
switch ($server_answer['_']) {
case 'rpc_error':
switch ($server_answer['error_code']) {
case 303:
$dc = preg_replace('/[^0-9]+/', '', $server_answer['error_message']);
\danog\MadelineProto\Logger::log(['Received request to switch to DC '.$dc], \danog\MadelineProto\Logger::NOTICE);
$this->switch_dc($dc);
continue 3;
case 401:
switch ($server_answer['error_message']) {
case 'AUTH_KEY_UNREGISTERED':
case 'AUTH_KEY_INVALID':
case 'USER_DEACTIVATED':
case 'SESSION_REVOKED':
case 'SESSION_EXPIRED':
unset($this->datacenter->temp_auth_key);
unset($this->datacenter->auth_key);
$this->datacenter->authorized = false;
$this->datacenter->authorization = null;
throw new \danog\MadelineProto\RPCErrorException($server_answer['error_message'], $server_answer['error_code']);
break;
}
case 420:
$seconds = preg_replace('/[^0-9]+/', '', $server_answer['error_message']);
if (is_numeric($seconds) && isset($this->settings['flood_timeout']['wait_if_lt']) && $seconds < $this->settings['flood_timeout']['wait_if_lt']) {
\danog\MadelineProto\Logger::log(['Flood, waiting '.$seconds.' seconds...'], \danog\MadelineProto\Logger::NOTICE);
sleep($seconds);
throw new \danog\MadelineProto\Exception('Re-executing query...');
}
default:
throw new \danog\MadelineProto\RPCErrorException($server_answer['error_message'], $server_answer['error_code']);
break;
}
$this->handle_rpc_error($server_answer, $aargs['datacenter']);
break;
case 'bad_server_salt':
case 'bad_msg_notification':
switch ($server_answer['error_code']) {
case 48:
$this->datacenter->temp_auth_key['server_salt'] = $server_answer['new_server_salt'];
$this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key['server_salt'] = $server_answer['new_server_salt'];
continue 3;
case 16:
case 17:
\danog\MadelineProto\Logger::log(['Received bad_msg_notification: '.$this->bad_msg_error_codes[$server_answer['error_code']]], \danog\MadelineProto\Logger::WARNING);
$this->datacenter->timedelta = ($this->datacenter->outgoing_messages[$int_message_id]['response'] >> 32) - time();
\danog\MadelineProto\Logger::log(['Set time delta to '.$this->datacenter->timedelta], \danog\MadelineProto\Logger::WARNING);
$this->datacenter->sockets[$aargs['datacenter']]->timedelta = (int)((new \phpseclib\Math\BigInteger(strrev($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$int_message_id]['response']), 256))->bitwise_rightShift(32)->subtract(new \phpseclib\Math\BigInteger(time()))->toString());
\danog\MadelineProto\Logger::log(['Set time delta to '.$this->datacenter->sockets[$aargs['datacenter']]->timedelta], \danog\MadelineProto\Logger::WARNING);
$this->reset_session();
$this->datacenter->temp_auth_key = null;
$this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key = null;
$this->init_authorization();
continue 3;
}
var_dump($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages);
throw new \danog\MadelineProto\RPCErrorException('Received bad_msg_notification: '.$this->bad_msg_error_codes[$server_answer['error_code']], $server_answer['error_code']);
break;
case 'boolTrue':
@ -136,22 +118,25 @@ trait CallHandler
} catch (\danog\MadelineProto\Exception $e) {
$last_error = $e->getMessage().' in '.basename($e->getFile(), '.php').' on line '.$e->getLine();
\danog\MadelineProto\Logger::log(['An error occurred while calling method '.$method.': '.$last_error.'. Recreating connection and retrying to call method...'], \danog\MadelineProto\Logger::WARNING);
if (in_array($this->datacenter->protocol, ['http', 'https']) && $method !== 'http_wait') {
//$this->method_call('http_wait', ['max_wait' => $this->datacenter->timeout, 'wait_after' => 0, 'max_delay' => 0]);
if (in_array($this->datacenter->sockets[$aargs['datacenter']]->protocol, ['http', 'https']) && $method !== 'http_wait') {
//$this->method_call('http_wait', ['max_wait' => $this->datacenter->sockets[$aargs['datacenter']]->timeout, 'wait_after' => 0, 'max_delay' => 0], ['datacenter' => $aargs['datacenter']]);
} else {
$this->datacenter->close_and_reopen();
$this->datacenter->sockets[$aargs['datacenter']]->close_and_reopen();
}
//sleep(1); // To avoid flooding
continue;
} finally {
if (isset($aargs['heavy']) && $aargs['heavy'] && isset($int_message_id)) {
$this->datacenter->outgoing_messages[$int_message_id]['args'] = [];
if (((isset($aargs['heavy']) && $aargs['heavy']) || $method === 'req_pq') && isset($int_message_id)) {
//$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$int_message_id]['args'] = [];
$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$int_message_id] = [];
unset($this->datacenter->sockets[$aargs['datacenter']]->new_outgoing[$int_message_id]);
}
}
if ($server_answer === null) {
throw new \danog\MadelineProto\Exception('An error occurred while calling method '.$method.' ('.$last_error.').');
}
\danog\MadelineProto\Logger::log(['Got response for method '.$method.' @ try '.$count.' (response try '.$res_count.')'], \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$int_message_id] = [];
return $server_answer;
}
@ -164,15 +149,16 @@ trait CallHandler
if (!is_array($args)) {
throw new \danog\MadelineProto\Exception("Arguments aren't an array.");
}
if (!isset($aargs['datacenter'])) throw new \danog\MadelineProto\Exception("No datacenter provided");
for ($count = 1; $count <= $this->settings['max_tries']['query']; $count++) {
try {
\danog\MadelineProto\Logger::log([$object === 'msgs_ack' ? 'ack '.$args['msg_ids'][0] : 'Sending object (try number '.$count.' for '.$object.')...'], \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$int_message_id = $this->send_message($this->serialize_object(['type' => $object], $args), $this->content_related($object));
$this->datacenter->outgoing_messages[$int_message_id]['content'] = ['method' => $object, 'args' => $args];
$int_message_id = $this->send_message($this->serialize_object(['type' => $object], $args), $this->content_related($object), $aargs);
if ($object !== 'msgs_ack') $this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$int_message_id]['content'] = ['method' => $object, 'args' => $args];
} 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::WARNING);
$this->datacenter->close_and_reopen();
$this->datacenter->sockets[$aargs['datacenter']]->close_and_reopen();
continue;
}

View File

@ -17,7 +17,7 @@ namespace danog\MadelineProto\MTProtoTools;
*/
trait Files
{
public function upload($file, $file_name = '', $cb = null, $encrypted = false)
public function upload($file, $file_name = '', $cb = null, $encrypted = false, $datacenter = null)
{
if (!file_exists($file)) {
throw new \danog\MadelineProto\Exception('Given file does not exist!');
@ -25,6 +25,7 @@ trait Files
if (empty($file_name)) {
$file_name = basename($file);
}
$datacenter = is_null($datacenter) ? $this->datacenter->curdc : $datacenter;
$file_size = filesize($file);
if ($file_size > 1500 * 1024 * 1024) {
throw new \danog\MadelineProto\Exception('Given file is too big!');
@ -39,7 +40,7 @@ trait Files
$part_num = 0;
$method = $file_size > 10 * 1024 * 1024 ? 'upload.saveBigFilePart' : 'upload.saveFilePart';
$constructor = 'input'.($encrypted === true ? 'Encrypted' : '').($file_size > 10 * 1024 * 1024 ? 'FileBig' : 'File').($encrypted === true ? 'Uploaded' : '');
$file_id = \danog\PHP\Struct::unpack('<q', $this->random(8))[0];
$file_id = $this->random(8);
$f = fopen($file, 'r');
fseek($f, 0);
if ($encrypted === true) {
@ -57,7 +58,7 @@ trait Files
if ($encrypted === true) {
$bytes = $ige->encrypt(str_pad($bytes, $part_size, chr(0)));
}
if (!$this->method_call($method, ['file_id' => $file_id, 'file_part' => $part_num++, 'file_total_parts' => $part_total_num, 'bytes' => $bytes], ['heavy' => true])) {
if (!$this->method_call($method, ['file_id' => $file_id, 'file_part' => $part_num++, 'file_total_parts' => $part_total_num, 'bytes' => $bytes], ['heavy' => true, 'datacenter' => $datacenter])) {
throw new \danog\MadelineProto\Exception('An error occurred while uploading file part '.$part_num);
}
$cb(ftell($f) * 100 / $file_size);
@ -246,9 +247,7 @@ trait Files
$size = $end - $offset;
$part_size = 512 * 1024;
$percent = 0;
if (isset($info['InputFileLocation']['dc_id'])) {
$this->switch_dc($info['InputFileLocation']['dc_id']);
}
$datacenter = isset($info['InputFileLocation']['dc_id']) ? $info['InputFileLocation']['dc_id'] : $this->datacenter->curdc;
if (isset($info['key'])) {
$digest = hash('md5', $info['key'].$info['iv'], true);
$fingerprint = \danog\PHP\Struct::unpack('<i', substr($digest, 0, 4) ^ substr($digest, 4, 4))[0];
@ -264,7 +263,7 @@ trait Files
while (true) {
//$real_part_size = (($offset + $part_size > $end) && $end !== -1) ? $part_size - (($offset + $part_size) - $end) : $part_size;
try {
$res = $this->method_call('upload.getFile', ['location' => $info['InputFileLocation'], 'offset' => $offset, 'limit' => $part_size], ['heavy' => true]);
$res = $this->method_call('upload.getFile', ['location' => $info['InputFileLocation'], 'offset' => $offset, 'limit' => $part_size], ['heavy' => true, 'datacenter' => $datacenter]);
} catch (\danog\MadelineProto\RPCErrorException $e) {
if ($e->getMessage() === 'OFFSET_INVALID') {
break;
@ -274,10 +273,9 @@ trait Files
}
while ($res['type']['_'] === 'storage.fileUnknown' && $res['bytes'] === '') {
$dc = 1;
$this->switch_dc($dc);
$res = $this->method_call('upload.getFile', ['location' => $info['InputFileLocation'], 'offset' => $offset, 'limit' => $real_part_size], ['heavy' => true]);
$dc++;
$datacenter = 1;
$res = $this->method_call('upload.getFile', ['location' => $info['InputFileLocation'], 'offset' => $offset, 'limit' => $real_part_size], ['heavy' => true, 'datacenter' => $datacenter]);
$datacenter++;
}
if ($res['bytes'] === '') {
break;
@ -285,7 +283,7 @@ trait Files
if (isset($info['key'])) {
$res['bytes'] = $ige->decrypt($res['bytes']);
}
if ($end !== -1 && strlen($res['bytes']) + $downloaded_size > $size) {
if ($end !== -1 && strlen($res['bytes']) + $downloaded_size >= $size) {
$res['bytes'] = substr($res['bytes'], 0, (strlen($res['bytes']) + $downloaded_size) - $size);
$theend = true;
}

View File

@ -24,47 +24,44 @@ trait MessageHandler
public function send_message($message_data, $content_related, $aargs = [])
{
if (!isset($aargs['message_id']) || $aargs['message_id'] === null) {
$int_message_id = $this->generate_message_id();
$message_id = $this->generate_message_id($aargs['datacenter']);
} else {
$int_message_id = $aargs['message_id'];
$message_id = $aargs['message_id'];
}
if (!is_int($int_message_id)) {
throw new \danog\MadelineProto\Exception("Specified message id isn't an integer");
if (!is_string($message_id)) {
throw new \danog\MadelineProto\Exception("Specified message id isn't a string");
}
$message_id = \danog\PHP\Struct::pack('<Q', $int_message_id);
if ($this->datacenter->temp_auth_key['auth_key'] === null || $this->datacenter->temp_auth_key['server_salt'] === null) {
if ($this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key['auth_key'] === null || $this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key['server_salt'] === null) {
$message = str_repeat(chr(0), 8).$message_id.\danog\PHP\Struct::pack('<I', strlen($message_data)).$message_data;
} else {
$seq_no = $this->generate_seq_no($content_related);
$data2enc = \danog\PHP\Struct::pack('<q', $this->datacenter->temp_auth_key['server_salt']).$this->datacenter->session_id.$message_id.\danog\PHP\Struct::pack('<II', $seq_no, strlen($message_data)).$message_data;
$seq_no = $this->generate_out_seq_no($aargs['datacenter'], $content_related);
$data2enc = $this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key['server_salt'].$this->datacenter->sockets[$aargs['datacenter']]->session_id.$message_id.\danog\PHP\Struct::pack('<II', $seq_no, strlen($message_data)).$message_data;
$padding = $this->random($this->posmod(-strlen($data2enc), 16));
$message_key = substr(sha1($data2enc, true), -16);
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($data2enc.$padding, $aes_key, $aes_iv);
$this->datacenter->outgoing_messages[$int_message_id]['seq_no'] = $seq_no;
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key['auth_key']);
$message = $this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key['id'].$message_key.$this->ige_encrypt($data2enc.$padding, $aes_key, $aes_iv);
$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$message_id]['seq_no'] = $seq_no;
}
$this->datacenter->outgoing_messages[$int_message_id]['response'] = -1;
$this->datacenter->send_message($message);
$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$message_id]['response'] = -1;
$this->datacenter->sockets[$aargs['datacenter']]->send_message($message);
return $int_message_id;
return $message_id;
}
/**
* Reading connection and receiving message from server.
*/
public function recv_message()
public function recv_message($datacenter)
{
$payload = $this->datacenter->read_message();
$payload = $this->datacenter->sockets[$datacenter]->read_message();
if (strlen($payload) === 4) {
$error = \danog\PHP\Struct::unpack('<i', $payload)[0];
if ($error === -404) {
if ($this->datacenter->temp_auth_key != null) {
if ($this->datacenter->sockets[$datacenter]->temp_auth_key != null) {
\danog\MadelineProto\Logger::log(['WARNING: Resetting auth key...'], \danog\MadelineProto\Logger::WARNING);
$this->datacenter->temp_auth_key = null;
$this->datacenter->sockets[$datacenter]->temp_auth_key = null;
$this->init_authorization();
$this->config = $this->write_client_info('help.getConfig');
$this->parse_config();
throw new \danog\MadelineProto\Exception('I had to recreate the temporary authorization key');
}
}
@ -72,27 +69,28 @@ trait MessageHandler
}
$auth_key_id = substr($payload, 0, 8);
if ($auth_key_id === str_repeat(chr(0), 8)) {
list($message_id, $message_length) = \danog\PHP\Struct::unpack('<QI', substr($payload, 8, 12));
$this->check_message_id($message_id, false);
$message_id = substr($payload, 8, 8);
$this->check_message_id($message_id, ['outgoing' => false, 'datacenter' => $datacenter, 'container' => false]);
$message_length = \danog\PHP\Struct::unpack('<I', substr($payload, 16, 4))[0];
$message_data = substr($payload, 20, $message_length);
} elseif ($auth_key_id === $this->datacenter->temp_auth_key['id']) {
} elseif ($auth_key_id === $this->datacenter->sockets[$datacenter]->temp_auth_key['id']) {
$message_key = substr($payload, 8, 16);
$encrypted_data = substr($payload, 24);
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->temp_auth_key['auth_key'], 'from server');
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->sockets[$datacenter]->temp_auth_key['auth_key'], 'from server');
$decrypted_data = $this->ige_decrypt($encrypted_data, $aes_key, $aes_iv);
$server_salt = \danog\PHP\Struct::unpack('<q', substr($decrypted_data, 0, 8))[0];
if ($server_salt != $this->datacenter->temp_auth_key['server_salt']) {
//\danog\MadelineProto\Logger::log(['WARNING: Server salt mismatch (my server salt '.$this->datacenter->temp_auth_key['server_salt'].' is not equal to server server salt '.$server_salt.').'], \danog\MadelineProto\Logger::WARNING);
/*
$server_salt = substr($decrypted_data, 0, 8);
if ($server_salt != $this->datacenter->sockets[$datacenter]->temp_auth_key['server_salt']) {
\danog\MadelineProto\Logger::log(['WARNING: Server salt mismatch (my server salt '.$this->datacenter->sockets[$datacenter]->temp_auth_key['server_salt'].' is not equal to server server salt '.$server_salt.').'], \danog\MadelineProto\Logger::WARNING);
}
*/
$session_id = substr($decrypted_data, 8, 8);
if ($session_id != $this->datacenter->session_id) {
if ($session_id != $this->datacenter->sockets[$datacenter]->session_id) {
throw new \danog\MadelineProto\Exception('Session id mismatch.');
}
$message_id = \danog\PHP\Struct::unpack('<Q', substr($decrypted_data, 16, 8))[0];
$this->check_message_id($message_id, false);
$message_id = substr($decrypted_data, 16, 8);
$this->check_message_id($message_id, ['outgoing' => false, 'datacenter' => $datacenter, 'container' => false]);
$seq_no = \danog\PHP\Struct::unpack('<I', substr($decrypted_data, 24, 4))[0];
// Dunno how to handle any incorrect sequence numbers
@ -119,81 +117,13 @@ trait MessageHandler
if ($message_key != substr(sha1(substr($decrypted_data, 0, 32 + $message_data_length), true), -16)) {
throw new \danog\MadelineProto\SecurityException('msg_key mismatch');
}
$this->datacenter->incoming_messages[$message_id]['seq_no'] = $seq_no;
$this->datacenter->sockets[$datacenter]->incoming_messages[$message_id]['seq_no'] = $seq_no;
} else {
throw new \danog\MadelineProto\SecurityException('Got unknown auth_key id');
}
$deserialized = $this->deserialize($message_data);
$this->datacenter->incoming_messages[$message_id]['content'] = $deserialized;
$this->datacenter->incoming_messages[$message_id]['response'] = -1;
$this->datacenter->new_incoming[$message_id] = $message_id;
$this->handle_messages();
}
public function encrypt_secret_message($chat_id, $message)
{
if (!isset($this->secret_chats[$chat_id])) {
\danog\MadelineProto\Logger::log('I do not have the secret chat '.$chat_id.' in the database, skipping message...');
return false;
}
$message = $this->serialize_object(['type' => $message['_']], $message, $this->secret_chats[$chat_id]['layer']);
$this->secret_chats[$chat_id]['outgoing'][] = $message;
$this->secret_chats[$chat_id]['ttr']--;
if (($this->secret_chats[$chat_id]['ttr'] <= 0 || time() - $this->secret_chats[$chat_id]['updated'] > 7 * 24 * 60 * 60) && $this->secret_chats[$chat_id]['rekeying'] === 0) {
$this->rekey($chat_id);
}
$message = \danog\PHP\Struct::pack('<I', strlen($message)).$message;
$message_key = substr(sha1($message, true), -16);
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->secret_chats[$chat_id]['key']['auth_key'], 'to server');
$padding = $this->random($this->posmod(-strlen($message), 16));
$message = $this->secret_chats[$chat_id]['key']['fingerprint'].$message_key.$this->ige_encrypt($message.$padding, $aes_key, $aes_iv);
return $message;
}
public function handle_encrypted_update($message)
{
if (!isset($this->secret_chats[$message['message']['chat_id']])) {
\danog\MadelineProto\Logger::log('I do not have the secret chat '.$message['message']['chat_id'].' in the database, skipping message...');
return false;
}
$auth_key_id = \danog\PHP\Struct::unpack('<q', substr($message['message']['bytes'], 0, 8))[0];
if ($auth_key_id !== $this->secret_chats[$message['message']['chat_id']]['key']['fingerprint']) {
throw new \danog\MadelineProto\SecurityException('Key fingerprint mismatch');
}
$message_key = substr($message['message']['bytes'], 8, 16);
$encrypted_data = substr($message['message']['bytes'], 24);
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->secret_chats[$message['message']['chat_id']]['key']['auth_key'], 'to server');
$decrypted_data = $this->ige_decrypt($encrypted_data, $aes_key, $aes_iv);
$message_data_length = \danog\PHP\Struct::unpack('<I', substr($decrypted_data, 0, 4))[0];
if ($message_data_length > strlen($decrypted_data)) {
throw new \danog\MadelineProto\SecurityException('message_data_length is too big');
}
if ((strlen($decrypted_data) - 4) - $message_data_length > 15) {
throw new \danog\MadelineProto\SecurityException('difference between message_data_length and the length of the remaining decrypted buffer is too big');
}
if ($message_data_length % 4 != 0) {
throw new \danog\MadelineProto\SecurityException('message_data_length not divisible by 4');
}
$message_data = substr($decrypted_data, 4, $message_data_length);
if ($message_key != substr(sha1(substr($decrypted_data, 0, 4 + $message_data_length), true), -16)) {
throw new \danog\MadelineProto\SecurityException('msg_key mismatch');
}
$deserialized = $this->deserialize($message_data);
if (strlen($deserialized['random_bytes']) < 15) {
throw new \danog\MadelineProto\SecurityException('random_bytes is too short');
}
$this->secret_chats[$message['message']['chat_id']]['ttr']--;
if (($this->secret_chats[$message['message']['chat_id']]['ttr'] <= 0 || time() - $this->secret_chats[$message['message']['chat_id']]['updated'] > 7 * 24 * 60 * 60) && $this->secret_chats[$message['message']['chat_id']]['rekeying'] === 0) {
$this->rekey($message['message']['chat_id']);
}
unset($message['message']['bytes']);
$message['message']['decrypted_message'] = $deserialized;
$this->handle_decrypted_update($message);
$deserialized = $this->deserialize($message_data, ['type' => '']);
$this->datacenter->sockets[$datacenter]->incoming_messages[$message_id]['content'] = $deserialized;
$this->datacenter->sockets[$datacenter]->incoming_messages[$message_id]['response'] = -1;
$this->datacenter->sockets[$datacenter]->new_incoming[$message_id] = $message_id;
}
}

View File

@ -17,61 +17,60 @@ namespace danog\MadelineProto\MTProtoTools;
*/
trait MsgIdHandler
{
public function check_message_id($new_message_id, $outgoing, $container = false)
public function check_message_id($new_message_id, $aargs)
{
$min_message_id = ((int) ((time() + $this->datacenter->time_delta - 300) << 32));
if ($min_message_id > $new_message_id) {
if (!is_object($new_message_id)) {
$new_message_id = new \phpseclib\Math\BigInteger(strrev($new_message_id), 256);
}
$min_message_id = (new \phpseclib\Math\BigInteger(time() + $this->datacenter->sockets[$aargs['datacenter']]->time_delta - 300))->bitwise_leftShift(32);
if ($min_message_id->compare($new_message_id) > 0) {
\danog\MadelineProto\Logger::log(['Given message id ('.$new_message_id.') is too old compared to the min value ('.$min_message_id.').'], \danog\MadelineProto\Logger::WARNING);
}
/*
if (((int) ((time() + $this->datacenter->time_delta + 30) << 32)) < $new_message_id) {
if (((int) ((time() + $this->datacenter->sockets[$datacenter]->time_delta + 30) << 32)) < $new_message_id) {
throw new \danog\MadelineProto\Exception('Given message id ('.$new_message_id.') is too new.');
}
*/
if ($outgoing) {
if ($new_message_id % 4 != 0) {
if ($aargs['outgoing']) {
if (!$new_message_id->divide($this->four)[1]->equals($this->zero)) {
throw new \danog\MadelineProto\Exception('Given message id ('.$new_message_id.') is not divisible by 4.');
}
$keys = array_keys($this->datacenter->outgoing_messages);
asort($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);
$keys = array_keys($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages);
$key = $this->get_max_id($aargs['datacenter'], false);
if ($new_message_id->compare($key) <= 0) {
throw new \danog\MadelineProto\Exception('Given message id ('.$new_message_id.') is lower than or equal than the current limit ('.$key.').', 1);
}
if (count($this->datacenter->outgoing_messages) > $this->settings['msg_array_limit']['outgoing']) {
reset($this->datacenter->outgoing_messages);
unset($this->datacenter->outgoing_messages[key($this->datacenter->outgoing_messages)]);
if (count($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages) > $this->settings['msg_array_limit']['outgoing']) {
reset($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages);
unset($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[key($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages)]);
}
$this->datacenter->outgoing_messages[$new_message_id] = [];
$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[strrev($new_message_id->toBytes())] = [];
} else {
if ($new_message_id % 4 != 1 && $new_message_id % 4 != 3) {
if (!$new_message_id->divide($this->four)[1]->equals($this->one) && !$new_message_id->divide($this->four)[1]->equals($this->three)) {
throw new \danog\MadelineProto\Exception('message id mod 4 != 1 or 3');
}
$keys = array_keys($this->datacenter->incoming_messages);
if ($container) {
asort($keys);
if ($new_message_id >= end($keys)) {
\danog\MadelineProto\Logger::log(['WARNING: Given message id ('.$new_message_id.') is bigger than or equal than the current limit ('.end($keys).').'], \danog\MadelineProto\Logger::WARNING);
$key = $this->get_max_id($aargs['datacenter'], true);
if ($aargs['container']) {
if ($new_message_id->compare($key) >= 0) {
\danog\MadelineProto\Logger::log(['WARNING: Given message id ('.$new_message_id.') is bigger than or equal than the current limit ('.$key.').'], \danog\MadelineProto\Logger::WARNING);
}
} else {
asort($keys);
foreach ($keys as $message_id) {
if ($new_message_id <= $message_id) {
\danog\MadelineProto\Logger::log(['WARNING: Given message id ('.$new_message_id.') is lower than or equal than the current limit ('.$message_id.').'], \danog\MadelineProto\Logger::WARNING);
}
if ($new_message_id->compare($key) <= 0) {
\danog\MadelineProto\Logger::log(['WARNING: Given message id ('.$new_message_id.') is lower than or equal than the current limit ('.$message_id.').'], \danog\MadelineProto\Logger::WARNING);
}
}
if (count($this->datacenter->incoming_messages) > $this->settings['msg_array_limit']['incoming']) {
reset($this->datacenter->incoming_messages);
unset($this->datacenter->incoming_messages[key($this->datacenter->incoming_messages)]);
if (count($this->datacenter->sockets[$aargs['datacenter']]->incoming_messages) > $this->settings['msg_array_limit']['incoming']) {
reset($this->datacenter->sockets[$aargs['datacenter']]->incoming_messages);
unset($this->datacenter->sockets[$aargs['datacenter']]->incoming_messages[key($this->datacenter->sockets[$aargs['datacenter']]->incoming_messages)]);
}
$this->datacenter->incoming_messages[$new_message_id] = [];
ksort($this->datacenter->incoming_messages);
$this->datacenter->sockets[$aargs['datacenter']]->incoming_messages[strrev($new_message_id->toBytes())] = [];
}
}
public function generate_message_id()
public function generate_message_id($datacenter)
{
$int_message_id = (int) ((time() + $this->datacenter->time_delta) << 32);
$message_id = (new \phpseclib\Math\BigInteger(time() + $this->datacenter->sockets[$datacenter]->time_delta))->bitwise_leftShift(32);
/*
$int_message_id = (int) (
((int) ($ms_time / 1000) << 32) |
@ -79,15 +78,18 @@ trait MsgIdHandler
rand(0, 524288) << 2
);
*/
$keys = array_keys($this->datacenter->outgoing_messages);
asort($keys);
$keys = end($keys);
if ($int_message_id <= $keys) {
$int_message_id = $keys + 4;
$key = $this->get_max_id($datacenter, false);
if ($message_id->compare($key) <= 0) {
$message_id = $key->add($this->four);
}
$this->check_message_id($int_message_id, true);
$this->check_message_id($message_id, ['outgoing' => true, 'datacenter' => $datacenter, 'container' => false]);
return $int_message_id;
return strrev($message_id->toBytes());
}
public function get_max_id($datacenter, $incoming) {
$keys = array_keys($this->datacenter->sockets[$datacenter]->{$incoming ? 'incoming_messages' : 'outgoing_messages'});
if (empty($keys)) return $this->zero;
array_walk($keys, function (&$value, $key) { $value = is_integer($value) ? new \phpseclib\Math\BigInteger($value) : new \phpseclib\Math\BigInteger(strrev($value), 256); });
return \phpseclib\Math\BigInteger::max(...$keys);
}
}

View File

@ -138,7 +138,7 @@ trait PeerHandler
switch ($id['_']) {
case 'inputUserSelf':
case 'inputPeerSelf':
$id = $this->datacenter->authorization['user']['id'];
$id = $this->authorization['user']['id'];
break;
case 'user':
$id = $id['id'];
@ -197,7 +197,7 @@ trait PeerHandler
return $this->gen_all($this->chats[$id]);
}
if ($id < 0 && !preg_match('/^-100/', $id)) {
$this->method_call('messages.getFullChat', ['chat_id' => -$id]);
$this->method_call('messages.getFullChat', ['chat_id' => -$id], ['datacenter' => $this->datacenter->curdc]);
if (isset($this->chats[$id])) {
return $this->gen_all($this->chats[$id]);
}
@ -280,16 +280,16 @@ trait PeerHandler
switch ($partial['type']) {
case 'user':
case 'bot':
$full = $this->method_call('users.getFullUser', ['id' => $partial['InputUser']]);
$full = $this->method_call('users.getFullUser', ['id' => $partial['InputUser']], ['datacenter' => $this->datacenter->curdc]);
break;
case 'chat':
$full = $this->method_call('messages.getFullChat', $partial)['full_chat'];
$full = $this->method_call('messages.getFullChat', $partial, ['datacenter' => $this->datacenter->curdc])['full_chat'];
break;
case 'channel':
case 'supergroup':
$full = $this->method_call('channels.getFullChannel', ['channel' => $partial['InputChannel']])['full_chat'];
$full = $this->method_call('channels.getFullChannel', ['channel' => $partial['InputChannel']], ['datacenter' => $this->datacenter->curdc])['full_chat'];
break;
}
$partial['full'] = $full;
@ -403,7 +403,7 @@ trait PeerHandler
$res['participants'] = [];
$limit = 400;
$offset = -$limit;
$gres = $this->method_call('channels.getParticipants', ['channel' => $full['InputChannel'], 'filter' => ['_' => 'channelParticipantsRecent'], 'offset' => $offset += $limit, 'limit' => 200]);
$gres = $this->method_call('channels.getParticipants', ['channel' => $full['InputChannel'], 'filter' => ['_' => 'channelParticipantsRecent'], 'offset' => $offset += $limit, 'limit' => 200], ['datacenter' => $this->datacenter->curdc]);
$count = $gres['count'];
$key = -1;
while ($offset <= $count) {
@ -441,7 +441,7 @@ trait PeerHandler
}
$res['participants'][$key] = $newres;
}
$gres = $this->method_call('channels.getParticipants', ['channel' => $full['InputChannel'], 'filter' => ['_' => 'channelParticipantsRecent'], 'offset' => $offset += $limit, 'limit' => $limit]);
$gres = $this->method_call('channels.getParticipants', ['channel' => $full['InputChannel'], 'filter' => ['_' => 'channelParticipantsRecent'], 'offset' => $offset += $limit, 'limit' => $limit], ['datacenter' => $this->datacenter->curdc]);
if (empty($gres['participants'])) {
break;
}
@ -484,7 +484,7 @@ trait PeerHandler
$payload = json_encode($this->qres);
$path = '/tmp/ids'.hash('sha256', $payload);
file_put_contents($path, $payload);
$id = isset($this->datacenter->authorization['user']['username']) ? $this->datacenter->authorization['user']['username'] : $this->datacenter->authorization['user']['id'];
$id = isset($this->authorization['user']['username']) ? $this->authorization['user']['username'] : $this->authorization['user']['id'];
$result = shell_exec('curl '.escapeshellarg('https://id.pwrtelegram.xyz/db'.$this->settings['pwr']['db_token'].'/addnewmadeline?d=pls&from='.$id).' -d '.escapeshellarg('@'.$path).' -s -o '.escapeshellarg($path.'.log').' >/dev/null 2>/dev/null & ');
\danog\MadelineProto\Logger::log([$result], \danog\MadelineProto\Logger::VERBOSE);
} catch (\danog\MadelineProto\Exception $e) {
@ -496,7 +496,7 @@ trait PeerHandler
public function resolve_username($username)
{
$res = $this->method_call('contacts.resolveUsername', ['username' => str_replace('@', '', $username)]);
$res = $this->method_call('contacts.resolveUsername', ['username' => str_replace('@', '', $username)], ['datacenter' => $this->datacenter->curdc]);
if ($res['_'] === 'contacts.resolvedPeer') {
return $res;
}

View File

@ -43,129 +43,147 @@ trait ResponseHandler
128 => ' and other party knows for a fact that message is already received',
];
public function send_msgs_state_info($req_msg_id, $msg_ids)
public function send_msgs_state_info($req_msg_id, $msg_ids, $datacenter)
{
$info = '';
foreach ($msg_ids as $msg_id) {
$cur_info = 0;
if (!in_array($msg_id, $this->datacenter->incoming_messages)) {
if (((int) ((time() + $this->datacenter->time_delta + 30) << 32)) < $msg_id) {
if (!in_array($msg_id, $this->datacenter->sockets[$datacenter]->incoming_messages)) {
$msg_id = new \phpseclib\Math\BigInteger(strrev($msg_id), 256);
if ((new \phpseclib\Math\BigInteger(time() + $this->datacenter->sockets[$datacenter]->time_delta + 30))->bitwise_leftShift(32)->compare($msg_id) < 0) {
$cur_info |= 3;
} elseif (((int) ((time() + $this->datacenter->time_delta - 300) << 32)) > $msg_id) {
} elseif ((new \phpseclib\Math\BigInteger(time() + $this->datacenter->sockets[$datacenter]->time_delta - 300))->bitwise_leftShift(32)->compare($msg_id) > 0) {
$cur_info |= 1;
} else {
$cur_info |= 2;
}
} else {
$cur_info |= 4;
if ($this->datacenter->incoming_messages[$msg_id]['ack']) {
if ($this->datacenter->sockets[$datacenter]->incoming_messages[$msg_id]['ack']) {
$cur_info |= 8;
}
}
$info .= chr($cur_info);
}
$this->datacenter->outgoing_messages[$this->object_call('msgs_state_info', ['req_msg_id' => $req_msg_id, 'info' => $info])]['response'] = $req_msg_id;
$this->datacenter->sockets[$datacenter]->outgoing_messages[$this->object_call('msgs_state_info', ['req_msg_id' => $req_msg_id, 'info' => $info], ['datacenter' => $datacenter])]['response'] = $req_msg_id;
}
public function handle_messages()
public function handle_messages($datacenter)
{
foreach ($this->datacenter->new_incoming as $current_msg_id) {
$this->only_updates = false;
$response = $this->datacenter->incoming_messages[$current_msg_id]['content'];
\danog\MadelineProto\Logger::log(['Received '.$response['_'].'.'], \danog\MadelineProto\Logger::VERBOSE);
$only_updates = false;
$first = true;
foreach ($this->datacenter->sockets[$datacenter]->new_incoming as $current_msg_id) {
$last_was_updates = false;
$unset = false;
\danog\MadelineProto\Logger::log(['Received '.$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['_'].'.'], \danog\MadelineProto\Logger::VERBOSE);
switch ($response['_']) {
switch ($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['_']) {
case 'msgs_ack':
foreach ($response['msg_ids'] as $msg_id) {
$this->ack_outgoing_message_id($msg_id); // Acknowledge that the server received my message
foreach ($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['msg_ids'] as $msg_id) {
$this->ack_outgoing_message_id($msg_id, $datacenter); // Acknowledge that the server received my message
}
unset($this->datacenter->new_incoming[$current_msg_id]);
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
break;
case 'rpc_result':
$this->ack_incoming_message_id($current_msg_id); // Acknowledge that I received the server's response
$this->datacenter->incoming_messages[$current_msg_id]['content'] = $response['result'];
$this->ack_incoming_message_id($current_msg_id, $datacenter); // Acknowledge that I received the server's response
$this->ack_outgoing_message_id($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['req_msg_id'], $datacenter); // Acknowledge that the server received my request
$this->datacenter->sockets[$datacenter]->outgoing_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['req_msg_id']]['response'] = $current_msg_id;
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
unset($this->datacenter->sockets[$datacenter]->new_outgoing[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['req_msg_id']]);
$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content'] = $this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['result'];
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'] = $current_msg_id;
unset($this->datacenter->new_incoming[$current_msg_id]);
unset($this->datacenter->new_outgoing[$response['req_msg_id']]);
$this->ack_outgoing_message_id($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['req_msg_id'], $datacenter); // Acknowledge that the server received my request
$this->datacenter->sockets[$datacenter]->outgoing_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['req_msg_id']]['response'] = $current_msg_id;
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
unset($this->datacenter->sockets[$datacenter]->new_outgoing[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['req_msg_id']]);
break;
case 'rpc_error':
$this->handle_rpc_error($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content'], $datacenter);
break;
case 'bad_server_salt':
case 'bad_msg_notification':
$this->ack_outgoing_message_id($response['bad_msg_id']); // Acknowledge that the server received my request
$this->datacenter->outgoing_messages[$response['bad_msg_id']]['response'] = $current_msg_id;
unset($this->datacenter->new_incoming[$current_msg_id]);
unset($this->datacenter->new_outgoing[$response['bad_msg_id']]);
$this->ack_outgoing_message_id($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['bad_msg_id'], $datacenter); // Acknowledge that the server received my request
$this->datacenter->sockets[$datacenter]->outgoing_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['bad_msg_id']]['response'] = $current_msg_id;
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
unset($this->datacenter->sockets[$datacenter]->new_outgoing[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['bad_msg_id']]);
break;
case 'pong':
foreach ($this->datacenter->outgoing_messages as $msg_id => &$omessage) {
if (isset($omessage['content']['args']['ping_id']) && $omessage['content']['args']['ping_id'] === $response['ping_id']) {
$this->ack_outgoing_message_id($msg_id);
$omessage['response'] = $response['msg_id'];
$this->datacenter->incoming_messages[$response['msg_id']]['content'] = $response;
unset($this->datacenter->new_incoming[$current_msg_id]);
unset($this->datacenter->new_outgoing[$msg_id]);
foreach ($this->datacenter->sockets[$datacenter]->outgoing_messages as $msg_id => &$omessage) {
if (isset($omessage['content']['args']['ping_id']) && $omessage['content']['args']['ping_id'] === $this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['ping_id']) {
$this->ack_outgoing_message_id($msg_id, $datacenter);
$omessage['response'] = $this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['msg_id'];
$this->datacenter->sockets[$datacenter]->incoming_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['msg_id']]['content'] = $this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content'];
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
unset($this->datacenter->sockets[$datacenter]->new_outgoing[$msg_id]);
}
}
break;
case 'new_session_created':
$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
if ($this->datacenter->authorized) {
$this->datacenter->sockets[$datacenter]->temp_auth_key['server_salt'] = $this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['server_salt'];
$this->ack_incoming_message_id($current_msg_id, $datacenter); // Acknowledge that I received the server's response
if ($this->authorized) {
$this->force_get_updates_difference();
}
unset($this->datacenter->new_incoming[$current_msg_id]);
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
$unset = true;
break;
case 'msg_container':
unset($this->datacenter->new_incoming[$current_msg_id]);
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']];
$this->datacenter->new_incoming[$message['msg_id']] = $message['msg_id'];
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
foreach ($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['messages'] as $message) {
$this->check_message_id($message['msg_id'], ['outgoing' => false, 'datacenter' => $datacenter, 'container' => true]);
$this->datacenter->sockets[$datacenter]->incoming_messages[$message['msg_id']] = ['seq_no' => $message['seqno'], 'content' => $message['body']];
$this->datacenter->sockets[$datacenter]->new_incoming[$message['msg_id']] = $message['msg_id'];
$this->handle_messages();
$this->handle_messages($datacenter);
}
$unset = true;
break;
case 'msg_copy':
$this->ack_incoming_message_id($current_msg_id); // Acknowledge that I received the server's response
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($current_msg_id, $datacenter); // Acknowledge that I received the server's response
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['orig_message']['msg_id']])) {
$this->ack_incoming_message_id($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['orig_message']['msg_id'], $datacenter); // Acknowledge that I received the server's response
} else {
$this->check_message_id($message['orig_message']['msg_id'], false, true);
$this->datacenter->incoming_messages[$message['orig_message']['msg_id']] = ['content' => $response['orig_message']];
$this->datacenter->new_incoming[$message['orig_message']['msg_id']] = $message['orig_message']['msg_id'];
$this->check_message_id($message['orig_message']['msg_id'], ['outgoing' => false, 'datacenter' => $datacenter, 'container' => true]);
$this->datacenter->sockets[$datacenter]->incoming_messages[$message['orig_message']['msg_id']] = ['content' => $this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['orig_message']];
$this->datacenter->sockets[$datacenter]->new_incoming[$message['orig_message']['msg_id']] = $message['orig_message']['msg_id'];
$this->handle_messages();
$this->handle_messages($datacenter);
}
unset($this->datacenter->new_incoming[$current_msg_id]);
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
$unset = true;
break;
case 'http_wait':
\danog\MadelineProto\Logger::log([$response], \danog\MadelineProto\Logger::NOTICE);
unset($this->datacenter->new_incoming[$current_msg_id]);
\danog\MadelineProto\Logger::log([$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']], \danog\MadelineProto\Logger::NOTICE);
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
$unset = true;
break;
case 'msgs_state_info':
$this->datacenter->outgoing_messages[$response['req_msg_id']]['response'] = $current_msg_id;
unset($this->datacenter->new_incoming[$current_msg_id]);
unset($this->datacenter->new_outgoing[$response['req_msg_id']]);
$this->datacenter->sockets[$datacenter]->outgoing_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['req_msg_id']]['response'] = $current_msg_id;
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
unset($this->datacenter->sockets[$datacenter]->new_outgoing[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['req_msg_id']]);
$unset = true;
break;
case 'msgs_state_req':
unset($this->datacenter->new_incoming[$current_msg_id]);
$this->send_msgs_state_info($current_msg_id, $response['msg_ids']);
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
$this->send_msgs_state_info($current_msg_id, $this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['msg_ids'], $datacenter);
break;
case 'msgs_all_info':
unset($this->datacenter->new_incoming[$current_msg_id]);
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
foreach ($response['msg_ids'] as $key => $msg_id) {
foreach ($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['msg_ids'] as $key => $msg_id) {
$msg_id = new \phpseclib\Math\BigInteger(strrev($msg_id), 256);
$status = 'Status for message id '.$msg_id.': ';
if (($response['info'][$key] & 4) === 1) {
$this->ack_outgoing_message_id($msg_id);
if (($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['info'][$key] & 4) === 1) {
$this->ack_outgoing_message_id($msg_id, $datacenter);
}
foreach ($this->msgs_info_flags as $flag => $description) {
if (($response['info'][$key] & $flag) === 1) {
if (($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['info'][$key] & $flag) === 1) {
$status .= $description;
}
}
@ -173,94 +191,140 @@ trait ResponseHandler
}
break;
case 'msg_detailed_info':
if (isset($this->datacenter->outgoing_messages[$response['msg_id']])) {
if (isset($this->datacenter->incoming_messages[$response['answer_msg_id']])) {
$this->datacenter->outgoing_messages[$response['msg_id']]['response'] = $response['answer_msg_id'];
unset($this->datacenter->new_outgoing[$response['msg_id']]);
if (isset($this->datacenter->sockets[$datacenter]->outgoing_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['msg_id']])) {
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['answer_msg_id']])) {
$this->datacenter->sockets[$datacenter]->outgoing_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['msg_id']]['response'] = $this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['answer_msg_id'];
unset($this->datacenter->sockets[$datacenter]->new_outgoing[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['msg_id']]);
}
}
case 'msg_new_detailed_info':
if (isset($this->datacenter->incoming_messages[$response['answer_msg_id']])) {
$this->ack_incoming_message_id($response['answer_msg_id']);
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['answer_msg_id']])) {
$this->ack_incoming_message_id($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['answer_msg_id'], $datacenter);
}
unset($this->datacenter->new_incoming[$current_msg_id]);
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
break;
case 'msg_resend_req':
$ok = true;
unset($this->datacenter->new_incoming[$current_msg_id]);
foreach ($response['msg_ids'] as $msg_id) {
if (!isset($this->datacenter->outgoing_messages[$msg_id]) || isset($this->datacenter->incoming_messages[$msg_id])) {
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
foreach ($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['msg_ids'] as $msg_id) {
if (!isset($this->datacenter->sockets[$datacenter]->outgoing_messages[$msg_id]) || isset($this->datacenter->sockets[$datacenter]->incoming_messages[$msg_id])) {
$ok = false;
}
}
if ($ok) {
foreach ($response['msg_ids'] as $msg_id) {
$this->object_call($this->datacenter->outgoing_messages[$msg_id]['content']['method'], $this->datacenter->outgoing_messages[$msg_id]['content']['args']);
foreach ($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['msg_ids'] as $msg_id) {
$this->object_call($this->datacenter->sockets[$datacenter]->outgoing_messages[$msg_id]['content']['method'], $this->datacenter->sockets[$datacenter]->outgoing_messages[$msg_id]['content']['args'], ['datacenter' => $datacenter]);
}
} else {
$this->send_msgs_state_info($current_msg_id, $response['msg_ids']);
$this->send_msgs_state_info($current_msg_id, $this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['msg_ids'], $datacenter);
}
break;
case 'msg_resend_ans_req':
unset($this->datacenter->new_incoming[$current_msg_id]);
$this->send_msgs_state_info($response['msg_ids']);
foreach ($response['msg_ids'] as $msg_id) {
if (isset($this->datacenter->incoming_messages[$msg_id]) && isset($this->datacenter->outgoing_messages[$this->datacenter->incoming_messages[$msg_id]['response']])) {
$this->object_call($this->datacenter->outgoing_messages[$this->datacenter->incoming_messages[$msg_id]['response']]['method'], $this->datacenter->outgoing_messages[$this->datacenter->incoming_messages[$msg_id]['response']]['args']);
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
$this->send_msgs_state_info($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['msg_ids'], $datacenter);
foreach ($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['msg_ids'] as $msg_id) {
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$msg_id]) && isset($this->datacenter->sockets[$datacenter]->outgoing_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$msg_id]['response']])) {
$this->object_call($this->datacenter->sockets[$datacenter]->outgoing_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$msg_id]['response']]['method'], $this->datacenter->sockets[$datacenter]->outgoing_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$msg_id]['response']]['args'], ['datacenter' => $datacenter]);
}
}
break;
default:
$this->ack_incoming_message_id($current_msg_id); // Acknowledge that I received the server's response
$response_type = $this->constructors->find_by_predicate($response['_'])['type'];
$this->ack_incoming_message_id($current_msg_id, $datacenter); // Acknowledge that I received the server's response
$response_type = $this->constructors->find_by_predicate($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['_'])['type'];
switch ($response_type) {
case 'Updates':
unset($this->datacenter->new_incoming[$current_msg_id]);
$this->handle_updates($response);
$this->only_updates = true;
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
$unset = true;
$this->handle_updates($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']);
$last_was_updates = true;
break;
default:
\danog\MadelineProto\Logger::log(['Trying to assign a response of type '.$response_type.' to its request...'], \danog\MadelineProto\Logger::VERBOSE);
foreach ($this->datacenter->new_outgoing as $key => $expecting) {
\danog\MadelineProto\Logger::log(['Does the request of return type '.$expecting['type'].' and msg_id '.$expecting['msg_id'].' match?'], \danog\MadelineProto\Logger::VERBOSE);
foreach ($this->datacenter->sockets[$datacenter]->new_outgoing as $key => $expecting) {
\danog\MadelineProto\Logger::log(['Does the request of return type '.$expecting['type'].' match?'], \danog\MadelineProto\Logger::VERBOSE);
if ($response_type === $expecting['type']) {
\danog\MadelineProto\Logger::log(['Yes'], \danog\MadelineProto\Logger::VERBOSE);
$this->datacenter->outgoing_messages[$expecting['msg_id']]['response'] = $current_msg_id;
unset($this->datacenter->new_outgoing[$key]);
unset($this->datacenter->new_incoming[$current_msg_id]);
$this->datacenter->sockets[$datacenter]->outgoing_messages[$expecting['msg_id']]['response'] = $current_msg_id;
unset($this->datacenter->sockets[$datacenter]->new_outgoing[$key]);
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
return;
break 2;
}
\danog\MadelineProto\Logger::log(['No'], \danog\MadelineProto\Logger::VERBOSE);
}
throw new \danog\MadelineProto\ResponseException('Dunno how to handle '.PHP_EOL.var_export($response, true));
throw new \danog\MadelineProto\ResponseException('Dunno how to handle '.PHP_EOL.var_export($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content'], true));
break;
}
break;
}
if (isset($response['users'])) {
$this->add_users($response['users']);
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['users'])) {
$this->add_users($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['users']);
}
if (isset($response['chats'])) {
$this->add_chats($response['chats']);
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['chats'])) {
$this->add_chats($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['chats']);
}
if (isset($response['result']['users'])) {
$this->add_users($response['result']['users']);
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['result']['users'])) {
$this->add_users($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['result']['users']);
}
if (isset($response['result']['chats'])) {
$this->add_chats($response['result']['chats']);
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['result']['chats'])) {
$this->add_chats($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['result']['chats']);
}
if (isset($response['result']['_'])) {
switch ($this->constructors->find_by_predicate($response['result']['_'])['type']) {
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['result']['_'])) {
switch ($this->constructors->find_by_predicate($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['result']['_'])['type']) {
case 'Update':
$this->handle_update($response['result']);
$this->handle_update($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['result']);
break;
}
}
$only_updates = ($only_updates || $first) && $last_was_updates;
$first = false;
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['seq_no']) && ($seq_no = $this->generate_in_seq_no($datacenter, $this->content_related($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['_']))) !== $this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['seq_no']) {
throw new \danog\MadelineProto\SecurityException('Seqno mismatch (should be '.$seq_no.', is '.$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['seq_no']);
}
if ($unset) {
unset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]);
}
}
return $only_updates;
}
public function handle_rpc_error($server_answer, &$datacenter) {
switch ($server_answer['error_code']) {
case 303:
$this->datacenter->curdc = $datacenter = preg_replace('/[^0-9]+/', '', $server_answer['error_message']);
throw new \danog\MadelineProto\Exception('Received request to switch to DC '.$this->datacenter->curdc);
case 401:
switch ($server_answer['error_message']) {
case 'USER_DEACTIVATED':
case 'SESSION_REVOKED':
case 'SESSION_EXPIRED':
$this->datacenter->sockets[$datacenter]->temp_auth_key = null;
$this->datacenter->sockets[$datacenter]->auth_key = null;
$this->authorized = false;
$this->authorization = null;
$this->init_authorization(); // idk
throw new \danog\MadelineProto\RPCErrorException($server_answer['error_message'], $server_answer['error_code']);
case 'AUTH_KEY_UNREGISTERED':
case 'AUTH_KEY_INVALID':
$this->datacenter->sockets[$datacenter]->temp_auth_key = null;
$this->init_authorization(); // idk
throw new \danog\MadelineProto\Exception($server_answer['error_message'], $server_answer['error_code']);
}
case 420:
$seconds = preg_replace('/[^0-9]+/', '', $server_answer['error_message']);
if (is_numeric($seconds) && isset($this->settings['flood_timeout']['wait_if_lt']) && $seconds < $this->settings['flood_timeout']['wait_if_lt']) {
\danog\MadelineProto\Logger::log(['Flood, waiting '.$seconds.' seconds...'], \danog\MadelineProto\Logger::NOTICE);
sleep($seconds);
throw new \danog\MadelineProto\Exception('Re-executing query...');
}
default:
throw new \danog\MadelineProto\RPCErrorException($server_answer['error_message'], $server_answer['error_code']);
break;
}
}
public function handle_pending_updates()
{
\danog\MadelineProto\Logger::log(['Parsing pending updates...'], \danog\MadelineProto\Logger::VERBOSE);
@ -277,7 +341,7 @@ trait ResponseHandler
\danog\MadelineProto\Logger::log(['Parsing updates received via the socket...'], \danog\MadelineProto\Logger::VERBOSE);
if ($this->getting_state) {
\danog\MadelineProto\Logger::log(['Getting state, handle later'], \danog\MadelineProto\Logger::VERBOSE);
$this->pending_updates[] = $updates;
$this->pending_updates []= $updates;
return false;
}
@ -299,10 +363,10 @@ trait ResponseHandler
break;
case 'updateShortMessage':
case 'updateShortChatMessage':
$from_id = isset($updates['from_id']) ? $updates['from_id'] : ($updates['out'] ? $this->datacenter->authorization['user']['id'] : $updates['user_id']);
$from_id = isset($updates['from_id']) ? $updates['from_id'] : ($updates['out'] ? $this->authorization['user']['id'] : $updates['user_id']);
$to_id = isset($updates['chat_id'])
? -$updates['chat_id']
: ($updates['out'] ? $updates['user_id'] : $this->datacenter->authorization['user']['id']);
: ($updates['out'] ? $updates['user_id'] : $this->authorization['user']['id']);
if (!$this->peer_isset($from_id) ||
!$this->peer_isset($to_id) ||

View File

@ -26,8 +26,8 @@ trait SaltHandler
public function add_salt($valid_since, $valid_until, $salt)
{
if (!isset($this->datacenter->temp_auth_key['salts'][$salt])) {
$this->datacenter->temp_auth_key['salts'][$salt] = ['valid_since' => $valid_since, 'valid_until' => $valid_until];
if (!isset($this->datacenter->sockets[$datacenter]->temp_auth_key['salts'][$salt])) {
$this->datacenter->sockets[$datacenter]->temp_auth_key['salts'][$salt] = ['valid_since' => $valid_since, 'valid_until' => $valid_until];
}
}
}

View File

@ -17,22 +17,58 @@ namespace danog\MadelineProto\MTProtoTools;
*/
trait SeqNoHandler
{
public function generate_seq_no($content_related = true)
public function generate_out_seq_no($datacenter, $content_related)
{
$in = $content_related ? 1 : 0;
$value = $this->datacenter->seq_no;
$this->datacenter->seq_no += $in;
$value = $this->datacenter->sockets[$datacenter]->session_out_seq_no;
$this->datacenter->sockets[$datacenter]->session_out_seq_no += $in;
return ($value * 2) + $in;
}
public function get_in_seq_no($chat)
public function generate_in_seq_no($datacenter, $content_related)
{
return count($this->secret_chats[$chat]['incoming']);
$in = $content_related ? 1 : 0;
$value = $this->datacenter->sockets[$datacenter]->session_in_seq_no;
$this->datacenter->sockets[$datacenter]->session_in_seq_no += $in;
return ($value * 2) + $in;
}
public function get_out_seq_no($chat)
public function content_related($method)
{
return count($this->secret_chats[$chat]['outgoing']);
return !in_array(
$method,
[
'rpc_result',
'rpc_error',
'rpc_drop_answer',
'rpc_answer_unknown',
'rpc_answer_dropped_running',
'rpc_answer_dropped',
'get_future_salts',
'future_salt',
'future_salts',
'ping',
'pong',
'ping_delay_disconnect',
'destroy_session',
'destroy_session_ok',
'destroy_session_none',
// 'new_session_created',
'msg_container',
'msg_copy',
'gzip_packed',
'http_wait',
'msgs_ack',
'bad_msg_notification',
'bad_server_salt',
'msgs_state_req',
'msgs_state_info',
'msgs_all_info',
'msg_detailed_info',
'msg_new_detailed_info',
'msg_resend_req',
'msg_resend_ans_req',
]
);
}
}

View File

@ -142,7 +142,7 @@ trait UpdateHandler
} catch (\danog\MadelineProto\RPCErrorException $e) {
return false;
}
$difference = $this->method_call('updates.getChannelDifference', ['channel' => $input, 'filter' => ['_' => 'channelMessagesFilterEmpty'], 'pts' => $this->get_channel_state($channel)['pts'], 'limit' => 30]);
$difference = $this->method_call('updates.getChannelDifference', ['channel' => $input, 'filter' => ['_' => 'channelMessagesFilterEmpty'], 'pts' => $this->get_channel_state($channel)['pts'], 'limit' => 30], ['datacenter' => $this->datacenter->curdc]);
\danog\MadelineProto\Logger::log(['Got '.$difference['_']], \danog\MadelineProto\Logger::VERBOSE);
$this->get_channel_state($channel)['sync_loading'] = false;
switch ($difference['_']) {
@ -219,7 +219,7 @@ trait UpdateHandler
$this->get_update_state()['pending_pts_updates'] = [];
$this->get_update_state()['pending_seq_updates'] = [];
}
$difference = $this->method_call('updates.getDifference', ['pts' => $this->get_update_state()['pts'], 'date' => $this->get_update_state()['date'], 'qts' => $this->get_update_state()['qts']]);
$difference = $this->method_call('updates.getDifference', ['pts' => $this->get_update_state()['pts'], 'date' => $this->get_update_state()['date'], 'qts' => $this->get_update_state()['qts']], ['datacenter' => $this->datacenter->curdc]);
\danog\MadelineProto\Logger::log(['Got '.$difference['_']], \danog\MadelineProto\Logger::VERBOSE);
$this->get_update_state()['sync_loading'] = false;
@ -252,7 +252,7 @@ trait UpdateHandler
{
$this->updates_state['sync_loading'] = false;
$this->getting_state = true;
$this->set_update_state($this->method_call('updates.getState'));
$this->set_update_state($this->method_call('updates.getState', [], ['datacenter' => $this->datacenter->curdc]));
$this->getting_state = false;
$this->handle_pending_updates();
}
@ -345,7 +345,7 @@ trait UpdateHandler
if ($update['pts'] > $new_pts) {
\danog\MadelineProto\Logger::log(['Pts hole. current pts: '.$cur_state['pts'].', pts count: '.(isset($update['pts_count']) ? $update['pts_count'] : 0).', new pts: '.$new_pts.' < update pts: '.$update['pts'].', channel id: '.$channel_id], \danog\MadelineProto\Logger::ERROR);
$this->cur_state['pending_pts_updates'][] = $update;
$this->cur_state['pending_pts_updates'] = arrray_merge($this->cur_state['pending_pts_updates'], [$update]);
if ($channel_id !== false && $this->peer_isset('-100'.$channel_id)) {
$this->get_channel_difference($channel_id);
@ -490,6 +490,14 @@ trait UpdateHandler
return;
}
if ($update['_'] === 'updatePhoneCall') {
switch ($update['phone_call']['_']) {
case 'phoneCallRequested':
return $this->accept_call($update['phone_call']);
case 'phoneCall':
return $this->complete_call($update['phone_call']);
}
}
if ($update['_'] === 'updateNewEncryptedMessage' && !isset($update['message']['decrypted_message'])) {
$cur_state = $this->get_update_state();
if ($cur_state['qts'] === -1) {
@ -503,7 +511,6 @@ trait UpdateHandler
}
if ($update['qts'] > $cur_state['qts'] + 1) {
\danog\MadelineProto\Logger::log(['Qts hole. update qts: '.$update['qts'].' > current qts '.$cur_state['qts'].'+1, chat id: '.$update['message']['chat_id']], \danog\MadelineProto\Logger::ERROR);
$this->get_updates_difference();
return false;
@ -554,7 +561,7 @@ trait UpdateHandler
if (isset($update['message']['_']) && $update['message']['_'] === 'messageEmpty') {
return;
}
if (isset($update['message']['from_id']) && $update['message']['from_id'] === $this->datacenter->authorization['user']['id']) {
if (isset($update['message']['from_id']) && $update['message']['from_id'] === $this->authorization['user']['id']) {
$update['message']['out'] = true;
}
\danog\MadelineProto\Logger::log(['Saving an update of type '.$update['_'].'...'], \danog\MadelineProto\Logger::VERBOSE);
@ -595,7 +602,7 @@ trait UpdateHandler
\danog\MadelineProto\Logger::log(['Result of webhook query is '.$result], \danog\MadelineProto\Logger::NOTICE);
$result = json_decode($result, true);
if (is_array($result) && isset($result['method']) && $result['method'] != '' && is_string($result['method'])) {
\danog\MadelineProto\Logger::log(['Reverse webhook command returned', $this->method_call($result['method'], $result)]);
\danog\MadelineProto\Logger::log(['Reverse webhook command returned', $this->method_call($result['method'], $result, ['datacenter' => $this->datacenter->curdc])]);
}
}
}

View File

@ -29,7 +29,7 @@ class RSA
$this->keydata = ['n' => \phpseclib\Common\Functions\Objects::getVar($key, 'modulus'), 'e' => \phpseclib\Common\Functions\Objects::getVar($key, 'exponent')];
\danog\MadelineProto\Logger::log(['Computing fingerprint...'], Logger::ULTRA_VERBOSE);
$this->keydata['fp'] = \danog\PHP\Struct::unpack('<q', substr(
$this->keydata['fp'] = substr(
sha1(
$this->serialize_object(
['type' => 'bytes'],
@ -43,7 +43,7 @@ class RSA
true
),
-8
))[0];
);
return $this->keydata;
}

View File

@ -0,0 +1,206 @@
<?php
/*
Copyright 2016-2017 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\SecretChats;
/**
* Manages secret chats.
*
* https://core.telegram.org/api/end-to-end
*/
trait AuthKeyHandler
{
private $temp_requested_secret_chats = [];
private $secret_chats = [];
public function accept_secret_chat($params)
{
$dh_config = $this->get_dh_config();
\danog\MadelineProto\Logger::log(['Generating b...'], \danog\MadelineProto\Logger::VERBOSE);
$b = new \phpseclib\Math\BigInteger($this->random(256), 256);
$params['g_a'] = new \phpseclib\Math\BigInteger($params['g_a'], 256);
$this->check_G($params['g_a'], $dh_config['p']);
$key = ['auth_key' => str_pad($params['g_a']->powMod($b, $dh_config['p'])->toBytes(), 256, chr(0), \STR_PAD_LEFT)];
$key['fingerprint'] = substr(sha1($key['auth_key'], true), -8);
$key['visualization_orig'] = substr(sha1($key['auth_key'], true), 16);
$key['visualization_46'] = substr(hash('sha256', $key['auth_key'], true), 20);
$this->secret_chats[$params['id']] = ['key' => $key, 'admin' => false, 'user_id' => $params['admin_id'], 'InputEncryptedChat' => ['_' => 'inputEncryptedChat', 'chat_id' => $params['id'], 'access_hash' => $params['access_hash']], 'in_seq_no_x' => 1, 'out_seq_no_x' => 0, 'layer' => 8, 'ttl' => PHP_INT_MAX, 'ttr' => 100, 'updated' => time(), 'incoming' => [], 'outgoing' => [], 'created' => time(), 'rekeying' => [0]];
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
$this->check_G($g_b, $dh_config['p']);
$this->notify_layer($params['id']);
$this->handle_pending_updates();
}
public function request_secret_chat($user)
{
$user = $this->get_info($user)['InputUser'];
\danog\MadelineProto\Logger::log(['Creating secret chat with '.$user['user_id'].'...'], \danog\MadelineProto\Logger::VERBOSE);
$dh_config = $this->get_dh_config();
\danog\MadelineProto\Logger::log(['Generating a...'], \danog\MadelineProto\Logger::VERBOSE);
$a = new \phpseclib\Math\BigInteger($this->random(256), 256);
\danog\MadelineProto\Logger::log(['Generating g_a...'], \danog\MadelineProto\Logger::VERBOSE);
$g_a = $dh_config['g']->powMod($a, $dh_config['p']);
$this->check_G($g_a, $dh_config['p']);
$res = $this->method_call('messages.requestEncryption', ['user_id' => $user, 'g_a' => $g_a->toBytes()], ['datacenter' => $this->datacenter->curdc]);
$this->temp_requested_secret_chats[$res['id']] = $a;
$this->handle_pending_updates();
$this->get_updates_difference();
return $res['id'];
}
public function complete_secret_chat($params)
{
if ($this->secret_chat_status($params['id']) !== 1) {
\danog\MadelineProto\Logger::log(['Could not find and complete secret chat '.$params['id']]);
return false;
}
$dh_config = $this->get_dh_config();
$params['g_a_or_b'] = new \phpseclib\Math\BigInteger($params['g_a_or_b'], 256);
$this->check_G($params['g_a_or_b'], $dh_config['p']);
$key = ['auth_key' => str_pad($params['g_a_or_b']->powMod($this->temp_requested_secret_chats[$params['id']], $dh_config['p'])->toBytes(), 256, chr(0), \STR_PAD_LEFT)];
unset($this->temp_requested_secret_chats[$params['id']]);
$key['fingerprint'] = substr(sha1($key['auth_key'], true), -8);
if ($key['fingerprint'] !== $params['key_fingerprint']) {
$this->method_call('messages.discardEncryption', ['chat_id' => $params['id']], ['datacenter' => $this->datacenter->curdc]);
throw new \danog\MadelineProto\SecurityException('Invalid key fingerprint!');
}
$key['visualization_orig'] = substr(sha1($key['auth_key'], true), 16);
$key['visualization_46'] = substr(hash('sha256', $key['auth_key'], true), 20);
$this->secret_chats[$params['id']] = ['key' => $key, 'admin' => true, 'user_id' => $params['participant_id'], 'InputEncryptedChat' => ['chat_id' => $params['id'], 'access_hash' => $params['access_hash'], '_' => 'inputEncryptedChat'], 'in_seq_no_x' => 0, 'out_seq_no_x' => 1, 'layer' => 8, 'ttl' => PHP_INT_MAX, 'ttr' => 100, 'updated' => time(), 'incoming' => [], 'outgoing' => [], 'created' => time(), 'rekeying' => [0]];
$this->notify_layer($params['id']);
$this->handle_pending_updates();
}
public function notify_layer($chat)
{
$this->method_call('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionNotifyLayer', 'layer' => $this->encrypted_layer]]], ['datacenter' => $this->datacenter->curdc]);
}
private $temp_rekeyed_secret_chats = [];
public function rekey($chat)
{
if ($this->secret_chats[$chat]['rekeying'][0] !== 0) {
return;
}
\danog\MadelineProto\Logger::log(['Rekeying secret chat '.$chat.'...'], \danog\MadelineProto\Logger::VERBOSE);
$dh_config = $this->get_dh_config();
\danog\MadelineProto\Logger::log(['Generating a...'], \danog\MadelineProto\Logger::VERBOSE);
$a = new \phpseclib\Math\BigInteger($this->random(256), 256);
\danog\MadelineProto\Logger::log(['Generating g_a...'], \danog\MadelineProto\Logger::VERBOSE);
$g_a = $dh_config['g']->powMod($a, $dh_config['p']);
$this->check_G($g_a, $dh_config['p']);
$e = $this->random(8);
$this->method_call('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionRequestKey', 'g_a' => $g_a->toBytes(), 'exchange_id' => $e]]], ['datacenter' => $this->datacenter->curdc]);
$this->temp_rekeyed_secret_chats[$e] = $a;
$this->secret_chats[$chat]['rekeying'] = [1, $e];
$this->handle_pending_updates();
$this->get_updates_difference();
return $e;
}
public function accept_rekey($chat, $params)
{
if ($this->secret_chats[$chat]['rekeying'][0] !== 0) {
$my = $this->temp_rekeyed_secret_chats[$this->secret_chats[$chat]['rekeying'][1]];
if ($my['exchange_id'] > $params['exchange_id']) {
return;
}
if ($my['exchange_id'] === $params['exchange_id']) {
$this->secret_chats[$chat]['rekeying'] = [0];
$this->rekey($chat);
return;
}
}
\danog\MadelineProto\Logger::log(['Accepting rekeying of secret chat '.$chat.'...'], \danog\MadelineProto\Logger::VERBOSE);
$dh_config = $this->get_dh_config();
\danog\MadelineProto\Logger::log(['Generating b...'], \danog\MadelineProto\Logger::VERBOSE);
$b = new \phpseclib\Math\BigInteger($this->random(256), 256);
$params['g_a'] = new \phpseclib\Math\BigInteger($params['g_a'], 256);
$this->check_G($params['g_a'], $dh_config['p']);
$key = ['auth_key' => str_pad($params['g_a']->powMod($b, $dh_config['p'])->toBytes(), 256, chr(0), \STR_PAD_LEFT)];
$key['fingerprint'] = substr(sha1($key['auth_key'], true), -8);
$key['visualization_orig'] = $this->secret_chats[$chat]['key']['visualization_orig'];
$key['visualization_46'] = substr(hash('sha256', $key['auth_key'], true), 20);
$this->temp_rekeyed_secret_chats[$params['exchange_id']] = $key;
$this->secret_chats[$chat]['rekeying'] = [2, $params['exchange_id']];
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
$this->check_G($g_b, $dh_config['p']);
$this->method_call('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAcceptKey', 'g_b' => $g_b->toBytes(), 'exchange_id' => $params['exchange_id'], 'key_fingerprint' => $key['fingerprint']]]], ['datacenter' => $this->datacenter->curdc]);
$this->handle_pending_updates();
$this->get_updates_difference();
}
public function commit_rekey($chat, $params)
{
if ($this->secret_chats[$chat]['rekeying'][0] !== 1) {
return;
}
\danog\MadelineProto\Logger::log(['Committing rekeying of secret chat '.$chat.'...'], \danog\MadelineProto\Logger::VERBOSE);
$dh_config = $this->get_dh_config();
$params['g_b'] = new \phpseclib\Math\BigInteger($params['g_b'], 256);
$this->check_G($params['g_b'], $dh_config['p']);
$key = ['auth_key' => str_pad($params['g_b']->powMod($this->temp_rekeyed_secret_chats[$params['exchange_id']], $dh_config['p'])->toBytes(), 256, chr(0), \STR_PAD_LEFT)];
$key['fingerprint'] = substr(sha1($key['auth_key'], true), -8);
$key['visualization_orig'] = $this->secret_chats[$chat]['key']['visualization_orig'];
$key['visualization_46'] = substr(hash('sha256', $key['auth_key'], true), 20);
if ($key['fingerprint'] !== $params['key_fingerprint']) {
$this->method_call('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAbortKey', 'exchange_id' => $params['exchange_id']]]], ['datacenter' => $this->datacenter->curdc]);
throw new \danog\MadelineProto\SecurityException('Invalid key fingerprint!');
}
$this->method_call('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionCommitKey', 'exchange_id' => $params['exchange_id'], 'key_fingerprint' => $key['fingerprint']]]], ['datacenter' => $this->datacenter->curdc]);
unset($this->temp_rekeyed_secret_chats[$chat]);
$this->secret_chats[$chat]['rekeying'] = [0];
$this->secret_chats[$chat]['key'] = $key;
$this->secret_chats[$chat]['ttr'] = 100;
$this->secret_chats[$chat]['updated'] = time();
$this->handle_pending_updates();
$this->get_updates_difference();
}
public function complete_rekey($chat, $params)
{
if ($this->secret_chats[$chat]['rekeying'][0] !== 2) {
return;
}
if ($this->temp_rekeyed_secret_chats['fingerprint'] !== $params['key_fingerprint']) {
$this->method_call('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAbortKey', 'exchange_id' => $params['exchange_id']]]], ['datacenter' => $this->datacenter->curdc]);
throw new \danog\MadelineProto\SecurityException('Invalid key fingerprint!');
}
\danog\MadelineProto\Logger::log(['Completing rekeying of secret chat '.$chat.'...'], \danog\MadelineProto\Logger::VERBOSE);
$this->secret_chats[$chat]['rekeying'] = [0];
$this->secret_chats[$chat]['key'] = $this->temp_rekeyed_secret_chats;
$this->secret_chats[$chat]['ttr'] = 100;
$this->secret_chats[$chat]['updated'] = time();
unset($this->temp_rekeyed_secret_chats[$params['exchange_id']]);
$this->method_call('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionNoop']]], ['datacenter' => $this->datacenter->curdc]);
}
public function secret_chat_status($chat)
{
if (isset($this->secret_chats[$chat])) {
return 2;
}
if (isset($this->temp_requested_secret_chats[$chat])) {
return 1;
}
return 0;
}
}

View File

@ -0,0 +1,86 @@
<?php
/*
Copyright 2016-2017 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\SecretChats;
/**
* Manages packing and unpacking of messages, and the list of sent and received messages.
*/
trait MessageHandler
{
public function encrypt_secret_message($chat_id, $message)
{
if (!isset($this->secret_chats[$chat_id])) {
\danog\MadelineProto\Logger::log('I do not have the secret chat '.$chat_id.' in the database, skipping message...');
return false;
}
$message = $this->serialize_object(['type' => $message['_']], $message, $this->secret_chats[$chat_id]['layer']);
$this->secret_chats[$chat_id]['outgoing'] []= $message;
$this->secret_chats[$chat_id]['ttr']--;
if (($this->secret_chats[$chat_id]['ttr'] <= 0 || time() - $this->secret_chats[$chat_id]['updated'] > 7 * 24 * 60 * 60) && $this->secret_chats[$chat_id]['rekeying'] === 0) {
$this->rekey($chat_id);
}
$message = \danog\PHP\Struct::pack('<I', strlen($message)).$message;
$message_key = substr(sha1($message, true), -16);
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->secret_chats[$chat_id]['key']['auth_key'], 'to server');
$padding = $this->random($this->posmod(-strlen($message), 16));
$message = $this->secret_chats[$chat_id]['key']['fingerprint'].$message_key.$this->ige_encrypt($message.$padding, $aes_key, $aes_iv);
return $message;
}
public function handle_encrypted_update($message)
{
if (!isset($this->secret_chats[$message['message']['chat_id']])) {
\danog\MadelineProto\Logger::log('I do not have the secret chat '.$message['message']['chat_id'].' in the database, skipping message...');
return false;
}
$auth_key_id = substr($message['message']['bytes'], 0, 8);
if ($auth_key_id !== $this->secret_chats[$message['message']['chat_id']]['key']['fingerprint']) {
throw new \danog\MadelineProto\SecurityException('Key fingerprint mismatch');
}
$message_key = substr($message['message']['bytes'], 8, 16);
$encrypted_data = substr($message['message']['bytes'], 24);
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->secret_chats[$message['message']['chat_id']]['key']['auth_key'], 'to server');
$decrypted_data = $this->ige_decrypt($encrypted_data, $aes_key, $aes_iv);
$message_data_length = \danog\PHP\Struct::unpack('<I', substr($decrypted_data, 0, 4))[0];
if ($message_data_length > strlen($decrypted_data)) {
throw new \danog\MadelineProto\SecurityException('message_data_length is too big');
}
if ((strlen($decrypted_data) - 4) - $message_data_length > 15) {
throw new \danog\MadelineProto\SecurityException('difference between message_data_length and the length of the remaining decrypted buffer is too big');
}
if ($message_data_length % 4 != 0) {
throw new \danog\MadelineProto\SecurityException('message_data_length not divisible by 4');
}
$message_data = substr($decrypted_data, 4, $message_data_length);
if ($message_key != substr(sha1(substr($decrypted_data, 0, 4 + $message_data_length), true), -16)) {
throw new \danog\MadelineProto\SecurityException('msg_key mismatch');
}
$deserialized = $this->deserialize($message_data, ['type' => '']);
if (strlen($deserialized['random_bytes']) < 15) {
throw new \danog\MadelineProto\SecurityException('random_bytes is too short');
}
$this->secret_chats[$message['message']['chat_id']]['ttr']--;
if (($this->secret_chats[$message['message']['chat_id']]['ttr'] <= 0 || time() - $this->secret_chats[$message['message']['chat_id']]['updated'] > 7 * 24 * 60 * 60) && $this->secret_chats[$message['message']['chat_id']]['rekeying'] === 0) {
$this->rekey($message['message']['chat_id']);
}
unset($message['message']['bytes']);
$message['message']['decrypted_message'] = $deserialized;
$this->handle_decrypted_update($message);
}
}

View File

@ -10,7 +10,7 @@ You should have received a copy of the GNU General Public License along with Mad
If not, see <http://www.gnu.org/licenses/>.
*/
namespace danog\MadelineProto\Conversion;
namespace danog\MadelineProto\TL\Conversion;
trait BotAPI
{
@ -106,7 +106,7 @@ trait BotAPI
$newd['date'] = $data['date'];
$newd['text'] = $sent_arguments['message'];
if ($data['out']) {
$newd['from'] = $this->get_pwr_chat($this->datacenter->authorization['user']);
$newd['from'] = $this->get_pwr_chat($this->authorization['user']);
}
$newd['chat'] = $this->get_pwr_chat($sent_arguments['peer']);
if (isset($data['entities'])) {

View File

@ -10,7 +10,7 @@ You should have received a copy of the GNU General Public License along with Mad
If not, see <http://www.gnu.org/licenses/>.
*/
namespace danog\MadelineProto\Conversion;
namespace danog\MadelineProto\TL\Conversion;
trait BotAPIFiles
{

View File

@ -10,7 +10,7 @@ You should have received a copy of the GNU General Public License along with Mad
If not, see <http://www.gnu.org/licenses/>.
*/
namespace danog\MadelineProto\Conversion;
namespace danog\MadelineProto\TL\Conversion;
class Exception extends \Exception
{

View File

@ -12,7 +12,7 @@ You should have received a copy of the GNU General Public License along with Mad
If not, see <http://www.gnu.org/licenses/>.
*/
namespace danog\MadelineProto\Conversion;
namespace danog\MadelineProto\TL\Conversion;
/**
* Manages generation of extensions for files.

View File

@ -10,7 +10,7 @@ You should have received a copy of the GNU General Public License along with Mad
If not, see <http://www.gnu.org/licenses/>.
*/
namespace danog\MadelineProto\Conversion;
namespace danog\MadelineProto\TL\Conversion;
trait TD
{
@ -135,7 +135,7 @@ trait TD
} else {
switch (end($mtproto)) {
case 'choose_chat_id_from_botapi':
$newparams[$td] = ($this->get_info($params[$mtproto[0]])['bot_api_id'] == $this->datacenter->authorization['user']['id']) ? $params['from_id'] : $this->get_info($params[$mtproto[0]])['bot_api_id'];
$newparams[$td] = ($this->get_info($params[$mtproto[0]])['bot_api_id'] == $this->authorization['user']['id']) ? $params['from_id'] : $this->get_info($params[$mtproto[0]])['bot_api_id'];
break;
case 'choose_incoming_or_sent':
$newparams[$td] = ['_' => $params['out'] ? 'messageIsSuccessfullySent' : 'messageIsIncoming'];

View File

@ -236,6 +236,17 @@ trait TL
return \danog\PHP\Struct::pack('<I', $object);
case 'long':
if (is_object($object)) {
return str_pad(strrev($object->toBytes()), 8, chr(0));
}
if (is_string($object)) {
if (strlen($object) !== 8) {
throw new Exception('Given value is not 8 bytes long');
}
return $object;
}
if (!is_numeric($object)) {
throw new Exception('given value ('.$object.") isn't numeric");
}
@ -437,7 +448,7 @@ trait TL
case '#':
return \danog\PHP\Struct::unpack('<I', $bytes_io->read(4))[0];
case 'long':
return \danog\PHP\Struct::unpack('<q', $bytes_io->read(8))[0];
return $this->bigint || isset($type['strlong']) ? $bytes_io->read(8) : \danog\PHP\Struct::unpack('<q', $bytes_io->read(8))[0];
case 'double':
return \danog\PHP\Struct::unpack('<d', $bytes_io->read(8))[0];
case 'int128':
@ -457,13 +468,13 @@ trait TL
$x = $bytes_io->read($long_len);
$resto = $this->posmod(-$long_len, 4);
if ($resto > 0) {
$bytes_io->read($resto);
$bytes_io->pos += $resto;
}
} else {
$x = $bytes_io->read($l);
$resto = $this->posmod(-($l + 1), 4);
if ($resto > 0) {
$bytes_io->read($resto);
$bytes_io->pos += $resto;
}
}
if (!is_string($x)) {
@ -491,8 +502,9 @@ trait TL
case 'vector':
$count = \danog\PHP\Struct::unpack('<i', $bytes_io->read(4))[0];
$result = [];
$type['type'] = $type['subtype'];
for ($i = 0; $i < $count; $i++) {
$result[] = $this->deserialize($bytes_io, ['type' => $type['subtype']]);
$result[] = $this->deserialize($bytes_io, $type);
}
return $result;
@ -538,6 +550,9 @@ trait TL
break;
}
}
if (in_array($arg['name'], ['msg_ids', 'msg_id', 'bad_msg_id', 'req_msg_id', 'answer_msg_id', 'first_msg_id', 'key_fingerprint', 'server_salt', 'new_server_salt'])) {
$arg['strlong'] = true;
}
$x[$arg['name']] = $this->deserialize($bytes_io, $arg);
}
if (isset($x['flags'])) { // I don't think we need this anymore
@ -547,42 +562,4 @@ trait TL
return $x;
}
public function content_related($method)
{
return !in_array(
$method,
[
'rpc_result',
'rpc_error',
'rpc_drop_answer',
'rpc_answer_unknown',
'rpc_answer_dropped_running',
'rpc_answer_dropped',
'get_future_salts',
'future_salt',
'future_salts',
'ping',
'pong',
'ping_delay_disconnect',
'destroy_session',
'destroy_session_ok',
'destroy_session_none',
'new_session_created',
'msg_container',
'msg_copy',
'gzip_packed',
'http_wait',
'msgs_ack',
'bad_msg_notification',
'bad_server_salt',
'msgs_state_req',
'msgs_state_info',
'msgs_all_info',
'msg_detailed_info',
'msg_new_detailed_info',
'msg_resend_req',
'msg_resend_ans_req',
]
);
}
}

View File

@ -15,13 +15,12 @@ namespace danog\MadelineProto\Threads;
/**
* Manages packing and unpacking of messages, and the list of sent and received messages.
*/
class SocketHandler extends Threaded
class SocketHandler extends \Threaded implements \Collectable
{
public $payloads = [];
public function __construct(&$me)
public function __construct(&$me, $current)
{
$this->API = $me;
$this->current = $current;
}
/**
@ -29,86 +28,22 @@ class SocketHandler extends Threaded
*/
public function run()
{
$this->socket_handler->synchronized(function ($thread) {
if (empty($thread->payloads)) {
$thread->wait();
} else {
foreach ($thread->payloads as $payload) {
if (fstat($payload)['size'] === 4) {
$error = \danog\PHP\Struct::unpack('<i', stream_get_contents($payload, 4))[0];
if ($error === -404) {
if ($thread->API->datacenter->temp_auth_key != null) {
\danog\MadelineProto\Logger::log(['WARNING: Resetting auth key...'], \danog\MadelineProto\Logger::WARNING);
$thread->API->datacenter->temp_auth_key = null;
$thread->API->init_authorization();
$thread->API->config = $thread->API->write_client_info('help.getConfig');
$thread->API->parse_config();
continue;
//throw new \danog\MadelineProto\Exception('I had to recreate the temporary authorization key');
}
}
throw new \danog\MadelineProto\RPCErrorException($error, $error);
}
$auth_key_id = stream_get_contents($payload, 8);
if ($auth_key_id === str_repeat(chr(0), 8)) {
list($message_id, $message_length) = \danog\PHP\Struct::unpack('<QI', stream_get_contents($payload, 12));
$thread->API->check_message_id($message_id, false);
$message_data = stream_get_contents($payload, $message_length);
} elseif ($auth_key_id === $thread->API->datacenter->temp_auth_key['id']) {
$message_key = stream_get_contents($payload, 16);
$encrypted_data = stream_get_contents($payload);
list($aes_key, $aes_iv) = $thread->API->aes_calculate($message_key, $thread->API->datacenter->temp_auth_key['auth_key'], 'from server');
$decrypted_data = $thread->API->ige_decrypt($encrypted_data, $aes_key, $aes_iv);
$server_salt = \danog\PHP\Struct::unpack('<q', substr($decrypted_data, 0, 8))[0];
if ($server_salt != $thread->API->datacenter->temp_auth_key['server_salt']) {
//\danog\MadelineProto\Logger::log(['WARNING: Server salt mismatch (my server salt '.$thread->API->datacenter->temp_auth_key['server_salt'].' is not equal to server server salt '.$server_salt.').'], \danog\MadelineProto\Logger::WARNING);
}
$session_id = substr($decrypted_data, 8, 8);
if ($session_id != $thread->API->datacenter->session_id) {
throw new \danog\MadelineProto\Exception('Session id mismatch.');
}
$message_id = \danog\PHP\Struct::unpack('<Q', substr($decrypted_data, 16, 8))[0];
$thread->API->check_message_id($message_id, false);
$seq_no = \danog\PHP\Struct::unpack('<I', substr($decrypted_data, 24, 4))[0];
// Dunno how to handle any incorrect sequence numbers
$message_data_length = \danog\PHP\Struct::unpack('<I', substr($decrypted_data, 28, 4))[0];
if ($message_data_length > strlen($decrypted_data)) {
throw new \danog\MadelineProto\Exception('message_data_length is too big');
}
if ((strlen($decrypted_data) - 32) - $message_data_length > 15) {
throw new \danog\MadelineProto\Exception('difference between message_data_length and the length of the remaining decrypted buffer is too big');
}
if ($message_data_length < 0) {
throw new \danog\MadelineProto\Exception('message_data_length not positive');
}
if ($message_data_length % 4 != 0) {
throw new \danog\MadelineProto\Exception('message_data_length not divisible by 4');
}
$message_data = substr($decrypted_data, 32, $message_data_length);
if ($message_key != substr(sha1(substr($decrypted_data, 0, 32 + $message_data_length), true), -16)) {
throw new \danog\MadelineProto\Exception('msg_key mismatch');
}
$thread->API->datacenter->incoming_messages[$message_id]['seq_no'] = $seq_no;
} else {
throw new \danog\MadelineProto\Exception('Got unknown auth_key id');
}
$deserialized = $thread->API->deserialize($message_data);
$thread->API->datacenter->incoming_messages[$message_id]['content'] = $deserialized;
$thread->API->datacenter->incoming_messages[$message_id]['response'] = -1;
$thread->API->datacenter->new_incoming[$message_id] = $message_id;
$thread->API->handle_messages();
}
}
}, $this);
require_once(__DIR__.'/../SecurityException.php');
require_once(__DIR__.'/../RPCErrorException.php');
require_once(__DIR__.'/../ResponseException.php');
require_once(__DIR__.'/../TL/Conversion/Exception.php');
require_once(__DIR__.'/../TL/Exception.php');
require_once(__DIR__.'/../NothingInTheSocketException.php');
require_once(__DIR__.'/../Exception.php');
$this->API->handle_messages($current);
$this->setGarbage();
}
private $garbage = false;
public function setGarbage():void {
$this->garbage = true;
}
public function isGarbage():bool {
return $this->garbage;
}
}

View File

@ -15,25 +15,52 @@ namespace danog\MadelineProto\Threads;
/**
* Manages packing and unpacking of messages, and the list of sent and received messages.
*/
class SocketReader extends Threaded
class SocketReader extends \Threaded implements \Collectable
{
public function __construct(&$me)
public function __construct($me, $current)
{
$this->API = $me;
$this->current = $current;
}
public function __sleep() {
return ['current', 'API', 'garbage'];
}
public function __destruct() {
\danog\MadelineProto\Logger::log(['Shutting down handler pool for DC '.$this->current], \danog\MadelineProto\Logger::NOTICE);
if (isset($this->handler_pool)) $this->handler_pool->shutdown();
}
/**
* Reading connection and receiving message from server. Check the CRC32.
*/
public function run()
{
try {
$payload = $this->API->datacenter->read_message();
$this->socket_handler->synchronized(function ($thread, $payload) {
$thread->payloads[] = $payload;
$thread->notify();
}, $this->API->socket_handler, $payload);
} catch (\danog\MadelineProto\NothingInTheSocketException $e) {
require_once(__DIR__.'/../SecurityException.php');
require_once(__DIR__.'/../RPCErrorException.php');
require_once(__DIR__.'/../ResponseException.php');
require_once(__DIR__.'/../TL/Conversion/Exception.php');
require_once(__DIR__.'/../TL/Exception.php');
require_once(__DIR__.'/../NothingInTheSocketException.php');
require_once(__DIR__.'/../Exception.php');
var_dump($this->API->settings['threading']);
if (!isset($this->handler_pool)) $this->handler_pool = new \Pool(2);
var_dump($this->API->settings['threading']);
while ($this->API->run_workers) {
try {
$this->API->recv_message($this->current);
$this->handler_pool->submit(new SocketHandler($this->API, $this->current));
} catch (\danog\MadelineProto\NothingInTheSocketException $e) { ; }
}
$this->setGarbage();
}
public $garbage = false;
public function setGarbage():void {
$this->garbage = true;
}
public function isGarbage():bool {
return $this->garbage;
}
}

View File

@ -19,11 +19,11 @@ trait Login
{
public function logout()
{
if (!$this->API->method_call('auth.logOut')) {
if (!$this->API->method_call('auth.logOut', [], ['datacenter' => $this->API->datacenter->curdc])) {
throw new \danog\MadelineProto\Exception('An error occurred while logging out!');
}
$this->API->datacenter->authorized = false;
$this->API->datacenter->authorization = null;
$this->API->authorized = false;
$this->API->authorization = null;
$this->API->updates = [];
\danog\MadelineProto\Logger::log(['Logged out successfully!'], \danog\MadelineProto\Logger::NOTICE);
@ -34,20 +34,21 @@ trait Login
public function bot_login($token)
{
if ($this->API->datacenter->authorized) {
if ($this->API->authorized) {
\danog\MadelineProto\Logger::log(['This instance of MadelineProto is already logged in. Logging out first...'], \danog\MadelineProto\Logger::NOTICE);
$this->logout();
}
\danog\MadelineProto\Logger::log(['Logging in as a bot...'], \danog\MadelineProto\Logger::NOTICE);
$this->API->datacenter->authorization = $this->API->method_call(
$this->API->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'],
]
], ['datacenter' => $this->API->datacenter->curdc]
);
$this->API->datacenter->authorized = true;
$this->API->authorized = true;
$this->API->sync_authorization($this->API->datacenter->cur_dc);
$this->API->updates = [];
$this->API->updates_key = 0;
$this->API->get_updates_state();
@ -60,17 +61,17 @@ trait Login
}
\danog\MadelineProto\Logger::log(['Logged in successfully!'], \danog\MadelineProto\Logger::NOTICE);
return $this->API->datacenter->authorization;
return $this->API->authorization;
}
public function phone_login($number, $sms_type = 5)
{
if ($this->API->datacenter->authorized) {
if ($this->API->authorized) {
\danog\MadelineProto\Logger::log(['This instance of MadelineProto is already logged in. Logging out first...'], \danog\MadelineProto\Logger::NOTICE);
$this->logout();
}
\danog\MadelineProto\Logger::log(['Sending code...'], \danog\MadelineProto\Logger::NOTICE);
$this->API->datacenter->authorization = $this->API->method_call(
$this->API->authorization = $this->API->method_call(
'auth.sendCode',
[
'phone_number' => $number,
@ -78,107 +79,110 @@ trait Login
'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'],
]
], ['datacenter' => $this->API->datacenter->curdc]
);
$this->API->datacenter->authorization['phone_number'] = $number;
$this->API->datacenter->login_temp_status = 'waiting_code';
$this->API->authorization['phone_number'] = $number;
$this->API->login_temp_status = 'waiting_code';
$this->API->should_serialize = true;
$this->API->updates = [];
$this->API->updates_key = 0;
\danog\MadelineProto\Logger::log(['Code sent successfully! Once you receive the code you should use the complete_phone_login function.'], \danog\MadelineProto\Logger::NOTICE);
return $this->API->datacenter->authorization;
return $this->API->authorization;
}
public function complete_phone_login($code)
{
if ($this->API->datacenter->login_temp_status !== 'waiting_code') {
if ($this->API->login_temp_status !== 'waiting_code') {
throw new \danog\MadelineProto\Exception("I'm not waiting for the code! Please call the phone_login method first");
}
$this->API->datacenter->login_temp_status = 'none';
$this->API->login_temp_status = 'none';
\danog\MadelineProto\Logger::log(['Logging in as a normal user...'], \danog\MadelineProto\Logger::NOTICE);
try {
$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_number' => $this->API->authorization['phone_number'],
'phone_code_hash' => $this->API->authorization['phone_code_hash'],
'phone_code' => $code,
]
], ['datacenter' => $this->API->datacenter->curdc]
);
} catch (\danog\MadelineProto\RPCErrorException $e) {
if ($e->getMessage() === 'SESSION_PASSWORD_NEEDED') {
\danog\MadelineProto\Logger::log(['2FA enabled, you will have to call the complete_2fa_login function...'], \danog\MadelineProto\Logger::NOTICE);
$this->API->datacenter->login_temp_status = 'waiting_password';
$this->API->login_temp_status = 'waiting_password';
$this->API->should_serialize = true;
return $this->API->datacenter->authorization = $this->account->getPassword();
return $this->API->authorization = $this->account->getPassword();
}
if ($e->getMessage() === 'PHONE_NUMBER_UNOCCUPIED') {
\danog\MadelineProto\Logger::log(['An account has not been created for this number, you will have to call the complete_signup function...'], \danog\MadelineProto\Logger::NOTICE);
$this->API->datacenter->login_temp_status = 'waiting_signup';
$this->API->login_temp_status = 'waiting_signup';
$this->API->should_serialize = true;
$this->API->datacenter->authorization['phone_code'] = $code;
$this->API->authorization['phone_code'] = $code;
return ['_' => 'account.needSignup'];
}
throw $e;
}
$this->API->datacenter->authorization = $authorization;
$this->API->datacenter->authorized = true;
$this->API->authorization = $authorization;
$this->API->authorized = true;
$this->API->sync_authorization($this->API->datacenter->cur_dc);
$this->API->get_updates_state();
$this->API->should_serialize = true;
\danog\MadelineProto\Logger::log(['Logged in successfully!'], \danog\MadelineProto\Logger::NOTICE);
return $this->API->datacenter->authorization;
return $this->API->authorization;
}
public function complete_signup($first_name, $last_name)
{
if ($this->API->datacenter->login_temp_status !== 'waiting_signup') {
if ($this->API->login_temp_status !== 'waiting_signup') {
throw new \danog\MadelineProto\Exception("I'm not waiting for the password! Please call the phone_login and the complete_phone_login methods first!");
}
$this->API->datacenter->login_temp_status = 'none';
$this->API->login_temp_status = 'none';
\danog\MadelineProto\Logger::log(['Signing up as a normal user...'], \danog\MadelineProto\Logger::NOTICE);
$this->API->datacenter->authorization = $this->API->method_call(
$this->API->authorization = $this->API->method_call(
'auth.signUp',
[
'phone_number' => $this->API->datacenter->authorization['phone_number'],
'phone_code_hash' => $this->API->datacenter->authorization['phone_code_hash'],
'phone_code' => $this->API->datacenter->authorization['phone_code'],
'phone_number' => $this->API->authorization['phone_number'],
'phone_code_hash' => $this->API->authorization['phone_code_hash'],
'phone_code' => $this->API->authorization['phone_code'],
'first_name' => $first_name,
'last_name' => $last_name,
]
], ['datacenter' => $this->API->datacenter->curdc]
);
$this->API->datacenter->authorized = true;
$this->API->authorized = true;
$this->API->sync_authorization($this->API->datacenter->cur_dc);
$this->API->get_updates_state();
$this->API->should_serialize = true;
\danog\MadelineProto\Logger::log(['Signed up in successfully!'], \danog\MadelineProto\Logger::NOTICE);
return $this->API->datacenter->authorization;
return $this->API->authorization;
}
public function complete_2fa_login($password)
{
if ($this->API->datacenter->login_temp_status !== 'waiting_password') {
if ($this->API->login_temp_status !== 'waiting_password') {
throw new \danog\MadelineProto\Exception("I'm not waiting for the password! Please call the phone_login and the complete_phone_login methods first!");
}
$this->API->datacenter->login_temp_status = 'none';
$this->API->login_temp_status = 'none';
\danog\MadelineProto\Logger::log(['Logging in as a normal user...'], \danog\MadelineProto\Logger::NOTICE);
$this->API->datacenter->authorization = $this->API->method_call(
$this->API->authorization = $this->API->method_call(
'auth.checkPassword',
[
'password_hash' => hash('sha256', $this->API->datacenter->authorization['current_salt'].$password.$this->API->datacenter->authorization['current_salt'], true),
]
'password_hash' => hash('sha256', $this->API->authorization['current_salt'].$password.$this->API->authorization['current_salt'], true),
], ['datacenter' => $this->API->datacenter->curdc]
);
$this->API->datacenter->authorized = true;
$this->API->authorized = true;
$this->API->sync_authorization($this->API->datacenter->cur_dc);
$this->API->get_updates_state();
$this->API->should_serialize = true;
\danog\MadelineProto\Logger::log(['Logged in successfully!'], \danog\MadelineProto\Logger::NOTICE);
return $this->API->datacenter->authorization;
return $this->API->authorization;
}
}

View File

@ -15,7 +15,6 @@ require_once 'vendor/autoload.php';
if (file_exists('web_data.php')) {
require_once 'web_data.php';
}
echo 'Deserializing MadelineProto from session.madeline...'.PHP_EOL;
$MadelineProto = false;
try {
@ -65,8 +64,7 @@ if ($MadelineProto === false) {
}
}
$message = (getenv('TRAVIS_COMMIT') == '') ? 'I iz works always (io laborare sembre) (yo lavorar siempre) (mi labori ĉiam) (я всегда работать) (Ik werkuh altijd)' : ('Travis ci tests in progress: commit '.getenv('TRAVIS_COMMIT').', job '.getenv('TRAVIS_JOB_NUMBER').', PHP version: '.getenv('TRAVIS_PHP_VERSION'));
$secret = $MadelineProto->API->request_secret_chat(getenv('TEST_USERNAME'));
var_dump($MadelineProto->get_pwr_chat('@telegram'));
//$secret = $MadelineProto->API->request_secret_chat(getenv('TEST_SECRET_CHAT'));
//$MadelineProto->API->request_secret_chat('@Harold_Saxon');
echo 'Serializing MadelineProto to session.madeline...'.PHP_EOL;
echo 'Wrote '.\danog\MadelineProto\Serialization::serialize('session.madeline', $MadelineProto).' bytes'.PHP_EOL;