Merge alpha into master (async, huge bugfixes and more) (#546)

* Implement async and lots of bugfixes

* Implement more async

* Implement async, implement bugfixes for the connection module, for the datacenter module, huge bugfixes, huge perfomance improvements, media DCs for https, advanced selecting, custom var_dump, totally rewritten IOLoop and response mechanism, promises, improvements to the TL parser, custom mb_substr

* Apply fixes from StyleCI

* Bugfixes

* Apply fixes from StyleCI

* Bugfixes, implement combined promises

* Apply fixes from StyleCI

* Support passing method arguments as callable

* Starting to write async upload logic

* Apply fixes from StyleCI

* Start implementing async file upload

* Apply fixes from StyleCI

* bugfix

* Apply fixes from StyleCI

* Start rewriting connection module

* Add PHP file docblocks for all classes

* Start working on new async stream API

* Finish writing stream API

* More stream API fixes

* Apply fixes from StyleCI

* Rewrite DataCenter and Connection modules

* Clean up stream API documentation

* Fixes

* Apply fixes from StyleCI

* Add referenced parameter to get length of buffer to read in getReadBuffer API

* Moved all MessageHandler code in the Connection module, added a PHP version warning in the phar

* Start fixing reads

* Fix all protocol stream wrappers

* Apply fixes from StyleCI

* Implement disconnection, and remove end function

* Working async RPC

* Implement async file upload

* Bugfix

* Method recall bugfixes

* Bugfixes

* Trait bugfixes

* Fix FIFO buffer

* Bugfixes and speedtests

* Async logging

* Implement websocket streams

* Implement loop API, signal API, clean closing and start changing layer

* Small magna, websocket and HTTP fixes

* Clean up loop API

* Improved stack traces, 2FA and async

* Login fixes

* Added instructions for manual verification

* Small fixes

* More app info improvements

* More app info improvements

* TL and 2FA fixes

* Update to layer 89

* More bugfixes

* Implement broken media reporting

* Remove debug comments

* PHP 7.2 backwards compatibility

* Bugfixes

* Async key generation

* Some simplifications

* Transport fixes

* Cleanup

* async API

* Performance fixes

* Fixes to async API

* Bugfixes

* Implement one-time async loop

* Authorization and logging fixes

* Update to layer 91

* 7to5 fix

* Null coalesce conversion

* Implement socks5 proxy

* Implement HTTP proxy

* Fixes to HTTP proxy

* MTProxy and socks5 fixes

* Disable PHP 5 conversion

* Proxies have higher priority

* Avoid error handling in vendor

* Override composer dependencies

* Fix travis build

* Final composer fixes

* Proxy logic fixes

* Fix get_updates update handling

* Do not use parallel file driver if not supported

* Refactor loader and implement HTTP fixes

* Suppress errors in loader

* HTTP and authorization fixes

* HTTP fixes

* Improved peer management

* Use HTTP protocol on altervista

* Small bugfixes

* Minor fixes

* Docufix

* Docufix

* Legacy fixes

* Fix message queue

* Avoid updating if using MTProxy

* Improve logs and examples

* Trim final newlines while converting parse mode

* Reimplement noResponse flag

* Async combined event handler and APIFactory fixes

* Actually return config

* Case-insensitive methods

* Bugfix

* Apply fixes from StyleCI (#545)

* MTProxy fixes

* PHP 5 warning

* Improved PHP 5 warning

* Use <br> along with newlines in web logs

* Update docs
This commit is contained in:
Daniil Gentili 2018-12-26 20:51:14 +01:00 committed by GitHub
parent 367b0536b9
commit df24fa4611
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
153 changed files with 20653 additions and 9159 deletions

1
.gitignore vendored
View File

@ -110,3 +110,4 @@ phar5
madeline.phar
madeline.phar.version
big
*.phar

10
bot.php
View File

@ -32,13 +32,18 @@ class EventHandler extends \danog\MadelineProto\EventHandler
if (isset($update['message']['out']) && $update['message']['out']) {
return;
}
if (isset($update['message']['media'])) {
yield $this->messages->sendMedia(['peer' => $update, 'message' => $update['message']['message'], 'media' => $update]);
}
$res = json_encode($update, JSON_PRETTY_PRINT);
if ($res == '') {
$res = var_export($update, true);
}
yield $this->sleep_async(3);
try {
$this->messages->sendMessage(['peer' => $update, 'message' => $res, 'reply_to_msg_id' => isset($update['message']['id']) ? $update['message']['id'] : null, 'entities' => [['_' => 'messageEntityPre', 'offset' => 0, 'length' => strlen($res), 'language' => 'json']]]);
yield $this->messages->sendMessage(['peer' => $update, 'message' => "<code>$res</code>\n\nDopo 3 secondi, in modo asincrono", 'reply_to_msg_id' => isset($update['message']['id']) ? $update['message']['id'] : null, 'parse_mode' => 'HTML']); //'entities' => [['_' => 'messageEntityPre', 'offset' => 0, 'length' => strlen($res), 'language' => 'json']]]);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log((string) $e, \danog\MadelineProto\Logger::FATAL_ERROR);
} catch (\danog\MadelineProto\Exception $e) {
@ -48,8 +53,9 @@ class EventHandler extends \danog\MadelineProto\EventHandler
}
}
$MadelineProto = new \danog\MadelineProto\API('bot.madeline');
$MadelineProto = new \danog\MadelineProto\API('bot.madeline', ['logger' => ['logger_level' => 5]]);
$MadelineProto->start();
$MadelineProto->async(true);
$MadelineProto->setEventHandler('\EventHandler');
$MadelineProto->loop();

View File

@ -43,9 +43,9 @@ $docs = [
'readme' => false,
],
[
'tl_schema' => ['telegram' => __DIR__.'/src/danog/MadelineProto/TL_telegram_v82.tl', 'calls' => __DIR__.'/src/danog/MadelineProto/TL_calls.tl', 'secret' => __DIR__.'/src/danog/MadelineProto/TL_secret.tl', 'td' => __DIR__.'/src/danog/MadelineProto/TL_td.tl'],
'title' => 'MadelineProto API documentation (layer 82)',
'description' => 'MadelineProto API documentation (layer 82)',
'tl_schema' => ['telegram' => __DIR__.'/src/danog/MadelineProto/TL_telegram_v91.tl', 'calls' => __DIR__.'/src/danog/MadelineProto/TL_calls.tl', 'secret' => __DIR__.'/src/danog/MadelineProto/TL_secret.tl', 'td' => __DIR__.'/src/danog/MadelineProto/TL_td.tl'],
'title' => 'MadelineProto API documentation (layer 91)',
'description' => 'MadelineProto API documentation (layer 91)',
'output_dir' => __DIR__.'/docs/docs/API_docs',
'readme' => false,
],

72
combined_bot.php Executable file
View File

@ -0,0 +1,72 @@
#!/usr/bin/env php
<?php
/*
Copyright 2016-2018 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/>.
*/
set_include_path(get_include_path().':'.realpath(dirname(__FILE__).'/MadelineProto/'));
/*
* Various ways to load MadelineProto
*/
if (!file_exists(__DIR__.'/vendor/autoload.php')) {
echo 'You did not run composer update, using madeline.php'.PHP_EOL;
if (!file_exists('madeline.php')) {
copy('https://phar.madelineproto.xyz/madeline.php', 'madeline.php');
}
include 'madeline.php';
} else {
require_once 'vendor/autoload.php';
}
class EventHandler extends \danog\MadelineProto\CombinedEventHandler
{
public function onAny($update, $path)
{
if (isset($update['message']['out']) && $update['message']['out']) {
return;
}
$MadelineProto = $this->{$path};
if (isset($update['message']['media'])) {
yield $MadelineProto->messages->sendMedia(['peer' => $update, 'message' => $update['message']['message'], 'media' => $update]);
}
$res = json_encode($update, JSON_PRETTY_PRINT);
if ($res == '') {
$res = var_export($update, true);
}
yield $MadelineProto->sleep_async(3);
try {
yield $MadelineProto->messages->sendMessage(['peer' => $update, 'message' => "<code>$res</code>\n\nDopo 3 secondi, in modo asincrono", 'reply_to_msg_id' => isset($update['message']['id']) ? $update['message']['id'] : null, 'parse_mode' => 'HTML']); //'entities' => [['_' => 'messageEntityPre', 'offset' => 0, 'length' => strlen($res), 'language' => 'json']]]);
} catch (\danog\MadelineProto\RPCErrorException $e) {
\danog\MadelineProto\Logger::log((string) $e, \danog\MadelineProto\Logger::FATAL_ERROR);
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log((string) $e, \danog\MadelineProto\Logger::FATAL_ERROR);
//$MadelineProto->messages->sendMessage(['peer' => '@danogentili', 'message' => $e->getCode().': '.$e->getMessage().PHP_EOL.$e->getTraceAsString()]);
}
}
}
$settings = ['logger' => ['logger_level' => 5]];
$CombinedMadelineProto = new \danog\MadelineProto\CombinedAPI('combined_session.madeline', ['bot.madeline' => $settings, 'user.madeline' => $settings]);
\danog\MadelineProto\Logger::log('Bot login', \danog\MadelineProto\Logger::WARNING);
$CombinedMadelineProto->instances['bot.madeline']->start();
\danog\MadelineProto\Logger::log('Userbot login');
$CombinedMadelineProto->instances['user.madeline']->start();
$CombinedMadelineProto->setEventHandler('\EventHandler');
$CombinedMadelineProto->loop();
$CombinedMadelineProto->async(true);
$CombinedMadelineProto->setEventHandler('\EventHandler');
$CombinedMadelineProto->loop();

View File

@ -19,13 +19,21 @@
"ext-curl": "*",
"ext-mbstring": "*",
"ext-json": "*",
"ext-xml": "*"
"ext-xml": "*",
"amphp/amp": "^2.0",
"amphp/socket": "^0.10.11",
"amphp/log": "^1.0",
"amphp/parser": "^1.0",
"amphp/dns": "dev-master#861cc857b1ba6e02e8a7439c30403682785fce96 as 0.9.9",
"amphp/file": "dev-master#5a69fca406ac5fd220de0aa68c887bc8046eb93c as 0.3.3",
"amphp/uri": "dev-master#f3195b163275383909ded7770a11d8eb865cbc86 as 0.1.3",
"amphp/websocket-client": "dev-master"
},
"require-dev": {
"phpdocumentor/reflection-docblock": "^3.1"
},
"suggest": {
"ext-libtgvoip": "Install the php-libtgvoip extension to make phone calls (https:/github.com/danog/php-libtgvoip)",
"ext-libtgvoip": "Install the php-libtgvoip extension to make phone calls (https://github.com/danog/php-libtgvoip)",
"ext-sockets": "Install the socket extension to speed up MadelineProto"
},
"authors": [

2
docs

@ -1 +1 @@
Subproject commit 20e50a34f0b4aa2efd6b36fed66504999a404a57
Subproject commit 0fcff8e5a31af300511949c4dbafc7be6c0d4dd7

View File

@ -21,8 +21,9 @@ if (!file_exists(__DIR__.'/vendor/autoload.php')) {
} else {
require_once 'vendor/autoload.php';
}
if (file_exists('web_data.php')) {
require_once 'web_data.php';
if (!file_exists('songs.php')) {
copy('https://github.com/danog/MadelineProto/raw/master/songs.php', 'songs.php');
}
echo 'Deserializing MadelineProto from session.madeline...'.PHP_EOL;
@ -88,8 +89,10 @@ Propic art by @magnaluna on [deviantart](https://magnaluna.deviantart.com).", 'p
if (!isset($this->calls[$from_id]) && $message === '/call') {
$call = $this->request_call($from_id);
$this->configureCall($call);
$this->calls[$call->getOtherID()] = $call;
$this->times[$call->getOtherID()] = [time(), $this->messages->sendMessage(['peer' => $call->getOtherID(), 'message' => 'Total running calls: '.count($this->calls).PHP_EOL.PHP_EOL.$call->getDebugString()])['id']];
if ($call->getCallState() !== \danog\MadelineProto\VoIP::CALL_STATE_ENDED) {
$this->calls[$call->getOtherID()] = $call;
$this->times[$call->getOtherID()] = [time(), $this->messages->sendMessage(['peer' => $call->getOtherID(), 'message' => 'Total running calls: '.count($this->calls).PHP_EOL.PHP_EOL.$call->getDebugString()])['id']];
}
}
if (strpos($message, '/program') === 0) {
$time = strtotime(str_replace('/program ', '', $message));
@ -198,8 +201,10 @@ Propic art by @magnaluna on [deviantart](https://magnaluna.deviantart.com).", 'p
try {
$call = $this->request_call($user);
$this->configureCall($call);
$this->calls[$call->getOtherID()] = $call;
$this->times[$call->getOtherID()] = [time(), $this->messages->sendMessage(['peer' => $call->getOtherID(), 'message' => 'Total running calls: '.count($this->calls).PHP_EOL.PHP_EOL.$call->getDebugString()])['id']];
if ($call->getCallState() !== \danog\MadelineProto\VoIP::CALL_STATE_ENDED) {
$this->calls[$call->getOtherID()] = $call;
$this->times[$call->getOtherID()] = [time(), $this->messages->sendMessage(['peer' => $call->getOtherID(), 'message' => 'Total running calls: '.count($this->calls).PHP_EOL.PHP_EOL.$call->getDebugString()])['id']];
}
} catch (\danog\MadelineProto\RPCErrorException $e) {
try {
if ($e->rpc === 'USER_PRIVACY_RESTRICTED') {
@ -262,6 +267,8 @@ Propic art by @magnaluna on [deviantart](https://magnaluna.deviantart.com).", 'p
],
]);
}
} catch (\danog\MadelineProto\Exception $e) {
echo $e;
} catch (\danog\MadelineProto\RPCErrorException $e) {
echo $e;
} catch (\danog\MadelineProto\Exception $e) {
@ -282,7 +289,7 @@ Propic art by @magnaluna on [deviantart](https://magnaluna.deviantart.com).", 'p
}
}
}
$MadelineProto = new \danog\MadelineProto\API('session.madeline', ['secret_chats' => ['accept_chats' => false], 'logger' => ['logger' => 2, 'logger_param' => getcwd().'/MadelineProto.log']]);
$MadelineProto = new \danog\MadelineProto\API('session.madeline', ['secret_chats' => ['accept_chats' => false], 'logger' => ['logger' => 3, 'logger_param' => getcwd().'/MadelineProto.log']]);
$MadelineProto->start();
if (!isset($MadelineProto->programmed_call)) {

View File

@ -25,9 +25,9 @@ $p->addFromString('.git/refs/heads/master', $argv[3]);
$p->setStub('<?php
$backtrace = debug_backtrace();
if (in_array(basename($backtrace[0]["file"]), ["madeline.php", "phar.php"])) {
if (in_array(basename($backtrace[0]["file"]), ["madeline.php", "phar.php"]) && isset($backtrace[1]["file"]) && !defined("PHAR_DEBUG")) {
chdir(dirname($backtrace[1]["file"]));
if (!isset($phar_debug) && ($contents = file_get_contents("https://phar.madelineproto.xyz/phar.php?v=new"))) {
if ($contents = file_get_contents("https://phar.madelineproto.xyz/phar.php?v=new")) {
file_put_contents($backtrace[0]["file"], $contents);
}
}

View File

@ -1,24 +1,69 @@
<?php
if (!file_exists('madeline.phar') || !file_exists('madeline.phar.version') || (file_get_contents('madeline.phar.version') !== file_get_contents('https://phar.madelineproto.xyz/release?v=new') && file_get_contents('https://phar.madelineproto.xyz/release?v=new'))) {
$release = file_get_contents('https://phar.madelineproto.xyz/release?v=new');
$phar = file_get_contents('https://phar.madelineproto.xyz/madeline.phar?v=new');
if ($release && $phar) {
file_put_contents('madeline.phar', $phar);
file_put_contents('madeline.phar.version', $release);
if (PHP_MAJOR_VERSION === 5) {
if (PHP_MINOR_VERSION < 6) {
throw new \Exception('MadelineProto requires at least PHP 5.6 to run');
}
unset($release);
unset($phar);
$newline = PHP_EOL;
if (php_sapi_name() !== 'cli') $newline = '<br>'.$newline;
echo "**********************************************************************$newline";
echo "**********************************************************************$newline$newline";
echo "YOU ARE USING AN OLD AND BUGGED VERSION OF PHP, PLEASE UPDATE TO PHP 7$newline";
echo "PHP 5 USERS WILL NOT RECEIVE MADELINEPROTO UPDATES AND BUGFIXES$newline$newline";
echo "RECOMMENDED VERSION: PHP 7.3$newline";
echo "ALL PHP 7 VERSIONS (7.0, 7.1, 7.2, 7.3) ARE SUPPORTED$newline$newline";
echo "**********************************************************************$newline";
echo "**********************************************************************$newline";
unset($newline);
}
$file = debug_backtrace(0, 1)[0]['file'];
if (file_exists($file)) {
$contents = file_get_contents($file);
// Should've added the self-update code in mtproxyd right away, but it's too late now
if (strpos($contents, 'new \danog\MadelineProto\Server') && in_array($contents, [file_get_contents('https://github.com/danog/MadelineProtoPhar/raw/2270bd9a94d168a5e6731ffd7e61821ea244beff/mtproxyd'), file_get_contents('https://github.com/danog/MadelineProtoPhar/raw/7cabb718ec3ccb79e3c8e3d34f5bccbe3f63b0fd/mtproxyd')]) && ($mtproxyd = file_get_contents('https://phar.madelineproto.xyz/mtproxyd?v=new'))) {
file_put_contents($file, $mtproxyd);
unset($mtproxyd);
function ___install_madeline()
{
if (count(debug_backtrace(0)) === 1) {
die('You must include this file in another PHP script'.PHP_EOL);
}
// MTProxy update
$file = debug_backtrace(0, 1)[0]['file'];
if (file_exists($file)) {
$contents = file_get_contents($file);
if (strpos($contents, 'new \danog\MadelineProto\Server') && in_array($contents, [file_get_contents('https://github.com/danog/MadelineProtoPhar/raw/2270bd9a94d168a5e6731ffd7e61821ea244beff/mtproxyd'), file_get_contents('https://github.com/danog/MadelineProtoPhar/raw/7cabb718ec3ccb79e3c8e3d34f5bccbe3f63b0fd/mtproxyd')]) && ($mtproxyd = file_get_contents('https://phar.madelineproto.xyz/mtproxyd?v=new'))) {
file_put_contents($file, $mtproxyd);
return;
}
}
// MadelineProto update
$release_template = 'https://phar.madelineproto.xyz/release%s?v=new';
$phar_template = 'https://phar.madelineproto.xyz/madeline%s.phar?v=new';
$release_branch = defined('MADELINE_BRANCH') ? '-'.MADELINE_BRANCH : '';
$release_default = '';
if (PHP_MAJOR_VERSION === 5) {
$release_branch = '5'.$release_branch;
$release_default = '5';
}
if (!($release = @file_get_contents(sprintf($release_template, $release_branch)))) {
if (!($release = @file_get_contents(sprintf($release_template, $release_default)))) {
return;
}
$release_branch = $release_default;
}
if (!file_exists('madeline.phar') || !file_exists('madeline.phar.version') || file_get_contents('madeline.phar.version') !== $release) {
$phar = file_get_contents(sprintf($phar_template, $release_branch));
if ($phar) {
file_put_contents('madeline.phar', $phar);
file_put_contents('madeline.phar.version', $release);
}
}
}
___install_madeline();
require 'madeline.phar';

View File

@ -1,6 +1,9 @@
<?php
$songs = glob('*raw');
if (!$songs) {
die('No songs defined! Convert some songs as described in https://docs.madelineproto.xyz/docs/CALLS.html#playing-mp3-files');
}
$songs_length = count($songs);
for ($x = 0; $x < $songs_length; $x++) {

View File

@ -1,9 +1,26 @@
<?php
/**
* BigInteger placeholder for deserialization.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace phpseclib\Math;
if (PHP_MAJOR_VERSION < 7 && !(class_exists('\\Phar') && \Phar::running())) {
throw new \Exception('MadelineProto requires php 7 to run');
throw new \Exception('MadelineProto requires php 7 to run natively, use phar.madelineproto.xyz to run on PHP 5.6');
}
if (defined('HHVM_VERSION')) {
$engines = [['PHP64', ['OpenSSL']], ['BCMath', ['OpenSSL']], ['PHP32', ['OpenSSL']]];

View File

@ -1,5 +1,20 @@
<?php
/**
* CustomHTTPProxy module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
class CustomHTTPProxy implements \danog\MadelineProto\Proxy
{
private $sock;

View File

@ -1,15 +1,20 @@
<?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/>.
*/
/**
* HttpProxy module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
class HttpProxy implements \danog\MadelineProto\Proxy
{
private $domain;

View File

@ -1,14 +1,20 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* Socket module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
class FSocket
{
private $sock;

View File

@ -1,15 +1,20 @@
<?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/>.
*/
/**
* SocksProxy module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
class SocksProxy implements \danog\MadelineProto\Proxy
{
private $domain;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* API module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
@ -103,6 +109,19 @@ class API extends APIFactory
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
}
public function async($async)
{
$this->async = $async;
foreach ($this->API->get_methods_namespaced() as $pair) {
$namespace = key($pair);
$this->{$namespace}->async = $async;
}
if ($this->API->event_handler && class_exists($this->API->event_handler) && is_subclass_of($this->API->event_handler, '\danog\MadelineProto\EventHandler')) {
$this->API->setEventHandler($this->API->event_handler);
}
}
public function __wakeup()
{
$this->APIFactory();
@ -161,13 +180,61 @@ class API extends APIFactory
unset($this->API->storage[$name]);
}
private function from_camel_case($input)
{
preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches);
$ret = $matches[0];
foreach ($ret as &$match) {
$match = $match == strtoupper($match) ? strtolower($match) : lcfirst($match);
}
return implode('_', $ret);
}
public function APIFactory()
{
if ($this->API) {
foreach ($this->API->get_method_namespaces() as $namespace) {
$this->{$namespace} = new APIFactory($namespace, $this->API);
}
$methods = get_class_methods($this->API);
foreach ($methods as $key => $method) {
if ($method == 'method_call_async_read') {
unset($methods[array_search('method_call', $methods)]);
} elseif (stripos($method, 'async') !== false) {
if (strpos($method, '_async') !== false) {
unset($methods[array_search(str_ireplace('_async', '', $method), $methods)]);
} else {
unset($methods[array_search(str_ireplace('async', '', $method), $methods)]);
}
}
}
$this->methods = [];
foreach ($methods as $method) {
$actual_method = $method;
if ($method == 'method_call_async_read') {
$method = 'method_call';
} elseif (stripos($method, 'async') !== false) {
if (strpos($method, '_async') !== false) {
$method = str_ireplace('_async', '', $method);
} else {
$method = str_ireplace('async', '', $method);
}
}
$this->methods[strtolower($method)] = [$this->API, $actual_method];
if (strpos($method, '_') !== false) {
$this->methods[strtolower(str_replace('_', '', $method))] = [$this->API, $actual_method];
} else {
$this->methods[strtolower($this->from_camel_case($method))] = [$this->API, $actual_method];
}
}
$this->API->wrapper = $this;
if ($this->API->event_handler && class_exists($this->API->event_handler) && is_subclass_of($this->API->event_handler, '\danog\MadelineProto\EventHandler')) {
$this->API->setEventHandler($this->API->event_handler);
}
}
}

View File

@ -1,18 +1,26 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* APIFactory module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
use Amp\Promise;
class APIFactory
{
/**
@ -105,10 +113,14 @@ class APIFactory
* @var auth
*/
public $auth;
use Tools;
public $namespace = '';
public $API;
public $lua = false;
public $async = false;
protected $methods = [];
public function __construct($namespace, $API)
{
@ -132,9 +144,7 @@ class APIFactory
$this->API->__construct($this->API->settings);
}
$this->API->get_config([], ['datacenter' => $this->API->datacenter->curdc]);
$aargs = isset($arguments[1]) && is_array($arguments[1]) ? $arguments[1] : [];
$aargs['datacenter'] = $this->API->datacenter->curdc;
$aargs['apifactory'] = true;
if (isset($this->session) && !is_null($this->session) && time() - $this->serialized > $this->API->settings['serialization']['serialization_interval']) {
Logger::log("Didn't serialize in a while, doing that now...");
$this->serialize($this->session);
@ -142,13 +152,15 @@ class APIFactory
if ($name !== 'accept_tos' && $name !== 'decline_tos') {
$this->API->check_tos();
}
$lower_name = strtolower($name);
if ($this->lua === false) {
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] : [], $aargs);
return $this->namespace !== '' || !isset($this->methods[$lower_name]) ? $this->__mtproto_call($this->namespace.$name, $arguments) : $this->__api_call($lower_name, $arguments);
}
try {
$deserialized = 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] : [], $aargs);
$deserialized = $this->namespace !== '' || !isset($this->methods[$lower_name]) ? $this->__mtproto_call($this->namespace.$name, $arguments) : $this->__api_call($lower_name, $arguments);
Lua::convert_objects($deserialized);
return $deserialized;
@ -168,4 +180,40 @@ class APIFactory
return ['error_code' => $e->getCode(), 'error' => $e->getMessage()];
}
}
public function __api_call($name, $arguments)
{
$result = $this->methods[$name](...$arguments);
if (is_object($result) && ($result instanceof \Generator || $result instanceof Promise)) {
$async = isset(end($arguments)['async']) ? end($arguments)['async'] : $this->async;
if ($async && ($name !== 'loop' || isset(end($arguments)['async']))) {
return $result;
} else {
return $this->wait($result);
}
}
return $result;
}
public function __mtproto_call($name, $arguments)
{
if (array_key_exists($name, \danog\MadelineProto\MTProto::DISALLOWED_METHODS)) {
throw new \danog\MadelineProto\Exception(\danog\MadelineProto\MTProto::DISALLOWED_METHODS[$name], 0, null, 'MadelineProto', 1);
}
$aargs = isset($arguments[1]) && is_array($arguments[1]) ? $arguments[1] : [];
$aargs['datacenter'] = $this->API->datacenter->curdc;
$aargs['apifactory'] = true;
$args = isset($arguments[0]) && is_array($arguments[0]) ? $arguments[0] : [];
$async = isset(end($arguments)['async']) ? end($arguments)['async'] : $this->async;
$res = $this->API->method_call_async_read($name, $args, $aargs);
if ($async) {
return $res;
} else {
return $this->wait($res);
}
}
}

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* Absolute module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* AnnotationsBuilder module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -0,0 +1,69 @@
<?php
/**
* Async parameters class.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Async;
use Amp\Success;
/**
* Async parameters class.
*
* Manages asynchronous generation of method parameters
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class AsyncParameters extends Parameters
{
private $callable;
private $refetchable = true;
public function __construct(callable $callable, bool $refetchable = true)
{
$this->callable = $callable;
$this->refetchable = $refetchable;
}
public function setRefetchable(bool $refetchable)
{
$this->refetchable = $refetchable;
}
public function setCallable(callable $callable)
{
$this->callable = $callable;
}
public function isRefetchable(): bool
{
return $this->refetchable;
}
public function getParameters(): \Generator
{
$callable = $this->callable;
$params = $callable();
if ($params instanceof \Generator) {
$params = yield coroutine($params);
} else {
$params = yield new Success($params);
}
return $params;
}
}

View File

@ -0,0 +1,79 @@
<?php
/**
* Parameters module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Async;
use Amp\Promise;
use function Amp\call;
/**
* Parameters module.
*
* Manages asynchronous generation of method parameters
*
* @author Daniil Gentili <daniil@daniil.it>
*/
abstract class Parameters
{
private $fetched = false;
private $params = [];
/**
* Fetch parameters asynchronously.
*
* @return Promise
*/
public function fetchParameters(): Promise
{
return call([$this, 'fetchParametersAsync']);
}
/**
* Fetch parameters asynchronously.
*
* @return \Generator
*/
public function fetchParametersAsync(): \Generator
{
$refetchable = $this->isRefetchable();
if ($this->fetched && !$refetchable) {
return $this->params;
}
$params = yield call([$this, 'getParameters']);
if (!$refetchable) {
$this->params = $params;
}
return $params;
}
/**
* Check if the parameters can be fetched more than once.
*
* @return bool
*/
abstract public function isRefetchable(): bool;
/**
* Gets the parameters asynchronously.
*
* @return \Generator
*/
abstract public function getParameters(): \Generator;
}

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* Bug74586Exception module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -1,27 +1,38 @@
<?php
/*
Copyright 2016-2018 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/>.
/**
* CombinedAPI module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
use Amp\Loop;
use function Amp\Promise\all;
class CombinedAPI
{
use \danog\Serializable;
use Tools;
public $session;
public $instance_paths = [];
public $instances = [];
public $timeout = 5;
public $serialization_interval = 30;
public $serialized = 0;
protected $async;
public function __magic_construct($session, $paths = [])
{
@ -153,6 +164,7 @@ class CombinedAPI
public $event_handler;
private $event_handler_instance;
private $event_handler_methods = [];
public function setEventHandler($event_handler)
{
@ -168,23 +180,41 @@ class CombinedAPI
} elseif ($this->event_handler_instance) {
$this->event_handler_instance->__construct($this);
}
if (method_exists($this->event_handler_instance, 'onLoop')) {
$this->loop_callback = [$this->event_handler_instance, 'onLoop'];
$this->event_handler_methods = [];
foreach (\get_class_methods($this->event_handler) as $method) {
if ($method === 'onLoop') {
$this->loop_callback = [$this->event_handler_instance, 'onLoop'];
} elseif ($method === 'onAny') {
foreach (end($this->instances)->API->constructors->by_id as $id => $constructor) {
if ($constructor['type'] === 'Update' && !isset($this->event_handler_methods[$constructor['predicate']])) {
$this->event_handler_methods[$constructor['predicate']] = [$this->event_handler_instance, 'onAny'];
}
}
} else {
$method_name = lcfirst(substr($method, 2));
$this->event_handler_methods[$method_name] = [$this->event_handler_instance, $method];
}
}
}
public function event_update_handler($update, $instance)
{
$method_name = 'on'.ucfirst($update['_']);
if (method_exists($this->event_handler_instance, $method_name)) {
$this->event_handler_instance->$method_name($update, $instance);
} elseif (method_exists($this->event_handler_instance, 'onAny')) {
$this->event_handler_instance->onAny($update, $instance);
if (isset($this->event_handler_methods[$update['_']])) {
return $this->event_handler_methods[$update['_']]($update, $instance);
}
}
private $loop_callback;
public function async($async)
{
$this->async = $async;
foreach ($this->instances as $instance) {
$instance->async($async);
}
}
public function setLoopCallback($callback)
{
$this->loop_callback = $callback;
@ -207,89 +237,31 @@ class CombinedAPI
});
}
}
$loops = [];
foreach ($this->instances as $path => $instance) {
if ($instance->API->authorized !== MTProto::LOGGED_IN) {
continue;
}
if (!$instance->API->settings['updates']['handle_updates']) {
$instance->API->settings['updates']['handle_updates'] = true;
$instance->API->datacenter->sockets[$instance->API->settings['connection_settings']['default_dc']]->updater->start();
}
ksort($instance->API->updates);
foreach ($instance->API->updates as $key => $value) {
unset($instance->API->updates[$key]);
$this->event_update_handler($value, $path);
}
}
\danog\MadelineProto\Logger::log('Started update loop', \danog\MadelineProto\Logger::NOTICE);
while (true) {
$read = [];
$write = [];
$except = [];
foreach ($this->instances as $path => $instance) {
if ($instance->API->authorized !== MTProto::LOGGED_IN) {
continue;
}
if (time() - $instance->API->last_getdifference > $instance->API->settings['updates']['getdifference_interval']) {
$instance->API->get_updates_difference();
}
if (isset($instance->session) && !is_null($instance->session) && time() - $instance->serialized > $instance->API->settings['serialization']['serialization_interval']) {
$instance->API->logger->logger("Didn't serialize in a while, doing that now...");
$instance->serialize($instance->session);
}
foreach ($instance->API->datacenter->sockets as $id => $connection) {
$read[$id.'-'.$path] = $connection->getSocket();
}
}
if (time() - $this->serialized > $this->serialization_interval) {
\danog\MadelineProto\Logger::log('Serializing combined event handler');
$this->serialize();
}
try {
\Socket::select($read, $write, $except, $this->timeout);
if (count($read)) {
foreach (array_keys($read) as $id) {
list($dc, $path) = explode('-', $id, 2);
if (($error = $this->instances[$path]->API->recv_message($dc)) !== true) {
if ($error === -404) {
if ($this->instances[$path]->API->datacenter->sockets[$dc]->temp_auth_key !== null) {
$this->instances[$path]->API->logger->logger('WARNING: Resetting auth key...', \danog\MadelineProto\Logger::WARNING);
$this->instances[$path]->API->datacenter->sockets[$dc]->temp_auth_key = null;
$this->instances[$path]->API->init_authorization();
throw new \danog\MadelineProto\Exception('I had to recreate the temporary authorization key');
}
}
throw new \danog\MadelineProto\RPCErrorException($error, $error);
}
$only_updates = $this->instances[$path]->API->handle_messages($dc);
}
}
} catch (\danog\MadelineProto\NothingInTheSocketException $e) {
foreach ($this->instances as $instance) {
$instance->get_updates_difference();
}
} catch (\danog\MadelineProto\RPCErrorException $e) {
if ($e->rpc !== 'RPC_CALL_FAIL') {
throw $e;
}
} catch (\danog\MadelineProto\Exception $e) {
$this->instances[$path]->API->connect_to_all_dcs();
}
foreach ($this->instances as $path => $instance) {
ksort($instance->API->updates);
foreach ($instance->API->updates as $key => $value) {
unset($instance->API->updates[$key]);
$this->event_update_handler($value, $path);
}
}
$instance->setCallback(function ($update) use ($path) {
return $this->event_update_handler($update, $path);
});
if ($this->loop_callback !== null) {
$callback = $this->loop_callback;
$callback();
$instance->setLoopCallback($this->loop_callback);
}
$loops[] = $this->call($instance->loop(0, ['async' => true]));
}
Loop::repeat($this->serialization_interval * 1000, function () {
\danog\MadelineProto\Logger::log('Serializing combined event handler');
$this->serialize();
});
\danog\MadelineProto\Logger::log('Started update loop', \danog\MadelineProto\Logger::NOTICE);
$this->wait(all($loops));
}
}

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* CombinedEventHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -1,37 +1,60 @@
<?php
/*
Copyright 2016-2018 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.
The PWRTelegram API 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/>.
*/
/**
* Connection module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
use Amp\Deferred;
use Amp\Promise;
use danog\MadelineProto\Loop\Connection\CheckLoop;
use danog\MadelineProto\Loop\Connection\HttpWaitLoop;
use danog\MadelineProto\Loop\Connection\ReadLoop;
use danog\MadelineProto\Loop\Connection\UpdateLoop;
use danog\MadelineProto\Loop\Connection\WriteLoop;
use danog\MadelineProto\MTProtoTools\Crypt;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\MTProtoTools\MsgIdHandler;
use danog\MadelineProto\Stream\MTProtoTools\SeqNoHandler;
/**
* Manages connection to telegram servers.
* Connection class.
*
* Manages connection to Telegram datacenters
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class Connection
{
use Crypt;
use MsgIdHandler;
use SeqNoHandler;
use \danog\Serializable;
use \danog\MadelineProto\Tools;
use Tools;
const API_ENDPOINT = 0;
const VOIP_UDP_REFLECTOR_ENDPOINT = 1;
const VOIP_TCP_REFLECTOR_ENDPOINT = 2;
const VOIP_UDP_P2P_ENDPOINT = 3;
const VOIP_UDP_LAN_ENDPOINT = 4;
public $sock = null;
public $protocol = null;
public $ip = null;
public $port = null;
public $timeout = null;
public $parsed = [];
const PENDING_MAX = 2000000000;
public $stream;
public $time_delta = 0;
public $type = 0;
public $peer_tag;
@ -40,372 +63,227 @@ class Connection
public $session_id;
public $session_out_seq_no = 0;
public $session_in_seq_no = 0;
public $ipv6 = false;
public $incoming_messages = [];
public $outgoing_messages = [];
public $new_incoming = [];
public $new_outgoing = [];
public $pending_outgoing = [];
public $pending_outgoing_key = 0;
public $max_incoming_id;
public $max_outgoing_id;
public $proxy = '\\Socket';
public $extra = [];
public $obfuscated = [];
public $authorized = false;
public $call_queue = [];
public $object_queue = [];
public $ack_queue = [];
public $i = [];
public $must_open = true;
public $last_recv = 0;
public $last_http_wait = 0;
public function __magic_construct($proxy, $extra, $ip, $port, $protocol, $timeout, $ipv6)
public $datacenter;
public $API;
public $resumeWriterDeferred;
public $ctx;
public $pendingCheckWatcherId;
public $http_req_count = 0;
public $http_res_count = 0;
public function getCtx()
{
// Can use:
/*
- tcp_full
- tcp_abridged
- tcp_intermediate
- http
- https
- udp
*/
if ($proxy === '\\MTProxySocket') {
$proxy = '\\Socket';
$protocol = 'obfuscated2';
return $this->ctx;
}
/**
* Connect function.
*
* Connects to a telegram DC using the specified protocol, proxy and connection parameters
*
* @param string $proxy Proxy class name
*
* @internal
*
* @return \Amp\Promise
*/
public function connect(ConnectionContext $ctx): Promise
{
return $this->call($this->connectAsync($ctx));
}
/**
* Connect function.
*
* Connects to a telegram DC using the specified protocol, proxy and connection parameters
*
* @param string $proxy Proxy class name
*
* @internal
*
* @return \Amp\Promise
*/
public function connectAsync(ConnectionContext $ctx): \Generator
{
$this->API->logger->logger("Trying connection via $ctx", \danog\MadelineProto\Logger::WARNING);
$this->ctx = $ctx->getCtx();
$this->datacenter = $ctx->getDc();
$this->stream = yield $ctx->getStream();
if (isset($this->old)) {
unset($this->old);
}
$this->protocol = $protocol;
$this->timeout = $timeout;
$this->ipv6 = $ipv6;
$this->ip = $ip;
$this->port = $port;
$this->proxy = $proxy;
$this->extra = $extra;
if (($has_proxy = !in_array($proxy, ['\\MTProxySocket', '\\Socket'])) && !isset(class_implements($proxy)['danog\\MadelineProto\\Proxy'])) {
throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['proxy_class_invalid']);
if (!isset($this->writer)) {
$this->writer = new WriteLoop($this->API, $this->datacenter);
}
switch ($this->protocol) {
case 'tcp_abridged':
$this->sock = new $proxy($ipv6 ? \AF_INET6 : \AF_INET, \SOCK_STREAM, getprotobyname('tcp'));
if ($has_proxy && $this->extra !== []) {
$this->sock->setExtra($this->extra);
}
$this->sock->setOption(\SOL_SOCKET, \SO_RCVTIMEO, $timeout);
$this->sock->setOption(\SOL_SOCKET, \SO_SNDTIMEO, $timeout);
if (!$this->sock->connect($ip, $port)) {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['socket_con_error']);
}
$this->sock->setBlocking(true);
$this->write(chr(239));
break;
case 'tcp_intermediate':
$this->sock = new $proxy($ipv6 ? \AF_INET6 : \AF_INET, \SOCK_STREAM, getprotobyname('tcp'));
if ($has_proxy && $this->extra !== []) {
$this->sock->setExtra($this->extra);
}
$this->sock->setOption(\SOL_SOCKET, \SO_RCVTIMEO, $timeout);
$this->sock->setOption(\SOL_SOCKET, \SO_SNDTIMEO, $timeout);
if (!$this->sock->connect($ip, $port)) {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['socket_con_error']);
}
$this->sock->setBlocking(true);
$this->write(str_repeat(chr(238), 4));
break;
case 'tcp_full':
$this->sock = new $proxy($ipv6 ? \AF_INET6 : \AF_INET, \SOCK_STREAM, getprotobyname('tcp'));
if ($has_proxy && $this->extra !== []) {
$this->sock->setExtra($this->extra);
}
$this->sock->setOption(\SOL_SOCKET, \SO_RCVTIMEO, $timeout);
$this->sock->setOption(\SOL_SOCKET, \SO_SNDTIMEO, $timeout);
if (!$this->sock->connect($ip, $port)) {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['socket_con_error']);
}
$this->sock->setBlocking(true);
$this->out_seq_no = -1;
$this->in_seq_no = -1;
break;
case 'obfuscated2':
$this->sock = new $proxy($ipv6 ? \AF_INET6 : \AF_INET, \SOCK_STREAM, getprotobyname('tcp'));
if ($has_proxy && $this->extra !== []) {
$this->sock->setExtra($this->extra);
}
$this->sock->setOption(\SOL_SOCKET, \SO_RCVTIMEO, $timeout);
$this->sock->setOption(\SOL_SOCKET, \SO_SNDTIMEO, $timeout);
if (!$this->sock->connect($ip, $port)) {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['socket_con_error']);
}
$this->sock->setBlocking(true);
do {
$random = $this->random(64);
} while (in_array(substr($random, 0, 4), ['PVrG', 'GET ', 'POST', 'HEAD', str_repeat(chr(238), 4)]) || $random[0] === chr(0xef) || substr($random, 4, 4) === "\0\0\0\0");
$random[56] = $random[57] = $random[58] = $random[59] = chr(0xef);
if (!isset($this->reader)) {
$this->reader = new ReadLoop($this->API, $this->datacenter);
}
if (!isset($this->checker)) {
$this->checker = new CheckLoop($this->API, $this->datacenter);
}
if (!isset($this->waiter)) {
$this->waiter = new HttpWaitLoop($this->API, $this->datacenter);
}
if (!isset($this->updater)) {
$this->updater = new UpdateLoop($this->API, $this->datacenter);
}
foreach ($this->new_outgoing as $message_id) {
if ($this->outgoing_messages[$message_id]['unencrypted']) {
$promise = $this->outgoing_messages[$message_id]['promise'];
\Amp\Loop::defer(function () use ($promise) {
$promise->fail(new Exception('Restart'));
});
unset($this->new_outgoing[$message_id]);
unset($this->outgoing_messages[$message_id]);
}
}
$this->http_req_count = 0;
$this->http_res_count = 0;
$reversed = strrev(substr($random, 8, 48));
$this->obfuscated = ['encryption' => new \phpseclib\Crypt\AES('ctr'), 'decryption' => new \phpseclib\Crypt\AES('ctr')];
$this->obfuscated['encryption']->enableContinuousBuffer();
$this->obfuscated['decryption']->enableContinuousBuffer();
$this->obfuscated['encryption']->setKey(substr($random, 8, 32));
$this->obfuscated['encryption']->setIV(substr($random, 40, 16));
$this->obfuscated['decryption']->setKey(substr($reversed, 0, 32));
$this->obfuscated['decryption']->setIV(substr($reversed, 32, 16));
$random = substr_replace($random, substr(@$this->obfuscated['encryption']->encrypt($random), 56, 8), 56, 8);
$wrote = 0;
if (($wrote += $this->sock->write($random)) !== 64) {
while (($wrote += $this->sock->write(substr($what, $wrote))) !== 64) {
}
}
break;
case 'http':
case 'https':
$this->parsed = parse_url($ip);
if ($this->parsed['host'][0] === '[') {
$this->parsed['host'] = substr($this->parsed['host'], 1, -1);
}
if (strpos($this->protocol, 'https') === 0 && $proxy === '\\Socket') {
$proxy = '\\FSocket';
}
$this->sock = new $proxy($ipv6 ? \AF_INET6 : \AF_INET, \SOCK_STREAM, strpos($this->protocol, 'https') === 0 ? PHP_INT_MAX : getprotobyname('tcp'));
if ($has_proxy) {
if ($this->extra !== []) {
$this->sock->setExtra($this->extra);
}/*
if ($this->protocol === 'http') {
$this->parsed['path'] = $this->parsed['scheme'].'://'.$this->parsed['host'].
$this->parsed['path'];
$port = 80;
} elseif ($this->protocol === 'https') {
$port = 443;
}*/
}
$this->sock->setOption(\SOL_SOCKET, \SO_RCVTIMEO, $timeout);
$this->sock->setOption(\SOL_SOCKET, \SO_SNDTIMEO, $timeout);
if (!$this->sock->connect($this->parsed['host'], $port)) {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['socket_con_error']);
}
$this->sock->setBlocking(true);
break;
case 'udp':
throw new Exception(\danog\MadelineProto\Lang::$current_lang['protocol_not_implemented']);
default:
throw new Exception(\danog\MadelineProto\Lang::$current_lang['protocol_invalid']);
$this->writer->start();
$this->reader->start();
if (!$this->checker->start()) {
$this->checker->resume();
}
$this->waiter->start();
if ($this->datacenter === $this->API->settings['connection_settings']['default_dc']) {
$this->updater->start();
}
}
public function __destruct()
public function sendMessage($message, $flush = true): Promise
{
switch ($this->protocol) {
case 'tcp_abridged':
case 'tcp_intermediate':
case 'tcp_full':
case 'http':
case 'https':
case 'obfuscated2':
try {
unset($this->sock);
} catch (\danog\MadelineProto\Exception $e) {
}
break;
case 'udp':
throw new Exception(\danog\MadelineProto\Lang::$current_lang['protocol_not_implemented']);
default:
throw new Exception(\danog\MadelineProto\Lang::$current_lang['protocol_invalid']);
return $this->call($this->sendMessageGenerator($message, $flush));
}
public function sendMessageGenerator($message, $flush = true): \Generator
{
$deferred = new Deferred();
if (!isset($message['serialized_body'])) {
$body = is_object($message['body']) ? yield $message['body'] : $message['body'];
$refresh_next = isset($message['refresh_next']) && $message['refresh_next'];
//$refresh_next = true;
if ($refresh_next) {
$this->API->referenceDatabase->refreshNext(true);
}
if ($message['method']) {
$body = $this->API->serialize_method($message['_'], $body);
} else {
$body = $this->API->serialize_object(['type' => $message['_']], $body, $message['_']);
}
if ($refresh_next) {
$this->API->referenceDatabase->refreshNext(false);
}
$message['serialized_body'] = $body;
}
$message['send_promise'] = $deferred;
$this->pending_outgoing[$this->pending_outgoing_key++] = $message;
$this->pending_outgoing_key %= self::PENDING_MAX;
if ($flush) {
$this->writer->resume();
}
return yield $deferred->promise();
}
public function setExtra($extra)
{
$this->API = $extra;
}
public function disconnect()
{
$this->old = true;
foreach (['reader', 'writer', 'checker', 'waiter', 'updater'] as $loop) {
if (isset($this->{$loop}) && $this->{$loop}) {
$this->{$loop}->signal($loop === 'reader' ? new NothingInTheSocketException() : true);
}
}
if ($this->stream) {
$this->stream->disconnect();
}
}
public function close_and_reopen()
public function reconnect(): Promise
{
$this->__destruct();
\danog\MadelineProto\Logger::log('Reopening...', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->must_open = true;
return $this->call($this->reconnectAsync());
}
public function reconnectAsync(): \Generator
{
$this->API->logger->logger('Reconnecting');
$this->disconnect();
yield $this->API->datacenter->dc_connect_async($this->ctx->getDc());
}
public function hasPendingCalls()
{
$API = $this->API;
$datacenter = $this->datacenter;
$dc_config_number = isset($API->settings['connection_settings'][$datacenter]) ? $datacenter : 'all';
$timeout = $API->settings['connection_settings'][$dc_config_number]['timeout'];
foreach ($this->new_outgoing as $message_id) {
if (isset($this->outgoing_messages[$message_id]['sent'])
&& $this->outgoing_messages[$message_id]['sent'] + $timeout < time()
&& ($this->temp_auth_key === null) === $this->outgoing_messages[$message_id]['unencrypted']
&& $this->outgoing_messages[$message_id]['_'] !== 'msgs_state_req'
) {
return true;
}
}
return false;
}
public function getName(): string
{
return __CLASS__;
}
/**
* Sleep function.
*
* @internal
*
* @return array
*/
public function __sleep()
{
return ['proxy', 'extra', 'protocol', 'ip', 'port', 'timeout', 'parsed', 'time_delta', 'peer_tag', 'temp_auth_key', 'auth_key', 'session_id', 'session_out_seq_no', 'session_in_seq_no', 'ipv6', 'incoming_messages', 'outgoing_messages', 'new_incoming', 'new_outgoing', 'max_incoming_id', 'max_outgoing_id', 'obfuscated', 'authorized', 'object_queue', 'ack_queue'];
return ['peer_tag', 'temp_auth_key', 'auth_key', 'session_id', 'session_out_seq_no', 'session_in_seq_no', 'max_incoming_id', 'max_outgoing_id', 'authorized', 'ack_queue'];
}
public function __wakeup()
{
$keys = array_keys((array) get_object_vars($this));
if (count($keys) !== count(array_unique($keys))) {
throw new Bug74586Exception();
}
$this->time_delta = 0;
}
public function write($what, $length = null)
{
if ($length !== null) {
$what = substr($what, 0, $length);
} else {
$length = strlen($what);
}
switch ($this->protocol) {
case 'obfuscated2':
$what = @$this->obfuscated['encryption']->encrypt($what);
case 'tcp_abridged':
case 'tcp_intermediate':
case 'tcp_full':
case 'http':
case 'https':
return $this->sock->write($what);
case 'udp':
throw new Exception(\danog\MadelineProto\Lang::$current_lang['protocol_not_implemented']);
default:
throw new Exception(\danog\MadelineProto\Lang::$current_lang['protocol_invalid']);
}
}
public function read($length)
{
//\danog\MadelineProto\Logger::log("Asked to read $length", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
switch ($this->protocol) {
case 'obfuscated2':
return @$this->obfuscated['decryption']->encrypt($this->sock->read($length));
case 'tcp_abridged':
case 'tcp_intermediate':
case 'tcp_full':
case 'http':
case 'https':
return $this->sock->read($length);
case 'udp':
throw new Exception(\danog\MadelineProto\Lang::$current_lang['protocol_not_implemented']);
default:
throw new Exception(\danog\MadelineProto\Lang::$current_lang['protocol_invalid']);
}
}
public function read_message()
{
switch ($this->protocol) {
case 'tcp_full':
$packet_length_data = $this->read(4);
$packet_length = unpack('V', $packet_length_data)[1];
$packet = $this->read($packet_length - 4);
if (strrev(hash('crc32b', $packet_length_data.substr($packet, 0, -4), true)) !== substr($packet, -4)) {
throw new Exception('CRC32 was not correct!');
}
$this->in_seq_no++;
$in_seq_no = unpack('V', substr($packet, 0, 4))[1];
if ($in_seq_no != $this->in_seq_no) {
throw new Exception('Incoming seq_no mismatch');
}
return substr($packet, 4, $packet_length - 12);
case 'tcp_intermediate':
return $this->read(unpack('V', $this->read(4))[1]);
case 'obfuscated2':
case 'tcp_abridged':
$packet_length = ord($this->read(1));
return $this->read($packet_length < 127 ? $packet_length << 2 : unpack('V', $this->read(3)."\0")[1] << 2);
case 'http':
case 'https':
$response = $this->read_http_payload();
if ($response['code'] !== 200) {
Logger::log($response['body']);
return $this->pack_signed_int(-$response['code']);
//throw new Exception($response['description'], $response['code']);
}
$close = $response['protocol'] === 'HTTP/1.0';
if (isset($response['headers']['connection'])) {
$close = strtolower($response['headers']['connection']) === 'close';
}
if ($close) {
$this->close_and_reopen();
}
return $response['body'];
case 'udp':
throw new Exception(\danog\MadelineProto\Lang::$current_lang['protocol_not_implemented']);
default:
throw new Exception(\danog\MadelineProto\Lang::$current_lang['protocol_invalid']);
}
}
public function send_message($message)
{
$this->must_open = $this->must_open || $this->sock === null || $this->sock->getResource() === null;
if ($this->must_open) {
$this->__construct($this->proxy, $this->extra, $this->ip, $this->port, $this->protocol, $this->timeout, $this->ipv6);
$this->must_open = false;
}
switch ($this->protocol) {
case 'tcp_full':
$this->out_seq_no++;
$step1 = pack('VV', strlen($message) + 12, $this->out_seq_no).$message;
$step2 = $step1.strrev(hash('crc32b', $step1, true));
$this->write($step2);
break;
case 'tcp_intermediate':
$this->write(pack('V', strlen($message)).$message);
break;
case 'obfuscated2':
case 'tcp_abridged':
$len = strlen($message) / 4;
if ($len < 127) {
$message = chr($len).$message;
} else {
$message = chr(127).substr(pack('V', $len), 0, 3).$message;
}
$this->write($message);
break;
case 'http':
case 'https':
$this->write('POST '.$this->parsed['path']." HTTP/1.1\r\nHost: ".$this->parsed['host'].':'.$this->port."\r\n".$this->sock->getProxyHeaders()."Content-Type: application/x-www-form-urlencoded\r\nConnection: keep-alive\r\nKeep-Alive: timeout=100000, max=10000000\r\nContent-Length: ".strlen($message)."\r\n\r\n".$message);
break;
case 'udp':
throw new Exception(\danog\MadelineProto\Lang::$current_lang['protocol_not_implemented']);
default:
throw new Exception(\danog\MadelineProto\Lang::$current_lang['protocol_invalid']);
}
}
public function read_http_line()
{
$line = $lastchar = $curchar = '';
while ($lastchar.$curchar !== "\r\n") {
$line .= $lastchar;
$lastchar = $curchar;
$curchar = $this->sock->read(1);
}
return $line;
}
public function read_http_payload()
{
list($protocol, $code, $description) = explode(' ', $this->read_http_line(), 3);
list($protocol, $protocol_version) = explode('/', $protocol);
if ($protocol !== 'HTTP') {
throw new \danog\MadelineProto\Exception('Wrong protocol');
}
$code = (int) $code;
$headers = [];
while (strlen($current_header = $this->read_http_line())) {
$current_header = explode(':', $current_header, 2);
$headers[strtolower($current_header[0])] = trim($current_header[1]);
}
$read = '';
if (isset($headers['content-length'])) {
$read = $this->sock->read((int) $headers['content-length']);
}/* elseif (isset($headers['transfer-encoding']) && $headers['transfer-encoding'] === 'chunked') {
do {
$length = hexdec($this->read_http_line());
$read .= $this->sock->read($length);
$this->read_http_line();
} while ($length);
}*/
return ['protocol' => $protocol, 'protocol_version' => $protocol_version, 'code' => $code, 'description' => $description, 'body' => $read, 'headers' => $headers];
}
public function getSocket()
{
return $this->sock;
$this->pending_outgoing = [];
$this->new_outgoing = [];
$this->new_incoming = [];
$this->outgoing_messages = [];
$this->incoming_messages = [];
}
}

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* Conversion module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -0,0 +1,156 @@
<?php
/**
* Coroutine (modified version of AMP Coroutine).
*
* The MIT License (MIT)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* @copyright 2015-2018 amphp
* @copyright 2016 PHP Asynchronous Interoperability Group
* @license https://opensource.org/licenses/MIT MIT
*/
namespace danog\MadelineProto;
use Amp\Failure;
use Amp\Internal;
use Amp\Promise;
use Amp\Success;
/**
* Creates a promise from a generator function yielding promises.
*
* When a promise is yielded, execution of the generator is interrupted until the promise is resolved. A success
* value is sent into the generator, while a failure reason is thrown into the generator. Using a coroutine,
* asynchronous code can be written without callbacks and be structured like synchronous code.
*/
final class Coroutine implements Promise
{
use Internal\Placeholder;
/** @var \Generator */
private $generator;
/** @var callable(\Throwable|null $exception, mixed $value): void */
private $onResolve;
/** @var bool Used to control iterative coroutine continuation. */
private $immediate = true;
/** @var \Throwable|null Promise failure reason when executing next coroutine step, null at all other times. */
private $exception;
/** @var mixed Promise success value when executing next coroutine step, null at all other times. */
private $value;
/**
* @param \Generator $generator
*/
public function __construct(\Generator $generator)
{
$this->generator = $generator;
try {
$yielded = $this->generator->current();
if (!$yielded instanceof Promise) {
if (!$this->generator->valid()) {
$this->resolve($this->generator->getReturn());
return;
}
$yielded = $this->transform($yielded);
}
} catch (\Throwable $exception) {
$this->fail($exception);
return;
}
/*
* @param \Throwable|null $exception Exception to be thrown into the generator.
* @param mixed $value Value to be sent into the generator.
*/
$this->onResolve = function ($exception, $value) {
$this->exception = $exception;
$this->value = $value;
if (!$this->immediate) {
$this->immediate = true;
return;
}
try {
do {
if ($this->exception) {
// Throw exception at current execution point.
$yielded = $this->generator->throw($this->exception);
} else {
// Send the new value and execute to next yield statement.
$yielded = $this->generator->send($this->value);
}
if (!$yielded instanceof Promise) {
if (!$this->generator->valid()) {
$this->resolve($this->generator->getReturn());
$this->onResolve = null;
return;
}
$yielded = $this->transform($yielded);
}
$this->immediate = false;
$yielded->onResolve($this->onResolve);
} while ($this->immediate);
$this->immediate = true;
} catch (\Throwable $exception) {
$this->fail($exception);
$this->onResolve = null;
} finally {
$this->exception = null;
$this->value = null;
}
};
$yielded->onResolve($this->onResolve);
}
/**
* Attempts to transform the non-promise yielded from the generator into a promise, otherwise returns an instance
* `Amp\Failure` failed with an instance of `Amp\InvalidYieldError`.
*
* @param mixed $yielded Non-promise yielded from generator.
*
* @return \Amp\Promise
*/
private function transform($yielded): Promise
{
try {
if (\is_array($yielded)) {
foreach ($yielded as &$val) {
if ($val instanceof \Generator) {
$val = new self($val);
}
}
return Promise\all($yielded);
}
if ($yielded instanceof \Generator) {
return new self($yielded);
}
// No match, continue to returning Failure below.
} catch (\Throwable $exception) {
// Conversion to promise failed, fall-through to returning Failure below.
}
return $yielded instanceof \Throwable || $yielded instanceof \Exception ? new Failure($yielded) : new Success($yielded);
}
}

View File

@ -1,18 +1,40 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* DataCenter module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
use Amp\Socket\ClientConnectContext;
use danog\MadelineProto\Stream\Common\BufferedRawStream;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\MTProtoTransport\AbridgedStream;
use danog\MadelineProto\Stream\MTProtoTransport\FullStream;
use danog\MadelineProto\Stream\MTProtoTransport\HttpsStream;
use danog\MadelineProto\Stream\MTProtoTransport\HttpStream;
use danog\MadelineProto\Stream\MTProtoTransport\IntermediatePaddedStream;
use danog\MadelineProto\Stream\MTProtoTransport\IntermediateStream;
use danog\MadelineProto\Stream\MTProtoTransport\ObfuscatedStream;
use danog\MadelineProto\Stream\Proxy\HttpProxy;
use danog\MadelineProto\Stream\Proxy\SocksProxy;
use danog\MadelineProto\Stream\Transport\DefaultStream;
use danog\MadelineProto\Stream\Transport\WssStream;
use danog\MadelineProto\Stream\Transport\WsStream;
/**
* Manages datacenters.
*/
@ -22,6 +44,7 @@ class DataCenter
use \danog\Serializable;
public $sockets = [];
public $curdc = 0;
private $API;
private $dclist = [];
private $settings = [];
@ -30,15 +53,16 @@ class DataCenter
return ['sockets', 'curdc', 'dclist', 'settings'];
}
public function __magic_construct($dclist, $settings)
public function __magic_construct($API, $dclist, $settings)
{
$this->API = $API;
$this->dclist = $dclist;
$this->settings = $settings;
foreach ($this->sockets as $key => $socket) {
if ($socket instanceof Connection) {
\danog\MadelineProto\Logger::log(sprintf(\danog\MadelineProto\Lang::$current_lang['dc_con_stop'], $key), \danog\MadelineProto\Logger::VERBOSE);
$this->API->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['dc_con_stop'], $key), \danog\MadelineProto\Logger::VERBOSE);
$socket->old = true;
$socket->__destruct();
$socket->disconnect();
} else {
unset($this->sockets[$key]);
}
@ -46,109 +70,243 @@ class DataCenter
}
public function dc_connect($dc_number)
{
return $this->wait($this->dc_connect_async($dc_number));
}
public function dc_connect_async($dc_number): \Generator
{
if (isset($this->sockets[$dc_number]) && !isset($this->sockets[$dc_number]->old)) {
return false;
}
$dc_config_number = isset($this->settings[$dc_number]) ? $dc_number : 'all';
$test = $this->settings[$dc_config_number]['test_mode'] ? 'test' : 'main';
$x = 0;
do {
$ipv6 = $this->settings[$dc_config_number]['ipv6'] ? 'ipv6' : 'ipv4';
if (!isset($this->dclist[$test][$ipv6][$dc_number]['ip_address'])) {
unset($this->sockets[$dc_number]);
$ctxs = $this->generate_contexts($dc_number);
foreach ($ctxs as $ctx) {
try {
if (isset($this->sockets[$dc_number]->old)) {
$this->sockets[$dc_number]->setExtra($this->API);
yield $this->sockets[$dc_number]->connect($ctx);
} else {
$this->sockets[$dc_number] = new Connection();
$this->sockets[$dc_number]->setExtra($this->API);
yield $this->sockets[$dc_number]->connect($ctx);
}
$this->API->logger->logger('OK!', \danog\MadelineProto\Logger::WARNING);
\danog\MadelineProto\Logger::log("No info for DC $dc_number", \danog\MadelineProto\Logger::ERROR);
return false;
return true;
} catch (\Throwable $e) {
$this->API->logger->logger('Connection failed: '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);
} catch (\Exception $e) {
$this->API->logger->logger('Connection failed: '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);
}
$address = $this->dclist[$test][$ipv6][$dc_number]['ip_address'];
$port = $this->dclist[$test][$ipv6][$dc_number]['port'];
if (isset($this->dclist[$test][$ipv6][$dc_number]['tcpo_only']) && $this->dclist[$test][$ipv6][$dc_number]['tcpo_only']) {
if ($dc_config_number === 'all') {
$dc_config_number = $dc_number;
}
if (!isset($this->settings[$dc_config_number])) {
$this->settings[$dc_config_number] = $this->settings['all'];
}
$this->settings[$dc_config_number]['protocol'] = 'obfuscated2';
}
if (strpos($this->settings[$dc_config_number]['protocol'], 'https') === 0) {
$subdomain = $this->dclist['ssl_subdomains'][preg_replace('/\D+/', '', $dc_number)];
$path = $this->settings[$dc_config_number]['test_mode'] ? 'apiw_test1' : 'apiw1';
$address = 'https://'.$subdomain.'.web.telegram.org/'.$path;
$port = 443;
}
if ($this->settings[$dc_config_number]['protocol'] === 'http') {
if ($ipv6) {
$address = '['.$address.']';
}
$address = $this->settings[$dc_config_number]['protocol'].'://'.$address.'/api';
}
\danog\MadelineProto\Logger::log(sprintf(\danog\MadelineProto\Lang::$current_lang['dc_con_test_start'], $dc_number, $test, $ipv6, $this->settings[$dc_config_number]['protocol']), \danog\MadelineProto\Logger::VERBOSE);
foreach (array_unique([$port, 443, 80, 88]) as $port) {
\danog\MadelineProto\Logger::log('Trying connection on port '.$port.' of '.$address.'...', \danog\MadelineProto\Logger::WARNING);
try {
if (isset($this->sockets[$dc_number]->old)) {
$this->sockets[$dc_number]->__construct($this->settings[$dc_config_number]['proxy'], $this->settings[$dc_config_number]['proxy_extra'], $address, $port, $this->settings[$dc_config_number]['protocol'], $this->settings[$dc_config_number]['timeout'], $this->settings[$dc_config_number]['ipv6']);
unset($this->sockets[$dc_number]->old);
} else {
$this->sockets[$dc_number] = new Connection($this->settings[$dc_config_number]['proxy'], $this->settings[$dc_config_number]['proxy_extra'], $address, $port, $this->settings[$dc_config_number]['protocol'], $this->settings[$dc_config_number]['timeout'], $this->settings[$dc_config_number]['ipv6']);
}
\danog\MadelineProto\Logger::log('OK!', \danog\MadelineProto\Logger::WARNING);
return true;
} catch (\danog\MadelineProto\Exception $e) {
\danog\MadelineProto\Logger::log('Connection failed: '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);
} catch (\danog\MadelineProto\NothingInTheSocketException $e) {
\danog\MadelineProto\Logger::log('Connection failed: read timeout', \danog\MadelineProto\Logger::ERROR);
}
if (isset($this->settings[$dc_config_number]['do_not_retry']) && $this->settings[$dc_config_number]['do_not_retry']) {
break;
}
}
switch ($x) {
case 0:
$this->settings[$dc_config_number]['ipv6'] = !$this->settings[$dc_config_number]['ipv6'];
\danog\MadelineProto\Logger::log('Connection failed, retrying connection with '.($this->settings[$dc_config_number]['ipv6'] ? 'ipv6' : 'ipv4').'...', \danog\MadelineProto\Logger::WARNING);
continue;
case 1:
if (isset($this->dclist[$test][$ipv6][$dc_number.'_bk']['ip_address'])) {
$dc_number .= '_bk';
}
\danog\MadelineProto\Logger::log('Connection failed, retrying connection on backup DCs with '.($this->settings[$dc_config_number]['ipv6'] ? 'ipv6' : 'ipv4').'...', \danog\MadelineProto\Logger::WARNING);
continue;
case 2:
$this->settings[$dc_config_number]['ipv6'] = !$this->settings[$dc_config_number]['ipv6'];
\danog\MadelineProto\Logger::log('Connection failed, retrying connection on backup DCs with '.($this->settings[$dc_config_number]['ipv6'] ? 'ipv6' : 'ipv4').'...', \danog\MadelineProto\Logger::WARNING);
continue;
case 3:
$this->settings[$dc_config_number]['proxy'] = '\\Socket';
\danog\MadelineProto\Logger::log('Connection failed, retrying connection without the proxy with '.($this->settings[$dc_config_number]['ipv6'] ? 'ipv6' : 'ipv4').'...', \danog\MadelineProto\Logger::WARNING);
continue;
case 4:
$this->settings[$dc_config_number]['ipv6'] = !$this->settings[$dc_config_number]['ipv6'];
\danog\MadelineProto\Logger::log('Connection failed, retrying connection without the proxy with '.($this->settings[$dc_config_number]['ipv6'] ? 'ipv6' : 'ipv4').'...', \danog\MadelineProto\Logger::WARNING);
continue;
case 5:
$this->settings[$dc_config_number]['proxy'] = '\\HttpProxy';
$this->settings[$dc_config_number]['proxy_extra'] = ['address' => 'localhost', 'port' => 80];
$this->settings[$dc_config_number]['ipv6'] = !$this->settings[$dc_config_number]['ipv6'];
\danog\MadelineProto\Logger::log('Connection failed, retrying connection with localhost HTTP proxy with '.($this->settings[$dc_config_number]['ipv6'] ? 'ipv6' : 'ipv4').'...', \danog\MadelineProto\Logger::WARNING);
continue;
case 6:
$this->settings[$dc_config_number]['ipv6'] = !$this->settings[$dc_config_number]['ipv6'];
\danog\MadelineProto\Logger::log('Connection failed, retrying connection with localhost HTTP proxy with '.($this->settings[$dc_config_number]['ipv6'] ? 'ipv6' : 'ipv4').'...', \danog\MadelineProto\Logger::WARNING);
continue;
default:
throw new \danog\MadelineProto\Exception("Could not connect to DC $dc_number");
}
} while (++$x);
}
throw new \danog\MadelineProto\Exception("Could not connect to DC $dc_number");
}
public function generate_contexts($dc_number)
{
$ctxs = [];
$combos = [];
$dc_config_number = isset($this->settings[$dc_number]) ? $dc_number : 'all';
$test = $this->settings[$dc_config_number]['test_mode'] ? 'test' : 'main';
$ipv6 = $this->settings[$dc_config_number]['ipv6'] ? 'ipv6' : 'ipv4';
switch ($this->settings[$dc_config_number]['protocol']) {
case 'tcp_abridged':
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [AbridgedStream::getName(), []]];
break;
case 'tcp_intermediate':
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [IntermediateStream::getName(), []]];
break;
case 'tcp_intermediate_padded':
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [IntermediatePaddedStream::getName(), []]];
break;
case 'tcp_full':
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [FullStream::getName(), []]];
break;
case 'http':
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [HttpStream::getName(), []]];
break;
case 'https':
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [HttpsStream::getName(), []]];
break;
default:
throw new Exception(\danog\MadelineProto\Lang::$current_lang['protocol_invalid']);
}
if ($this->settings[$dc_config_number]['obfuscated'] && !in_array($default[1][0], [HttpsStream::getName(), HttpStream::getName()])) {
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], end($default)];
}
if ($this->settings[$dc_config_number]['transport'] && !in_array($default[1][0], [HttpsStream::getName(), HttpStream::getName()])) {
switch ($this->settings[$dc_config_number]['transport']) {
case 'tcp':
if ($this->settings[$dc_config_number]['obfuscated']) {
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], end($default)];
}
break;
case 'wss':
if ($this->settings[$dc_config_number]['obfuscated']) {
$default = [[DefaultStream::getName(), []], [WssStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], end($default)];
} else {
$default = [[DefaultStream::getName(), []], [WssStream::getName(), []], [BufferedRawStream::getName(), []], end($default)];
}
break;
case 'ws':
if ($this->settings[$dc_config_number]['obfuscated']) {
$default = [[DefaultStream::getName(), []], [WsStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], end($default)];
} else {
$default = [[DefaultStream::getName(), []], [WsStream::getName(), []], [BufferedRawStream::getName(), []], end($default)];
}
break;
}
}
$combos[] = $default;
if (!isset($this->settings[$dc_config_number]['do_not_retry'])) {
if ((isset($this->dclist[$test][$ipv6][$dc_number]['tcpo_only']) && $this->dclist[$test][$ipv6][$dc_number]['tcpo_only']) || isset($this->dclist[$test][$ipv6][$dc_number]['secret'])) {
$extra = isset($this->dclist[$test][$ipv6][$dc_number]['secret']) ? ['secret' => $this->dclist[$test][$ipv6][$dc_number]['secret']] : [];
$combos[] = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), $extra], [IntermediatePaddedStream::getName(), []]];
}
if (is_array($this->settings[$dc_config_number]['proxy'])) {
$proxies = $this->settings[$dc_config_number]['proxy'];
$proxy_extras = $this->settings[$dc_config_number]['proxy_extra'];
} else {
$proxies = [$this->settings[$dc_config_number]['proxy']];
$proxy_extras = [$this->settings[$dc_config_number]['proxy_extra']];
}
foreach ($proxies as $key => $proxy) {
// Convert old settings
if ($proxy === '\\Socket') {
$proxy = DefaultStream::getName();
}
if ($proxy === '\\SocksProxy') {
$proxy = SocksProxy::getName();
}
if ($proxy === '\\HttpProxy') {
$proxy = HttpProxy::getName();
}
if ($proxy === '\\MTProxySocket') {
$proxy = ObfuscatedStream::getName();
}
if ($proxy === DefaultStream::getName()) {
continue;
}
$extra = $proxy_extras[$key];
if (!isset(class_implements($proxy)['danog\\MadelineProto\\Stream\\StreamInterface'])) {
throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['proxy_class_invalid']);
}
if ($proxy === ObfuscatedStream::getName() && in_array(strlen($extra['secret']), [17, 34])) {
$combos []= [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [$proxy, $extra], [IntermediatePaddedStream::getName(), []]];
}
foreach ($combos as $k => $orig) {
$combo = [];
if ($proxy === ObfuscatedStream::getName()) {
$combo = $orig;
if ($combo[count($combo) - 2][0] === ObfuscatedStream::getName()) {
$combo[count($combo) - 2][1] = $extra;
} else {
$mtproto = end($combo);
$combo[count($combo) - 1] = [$proxy, $extra];
$combo[] = $mtproto;
}
} else {
if ($orig[1][0] === BufferedRawStream::getName()) {
list($first, $second) = [array_slice($orig, 0, 2), array_slice($orig, 2)];
$first[] = [$proxy, $extra];
$combo = array_merge($first, $second);
} elseif ($orig[1][0] === WssStream::getName()) {
list($first, $second) = [array_slice($orig, 0, 1), array_slice($orig, 1)];
$first[] = [BufferedRawStream::getName(), []];
$first[] = [$proxy, $extra];
$combo = array_merge($first, $second);
}
}
array_unshift($combos, $combo);
//unset($combos[$k]);
}
}
$combos[] = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [HttpsStream::getName(), []]];
$combos = array_unique($combos, SORT_REGULAR);
}
/* @var $context \Amp\ClientConnectContext */
$context = (new ClientConnectContext())->withMaxAttempts(1)->withConnectTimeout(1000 * $this->settings[$dc_config_number]['timeout']);
foreach ($combos as $combo) {
$ipv6 = [$this->settings[$dc_config_number]['ipv6'] ? 'ipv6' : 'ipv4', $this->settings[$dc_config_number]['ipv6'] ? 'ipv4' : 'ipv6'];
foreach ($ipv6 as $ipv6) {
if (!isset($this->dclist[$test][$ipv6][$dc_number]['ip_address'])) {
unset($this->sockets[$dc_number]);
$this->API->logger->logger("No info for DC $dc_number", \danog\MadelineProto\Logger::ERROR);
continue;
}
$address = $this->dclist[$test][$ipv6][$dc_number]['ip_address'];
$port = $this->dclist[$test][$ipv6][$dc_number]['port'];
foreach (array_unique([$port, 443, 80, 88, 5222]) as $port) {
$stream = end($combo)[0];
if ($stream === HttpsStream::getName()) {
$subdomain = $this->dclist['ssl_subdomains'][preg_replace('/\D+/', '', $dc_number)];
if (strpos($dc_number, '_media') !== false) {
$subdomain .= '-1';
}
$path = $this->settings[$dc_config_number]['test_mode'] ? 'apiw_test1' : 'apiw1';
$uri = 'tcp://'.$subdomain.'.web.telegram.org:'.$port.'/'.$path;
} elseif ($stream === HttpStream::getName()) {
$uri = 'tcp://'.$address.':'.$port.'/api';
} else {
$uri = 'tcp://'.$address.':'.$port;
}
if ($combo[1][0] === WssStream::getName()) {
$subdomain = $this->dclist['ssl_subdomains'][preg_replace('/\D+/', '', $dc_number)];
if (strpos($dc_number, '_media') !== false) {
$subdomain .= '-1';
}
$path = $this->settings[$dc_config_number]['test_mode'] ? 'apiws_test' : 'apiws';
$uri = 'tcp://'.$subdomain.'.web.telegram.org:'.$port.'/'.$path;
} elseif ($combo[1][0] === WsStream::getName()) {
$subdomain = $this->dclist['ssl_subdomains'][preg_replace('/\D+/', '', $dc_number)];
if (strpos($dc_number, '_media') !== false) {
$subdomain .= '-1';
}
$path = $this->settings[$dc_config_number]['test_mode'] ? 'apiws_test' : 'apiws';
//$uri = 'tcp://' . $subdomain . '.web.telegram.org:' . $port . '/' . $path;
$uri = 'tcp://'.$address.':'.$port.'/'.$path;
}
/** @var $ctx \danog\MadelineProto\Stream\ConnectionContext */
$ctx = (new ConnectionContext())
->setDc($dc_number)
->setTest($this->settings[$dc_config_number]['test_mode'])
->setSocketContext($context)
->setUri($uri)
->setIpv6($ipv6 === 'ipv6');
foreach ($combo as $stream) {
$ctx->addStream(...$stream);
}
$ctxs[] = $ctx;
}
}
}
if (isset($this->dclist[$test][$ipv6][$dc_number.'_bk']['ip_address'])) {
$ctxs = array_merge($ctxs, $this->generate_contexts($dc_number.'_bk'));
}
return $ctxs;
}
public function get_dcs($all = true)
{
$test = $this->settings['all']['test_mode'] ? 'test' : 'main';
@ -156,17 +314,4 @@ class DataCenter
return $all ? array_keys((array) $this->dclist[$test][$ipv6]) : array_keys((array) $this->sockets);
}
public function select($poll = false)
{
$read = [];
$write = [];
$except = [];
foreach ($this->sockets as $dc_id => $socket) {
$read[$dc_id] = $socket->getSocket();
}
\Socket::select($read, $write, $except, $poll ? 0 : $this->settings['all']['timeout']);
return array_keys($read);
}
}

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* DocsBuilder module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
@ -601,16 +607,21 @@ Any json-encodable data.
}
public $template = '<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* Lang module
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* Constructors module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\DocsBuilder;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* Methods module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\DocsBuilder;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* EventHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
@ -17,9 +23,12 @@ class EventHandler extends APIFactory
{
public function __construct($MadelineProto)
{
$this->API = $MadelineProto;
$this->API = $MadelineProto->API;
$this->async = $MadelineProto->async;
$this->methods = $MadelineProto->methods;
foreach ($this->API->get_method_namespaces() as $namespace) {
$this->{$namespace} = new APIFactory($namespace, $this->API);
$this->{$namespace}->async = $MadelineProto->async;
}
}

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* Exception module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
@ -20,7 +26,11 @@ class Exception extends \Exception
public function __toString()
{
return $this->file === 'MadelineProto' ? $this->message : '\\danog\\MadelineProto\\Exception'.($this->message !== '' ? ': ' : '').$this->message.' in '.$this->file.':'.$this->line.PHP_EOL.'Revision: '.@file_get_contents(__DIR__.'/../../../.git/refs/heads/master').PHP_EOL.'TL Trace (YOU ABSOLUTELY MUST READ THE TEXT BELOW):'.PHP_EOL.$this->getTLTrace();
$result = $this->file === 'MadelineProto' ? $this->message : '\\danog\\MadelineProto\\Exception'.($this->message !== '' ? ': ' : '').$this->message.' in '.$this->file.':'.$this->line.PHP_EOL.\danog\MadelineProto\Magic::$revision.PHP_EOL.'TL Trace (YOU ABSOLUTELY MUST READ THE TEXT BELOW):'.PHP_EOL.$this->getTLTrace();
if (php_sapi_name() !== 'cli') {
$result = str_replace(PHP_EOL, '<br>'.PHP_EOL, $result);
}
return $result;
}
public function __construct($message = null, $code = 0, self $previous = null, $file = null, $line = null)
@ -74,9 +84,8 @@ class Exception extends \Exception
public static function ExceptionErrorHandler($errno = 0, $errstr = null, $errfile = null, $errline = null)
{
// If error is suppressed with @, don't throw an exception
if (error_reporting() === 0) {
return true;
// return true to continue through the others error handlers
if (error_reporting() === 0 || $errfile && strpos($errfile, 'vendor/amphp') !== false) {
return false;
}
throw new self($errstr, $errno, null, $errfile, $errline);

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* FileCallback module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* FileCallbackInterface module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -100,7 +100,7 @@ interface auth
/**
* @param array params [
* bytes password_hash,
* InputCheckPasswordSRP password,
* ]
*
* @return auth_Authorization
@ -348,7 +348,7 @@ interface account
/**
* @param array params [
* bytes current_password_hash,
* InputCheckPasswordSRP password,
* ]
*
* @return account_PasswordSettings
@ -357,7 +357,7 @@ interface account
/**
* @param array params [
* bytes current_password_hash,
* InputCheckPasswordSRP password,
* account_PasswordInputSettings new_settings,
* ]
*
@ -388,7 +388,7 @@ interface account
/**
* @param array params [
* bytes password_hash,
* InputCheckPasswordSRP password,
* int period,
* ]
*
@ -536,6 +536,49 @@ interface account
* @return bool
*/
public function finishTakeoutSession(array $params);
/**
* @param array params [
* string code,
* ]
*
* @return bool
*/
public function confirmPasswordEmail(array $params);
/**
* @return bool
*/
public function resendPasswordEmail();
/**
* @return bool
*/
public function cancelPasswordEmail();
/**
* @return bool
*/
public function getContactSignUpNotification();
/**
* @param array params [
* Bool silent,
* ]
*
* @return bool
*/
public function setContactSignUpNotification(array $params);
/**
* @param array params [
* boolean compare_sound,
* InputNotifyPeer peer,
* ]
*
* @return Updates
*/
public function getNotifyExceptions(array $params);
}
interface users
@ -571,6 +614,15 @@ interface users
interface contacts
{
/**
* @param array params [
* int hash,
* ]
*
* @return Vector_of_int
*/
public function getContactIDs(array $params);
/**
* @return Vector_of_ContactStatus
*/
@ -612,6 +664,15 @@ interface contacts
*/
public function deleteContacts(array $params);
/**
* @param array params [
* string phones,
* ]
*
* @return bool
*/
public function deleteByPhones(array $params);
/**
* @param array params [
* InputUser id,
@ -640,20 +701,6 @@ interface contacts
*/
public function getBlocked(array $params);
/**
* @return Vector_of_int
*/
public function exportCard();
/**
* @param array params [
* int export_card,
* ]
*
* @return User
*/
public function importCard(array $params);
/**
* @param array params [
* string q,
@ -1337,6 +1384,7 @@ interface messages
* boolean silent,
* boolean background,
* boolean clear_draft,
* boolean hide_via,
* InputPeer peer,
* int reply_to_msg_id,
* long query_id,
@ -1360,14 +1408,12 @@ interface messages
/**
* @param array params [
* boolean no_webpage,
* boolean stop_geo_live,
* InputPeer peer,
* int id,
* string message,
* InputMedia media,
* ReplyMarkup reply_markup,
* MessageEntity entities,
* InputGeoPoint geo_point,
* ]
*
* @return Updates
@ -1377,13 +1423,11 @@ interface messages
/**
* @param array params [
* boolean no_webpage,
* boolean stop_geo_live,
* InputBotInlineMessageID id,
* string message,
* InputMedia media,
* ReplyMarkup reply_markup,
* MessageEntity entities,
* InputGeoPoint geo_point,
* ]
*
* @return bool
@ -1771,6 +1815,61 @@ interface messages
* @return Vector_of_DialogPeer
*/
public function getDialogUnreadMarks();
/**
* @return bool
*/
public function clearAllDrafts();
/**
* @param array params [
* boolean silent,
* InputPeer peer,
* int id,
* ]
*
* @return Updates
*/
public function updatePinnedMessage(array $params);
/**
* @param array params [
* InputPeer peer,
* int msg_id,
* bytes options,
* ]
*
* @return Updates
*/
public function sendVote(array $params);
/**
* @param array params [
* InputPeer peer,
* int msg_id,
* ]
*
* @return Updates
*/
public function getPollResults(array $params);
/**
* @param array params [
* InputPeer peer,
* ]
*
* @return ChatOnlines
*/
public function getOnlines(array $params);
/**
* @param array params [
* InputPeer peer,
* ]
*
* @return StatsURL
*/
public function getStatsURL(array $params);
}
interface updates
@ -1949,19 +2048,14 @@ interface help
*/
public function getNearestDc();
/**
* @return help_AppUpdate
*/
public function getAppUpdate();
/**
* @param array params [
* InputAppEvent events,
* string source,
* ]
*
* @return bool
* @return help_AppUpdate
*/
public function saveAppLog(array $params);
public function getAppUpdate(array $params);
/**
* @return help_InviteText
@ -2033,6 +2127,54 @@ interface help
* @return help_DeepLinkInfo
*/
public function getDeepLinkInfo(array $params);
/**
* @return JSONValue
*/
public function getAppConfig();
/**
* @param array params [
* InputAppEvent events,
* ]
*
* @return bool
*/
public function saveAppLog(array $params);
/**
* @param array params [
* int hash,
* ]
*
* @return help_PassportConfig
*/
public function getPassportConfig(array $params);
/**
* @return help_SupportName
*/
public function getSupportName();
/**
* @param array params [
* InputUser user_id,
* ]
*
* @return help_UserInfo
*/
public function getUserInfo(array $params);
/**
* @param array params [
* InputUser user_id,
* string message,
* MessageEntity entities,
* ]
*
* @return help_UserInfo
*/
public function editUserInfo(array $params);
}
interface channels
@ -2279,17 +2421,6 @@ interface channels
*/
public function toggleSignatures(array $params);
/**
* @param array params [
* boolean silent,
* InputChannel channel,
* int id,
* ]
*
* @return Updates
*/
public function updatePinnedMessage(array $params);
/**
* @return messages_Chats
*/
@ -2586,6 +2717,7 @@ interface langpack
{
/**
* @param array params [
* string lang_pack,
* string lang_code,
* ]
*
@ -2595,6 +2727,7 @@ interface langpack
/**
* @param array params [
* string lang_pack,
* string lang_code,
* string keys,
* ]
@ -2605,6 +2738,7 @@ interface langpack
/**
* @param array params [
* string lang_code,
* int from_version,
* ]
*
@ -2613,7 +2747,21 @@ interface langpack
public function getDifference(array $params);
/**
* @param array params [
* string lang_pack,
* ]
*
* @return Vector_of_LangPackLanguage
*/
public function getLanguages();
public function getLanguages(array $params);
/**
* @param array params [
* string lang_pack,
* string lang_code,
* ]
*
* @return LangPackLanguage
*/
public function getLanguage(array $params);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,29 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* Logger module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
/*
* Logger class
*/
namespace danog\MadelineProto;
use Amp\ByteStream\ResourceOutputStream;
class Logger
{
const foreground = ['default' => 39, 'black' => 30, 'red' => 31, 'green' => 32, 'yellow' => 33, 'blue' => 34, 'magenta' => 35, 'cyan' => 36, 'light_gray' => 37, 'dark_gray' => 90, 'light_red' => 91, 'light_green' => 92, 'light_yellow' => 93, 'light_blue' => 94, 'light_magenta' => 95, 'light_cyan' => 96, 'white' => 97];
@ -28,6 +36,7 @@ class Logger
public $prefix = '';
public $level = 3;
public $colors = [];
public $newline = "\n";
public static $default;
public static $printed = false;
@ -82,6 +91,23 @@ class Logger
$this->colors[self::WARNING] = implode(';', [self::foreground['white'], self::set['dim'], self::background['red']]);
$this->colors[self::ERROR] = implode(';', [self::foreground['white'], self::set['bold'], self::background['red']]);
$this->colors[self::FATAL_ERROR] = implode(';', [self::foreground['red'], self::set['bold'], self::background['light_gray']]);
$this->newline = PHP_EOL;
if ($this->mode === 3) {
$this->stdout = new ResourceOutputStream(STDOUT);
if (php_sapi_name() !== 'cli') $this->newline = '<br>'.$this->newline;
} elseif ($this->mode === 2) {
$this->stdout = new ResourceOutputStream(fopen($this->optional, 'a+'));
} elseif ($this->mode === 1) {
$result = @ini_get('error_log');
if ($result === 'syslog') {
$this->stdout = new ResourceOutputStream(STDERR);
} elseif ($result) {
$this->stdout = new ResourceOutputStream(fopen($result, 'a+'));
} else {
$this->stdout = new ResourceOutputStream(STDERR);
}
}
}
public static function log($param, $level = self::NOTICE)
@ -126,13 +152,10 @@ class Logger
$param = str_pad($file.$prefix.': ', 16 + strlen($prefix))."\t".$param;
switch ($this->mode) {
case 1:
error_log($param);
$this->stdout->write($param.$this->newline);
break;
case 2:
error_log($param.PHP_EOL, 3, $this->optional);
break;
case 3:
echo Magic::$isatty ? "\33[".$this->colors[$level].'m'.$param."\33[0m".PHP_EOL : $param.PHP_EOL;
default:
$this->stdout->write(Magic::$isatty ? "\33[".$this->colors[$level].'m'.$param."\33[0m".$this->newline : $param.$this->newline);
break;
}
}

View File

@ -0,0 +1,158 @@
<?php
/**
* RPC call status check loop.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop\Connection;
use Amp\Deferred;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
use function Amp\call;
/**
* RPC call status check loop.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class CheckLoop extends ResumableSignalLoop
{
public function loop(): \Generator
{
$API = $this->API;
$datacenter = $this->datacenter;
$connection = $this->connection;
$this->startedLoop();
$API->logger->logger("Entered check loop in DC {$datacenter}", Logger::ULTRA_VERBOSE);
$try_count = 0;
$timeout = $API->settings['connection_settings'][isset($API->settings['connection_settings'][$datacenter]) ? $datacenter : 'all']['timeout'];
while (true) {
while (empty($connection->new_outgoing)) {
if (yield $this->waitSignal($this->pause())) {
$API->logger->logger('Exiting check loop');
$this->exitedLoop();
return;
}
$try_count = 0;
}
if ($connection->hasPendingCalls()) {
$last_recv = $connection->last_recv;
if ($connection->temp_auth_key !== null) {
$message_ids = array_values($connection->new_outgoing);
$deferred = new Deferred();
$deferred->promise()->onResolve(
function ($e, $result) use ($message_ids, $API, $connection, $datacenter) {
if ($e) {
throw $e;
}
$reply = [];
foreach (str_split($result['info']) as $key => $chr) {
$message_id = $message_ids[$key];
if (!isset($connection->outgoing_messages[$message_id])) {
$API->logger->logger('Already got response for and forgot about message ID '.($message_id));
continue;
}
if (!isset($connection->new_outgoing[$message_id])) {
$API->logger->logger('Already got response for '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.($message_id));
continue;
}
$chr = ord($chr);
switch ($chr & 7) {
case 0:
$API->logger->logger('Wrong message status 0 for '.$connection->outgoing_messages[$message_id]['_'], \danog\MadelineProto\Logger::FATAL_ERROR);
break;
case 1:
case 2:
case 3:
if ($connection->outgoing_messages[$message_id]['_'] === 'msgs_state_req') {
break;
}
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.($message_id).' not received by server, resending...', \danog\MadelineProto\Logger::ERROR);
$API->method_recall('', ['message_id' => $message_id, 'datacenter' => $datacenter, 'postpone' => true]);
break;
case 4:
if ($chr & 32) {
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.($message_id).' received by server and is being processed, waiting...', \danog\MadelineProto\Logger::ERROR);
} elseif ($chr & 64) {
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.($message_id).' received by server and was already processed, requesting reply...', \danog\MadelineProto\Logger::ERROR);
$reply[] = $message_id;
} elseif ($chr & 128) {
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.($message_id).' received by server and was already sent, requesting reply...', \danog\MadelineProto\Logger::ERROR);
$reply[] = $message_id;
} else {
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.($message_id).' received by server, requesting reply...', \danog\MadelineProto\Logger::ERROR);
$reply[] = $message_id;
}
}
}
if ($reply) {
$API->object_call('msg_resend_ans_req', ['msg_ids' => $reply], ['datacenter' => $datacenter, 'postpone' => true]);
}
$connection->writer->resume();
}
);
$list = '';
foreach ($message_ids as $message_id) {
$list .= $connection->outgoing_messages[$message_id]['_'].', ';
}
$API->logger->logger("Still missing $list on DC $datacenter, sending state request", \danog\MadelineProto\Logger::ERROR);
yield $API->object_call_async('msgs_state_req', ['msg_ids' => $message_ids], ['datacenter' => $datacenter, 'promise' => $deferred]);
} else {
foreach ($connection->new_outgoing as $message_id) {
if (isset($connection->outgoing_messages[$message_id]['sent'])
&& $connection->outgoing_messages[$message_id]['sent'] + $timeout < time()
&& $connection->outgoing_messages[$message_id]['unencrypted']
) {
$API->logger->logger('Still missing '.$connection->outgoing_messages[$message_id]['_'].' with message id '.($message_id)." on DC $datacenter, resending", \danog\MadelineProto\Logger::ERROR);
$API->method_recall('', ['message_id' => $message_id, 'datacenter' => $datacenter, 'postpone' => true]);
}
}
$connection->writer->resume();
}
//$t = time();
if (yield $this->waitSignal($this->pause($timeout))) {
$API->logger->logger('Exiting check loop');
$this->exitedLoop();
return;
}
//var_dumP("after ".(time() - $t).", with timeout ".$timeout);
$try_count++;
if ($connection->last_recv === $last_recv) {
$API->logger->logger("Reconnecting and exiting check loop on DC $datacenter");
$this->exitedLoop();
yield $connection->reconnect();
return;
}
} else {
if (yield $this->waitSignal($this->pause($timeout))) {
$API->logger->logger('Exiting check loop');
$this->exitedLoop();
return;
}
$try_count = 0;
}
}
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* HttpWait loop.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop\Connection;
use Amp\Success;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
use danog\MadelineProto\Stream\MTProtoTransport\HttpsStream;
use danog\MadelineProto\Stream\MTProtoTransport\HttpStream;
/**
* HttpWait loop.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class HttpWaitLoop extends ResumableSignalLoop
{
public function loop(): \Generator
{
$API = $this->API;
$datacenter = $this->datacenter;
$connection = $this->connection;
if (!in_array($connection->getCtx()->getStreamName(), [HttpStream::getName(), HttpsStream::getName()])) {
yield new Success(0);
return;
}
$this->startedLoop();
$API->logger->logger("Entered HTTP wait loop in DC {$datacenter}", Logger::ULTRA_VERBOSE);
$timeout = $API->settings['connection_settings'][isset($API->settings['connection_settings'][$datacenter]) ? $datacenter : 'all']['timeout'];
while (true) {
//var_dump("http loop DC $datacenter");
if ($a = yield $this->waitSignal($this->pause())) {
$API->logger->logger('Exiting HTTP wait loop');
$this->exitedLoop();
return;
}
if (!in_array($connection->getCtx()->getStreamName(), [HttpStream::getName(), HttpsStream::getName()])) {
$this->exitedLoop();
yield new Success(0);
return;
}
while ($connection->temp_auth_key === null) {
if (yield $this->waitSignal($this->pause())) {
$API->logger->logger('Exiting HTTP wait loop');
$this->exitedLoop();
return;
}
}
//if (time() - $connection->last_http_wait >= $timeout) {
$API->logger->logger("DC $datacenter: request {$connection->http_req_count}, response {$connection->http_res_count}");
if ($connection->http_req_count === $connection->http_res_count && (!empty($connection->pending_outgoing) || (!empty($connection->new_outgoing) && !$connection->hasPendingCalls()))) {
yield $connection->sendMessage(['_' => 'http_wait', 'body' => ['max_wait' => 30000, 'wait_after' => 0, 'max_delay' => 0], 'content_related' => true, 'unencrypted' => false, 'method' => false]);
//var_dump('sent wait');
}
$API->logger->logger("DC $datacenter: request {$connection->http_req_count}, response {$connection->http_res_count}");
//($connection->last_http_wait + $timeout) - time()
}
}
}

View File

@ -0,0 +1,220 @@
<?php
/**
* Socket read loop.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop\Connection;
use Amp\Loop;
use Amp\Promise;
use Amp\Websocket\ClosedException;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Loop\Impl\SignalLoop;
use danog\MadelineProto\MTProtoTools\Crypt;
use danog\MadelineProto\NothingInTheSocketException;
use danog\MadelineProto\Tools;
use function Amp\call;
/**
* Socket read loop.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class ReadLoop extends SignalLoop
{
use Tools;
use Crypt;
public function loop(): \Generator
{
$API = $this->API;
$datacenter = $this->datacenter;
$connection = $this->connection;
$this->startedLoop();
$API->logger->logger("Entered read loop in DC {$datacenter}", Logger::ULTRA_VERBOSE);
$timeout = $API->settings['connection_settings'][isset($API->settings['connection_settings'][$datacenter]) ? $datacenter : 'all']['timeout'];
while (true) {
try {
$error = yield $this->waitSignal($this->readMessage());
} catch (NothingInTheSocketException $e) {
if (isset($connection->old)) {
$this->exitedLoop();
$API->logger->logger("Exiting read loop in DC $datacenter");
return;
}
$API->logger->logger("Got nothing in the socket in DC {$datacenter}, reconnecting...", Logger::ERROR);
yield $connection->reconnect();
continue;
} catch (ClosedException $e) {
$API->logger->logger($e->getMessage(), Logger::FATAL_ERROR);
throw $e;
}
if (is_int($error)) {
$this->exitedLoop();
yield $connection->reconnect();
if ($error === -404) {
if ($connection->temp_auth_key !== null) {
$API->logger->logger("WARNING: Resetting auth key in DC {$datacenter}...", \danog\MadelineProto\Logger::WARNING);
$connection->temp_auth_key = null;
$connection->session_id = null;
foreach ($connection->new_outgoing as $message_id) {
$connection->outgoing_messages[$message_id]['sent'] = 0;
}
$API->init_authorization();
} else {
//throw new \danog\MadelineProto\RPCErrorException($error, $error);
}
} elseif ($error === -1) {
$API->logger->logger("WARNING: Got quick ack from DC {$datacenter}", \danog\MadelineProto\Logger::WARNING);
} elseif ($error === 0) {
$API->logger->logger("Got NOOP from DC {$datacenter}", \danog\MadelineProto\Logger::WARNING);
} else {
throw new \danog\MadelineProto\RPCErrorException($error, $error);
}
return;
}
$connection->http_res_count++;
try {
$API->handle_messages($datacenter);
} finally {
$this->exitedLoop();
}
$this->startedLoop();
// Loop::defer(function () use ($datacenter) {
if ($this->API->is_http($datacenter)) {
$this->API->datacenter->sockets[$datacenter]->waiter->resume();
} // });
}
}
public function readMessage(): Promise
{
return call([$this, 'readMessageAsync']);
}
public function readMessageAsync(): \Generator
{
$API = $this->API;
$datacenter = $this->datacenter;
$connection = $this->connection;
try {
$buffer = yield $connection->stream->getReadBuffer($payload_length);
} catch (ClosedException $e) {
$API->logger->logger($e->getReason());
if (strpos($e->getReason(), ' ') === 0) {
$payload = -substr($e->getReason(), 7);
$API->logger->logger("Received $payload from DC ".$datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE);
return $payload;
}
throw $e;
}
if ($payload_length === 4) {
$payload = $this->unpack_signed_int(yield $buffer->bufferRead(4));
$API->logger->logger("Received $payload from DC ".$datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE);
return $payload;
}
$auth_key_id = yield $buffer->bufferRead(8);
if ($auth_key_id === "\0\0\0\0\0\0\0\0") {
$message_id = yield $buffer->bufferRead(8);
if (!in_array($message_id, [1, 0])) {
$connection->check_message_id($message_id, ['outgoing' => false, 'container' => false]);
}
$message_length = unpack('V', yield $buffer->bufferRead(4))[1];
$message_data = yield $buffer->bufferRead($message_length);
$left = $payload_length - $message_length - 4 - 8 - 8;
if ($left) {
$API->logger->logger('Padded unencrypted message', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
if ($left < (-$message_length & 15)) {
throw new \danog\MadelineProto\SecurityException('padding is too small');
}
yield $buffer->bufferRead($left);
}
$connection->incoming_messages[$message_id] = [];
} elseif ($auth_key_id === $connection->temp_auth_key['id']) {
$message_key = yield $buffer->bufferRead(16);
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $connection->temp_auth_key['auth_key'], false);
$encrypted_data = yield $buffer->bufferRead($payload_length - 24);
$protocol_padding = strlen($encrypted_data) % 16;
if ($protocol_padding) {
$encrypted_data = substr($encrypted_data, 0, -$protocol_padding);
}
$decrypted_data = $this->ige_decrypt($encrypted_data, $aes_key, $aes_iv);
/*
$server_salt = substr($decrypted_data, 0, 8);
if ($server_salt != $connection->temp_auth_key['server_salt']) {
$API->logger->logger('WARNING: Server salt mismatch (my server salt '.$connection->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 != $connection->session_id) {
throw new \danog\MadelineProto\Exception('Session id mismatch.');
}
$message_id = substr($decrypted_data, 16, 8);
$connection->check_message_id($message_id, ['outgoing' => false, 'container' => false]);
$seq_no = unpack('V', substr($decrypted_data, 24, 4))[1];
$message_data_length = unpack('V', substr($decrypted_data, 28, 4))[1];
if ($message_data_length > strlen($decrypted_data)) {
throw new \danog\MadelineProto\SecurityException('message_data_length is too big');
}
if (strlen($decrypted_data) - 32 - $message_data_length < 12) {
throw new \danog\MadelineProto\SecurityException('padding is too small');
}
if (strlen($decrypted_data) - 32 - $message_data_length > 1024) {
throw new \danog\MadelineProto\SecurityException('padding is too big');
}
if ($message_data_length < 0) {
throw new \danog\MadelineProto\SecurityException('message_data_length not positive');
}
if ($message_data_length % 4 != 0) {
throw new \danog\MadelineProto\SecurityException('message_data_length not divisible by 4');
}
$message_data = substr($decrypted_data, 32, $message_data_length);
if ($message_key != substr(hash('sha256', substr($connection->temp_auth_key['auth_key'], 96, 32).$decrypted_data, true), 8, 16)) {
throw new \danog\MadelineProto\SecurityException('msg_key mismatch');
}
$connection->incoming_messages[$message_id] = ['seq_no' => $seq_no];
} else {
throw new \danog\MadelineProto\Exception('Got unknown auth_key id');
}
$deserialized = $API->deserialize($message_data, ['type' => '', 'datacenter' => $datacenter]);
$API->referenceDatabase->reset();
$connection->incoming_messages[$message_id]['content'] = $deserialized;
$connection->incoming_messages[$message_id]['response'] = -1;
$connection->new_incoming[$message_id] = $message_id;
$connection->last_recv = time();
$connection->last_http_wait = 0;
$API->logger->logger('Received payload from DC '.$datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE);
return true;
}
}

View File

@ -0,0 +1,85 @@
<?php
/**
* Update loop.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop\Connection;
use Amp\Success;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
/**
* Update loop.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class UpdateLoop extends ResumableSignalLoop
{
public function loop(): \Generator
{
$API = $this->API;
$datacenter = $this->datacenter;
$connection = $this->connection;
if (!$this->API->settings['updates']['handle_updates']) {
yield new Success(0);
return false;
}
$this->startedLoop();
$API->logger->logger("Entered updates loop in DC {$datacenter}", Logger::ULTRA_VERBOSE);
$timeout = $API->settings['updates']['getdifference_interval'];
while (true) {
while (!$this->API->settings['updates']['handle_updates'] || !$this->has_all_auth()) {
if (yield $this->waitSignal($this->pause())) {
$API->logger->logger('Exiting update loop');
$this->exitedLoop();
return;
}
}
if (time() - $API->last_getdifference > $timeout) {
if (!$API->get_updates_difference()) {
return false;
}
}
if (yield $this->waitSignal($this->pause(($API->last_getdifference + $timeout) - time()))) {
$API->logger->logger('Exiting update loop');
$this->exitedLoop();
return;
}
}
}
public function has_all_auth()
{
if ($this->API->isInitingAuthorization()) {
return false;
}
foreach ($this->API->datacenter->sockets as $dc) {
if (!$dc->authorized || $dc->temp_auth_key === null) {
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,330 @@
<?php
/**
* Socket write loop.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop\Connection;
use Amp\Coroutine;
use Amp\Success;
use danog\MadelineProto\Connection;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Loop\Impl\ResumableSignalLoop;
use danog\MadelineProto\MTProtoTools\Crypt;
use danog\MadelineProto\Tools;
/**
* Socket write loop.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class WriteLoop extends ResumableSignalLoop
{
use Crypt;
use Tools;
public function loop(): \Generator
{
$API = $this->API;
$datacenter = $this->datacenter;
$connection = $this->connection;
$this->startedLoop();
$API->logger->logger("Entered write loop in DC {$datacenter}", Logger::ULTRA_VERBOSE);
while (true) {
if (empty($connection->pending_outgoing)) {
if (yield $this->waitSignal($this->pause())) {
$API->logger->logger('Exiting write loop');
$this->exitedLoop();
yield new Success(0);
return;
}
}
try {
if ($connection->temp_auth_key === null) {
$res = $this->unencryptedWriteLoopAsync();
} else {
$res = $this->encryptedWriteLoopAsync();
}
if ($res instanceof \Generator) {
yield new Coroutine($res);
}
} finally {
$this->exitedLoop();
}
$this->startedLoop();
//$connection->waiter->resume();
}
}
public function unencryptedWriteLoopAsync(): \Generator
{
$API = $this->API;
$datacenter = $this->datacenter;
$connection = $this->connection;
while ($connection->pending_outgoing) {
$skipped_all = true;
foreach ($connection->pending_outgoing as $k => $message) {
if ($connection->temp_auth_key !== null) {
return;
}
if (!$message['unencrypted']) {
continue;
}
$skipped_all = false;
$body = $message['serialized_body'];
$API->logger->logger("Sending {$message['_']} as unencrypted message to DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$message_id = isset($message['msg_id']) ? $message['msg_id'] : $connection->generate_message_id();
$length = strlen($body);
$pad_length = -$length & 15;
$pad_length += 16 * $this->random_int($modulus = 16);
$pad = $this->random($pad_length);
$buffer = yield $connection->stream->getWriteBuffer(8 + 8 + 4 + $pad_length + $length);
yield $buffer->bufferWrite("\0\0\0\0\0\0\0\0".$message_id.$this->pack_unsigned_int($length).$body.$pad);
//var_dump("plain ".bin2hex($message_id));
$connection->http_req_count++;
$connection->outgoing_messages[$message_id] = $message;
$connection->outgoing_messages[$message_id]['sent'] = time();
$connection->outgoing_messages[$message_id]['tries'] = 0;
$connection->outgoing_messages[$message_id]['unencrypted'] = true;
$connection->new_outgoing[$message_id] = $message_id;
unset($connection->pending_outgoing[$k]);
$API->logger->logger("Sent {$message['_']} as unencrypted message to DC {$datacenter}!", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$message['send_promise']->resolve(isset($message['promise']) ? $message['promise'] : true);
}
if ($skipped_all) {
break;
}
}
}
public function encryptedWriteLoopAsync(): \Generator
{
$API = $this->API;
$datacenter = $this->datacenter;
$connection = $this->connection;
do {
if ($connection->temp_auth_key === null) {
return;
}
if ($this->API->is_http($datacenter) && empty($connection->pending_outgoing)) {
return;
}
if (count($to_ack = $connection->ack_queue)) {
$connection->pending_outgoing[$connection->pending_outgoing_key++] = ['_' => 'msgs_ack', 'serialized_body' => $this->API->serialize_object(['type' => 'msgs_ack'], ['msg_ids' => $connection->ack_queue], 'msgs_ack'), 'content_related' => false, 'unencrypted' => false, 'method' => false];
$connection->pending_outgoing_key %= Connection::PENDING_MAX;
}
$has_http_wait = false;
$messages = [];
$keys = [];
foreach ($connection->pending_outgoing as $message) {
if ($message['_'] === 'http_wait') {
$has_http_wait = true;
break;
}
}
if ($API->is_http($datacenter) && !$has_http_wait) {
$dc_config_number = isset($API->settings['connection_settings'][$datacenter]) ? $datacenter : 'all';
//$connection->pending_outgoing[$connection->pending_outgoing_key++] = ['_' => 'http_wait', 'serialized_body' => $this->API->serialize_object(['type' => ''], ['_' => 'http_wait', 'max_wait' => $API->settings['connection_settings'][$dc_config_number]['timeout'] * 1000 - 100, 'wait_after' => 0, 'max_delay' => 0], 'http_wait'), 'content_related' => true, 'unencrypted' => false, 'method' => true];
$connection->pending_outgoing[$connection->pending_outgoing_key++] = ['_' => 'http_wait', 'serialized_body' => $this->API->serialize_object(['type' => ''], ['_' => 'http_wait', 'max_wait' => 30000, 'wait_after' => 0, 'max_delay' => 1], 'http_wait'), 'content_related' => true, 'unencrypted' => false, 'method' => true];
$connection->pending_outgoing_key %= Connection::PENDING_MAX;
$has_http_wait = true;
}
$total_length = 0;
$count = 0;
ksort($connection->pending_outgoing);
foreach ($connection->pending_outgoing as $k => $message) {
if ($message['unencrypted']) {
continue;
}
if (isset($message['container'])) {
unset($connection->pending_outgoing[$k]);
continue;
}
$body = $message['serialized_body'];
$message_id = isset($message['msg_id']) ? $message['msg_id'] : $connection->generate_message_id($datacenter);
$API->logger->logger("Sending {$message['_']} as encrypted message to DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$MTmessage = ['_' => 'MTmessage', 'msg_id' => $message_id, 'body' => $body, 'seqno' => $connection->generate_out_seq_no($message['content_related'])];
if (isset($message['method']) && $message['method'] && $message['_'] !== 'http_wait') {
if ((!isset($connection->temp_auth_key['connection_inited']) || $connection->temp_auth_key['connection_inited'] === false) && $message['_'] !== 'auth.bindTempAuthKey') {
$API->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['write_client_info'], $message['_']), \danog\MadelineProto\Logger::NOTICE);
$MTmessage['body'] = $API->serialize_method(
'invokeWithLayer',
[
'layer' => $API->settings['tl_schema']['layer'],
'query' => $API->serialize_method(
'initConnection',
[
'api_id' => $API->settings['app_info']['api_id'],
'api_hash' => $API->settings['app_info']['api_hash'],
'device_model' => strpos($datacenter, 'cdn') === false ? $API->settings['app_info']['device_model'] : 'n/a',
'system_version' => strpos($datacenter, 'cdn') === false ? $API->settings['app_info']['system_version'] : 'n/a',
'app_version' => $API->settings['app_info']['app_version'],
'system_lang_code' => $API->settings['app_info']['lang_code'],
'lang_code' => $API->settings['app_info']['lang_code'],
'lang_pack' => $API->settings['app_info']['lang_pack'],
'query' => $MTmessage['body'],
]
),
]
);
} else {
if (isset($message['queue'])) {
if (!isset($connection->call_queue[$message['queue']])) {
$connection->call_queue[$message['queue']] = [];
}
$MTmessage['body'] = $API->serialize_method('invokeAfterMsgs', ['msg_ids' => $connection->call_queue[$message['queue']], 'query' => $MTmessage['body']]);
$connection->call_queue[$message['queue']][$message_id] = $message_id;
if (count($connection->call_queue[$message['queue']]) > $API->settings['msg_array_limit']['call_queue']) {
reset($connection->call_queue[$message['queue']]);
$key = key($connection->call_queue[$message['queue']]);
unset($connection->call_queue[$message['queue']][$key]);
}
}
/* if ($API->settings['requests']['gzip_encode_if_gt'] !== -1 && ($l = strlen($MTmessage['body'])) > $API->settings['requests']['gzip_encode_if_gt']) {
if (($g = strlen($gzipped = gzencode($MTmessage['body']))) < $l) {
$MTmessage['body'] = $API->serialize_object(['type' => 'gzip_packed'], ['packed_data' => $gzipped], 'gzipped data');
$API->logger->logger('Using GZIP compression for ' . $message['_'] . ', saved ' . ($l - $g) . ' bytes of data, reduced call size by ' . $g * 100 / $l . '%', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
}
unset($gzipped);
}*/
}
}
$body_length = strlen($MTmessage['body']);
if ($total_length && $total_length + $body_length + 32 > 655360) {
$API->logger->logger('Length overflow, postponing part of payload', \danog\MadelineProto\Logger::NOTICE);
break;
}
$count++;
$total_length += $body_length + 32;
$MTmessage['bytes'] = $body_length;
$messages[] = $MTmessage;
$keys[$k] = $message_id;
if ($total_length && $total_length + 32 > 655360) {
$API->logger->logger('Length overflow, postponing part of payload', \danog\MadelineProto\Logger::NOTICE);
break;
}
}
if (count($messages) > 1) {
$API->logger->logger("Wrapping in msg_container as encrypted message for DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$message_id = $connection->generate_message_id($datacenter);
$connection->pending_outgoing[$connection->pending_outgoing_key] = ['_' => 'msg_container', 'container' => array_values($keys), 'content_related' => false, 'method' => false];
//var_dumP("container ".bin2hex($message_id));
$keys[$connection->pending_outgoing_key++] = $message_id;
$connection->pending_outgoing_key %= Connection::PENDING_MAX;
$message_data = $API->serialize_object(['type' => ''], ['_' => 'msg_container', 'messages' => $messages], 'container');
$message_data_length = strlen($message_data);
$seq_no = $connection->generate_out_seq_no(false);
} elseif (count($messages)) {
$message = $messages[0];
$message_data = $message['body'];
$message_data_length = $message['bytes'];
$message_id = $message['msg_id'];
$seq_no = $message['seqno'];
} else {
$API->logger->logger('NO MESSAGE SENT', \danog\MadelineProto\Logger::WARNING);
return;
}
unset($messages);
$plaintext = $connection->temp_auth_key['server_salt'].$connection->session_id.$message_id.pack('VV', $seq_no, $message_data_length).$message_data;
$padding = $this->posmod(-strlen($plaintext), 16);
if ($padding < 12) {
$padding += 16;
}
$padding = $this->random($padding);
$message_key = substr(hash('sha256', substr($connection->temp_auth_key['auth_key'], 88, 32).$plaintext.$padding, true), 8, 16);
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $connection->temp_auth_key['auth_key']);
$message = $connection->temp_auth_key['id'].$message_key.$this->ige_encrypt($plaintext.$padding, $aes_key, $aes_iv);
$buffer = yield $connection->stream->getWriteBuffer($len = strlen($message));
$t = microtime(true);
yield $buffer->bufferWrite($message);
$connection->http_req_count++;
$API->logger->logger("Sent encrypted payload to DC {$datacenter}, speed ".((($len * 8) / (microtime(true) - $t)) / 1000000).' mbps!', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$sent = time();
if ($to_ack) {
$connection->ack_queue = [];
}
if ($has_http_wait) {
$connection->last_http_wait = $sent;
} elseif ($API->isAltervista()) {
$connection->last_http_wait = PHP_INT_MAX;
}
foreach ($keys as $key => $message_id) {
$connection->outgoing_messages[$message_id] = &$connection->pending_outgoing[$key];
if (isset($connection->outgoing_messages[$message_id]['promise'])) {
$connection->new_outgoing[$message_id] = $message_id;
$connection->outgoing_messages[$message_id]['sent'] = $sent;
$connection->outgoing_messages[$message_id]['tries'] = 0;
}
if (isset($connection->outgoing_messages[$message_id]['send_promise'])) {
$connection->outgoing_messages[$message_id]['send_promise']->resolve(isset($connection->outgoing_messages[$message_id]['promise']) ? $connection->outgoing_messages[$message_id]['promise'] : true);
}
//var_dumP("encrypted ".bin2hex($message_id)." ".$connection->outgoing_messages[$message_id]['_']);
unset($connection->pending_outgoing[$key]);
}
//if (!empty($connection->pending_outgoing)) $connection->select();
} while (!empty($connection->pending_outgoing));
$connection->pending_outgoing_key = 0;
}
}

View File

@ -0,0 +1,75 @@
<?php
/**
* Loop helper trait.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop\Impl;
use Amp\Promise;
use danog\MadelineProto\Logger;
use danog\MadelineProto\Loop\LoopInterface;
/**
* Loop helper trait.
*
* Wraps the asynchronous generator methods with asynchronous promise-based methods
*
* @author Daniil Gentili <daniil@daniil.it>
*/
abstract class Loop implements LoopInterface
{
use \danog\MadelineProto\Tools;
private $count = 0;
protected $API;
protected $connection;
protected $datacenter;
public function __construct($API, $datacenter)
{
$this->API = $API;
$this->datacenter = $datacenter;
$this->connection = $API->datacenter->sockets[$datacenter];
}
public function start()
{
if ($this->count) {
$this->API->logger->logger("NOT entering check loop in DC {$this->datacenter} with running count {$this->count}", Logger::ERROR);
return false;
}
Promise\rethrow($this->call($this->loop()));
return true;
}
public function exitedLoop()
{
$this->count--;
}
public function startedLoop()
{
$this->count++;
}
public function isRunning()
{
return $this->count;
}
}

View File

@ -0,0 +1,76 @@
<?php
/**
* Loop helper trait.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop\Impl;
use Amp\Deferred;
use Amp\Loop;
use Amp\Promise;
use Amp\Success;
use danog\MadelineProto\Loop\ResumableLoopInterface;
/**
* Resumable signal loop helper trait.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
abstract class ResumableSignalLoop extends SignalLoop implements ResumableLoopInterface
{
private $resume;
private $resumeWatcher;
public function pause($time = null): Promise
{
if (!is_null($time)) {
if ($time <= 0) {
return new Success(0);
} else {
$resume = microtime(true) + $time;
if ($this->resumeWatcher) {
Loop::cancel($this->resumeWatcher);
$this->resumeWatcher = null;
}
$this->resumeWatcher = Loop::delay($time * 1000, [$this, 'resume'], $resume);
//var_dump("resume {$this->resumeWatcher} ".get_class($this)." DC {$this->datacenter} after ", ($time * 1000), $resume);
}
}
$this->resume = new Deferred();
return $this->resume->promise();
}
public function resume($watcherId = null, $expected = 0)
{
if ($this->resumeWatcher) {
$storedWatcherId = $this->resumeWatcher;
Loop::cancel($storedWatcherId);
$this->resumeWatcher = null;
if ($watcherId && $storedWatcherId !== $watcherId) {
return;
}
}
if ($expected) {
//var_dump("=======", "resume $watcherId ".get_class($this)." DC {$this->datacenter} diff ".(microtime(true) - $expected).": expected $expected, actual ".microtime(true));
}
if ($this->resume) {
$resume = $this->resume;
$this->resume = null;
$resume->resolve();
}
}
}

View File

@ -0,0 +1,62 @@
<?php
/**
* Loop helper trait.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop\Impl;
use Amp\Deferred;
use Amp\Promise;
use danog\MadelineProto\Loop\SignalLoopInterface;
/**
* Signal loop helper trait.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
abstract class SignalLoop extends Loop implements SignalLoopInterface
{
private $signalDeferred;
public function signal($what)
{
if ($this->signalDeferred) {
$deferred = $this->signalDeferred;
$this->signalDeferred = null;
if ($what instanceof \Exception || $what instanceof \Throwable) {
$deferred->fail($what);
} else {
$deferred->resolve($what);
}
}
}
public function waitSignal(Promise $promise): Promise
{
$this->signalDeferred = new Deferred();
$dpromise = $this->signalDeferred->promise();
$promise->onResolve(function () use ($promise) {
if ($this->signalDeferred !== null) {
$deferred = $this->signalDeferred;
$this->signalDeferred = null;
$deferred->resolve($promise);
}
});
return $dpromise;
}
}

View File

@ -0,0 +1,41 @@
<?php
/**
* Loop interface.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop;
/**
* Loop interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
interface LoopInterface
{
/**
* Start the loop.
*
* @return void
*/
public function start();
/**
* The actual loop.
*
* @return void
*/
public function loop(): \Generator;
}

View File

@ -0,0 +1,45 @@
<?php
/**
* Resumable loop interface.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop;
use Amp\Promise;
/**
* Resumable loop interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
interface ResumableLoopInterface extends LoopInterface
{
/**
* Pause the loop.
*
* @param int $time For how long to pause the loop
*
* @return Promise
*/
public function pause($time = null): Promise;
/**
* Resume the loop.
*
* @return void
*/
public function resume();
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Signal loop interface.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Loop;
use Amp\Promise;
/**
* Signal loop interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
interface SignalLoopInterface extends LoopInterface
{
/**
* Resolve the promise or return|throw the signal.
*
* @param Promise $promise The origin promise
*
* @return Promise
*/
public function waitSignal(Promise $promise): Promise;
/**
* Send a signal to the the loop.
*
* @param Exception|any $data Signal to send
*
* @return void
*/
public function signal($data);
}

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* Lua module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
@ -27,6 +33,8 @@ class Lua
}
$this->MadelineProto = $MadelineProto;
$this->MadelineProto->settings['updates']['handle_updates'] = true;
$this->MadelineProto->API->datacenter->sockets[$this->MadelineProto->settings['connection_settings']['default_dc']]->startUpdateLoop();
$this->script = $script;
$this->__wakeup();
}

File diff suppressed because one or more lines are too long

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* AckHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\MTProtoTools;
@ -26,24 +32,47 @@ trait AckHandler
return false;
}
//$this->logger->logger("Ack-ed ".$this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['_']." with message ID $message_id on DC $datacenter");
/*
if (isset($this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['body'])) {
unset($this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['body']);
}
if (isset($this->datacenter->sockets[$datacenter]->new_outgoing[$message_id])) {
unset($this->datacenter->sockets[$datacenter]->new_outgoing[$message_id]);
}*/
return true;
}
return $this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['ack'] = true;
public function got_response_for_outgoing_message_id($message_id, $datacenter)
{
// The server acknowledges that it received my message
if (!isset($this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id])) {
$this->logger->logger("WARNING: Couldn't find message id ".$message_id.' in the array of outgoing messages. Maybe try to increase its size?', \danog\MadelineProto\Logger::WARNING);
return false;
}
if (isset($this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['body'])) {
unset($this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['body']);
}
if (isset($this->datacenter->sockets[$datacenter]->new_outgoing[$message_id])) {
unset($this->datacenter->sockets[$datacenter]->new_outgoing[$message_id]);
}
return true;
}
public function ack_incoming_message_id($message_id, $datacenter)
{
// I let the server know that I received its message
if (!isset($this->datacenter->sockets[$datacenter]->incoming_messages[$message_id])) {
$this->logger->logger("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->sockets[$datacenter]->temp_auth_key['id'] === null || $this->datacenter->sockets[$datacenter]->temp_auth_key['id'] === "\0\0\0\0\0\0\0\0") {
// || (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$message_id]['ack']) && $this->datacenter->sockets[$datacenter]->incoming_messages[$message_id]['ack'])) {
return;
$this->logger->logger("WARNING: Couldn't find message id ".$message_id.' in the array of incoming messages. Maybe try to increase its size?', \danog\MadelineProto\Logger::WARNING);
}
/*if ($this->datacenter->sockets[$datacenter]->temp_auth_key['id'] === null || $this->datacenter->sockets[$datacenter]->temp_auth_key['id'] === "\0\0\0\0\0\0\0\0") {
// || (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$message_id]['ack']) && $this->datacenter->sockets[$datacenter]->incoming_messages[$message_id]['ack'])) {
return;
}*/
$this->datacenter->sockets[$datacenter]->ack_queue[$message_id] = $message_id;
//$this->object_call('msgs_ack', ['msg_ids' => [$message_id]], ['datacenter' => $datacenter]);
return true;
//$this->datacenter->sockets[$datacenter]->incoming_messages[$message_id]['ack'] = true;
}
}

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* AuthKeyHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\MTProtoTools;
@ -21,7 +27,10 @@ namespace danog\MadelineProto\MTProtoTools;
*/
trait AuthKeyHandler
{
public function create_auth_key($expires_in, $datacenter)
private $init_auth_dcs = [];
private $pending_auth = false;
public function create_auth_key_async($expires_in, $datacenter): \Generator
{
$req_pq = strpos($datacenter, 'cdn') ? 'req_pq' : 'req_pq_multi';
for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) {
@ -34,18 +43,18 @@ trait AuthKeyHandler
* @method req_pq
*
* @param [
* int128 $nonce : The value of nonce is selected randomly by the client (random number) and identifies the client within this communication
* int128 $nonce : The value of nonce is selected randomly by the client (random number) and identifies the client within this communication
* ]
*
* @return ResPQ [
* int128 $nonce : The value of nonce is selected randomly by the server
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* string $pq : This is a representation of a natural number (in binary big endian format). This number is the product of two different odd prime numbers
* Vector long $server_public_key_fingerprints : This is a list of public RSA key fingerprints
* int128 $nonce : The value of nonce is selected randomly by the server
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* string $pq : This is a representation of a natural number (in binary big endian format). This number is the product of two different odd prime numbers
* Vector long $server_public_key_fingerprints : This is a list of public RSA key fingerprints
* ]
*/
$nonce = $this->random(16);
$ResPQ = $this->method_call($req_pq, ['nonce' => $nonce], ['datacenter' => $datacenter]);
$ResPQ = yield $this->method_call_async_read($req_pq, ['nonce' => $nonce], ['datacenter' => $datacenter]);
/*
* ***********************************************************************
* Check if the client's nonce and the server's nonce are the same
@ -135,7 +144,7 @@ trait AuthKeyHandler
}
if (!$pq->equals($p->multiply($q))) {
throw new \danog\MadelineProto\SecurityException("couldn't compute p and q. Original pq: {$pq}, computed p: {$p}, computed q: {$q}, computed pq: ".$p->multiply($q));
throw new \danog\MadelineProto\SecurityException("Couldn't compute p and q, install prime.madelineproto.xyz to fix. Original pq: {$pq}, computed p: {$p}, computed q: {$q}, computed pq: ".$p->multiply($q));
}
}
}
@ -151,7 +160,7 @@ trait AuthKeyHandler
$p_bytes = $p->toBytes();
$q_bytes = $q->toBytes();
$new_nonce = $this->random(32);
$data_unserialized = ['pq' => $pq_bytes, 'p' => $p_bytes, 'q' => $q_bytes, 'nonce' => $nonce, 'server_nonce' => $server_nonce, 'new_nonce' => $new_nonce, 'expires_in' => $expires_in];
$data_unserialized = ['pq' => $pq_bytes, 'p' => $p_bytes, 'q' => $q_bytes, 'nonce' => $nonce, 'server_nonce' => $server_nonce, 'new_nonce' => $new_nonce, 'expires_in' => $expires_in, 'dc' => preg_replace('|_.*|', '', $datacenter)];
$p_q_inner_data = $this->serialize_object(['type' => 'p_q_inner_data'.($expires_in < 0 ? '' : '_temp')], $data_unserialized, 'p_q_inner_data');
/*
* ***********************************************************************
@ -167,22 +176,22 @@ trait AuthKeyHandler
* Starting Diffie Hellman key exchange, Server authentication
* @method req_DH_params
* @param [
* int128 $nonce : The value of nonce is selected randomly by the client (random number) and identifies the client within this communication
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* string $p : The value of BigInteger
* string $q : The value of BigInteger
* long $public_key_fingerprint : This is our key in the server_public_key_fingerprints vector
* string $encrypted_data
* int128 $nonce : The value of nonce is selected randomly by the client (random number) and identifies the client within this communication
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* string $p : The value of BigInteger
* string $q : The value of BigInteger
* long $public_key_fingerprint : This is our key in the server_public_key_fingerprints vector
* string $encrypted_data
* ]
* @return Server_DH_Params [
* int128 $nonce : The value of nonce is selected randomly by the server
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* string $new_nonce_hash : Return this value if server responds with server_DH_params_fail
* string $encrypted_answer : Return this value if server responds with server_DH_params_ok
* int128 $nonce : The value of nonce is selected randomly by the server
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* string $new_nonce_hash : Return this value if server responds with server_DH_params_fail
* string $encrypted_answer : Return this value if server responds with server_DH_params_ok
* ]
*/
//
$server_dh_params = $this->method_call('req_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'p' => $p_bytes, 'q' => $q_bytes, 'public_key_fingerprint' => $key->fp, 'encrypted_data' => $encrypted_data], ['datacenter' => $datacenter]);
$server_dh_params = yield $this->method_call_async_read('req_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'p' => $p_bytes, 'q' => $q_bytes, 'public_key_fingerprint' => $key->fp, 'encrypted_data' => $encrypted_data], ['datacenter' => $datacenter]);
/*
* ***********************************************************************
* Check if the client's nonce and the server's nonce are the same
@ -223,12 +232,12 @@ trait AuthKeyHandler
* ***********************************************************************
* Deserialize answer
* @return Server_DH_inner_data [
* int128 $nonce : The value of nonce is selected randomly by the client (random number) and identifies the client within this communication
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* int $g
* string $dh_prime
* string $g_a
* int $server_time
* int128 $nonce : The value of nonce is selected randomly by the client (random number) and identifies the client within this communication
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* int $g
* string $dh_prime
* string $g_a
* int $server_time
* ]
*/
$server_DH_inner_data = $this->deserialize($answer, ['type' => '']);
@ -280,10 +289,10 @@ trait AuthKeyHandler
* serialize client_DH_inner_data
* @method client_DH_inner_data
* @param Server_DH_inner_data [
* int128 $nonce : The value of nonce is selected randomly by the client (random number) and identifies the client within this communication
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* long $retry_id : First attempt
* string $g_b : g^b mod dh_prime
* int128 $nonce : The value of nonce is selected randomly by the client (random number) and identifies the client within this communication
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* long $retry_id : First attempt
* string $g_b : g^b mod dh_prime
* ]
*/
$data = $this->serialize_object(['type' => 'client_DH_inner_data'], ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'retry_id' => $retry_id, 'g_b' => $g_b_str], 'client_DH_inner_data');
@ -300,19 +309,19 @@ trait AuthKeyHandler
* Send set_client_DH_params query
* @method set_client_DH_params
* @param Server_DH_inner_data [
* int128 $nonce : The value of nonce is selected randomly by the client (random number) and identifies the client within this communication
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* string $encrypted_data
* int128 $nonce : The value of nonce is selected randomly by the client (random number) and identifies the client within this communication
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* string $encrypted_data
* ]
* @return Set_client_DH_params_answer [
* string $_ : This value is dh_gen_ok, dh_gen_retry OR dh_gen_fail
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* int128 $new_nonce_hash1 : Return this value if server responds with dh_gen_ok
* int128 $new_nonce_hash2 : Return this value if server responds with dh_gen_retry
* int128 $new_nonce_hash2 : Return this value if server responds with dh_gen_fail
* string $_ : This value is dh_gen_ok, dh_gen_retry OR dh_gen_fail
* int128 $server_nonce : The value of server_nonce is selected randomly by the server
* int128 $new_nonce_hash1 : Return this value if server responds with dh_gen_ok
* int128 $new_nonce_hash2 : Return this value if server responds with dh_gen_retry
* int128 $new_nonce_hash2 : Return this value if server responds with dh_gen_fail
* ]
*/
$Set_client_DH_params_answer = $this->method_call('set_client_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'encrypted_data' => $encrypted_data], ['datacenter' => $datacenter]);
$Set_client_DH_params_answer = yield $this->method_call_async_read('set_client_DH_params', ['nonce' => $nonce, 'server_nonce' => $server_nonce, 'encrypted_data' => $encrypted_data], ['datacenter' => $datacenter]);
/*
* ***********************************************************************
* Generate auth_key
@ -380,13 +389,7 @@ trait AuthKeyHandler
$this->logger->logger('An exception occurred while generating the authorization key: '.$e->getMessage().' in '.basename($e->getFile(), '.php').' on line '.$e->getLine().'. Retrying...', \danog\MadelineProto\Logger::WARNING);
$req_pq = $req_pq === 'req_pq_multi' ? 'req_pq' : 'req_pq_multi';
} catch (\danog\MadelineProto\RPCErrorException $e) {
if ($e->rpc === 'RPC_CALL_FAIL') {
throw $e;
}
$this->logger->logger('An RPCErrorException occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING);
} finally {
$this->datacenter->sockets[$datacenter]->new_outgoing = [];
$this->datacenter->sockets[$datacenter]->new_incoming = [];
}
}
if (strpos($datacenter, 'cdn') === false) {
@ -403,11 +406,11 @@ trait AuthKeyHandler
*/
$this->logger->logger('Executing g_a check (1/2)...', \danog\MadelineProto\Logger::VERBOSE);
if ($g_a->compare(\danog\MadelineProto\Magic::$one) <= 0 || $g_a->compare($p->subtract(\danog\MadelineProto\Magic::$one)) >= 0) {
throw new \danog\MadelineProto\SecurityException('g_a is invalid (1 < g_a < dh_prime - 1 is false).');
throw new \danog\MadelineProto\SecurityException('g_a is invalid (1 < g_a < p - 1 is false).');
}
$this->logger->logger('Executing g_a check (2/2)...', \danog\MadelineProto\Logger::VERBOSE);
if ($g_a->compare(\danog\MadelineProto\Magic::$twoe1984) < 0 || $g_a->compare($p->subtract(\danog\MadelineProto\Magic::$twoe1984)) >= 0) {
throw new \danog\MadelineProto\SecurityException('g_a is invalid (2^1984 < gA < dh_prime - 2^1984 is false).');
throw new \danog\MadelineProto\SecurityException('g_a is invalid (2^1984 < g_a < p - 2^1984 is false).');
}
return true;
@ -434,9 +437,9 @@ trait AuthKeyHandler
/*
$this->logger->logger('Executing p/g checks (2/3)...', \danog\MadelineProto\Logger::VERBOSE);
if (!$p->subtract(\danog\MadelineProto\Magic::$one)->divide(\danog\MadelineProto\Magic::$two)[0]->isPrime()) {
throw new \danog\MadelineProto\SecurityException("p isn't a safe 2048-bit prime ((p - 1) / 2 isn't a prime).");
throw new \danog\MadelineProto\SecurityException("p isn't a safe 2048-bit prime ((p - 1) / 2 isn't a prime).");
}
*/
*/
/*
* ***********************************************************************
* Check validity of p
@ -480,7 +483,7 @@ trait AuthKeyHandler
return $this->dh_config = $dh_config;
}
public function bind_temp_auth_key($expires_in, $datacenter)
public function bind_temp_auth_key_async($expires_in, $datacenter)
{
for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) {
try {
@ -491,14 +494,14 @@ trait AuthKeyHandler
$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, 'temp_auth_key_id' => $temp_auth_key_id, 'perm_auth_key_id' => $perm_auth_key_id, 'temp_session_id' => $temp_session_id, 'expires_at' => $expires_at], 'bind_temp_auth_key_inner');
$message_id = $this->generate_message_id($datacenter);
$message_id = $this->datacenter->sockets[$datacenter]->generate_message_id();
$seq_no = 0;
$encrypted_data = $this->random(16).$message_id.pack('VV', $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->old_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]);
$res = yield $this->method_call_async_read('auth.bindTempAuthKey', ['perm_auth_key_id' => $perm_auth_key_id, 'nonce' => $nonce, 'expires_at' => $expires_at, 'encrypted_message' => $encrypted_message], ['msg_id' => $message_id, 'datacenter' => $datacenter]);
if ($res === true) {
$this->logger->logger('Successfully binded temporary and permanent authorization keys, DC '.$datacenter, \danog\MadelineProto\Logger::NOTICE);
@ -510,9 +513,6 @@ trait AuthKeyHandler
$this->logger->logger('An exception occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger('An RPCErrorException occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING);
} finally {
$this->datacenter->sockets[$datacenter]->new_outgoing = [];
$this->datacenter->sockets[$datacenter]->new_incoming = [];
}
}
@ -522,70 +522,130 @@ trait AuthKeyHandler
// Creates authorization keys
public function init_authorization()
{
return $this->wait($this->init_authorization_async());
}
public function init_authorization_async()
{
if ($this->pending_auth) {
return;
}
$initing = $this->initing_authorization;
$this->initing_authorization = true;
$this->updates_state['sync_loading'] = true;
$this->postpone_updates = true;
try {
$dcs = [];
$postpone = [];
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;
if (strpos($id, 'media') !== false) {
$oid = intval($id);
if (isset($dcs[$oid])) {
$postpone[$id] = $socket;
}
continue;
}
$cdn = strpos($id, 'cdn');
$media = strpos($id, 'media');
if (isset($this->init_auth_dcs[$id])) {
$this->pending_auth = true;
continue;
}
$dcs[$id] = function () use ($id, $socket) {
return $this->init_authorization_socket($id, $socket);
};
}
yield array_shift($dcs)();
foreach ($dcs as &$dc) {
$dc = $dc();
}
yield $dcs;
if ($socket->temp_auth_key === null || $socket->auth_key === null) {
$dc_config_number = isset($this->settings['connection_settings'][$id]) ? $id : 'all';
if ($socket->auth_key === null && !$cdn && !$media) {
$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['gen_perm_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->auth_key = $this->create_auth_key(-1, $id);
$socket->authorized = false;
} elseif ($socket->auth_key === null && $media) {
$socket->auth_key = $this->datacenter->sockets[intval($id)]->auth_key;
$socket->authorized = &$this->datacenter->sockets[intval($id)]->authorized;
}
if ($media) {
$socket->authorized = &$this->datacenter->sockets[intval($id)]->authorized;
}
if ($this->settings['connection_settings'][$dc_config_number]['pfs']) {
if (!$cdn) {
$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->temp_auth_key = null;
$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);
$config = $this->method_call('help.getConfig', [], ['datacenter' => $id]);
$this->sync_authorization($id);
$this->get_config($config);
} elseif ($socket->temp_auth_key === null) {
$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->temp_auth_key = $this->create_auth_key($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
}
} else {
if (!$cdn) {
$socket->temp_auth_key = $socket->auth_key;
$config = $this->method_call('help.getConfig', [], ['datacenter' => $id]);
$this->sync_authorization($id);
$this->get_config($config);
} elseif ($socket->temp_auth_key === null) {
$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->temp_auth_key = $this->create_auth_key($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
}
}
} elseif (!$cdn) {
$this->sync_authorization($id);
}
foreach ($postpone as $id => $socket) {
yield $this->init_authorization_socket($id, $socket);
}
//foreach ($dcs as $dc) { yield $dc; }
if ($this->pending_auth && empty($this->init_auth_dcs)) {
$this->pending_auth = false;
yield $this->init_authorization_async();
}
} finally {
$this->pending_auth = false;
$this->postpone_updates = false;
$this->initing_authorization = false;
$this->initing_authorization = $initing;
$this->updates_state['sync_loading'] = false;
$this->handle_pending_updates();
}
}
public function sync_authorization($id)
public function init_authorization_socket($id, $socket)
{
$this->init_auth_dcs[$id] = true;
try {
if ($socket->session_id === null) {
$socket->session_id = $this->random(8);
$socket->session_in_seq_no = 0;
$socket->session_out_seq_no = 0;
}
$cdn = strpos($id, 'cdn');
$media = strpos($id, 'media');
if ($socket->temp_auth_key === null || $socket->auth_key === null) {
$dc_config_number = isset($this->settings['connection_settings'][$id]) ? $id : 'all';
if ($socket->auth_key === null && !$cdn && !$media) {
$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['gen_perm_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->auth_key = yield $this->create_auth_key_async(-1, $id);
$socket->authorized = false;
} elseif ($socket->auth_key === null && $media) {
$socket->auth_key = $this->datacenter->sockets[intval($id)]->auth_key;
$socket->authorized = &$this->datacenter->sockets[intval($id)]->authorized;
}
if ($media) {
$socket->authorized = &$this->datacenter->sockets[intval($id)]->authorized;
}
if ($this->settings['connection_settings'][$dc_config_number]['pfs']) {
if (!$cdn) {
$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
//$authorized = $socket->authorized;
//$socket->authorized = false;
$socket->temp_auth_key = null;
$socket->temp_auth_key = yield $this->create_auth_key_async($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
yield $this->bind_temp_auth_key_async($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
//$socket->authorized = $authorized;
$config = yield $this->method_call_async_read('help.getConfig', [], ['datacenter' => $id]);
yield $this->sync_authorization_async($id);
yield $this->get_config_async($config);
} elseif ($socket->temp_auth_key === null) {
$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->temp_auth_key = yield $this->create_auth_key_async($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
}
} else {
if (!$cdn) {
$socket->temp_auth_key = $socket->auth_key;
$config = yield $this->method_call_async_read('help.getConfig', [], ['datacenter' => $id]);
yield $this->sync_authorization_async($id);
yield $this->get_config_async($config);
} elseif ($socket->temp_auth_key === null) {
$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->temp_auth_key = yield $this->create_auth_key_async($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
}
}
} elseif (!$cdn) {
yield $this->sync_authorization_async($id);
}
} finally {
unset($this->init_auth_dcs[$id]);
}
}
public function sync_authorization_async($id)
{
if (!isset($this->datacenter->sockets[$id])) {
return false;
@ -599,8 +659,8 @@ trait AuthKeyHandler
if ($authorized_socket->temp_auth_key !== null && $authorized_socket->auth_key !== null && $authorized_socket->authorized === true && $this->authorized === self::LOGGED_IN && $socket->authorized === false && strpos($authorized_dc_id, 'cdn') === false) {
try {
$this->logger->logger('Trying to copy authorization from dc '.$authorized_dc_id.' to dc '.$id);
$exported_authorization = $this->method_call('auth.exportAuthorization', ['dc_id' => preg_replace('|_.*|', '', $id)], ['datacenter' => $authorized_dc_id]);
$authorization = $this->method_call('auth.importAuthorization', $exported_authorization, ['datacenter' => $id]);
$exported_authorization = yield $this->method_call_async_read('auth.exportAuthorization', ['dc_id' => preg_replace('|_.*|', '', $id)], ['datacenter' => $authorized_dc_id]);
$authorization = yield $this->method_call_async_read('auth.importAuthorization', $exported_authorization, ['datacenter' => $id]);
$socket->authorized = true;
break;
} catch (\danog\MadelineProto\Exception $e) {

View File

@ -1,96 +1,109 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* CallHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\MTProtoTools;
use Amp\Deferred;
use Amp\Promise;
use danog\MadelineProto\Async\Parameters;
use function Amp\call;
use function Amp\Promise\all;
/**
* Manages method and object calls.
*/
trait CallHandler
{
public $wrapper;
public function method_call($method, $args = [], $aargs = ['message_id' => null, 'heavy' => false])
/*
public function select()
{
if (!is_array($args)) {
throw new \danog\MadelineProto\Exception("Arguments aren't an array.");
}
if (!is_array($aargs)) {
throw new \danog\MadelineProto\Exception("Additonal arguments aren't an array.");
}
if (isset($args['id']['_']) && isset($args['id']['dc_id']) && $args['id']['_'] === 'inputBotInlineMessageID') {
$aargs['datacenter'] = $args['id']['dc_id'];
}
if (!isset($aargs['datacenter'])) {
throw new \danog\MadelineProto\Exception('No datacenter provided');
}
if (isset($aargs['apifactory']) && array_key_exists($method, self::DISALLOWED_METHODS)) {
throw new \danog\MadelineProto\Exception(self::DISALLOWED_METHODS[$method], 0, null, 'MadelineProto', 1);
}
if ($this->wrapper instanceof \danog\MadelineProto\API && isset($this->wrapper->session) && !is_null($this->wrapper->session) && time() - $this->wrapper->serialized > $this->settings['serialization']['serialization_interval']) {
$this->logger->logger("Didn't serialize in a while, doing that now...");
$this->wrapper->serialize($this->wrapper->session);
}
if (isset($aargs['file']) && $aargs['file'] && isset($this->datacenter->sockets[$aargs['datacenter'].'_media'])) {
\danog\MadelineProto\Logger::log('Using media DC');
$aargs['datacenter'] .= '_media';
}
if (isset($args['message']) && is_string($args['message']) && $this->mb_strlen($args['message']) > $this->config['message_length_max']) {
$arg_chunks = $this->split_to_chunks($args);
$args = array_shift($arg_chunks);
}
$args = $this->botAPI_to_MTProto($args);
if (isset($args['ping_id']) && is_int($args['ping_id'])) {
$args['ping_id'] = $this->pack_signed_long($args['ping_id']);
}
if (isset($args['chat_id']) && in_array($method, ['messages.addChatUser', 'messages.deleteChatUser', 'messages.editChatAdmin', 'messages.editChatPhoto', 'messages.editChatTitle', 'messages.getFullChat', 'messages.exportChatInvite', 'messages.editChatAdmin', 'messages.migrateChat']) && (!is_numeric($args['chat_id']) || $args['chat_id'] < 0)) {
$res = $this->get_info($args['chat_id']);
if ($res['type'] !== 'chat') {
throw new \danog\MadelineProto\Exception('chat_id is not a chat id (only normal groups allowed, not supergroups)!');
$result = [];
try {
/*
if ($this->is_http($this->settings['connection_settings']['default_dc']) || $this->altervista) {
$this->logger->logger("Initial HTTP short poll");
$waiting = $this->datacenter->select(0.1);
$result = $this->handle_select($waiting, $result);
}/
$tries = 10; // TODO add setting
$this->logger->logger('Long poll');
$t = microtime(true);
$waiting = $this->datacenter->select();
$t = microtime(true) - $t;
$this->logger->logger("Long poll took $t");
$result = $this->handle_select($waiting, $result);
do {
$this->logger->logger('Short poll');
$waiting = $this->datacenter->select($this->is_http($this->settings['connection_settings']['default_dc']) || $this->altervista ? $this->settings['connection_settings']['all']['timeout'] / 10 : true);
$result = $this->handle_select($waiting, $result);
} while ($tries-- && $waiting);
} catch (\danog\MadelineProto\NothingInTheSocketException $e) {
$this->logger->logger('Nothing in the socket while selecting', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
foreach ($this->datacenter->sockets as $dc => $socket) {
$this->close_and_reopen($dc);
$this->send_messages($dc);
}
$args['chat_id'] = $res['chat_id'];
}
if (in_array($method, ['messages.setEncryptedTyping', 'messages.readEncryptedHistory', 'messages.sendEncrypted', 'messages.sendEncryptedFile', 'messages.sendEncryptedService', 'messages.receivedQueue'])) {
$aargs['queue'] = 'secret';
}
if (isset($aargs['queue'])) {
$queue = $aargs['queue'];
if (!isset($this->datacenter->sockets[$aargs['datacenter']]->call_queue[$queue])) {
$this->datacenter->sockets[$aargs['datacenter']]->call_queue[$queue] = [];
return $result;
}
public $did = [];
public function handle_select($waiting, $result)
{
foreach ($waiting as $dc) {
$error = $this->recv_message($dc);
if ($error !== true) {
$this->close_and_reopen($dc);
if ($error === -404) {
if ($this->datacenter->sockets[$dc]->temp_auth_key !== null) {
$this->logger->logger('WARNING: Resetting auth key...', \danog\MadelineProto\Logger::WARNING);
$this->datacenter->sockets[$dc]->temp_auth_key = null;
$this->init_authorization();
return $result;
}
}
throw new \danog\MadelineProto\RPCErrorException($error, $error);
}
$result[$dc] = $this->handle_messages($dc) && (isset($result[$dc]) ? $result[$dc] : true);
if (($this->is_http($dc) || $this->altervista) && $this->datacenter->sockets[$dc]->new_outgoing) {
$this->send_messages($dc);
}
unset($aargs['queue']);
}
if (isset($aargs['serialized'])) {
$serialized = $args['serialized'];
} else {
$serialized = $this->serialize_method($method, $args);
}
if ($this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key !== null && (!isset($this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key['connection_inited']) || $this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key['connection_inited'] === false)) {
$this->logger->logger(sprintf(\danog\MadelineProto\Lang::$current_lang['write_client_info'], $method), \danog\MadelineProto\Logger::NOTICE);
$serialized = $this->serialize_method('invokeWithLayer', ['layer' => $this->settings['tl_schema']['layer'], 'query' => $this->serialize_method('initConnection', ['api_id' => $this->settings['app_info']['api_id'], 'api_hash' => $this->settings['app_info']['api_hash'], 'device_model' => strpos($aargs['datacenter'], 'cdn') === false ? $this->settings['app_info']['device_model'] : 'n/a', 'system_version' => strpos($aargs['datacenter'], 'cdn') === false ? $this->settings['app_info']['system_version'] : 'n/a', 'app_version' => $this->settings['app_info']['app_version'], 'system_lang_code' => $this->settings['app_info']['lang_code'], 'lang_code' => $this->settings['app_info']['lang_code'], 'lang_pack' => '', 'query' => $serialized])]);
}
$content_related = $this->content_related($method);
$type = $this->methods->find_by_method($method)['type'];
if (isset($queue)) {
$serialized = $this->serialize_method('invokeAfterMsgs', ['msg_ids' => $this->datacenter->sockets[$aargs['datacenter']]->call_queue[$queue], 'query' => $serialized]);
}
if ($this->settings['requests']['gzip_encode_if_gt'] !== -1 && ($l = strlen($serialized)) > $this->settings['requests']['gzip_encode_if_gt'] && ($g = strlen($gzipped = gzencode($serialized))) < $l) {
$serialized = $this->serialize_object(['type' => 'gzip_packed'], ['packed_data' => $gzipped], 'gzipped data');
$this->logger->logger('Using GZIP compression for '.$method.', saved '.($l - $g).' bytes of data, reduced call size by '.$g * 100 / $l.'%', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
}
$to_ack = [];
$last_recv = $this->datacenter->sockets[$aargs['datacenter']]->last_recv;
for ($count = 1; $count <= $this->settings['max_tries']['query']; $count++) {
return $result;
}
public function iorun($updates)
{
do {
if ($updates && time() - $this->last_getdifference > $this->settings['updates']['getdifference_interval']) {
$this->get_updates_difference();
return;
}
if ($canunset = !$this->updates_state['sync_loading']) {
$this->updates_state['sync_loading'] = true;
}
@ -101,240 +114,395 @@ trait CallHandler
$this->postpone_pwrchat = true;
}
try {
$this->logger->logger('Calling method (try number '.$count.' for '.$method.')...', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
if ($this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key !== null) {
if (isset($message_id)) {
$this->logger->logger('Clearing old method call', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
if (isset($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$message_id])) {
unset($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$message_id]);
}
if (isset($this->datacenter->sockets[$aargs['datacenter']]->new_outgoing[$message_id])) {
unset($this->datacenter->sockets[$aargs['datacenter']]->new_outgoing[$message_id]);
}
}
if (($this->is_http($this->settings['connection_settings']['default_dc']) || $this->altervista) && $updates) {
$this->send_messages($this->settings['connection_settings']['default_dc']);
}
foreach ($this->datacenter->sockets as $id => $datacenter) {
if ($datacenter->pending_outgoing) {
$this->send_messages($id);
}
}
$this->datacenter->sockets[$aargs['datacenter']]->object_queue[] = ['_' => $method, 'body' => $serialized, 'content_related' => $content_related, 'msg_id' => $message_id = isset($aargs['message_id']) ? $aargs['message_id'] : $this->generate_message_id($aargs['datacenter'])];
if (count($to_ack = $this->datacenter->sockets[$aargs['datacenter']]->ack_queue)) {
$this->datacenter->sockets[$aargs['datacenter']]->object_queue[] = ['_' => 'msgs_ack', 'body' => $this->serialize_object(['type' => 'msgs_ack'], ['msg_ids' => $this->datacenter->sockets[$aargs['datacenter']]->ack_queue], 'msgs_ack'), 'content_related' => false, 'msg_id' => $this->generate_message_id($aargs['datacenter'])];
$this->logger->logger('Polling for ' . ($updates ? 'updates' : 'replies') . ': selecting', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$t = microtime(true);
$only_updates = $this->select();
$t = microtime(true) - $t;
$this->logger->logger('Polling for ' . ($updates ? 'updates' : 'replies') . ': selecting took ' . $t, \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$response_result = $this->has_pending_calls();
$repeat = 0;
foreach ($this->datacenter->sockets as $id => $datacenter) {
if ($updates) {
if (isset($only_updates[$id])) {
if ($only_updates[$id]) {
$this->logger->logger("Polling for updates: got only updates for DC $id", \danog\MadelineProto\Logger::VERBOSE);
} else {
$this->logger->logger("Polling for updates: got also RPC replies for DC $id", \danog\MadelineProto\Logger::NOTICE);
}
if ($response_result[$id]) {
$this->logger->logger("Polling for updates: still pending requests, resending for DC $id", \danog\MadelineProto\Logger::WARNING);
$this->send_messages($id);
}
} else {
if ($response_result[$id] || $id === $this->settings['connection_settings']['default_dc']) {
$this->logger->logger("Polling for updates: got nothing for DC $id", \danog\MadelineProto\Logger::ERROR);
if ($this->is_http($id) || $this->altervista) {
$this->logger->logger("Polling for updates: closing and reopening DC $id since we're on HTTP, and we polled");
$this->close_and_reopen($id);
$datacenter->last_http_wait = 0;
$repeat |= 1;
$this->logger->logger("Polling for updates: and now repolling for DC $id");
$this->send_messages($id);
}
}
}
if ($this->is_http($aargs['datacenter']) && $method !== 'http_wait') {
$this->datacenter->sockets[$aargs['datacenter']]->object_queue[] = ['_' => 'http_wait', 'body' => $this->serialize_method('http_wait', ['max_wait' => 500, 'wait_after' => 150, 'max_delay' => 500]), 'content_related' => false, 'msg_id' => $this->generate_message_id($aargs['datacenter'])];
}
$this->send_messages($aargs['datacenter']);
} else {
$this->send_unencrypted_message($method, $serialized, $message_id = isset($aargs['message_id']) ? $aargs['message_id'] : $this->generate_message_id($aargs['datacenter']), $aargs['datacenter']);
$aargs['message_id'] = $message_id;
}
if (isset($only_updates[$id])) {
if ($only_updates[$id]) {
$this->logger->logger("Polling for replies: got only updates for DC $id", \danog\MadelineProto\Logger::WARNING);
if (isset($queue)) {
$this->datacenter->sockets[$aargs['datacenter']]->call_queue[$queue][] = $message_id;
if (count($this->datacenter->sockets[$aargs['datacenter']]->call_queue[$queue]) > $this->settings['msg_array_limit']['call_queue']) {
reset($this->datacenter->sockets[$aargs['datacenter']]->call_queue[$queue]);
$key = key($this->datacenter->sockets[$aargs['datacenter']]->call_queue[$queue]);
if ($key[0] === "\0") {
$key = $key;
}
unset($this->datacenter->sockets[$aargs['datacenter']]->call_queue[$queue][$key]);
}
}
if ($method === 'http_wait' || isset($aargs['noResponse']) && $aargs['noResponse']) {
return true;
}
//$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$message_id]['content'] = ['method' => $method, 'args' => $args];
$this->datacenter->sockets[$aargs['datacenter']]->new_outgoing[$message_id] = ['msg_id' => $message_id, 'method' => $method, 'type' => $type];
$res_count = 0;
$server_answer = null;
$update_count = 0;
$only_updates = false;
$response_tries = $this->settings['max_tries']['response'] + 1;
if ($last_recv) {
$additional = (int) floor((time() - $last_recv) / 10);
if ($additional > $this->settings['max_tries']['response'] * 2) {
$additional = $this->settings['max_tries']['response'] * 2;
}
$response_tries += $additional;
}
while ($server_answer === null && $res_count++ < $response_tries) {
// Loop until we get a response, loop for a max of $this->settings['max_tries']['response'] times
try {
$this->logger->logger('Getting response (try number '.$res_count.' for '.$method.')...', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
if (!isset($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$message_id]['response']) || !isset($this->datacenter->sockets[$aargs['datacenter']]->incoming_messages[$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$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;
} else {
$res_count--;
$update_count++;
}
if ($response_result[$id]) {
$this->logger->logger("Polling for replies: still pending requests, repolling for DC $id", \danog\MadelineProto\Logger::WARNING);
$this->send_messages($id);
$repeat |= 1;
} else {
$this->logger->logger("Polling for replies: got all RPC replies for DC $id", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
}
} else {
$server_answer = $this->datacenter->sockets[$aargs['datacenter']]->incoming_messages[$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$message_id]['response']]['content'];
$this->datacenter->sockets[$aargs['datacenter']]->incoming_messages[$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$message_id]['response']]['content'] = '';
break;
if ($response_result[$id]) {
$this->logger->logger("Polling for replies: still pending requests, repolling for DC $id", \danog\MadelineProto\Logger::WARNING);
$this->send_messages($id);
} else {
$this->logger->logger("Polling for replies: got all RPC replies for DC $id", \danog\MadelineProto\Logger::NOTICE);
}
}
if (($error = $this->recv_message($aargs['datacenter'])) !== true) {
if ($error === -404) {
if ($this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key !== null) {
$this->logger->logger('WARNING: Resetting auth key...', \danog\MadelineProto\Logger::WARNING);
$this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key = null;
$this->init_authorization();
} else {
if ($response_result[$id]) {
$this->logger->logger("Polling for replies: got nothing for DC $id", \danog\MadelineProto\Logger::ERROR);
$this->logger->logger("Polling for replies: closing and reopening DC $id", \danog\MadelineProto\Logger::ERROR);
$this->close_and_reopen($id);
$datacenter->last_http_wait = 0;
$repeat |= 1;
$this->logger->logger("Polling for replies: resending for DC $id", \danog\MadelineProto\Logger::WARNING);
$this->send_messages($id);
} else {
$this->logger->logger("Polling for replies: got all RPC replies for DC $id", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
}
}
}
}
$this->logger->logger('Running guzzle promise queue');
\GuzzleHttp\Promise\queue()->run();
throw new \danog\MadelineProto\Exception('I had to recreate the temporary authorization key');
if ($repeat) {
$this->logger->logger('Repeat iowait');
}
} while ($repeat);
if ($canunset) {
$this->updates_state['sync_loading'] = false;
}
if ($canunsetpostponepwrchat) {
$this->postpone_pwrchat = false;
$this->handle_pending_pwrchat();
}
if ($canunsetpostponeupdates) {
$this->postpone_updates = false;
$this->handle_pending_updates();
}
}
*/
public function has_pending_calls()
{
$result = [];
foreach ($this->datacenter->sockets as $id => $socket) {
$result[$id] = $this->has_pending_calls_dc($id);
}
return $result;
}
public function has_pending_calls_dc($datacenter)
{
//$result = 0;
$dc_config_number = isset($this->settings['connection_settings'][$datacenter]) ? $datacenter : 'all';
foreach ($this->datacenter->sockets[$datacenter]->new_outgoing as $message_id) {
if (isset($this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['sent']) && ($this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['sent'] + $this->settings['connection_settings'][$dc_config_number]['timeout'] < time()) && ($this->datacenter->sockets[$datacenter]->temp_auth_key === null) === (isset($this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['unencrypted']) && $this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['unencrypted']) && $this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['_'] !== 'msgs_state_req') {
return true;
//$result |= 1;
}
}
return false; //(bool) $result;
}
public function check_pending_calls()
{
foreach ($this->datacenter->sockets as $datacenter => $socket) {
$this->check_pending_calls_dc($datacenter);
}
}
public function check_pending_calls_dc($datacenter)
{
if (!empty($this->datacenter->sockets[$datacenter]->new_outgoing)) {
if ($this->has_pending_calls_dc($datacenter)) {
if ($this->datacenter->sockets[$datacenter]->temp_auth_key !== null) {
$message_ids = array_values($this->datacenter->sockets[$datacenter]->new_outgoing);
$deferred = new \danog\MadelineProto\ImmediatePromise();
$deferred->then(
function ($result) use ($datacenter, $message_ids) {
$reply = [];
foreach (str_split($result['info']) as $key => $chr) {
$message_id = $message_ids[$key];
if (!isset($this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id])) {
$this->logger->logger('Already got response for and forgot about message ID '.$this->unpack_signed_long($message_id));
continue;
}
if (!isset($this->datacenter->sockets[$datacenter]->new_outgoing[$message_id])) {
$this->logger->logger('Already got response for '.$this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['_'].' with message ID '.$this->unpack_signed_long($message_id));
continue;
}
$chr = ord($chr);
switch ($chr & 7) {
case 0:
$this->logger->logger('Wrong message status 0 for '.$this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['_'], \danog\MadelineProto\Logger::FATAL_ERROR);
break;
case 1:
case 2:
case 3:
$this->logger->logger('Message '.$this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['_'].' with message ID '.$this->unpack_signed_long($message_id).' not received by server, resending...', \danog\MadelineProto\Logger::ERROR);
$this->method_recall($message_id, $datacenter, false, true);
break;
case 4:
if ($chr & 32) {
$this->logger->logger('Message '.$this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['_'].' with message ID '.$this->unpack_signed_long($message_id).' received by server and is being processed, waiting...', \danog\MadelineProto\Logger::ERROR);
} elseif ($chr & 64) {
$this->logger->logger('Message '.$this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['_'].' with message ID '.$this->unpack_signed_long($message_id).' received by server and was already processed, requesting reply...', \danog\MadelineProto\Logger::ERROR);
$reply[] = $message_id;
} elseif ($chr & 128) {
$this->logger->logger('Message '.$this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['_'].' with message ID '.$this->unpack_signed_long($message_id).' received by server and was already sent, requesting reply...', \danog\MadelineProto\Logger::ERROR);
$reply[] = $message_id;
} else {
$this->logger->logger('Message '.$this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['_'].' with message ID '.$this->unpack_signed_long($message_id).' received by server, requesting reply...', \danog\MadelineProto\Logger::ERROR);
$reply[] = $message_id;
}
}
}
throw new \danog\MadelineProto\RPCErrorException($error, $error);
}
$only_updates = $this->handle_messages($aargs['datacenter']);
// This method receives data from the socket, and parses stuff
} catch (\danog\MadelineProto\Exception $e) {
$last_error = $e->getMessage().' in '.basename($e->getFile(), '.php').' on line '.$e->getLine();
if (in_array($e->getMessage(), ['Resend query', 'I had to recreate the temporary authorization key', 'Got bad message notification']) || $e->getCode() === 404) {
continue 2;
}
$this->logger->logger('An error getting response of method '.$method.': '.$e->getMessage().' in '.basename($e->getFile(), '.php').' on line '.$e->getLine().'. Retrying...', \danog\MadelineProto\Logger::WARNING);
$this->logger->logger('Full trace '.$e, \danog\MadelineProto\Logger::WARNING);
continue;
} catch (\danog\MadelineProto\NothingInTheSocketException $e) {
$last_error = 'Nothing in the socket';
$this->logger->logger('An error getting response of method '.$method.': '.$e->getMessage().' in '.basename($e->getFile(), '.php').' on line '.$e->getLine().'. Retrying...', \danog\MadelineProto\Logger::WARNING);
$only_updates = false;
if ($last_recv === $this->datacenter->sockets[$aargs['datacenter']]->last_recv) { // the socket is dead, resend request
$this->close_and_reopen($aargs['datacenter']);
if ($this->altervista) {
continue 2;
if ($reply) {
$this->object_call('msg_resend_ans_req', ['msg_ids' => $reply], ['datacenter' => $datacenter, 'postpone' => true]);
}
$this->send_messages($datacenter);
},
function ($error) use ($datacenter) {
throw $error;
}
);
$this->logger->logger("Still missing something on DC $datacenter, sending state request", \danog\MadelineProto\Logger::ERROR);
$this->object_call('msgs_state_req', ['msg_ids' => $message_ids], ['datacenter' => $datacenter, 'promise' => $deferred]);
} else {
$dc_config_number = isset($this->settings['connection_settings'][$datacenter]) ? $datacenter : 'all';
foreach ($this->datacenter->sockets[$datacenter]->new_outgoing as $message_id) {
if (isset($this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['sent']) && $this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['sent'] + $this->settings['connection_settings'][$dc_config_number]['timeout'] < time() && $this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['unencrypted']) {
$this->logger->logger('Still missing '.$this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['_'].' with message id '.$this->unpack_signed_long($message_id)." on DC $datacenter, resending", \danog\MadelineProto\Logger::ERROR);
$this->method_recall($message_id, $datacenter, false, true);
}
//if ($this->datacenter->sockets[$aargs['datacenter']]->last_recv < time() - 1 && $this->is_http($aargs['datacenter'])) {
// $this->close_and_reopen($aargs['datacenter']);
// continue 2;
//}
continue; //2;
}
}
if ($canunset) {
$this->updates_state['sync_loading'] = false;
}
if ($canunsetpostponepwrchat) {
$this->postpone_pwrchat = false;
$this->handle_pending_pwrchat();
}
if ($canunsetpostponeupdates) {
$this->postpone_updates = false;
$this->handle_pending_updates();
}
foreach ($to_ack as $msg_id) {
$this->datacenter->sockets[$aargs['datacenter']]->incoming_messages[$msg_id]['ack'] = true;
if (isset($this->datacenter->sockets[$aargs['datacenter']]->ack_queue[$msg_id])) {
unset($this->datacenter->sockets[$aargs['datacenter']]->ack_queue[$msg_id]);
}
}
if ($server_answer === null) {
throw new \danog\MadelineProto\Exception("Couldn't get response");
}
if (!isset($server_answer['_'])) {
return $server_answer;
}
switch ($server_answer['_']) {
case 'rpc_error':
$this->handle_rpc_error($server_answer, $aargs);
break;
case 'bad_server_salt':
case 'bad_msg_notification':
throw new \danog\MadelineProto\RPCErrorException('Received bad_msg_notification: '.self::BAD_MSG_ERROR_CODES[$server_answer['error_code']], $server_answer['error_code']);
case 'boolTrue':
case 'boolFalse':
$server_answer = $server_answer['_'] === 'boolTrue';
break;
}
if (isset($aargs['botAPI']) && $aargs['botAPI']) {
$server_answer = $this->MTProto_to_botAPI($server_answer, $args);
}
} catch (\danog\MadelineProto\Exception $e) {
$last_error = $e->getMessage().' in '.basename($e->getFile(), '.php').' on line '.$e->getLine();
if (strpos($e->getMessage(), 'Received request to switch to DC ') === 0) {
if (($method === 'users.getUsers' && $args = ['id' => [['_' => 'inputUserSelf']]]) || $method === 'auth.exportAuthorization' || $method === 'updates.getDifference') {
$this->settings['connection_settings']['default_dc'] = $this->authorized_dc = $this->datacenter->curdc;
}
$last_recv = $this->datacenter->sockets[$aargs['datacenter']]->last_recv;
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
continue;
}
$this->close_and_reopen($aargs['datacenter']);
if ($e->getMessage() === 'Re-executing query after server error...') {
return $this->method_call($method, $args, $aargs);
}
continue;
} catch (\RuntimeException $e) {
$last_error = $e->getMessage().' in '.basename($e->getFile(), '.php').' on line '.$e->getLine();
$this->logger->logger('An error occurred while calling method '.$method.': '.$last_error.'. Recreating connection and retrying to call method...', \danog\MadelineProto\Logger::WARNING);
$this->close_and_reopen($aargs['datacenter']);
continue;
} finally {
if (isset($aargs['heavy']) && $aargs['heavy'] && isset($message_id)) {
//$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$message_id]['args'] = [];
unset($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$message_id]);
unset($this->datacenter->sockets[$aargs['datacenter']]->new_outgoing[$message_id]);
}
if (isset($message_id) && $method === 'req_pq') {
unset($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$message_id]);
unset($this->datacenter->sockets[$aargs['datacenter']]->new_outgoing[$message_id]);
}
if ($canunset) {
$this->updates_state['sync_loading'] = false;
}
if ($canunsetpostponepwrchat) {
$this->postpone_pwrchat = false;
$this->handle_pending_pwrchat();
}
if ($canunsetpostponeupdates) {
$this->postpone_updates = false;
$this->handle_pending_updates();
}
}
if ($server_answer === null) {
if ($last_recv === $this->datacenter->sockets[$aargs['datacenter']]->last_recv && $this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key !== null) {
$this->logger->logger('WARNING: Resetting auth key...', \danog\MadelineProto\Logger::WARNING);
$this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key = null;
$this->init_authorization();
return $this->method_call($method, $args, $aargs);
}
throw new \danog\MadelineProto\Exception('An error occurred while calling method '.$method.' ('.$last_error.').');
}
$this->logger->logger('Got response for method '.$method.' @ try '.$count.' (response try '.$res_count.')', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
if ($this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key !== null && (!isset($this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key['connection_inited']) || $this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key['connection_inited'] === false)) {
$this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key['connection_inited'] = true;
}
$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$message_id] = [];
if (isset($arg_chunks) && count($arg_chunks)) {
$server_answer = [$server_answer];
foreach ($arg_chunks as $args) {
$server_answer[] = $this->method_call($method, $args, $aargs);
}
}
return $server_answer;
}
if ($method === 'req_pq') {
throw new \danog\MadelineProto\RPCErrorException('RPC_CALL_FAIL');
}
throw new \danog\MadelineProto\Exception('An error occurred while calling method '.$method.' ('.$last_error.').');
}
public function object_call($object, $args = [], $aargs = ['message_id' => null, 'heavy' => false])
public function method_recall($watcherId, $args)
{
if (!is_array($args)) {
throw new \danog\MadelineProto\Exception("Arguments aren't an array.");
$message_id = $args['message_id'];
$new_datacenter = $args['datacenter'];
$old_datacenter = $new_datacenter;
if (isset($args['old_datacenter'])) {
$old_datacenter = $args['old_datacenter'];
}
if (!isset($aargs['datacenter'])) {
throw new \danog\MadelineProto\Exception('No datacenter provided');
$postpone = false;
if (isset($args['postpone'])) {
$postpone = $args['postpone'];
}
if (isset($this->datacenter->sockets[$old_datacenter]->outgoing_messages[$message_id]['container'])) {
$message_ids = $this->datacenter->sockets[$old_datacenter]->outgoing_messages[$message_id]['container'];
} else {
$message_ids = [$message_id];
}
foreach ($message_ids as $message_id) {
if (isset($this->datacenter->sockets[$old_datacenter]->outgoing_messages[$message_id]['body'])) {
$this->datacenter->sockets[$new_datacenter]->sendMessage($this->datacenter->sockets[$old_datacenter]->outgoing_messages[$message_id], false);
$this->ack_outgoing_message_id($message_id, $old_datacenter);
$this->got_response_for_outgoing_message_id($message_id, $old_datacenter);
}
}
if (!$postpone) {
$this->datacenter->sockets[$new_datacenter]->writer->resume();
}
$serialized = $this->serialize_object(['type' => $object], $args, $object);
$this->datacenter->sockets[$aargs['datacenter']]->object_queue[] = ['_' => $object, 'body' => $serialized, 'content_related' => $this->content_related($object), 'msg_id' => $this->generate_message_id($aargs['datacenter'])];
}
public function method_call($method, $args = [], $aargs = ['msg_id' => null, 'heavy' => false])
{
$promise = $this->method_call_async_read($method, $args, $aargs);
return $this->wait($promise);
}
public function method_call_async_read($method, $args = [], $aargs = ['msg_id' => null, 'heavy' => false]): Promise
{
$deferred = new Deferred();
$this->method_call_async_write($method, $args, $aargs)->onResolve(function ($e, $read_deferred) use ($deferred) {
if ($e) {
$deferred->fail($e);
} else {
if (is_array($read_deferred)) {
$read_deferred = array_map(function ($value) {
return $value->promise();
}, $read_deferred);
$deferred->resolve(all($read_deferred));
} else {
$deferred->resolve($read_deferred->promise());
}
}
});
return isset($aargs['noResponse']) && $aargs['noResponse'] ? new \Amp\Success(0) : $deferred->promise();
}
public function method_call_async_write($method, $args = [], $aargs = ['msg_id' => null, 'heavy' => false]): Promise
{
return call([$this, 'method_call_async_write_generator'], $method, $args, $aargs);
}
public function method_call_async_write_generator($method, $args = [], $aargs = ['msg_id' => null, 'heavy' => false]): \Generator
{
if (is_array($args) && isset($args['id']['_']) && isset($args['id']['dc_id']) && $args['id']['_'] === 'inputBotInlineMessageID') {
$aargs['datacenter'] = $args['id']['dc_id'];
}
if ($this->wrapper instanceof \danog\MadelineProto\API && isset($this->wrapper->session) && !is_null($this->wrapper->session) && time() - $this->wrapper->serialized > $this->settings['serialization']['serialization_interval']) {
$this->logger->logger("Didn't serialize in a while, doing that now...");
$this->wrapper->serialize($this->wrapper->session);
}
if (isset($aargs['file']) && $aargs['file'] && isset($this->datacenter->sockets[$aargs['datacenter'].'_media'])) {
\danog\MadelineProto\Logger::log('Using media DC');
$aargs['datacenter'] .= '_media';
}
if (in_array($method, ['messages.setEncryptedTyping', 'messages.readEncryptedHistory', 'messages.sendEncrypted', 'messages.sendEncryptedFile', 'messages.sendEncryptedService', 'messages.receivedQueue'])) {
$aargs['queue'] = 'secret';
}
if (is_array($args)) {
if (isset($args['message']) && is_string($args['message']) && $this->mb_strlen($args['message']) > $this->config['message_length_max']) {
$arg_chunks = $this->split_to_chunks($args);
$promises = [];
$new_aargs = $aargs;
$new_aargs['postpone'] = true;
$new_aargs['queue'] = $method;
foreach ($arg_chunks as $args) {
$promises[] = $this->method_call_async_write($method, $args, $new_aargs);
}
if (!isset($aargs['postpone'])) {
$this->datacenter->sockets[$aargs['datacenter']]->writer->resume();
}
return yield $promises;
}
$args = $this->botAPI_to_MTProto($args);
if (isset($args['ping_id']) && is_int($args['ping_id'])) {
$args['ping_id'] = $this->pack_signed_long($args['ping_id']);
}
}
$deferred = new Deferred();
$message = ['_' => $method, 'type' => $this->methods->find_by_method($method)['type'], 'content_related' => $this->content_related($method), 'promise' => $deferred, 'method' => true, 'unencrypted' => $this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key === null && strpos($method, '.') === false];
if (is_object($args) && $args instanceof Parameters) {
$message['body'] = call([$args, 'fetchParameters']);
} else {
$message['body'] = $args;
}
if (isset($aargs['msg_id'])) {
$message['msg_id'] = $aargs['msg_id'];
}
if (isset($aargs['queue'])) {
$message['queue'] = $aargs['queue'];
}
if (isset($aargs['file'])) {
$message['file'] = $aargs['file'];
}
if (isset($aargs['botAPI'])) {
$message['botAPI'] = $aargs['botAPI'];
}
if (($method === 'users.getUsers' && $args === ['id' => [['_' => 'inputUserSelf']]]) || $method === 'auth.exportAuthorization' || $method === 'updates.getDifference') {
$message['user_related'] = true;
}
$write_deferred = yield $this->datacenter->sockets[$aargs['datacenter']]->sendMessage($message, isset($aargs['postpone']) ? !$aargs['postpone'] : true);
$deferred = new Deferred();
$write_promise = $write_deferred->promise();
$write_promise->onResolve(
function ($e, $result) use ($aargs, $deferred) {
//$this->datacenter->sockets[$aargs['datacenter']]->checker->resume();
if ($e) {
return $deferred->fail($e);
}
$deferred->resolve($result);
}
);
$this->datacenter->sockets[$aargs['datacenter']]->checker->resume();
return $deferred;
}
public function object_call($object, $args = [], $aargs = ['msg_id' => null, 'heavy' => false])
{
return $this->wait($this->object_call_async($object, $args, $aargs));
}
public function object_call_async($object, $args = [], $aargs = ['msg_id' => null, 'heavy' => false]): Promise
{
$message = ['_' => $object, 'body' => $args, 'content_related' => $this->content_related($object), 'unencrypted' => $this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key === null, 'method' => false];
if (isset($aargs['promise'])) {
$message['promise'] = $aargs['promise'];
}
return $this->datacenter->sockets[$aargs['datacenter']]->sendMessage($message, isset($aargs['postpone']) ? !$aargs['postpone'] : true);
}
/*
$message = [
// only in outgoing messages
'body' => 'serialized body', (optional if container)
'content_related' => bool,
'_' => 'predicate',
'promise' => deferred promise that gets resolved when a response to the message is received (optional),
'send_promise' => deferred promise that gets resolved when the message is sent (optional),
'file' => bool (optional),
'type' => 'type' (optional),
'queue' => queue ID (optional),
'container' => [message ids] (optional),
// only in incoming messages
'content' => deserialized body,
'seq_no' => number (optional),
'from_container' => bool (optional),
// can be present in both
'response' => message id (optional),
'msg_id' => message id (optional),
'sent' => timestamp,
'tries' => number
];
*/
}

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* Crypt module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\MTProtoTools;

View File

@ -1,24 +1,36 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* Files module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\MTProtoTools;
use danog\MadelineProto\Async\AsyncParameters;
use danog\MadelineProto\Exception;
use danog\MadelineProto\Logger;
use danog\MadelineProto\RPCErrorException;
use function Amp\call;
/**
* Manages upload and download of files.
*/
trait Files
{
public function upload($file, $file_name = '', $cb = null, $encrypted = false, $datacenter = null)
public function upload_async($file, $file_name = '', $cb = null, $encrypted = false, $datacenter = null): \Generator
{
if (is_object($file)) {
if (!isset(class_implements($file)['danog\MadelineProto\FileCallbackInterface'])) {
@ -34,7 +46,10 @@ trait Files
if (empty($file_name)) {
$file_name = basename($file);
}
$datacenter = is_null($datacenter) ? $this->datacenter->curdc : $datacenter;
$datacenter = is_null($datacenter) ? $this->settings['connection_settings']['default_dc'] : $datacenter;
if (isset($this->datacenter->sockets[$datacenter.'_media'])) {
$datacenter .= '_media';
}
$file_size = filesize($file);
if ($file_size > 512 * 1024 * 3000) {
throw new \danog\MadelineProto\Exception('Given file is too big!');
@ -51,7 +66,12 @@ trait Files
$constructor = 'input'.($encrypted === true ? 'Encrypted' : '').($file_size > 10 * 1024 * 1024 ? 'FileBig' : 'File').($encrypted === true ? 'Uploaded' : '');
$file_id = $this->random(8);
$f = fopen($file, 'r');
fseek($f, 0);
$seekable = stream_get_meta_data($f)['seekable'];
if ($seekable) {
fseek($f, 0);
}
$ige = null;
if ($encrypted === true) {
$key = $this->random(32);
$iv = $this->random(32);
@ -63,35 +83,69 @@ trait Files
$ige->enableContinuousBuffer();
}
$ctx = hash_init('md5');
while (ftell($f) !== $file_size) {
$bytes = stream_get_contents($f, $part_size);
if ($encrypted === true) {
$bytes = $ige->encrypt(str_pad($bytes, $part_size, chr(0)));
}
hash_update($ctx, $bytes);
if (!$this->method_call($method, ['file_id' => $file_id, 'file_part' => $part_num++, 'file_total_parts' => $part_total_num, 'bytes' => $bytes], ['heavy' => true, 'file' => true, 'datacenter' => &$datacenter])) {
throw new \danog\MadelineProto\Exception('An error occurred while uploading file part '.$part_num);
}
$cb(ftell($f) * 100 / $file_size);
$promises = [];
$cur_part_num = 0;
while ($part_num < $part_total_num) {
$t = microtime(true);
$read_deferred = yield $this->method_call_async_write(
$method,
new AsyncParameters(
static function () use ($file_id, $part_num, $part_total_num, $part_size, $f, $ctx, $ige, $seekable) {
if ($seekable) {
fseek($f, $part_num * $part_size);
}
$bytes = stream_get_contents($f, $part_size);
if ($ige) {
$bytes = $ige->encrypt(str_pad($bytes, $part_size, chr(0)));
}
hash_update($ctx, $bytes);
return ['file_id' => $file_id, 'file_part' => $part_num, 'file_total_parts' => $part_total_num, 'bytes' => $bytes];
},
$seekable
),
['heavy' => true, 'file' => true, 'datacenter' => $datacenter]
);
$this->logger->logger('Speed for chunk: '.(($part_size * 8 / 1000000) / (microtime(true) - $t)));
$part_num++;
$promises[] = $read_deferred->promise();
}
fclose($f);
$constructor = ['_' => $constructor, 'id' => $file_id, 'parts' => $part_total_num, 'name' => $file_name, 'md5_checksum' => hash_final($ctx), 'mime_type' => mime_content_type($file)];
$result = yield $promises;
foreach ($result as $key => $result) {
if (!$result) {
throw new \danog\MadelineProto\Exception('Upload of part '.$key.' failed');
}
}
$constructor = ['_' => $constructor, 'id' => $file_id, 'parts' => $part_total_num, 'name' => $file_name, 'mime_type' => $this->get_mime_from_file($file)];
if ($encrypted === true) {
$constructor['key_fingerprint'] = $fingerprint;
$constructor['key'] = $key;
$constructor['iv'] = $iv;
$constructor['md5_checksum'] = '';
}
fclose($f);
return $constructor;
}
public function upload($file, $file_name = '', $cb = null, $encrypted = false, $datacenter = null)
{
$t = microtime(true);
$res = $this->wait(call([$this, 'upload_async'], $file, $file_name, $cb, $encrypted, $datacenter));
$this->logger->logger('Speed: '.((filesize($file) * 8) / (microtime(true) - $t) / 1000000));
return $res;
}
public function upload_encrypted($file, $file_name = '', $cb = null)
{
return $this->upload($file, $file_name, $cb, true);
}
public function gen_all_file($media)
public function gen_all_file($media, $regenerate)
{
$res = [$this->constructors->find_by_predicate($media['_'])['type'] => $media];
switch ($media['_']) {
@ -100,7 +154,7 @@ trait Files
throw new \danog\MadelineProto\Exception('No access hash');
}
$res['Photo'] = $media['photo'];
$res['InputPhoto'] = ['_' => 'inputPhoto', 'id' => $media['photo']['id'], 'access_hash' => $media['photo']['access_hash']];
$res['InputPhoto'] = ['_' => 'inputPhoto', 'id' => $media['photo']['id'], 'access_hash' => $media['photo']['access_hash'], 'file_reference' => $this->wait($this->referenceDatabase->getReference(ReferenceDatabase::PHOTO_LOCATION, $media['photo']))];
$res['InputMedia'] = ['_' => 'inputMediaPhoto', 'id' => $res['InputPhoto']];
if (isset($media['ttl_seconds'])) {
$res['InputMedia']['ttl_seconds'] = $media['ttl_seconds'];
@ -111,7 +165,7 @@ trait Files
throw new \danog\MadelineProto\Exception('No access hash');
}
$res['Document'] = $media['document'];
$res['InputDocument'] = ['_' => 'inputDocument', 'id' => $media['document']['id'], 'access_hash' => $media['document']['access_hash']];
$res['InputDocument'] = ['_' => 'inputDocument', 'id' => $media['document']['id'], 'access_hash' => $media['document']['access_hash'], 'file_reference' => $this->wait($this->referenceDatabase->getReference(ReferenceDatabase::DOCUMENT_LOCATION, $media['document']))];
$res['InputMedia'] = ['_' => 'inputMediaDocument', 'id' => $res['InputDocument']];
if (isset($media['ttl_seconds'])) {
$res['InputMedia']['ttl_seconds'] = $media['ttl_seconds'];
@ -121,7 +175,7 @@ trait Files
if (!isset($media['access_hash'])) {
throw new \danog\MadelineProto\Exception('No access hash');
}
$res['InputDocument'] = ['_' => 'inputDocument', 'id' => $media['id'], 'access_hash' => $media['access_hash']];
$res['InputDocument'] = ['_' => 'inputDocument', 'id' => $media['id'], 'access_hash' => $media['access_hash'], 'file_reference' => $this->wait($this->referenceDatabase->getReference(ReferenceDatabase::DOCUMENT_LOCATION, $media))];
$res['InputMedia'] = ['_' => 'inputMediaDocument', 'id' => $res['InputDocument']];
$res['MessageMedia'] = ['_' => 'messageMediaDocument', 'document' => $media];
break;
@ -129,8 +183,8 @@ trait Files
if (!isset($media['access_hash'])) {
throw new \danog\MadelineProto\Exception('No access hash');
}
$res['InputDocument'] = ['_' => 'inputDocument', 'id' => $media['id'], 'access_hash' => $media['access_hash']];
$res['InputMedia'] = ['_' => 'inputMediaDocument', 'id' => $res['InputDocument']];
$res['InputPhoto'] = ['_' => 'inputPhoto', 'id' => $media['id'], 'access_hash' => $media['access_hash'], 'file_reference' => $this->wait($this->referenceDatabase->getReference(ReferenceDatabase::PHOTO_LOCATION, $media))];
$res['InputMedia'] = ['_' => 'inputMediaPhoto', 'id' => $res['InputPhoto']];
$res['MessageMedia'] = ['_' => 'messageMediaPhoto', 'photo' => $media];
break;
default:
@ -140,7 +194,7 @@ trait Files
return $res;
}
public function get_file_info($constructor)
public function get_file_info($constructor, $regenerate = false)
{
if (is_string($constructor)) {
$constructor = $this->unpack_file_id($constructor)['MessageMedia'];
@ -148,13 +202,13 @@ trait Files
switch ($constructor['_']) {
case 'updateNewMessage':
case 'updateNewChannelMessage':
$constructor = $constructor['message'];
$constructor = $constructor['message'];
case 'message':
$constructor = $constructor['media'];
$constructor = $constructor['media'];
}
return $this->gen_all_file($constructor);
return $this->gen_all_file($constructor, $regenerate);
}
public function get_download_info($message_media)
@ -167,13 +221,16 @@ trait Files
}
$res = [];
switch ($message_media['_']) {
// Updates
case 'updateNewMessage':
case 'updateNewChannelMessage':
$message_media = $message_media['message'];
$message_media = $message_media['message'];
case 'message':
return $this->get_download_info($message_media['media']);
case 'updateNewEncryptedMessage':
$message_media = $message_media['message'];
$message_media = $message_media['message'];
// Secret media
case 'encryptedMessage':
if ($message_media['decrypted_message']['media']['_'] === 'decryptedMessageMediaExternalDocument') {
return $this->get_download_info($message_media['decrypted_message']['media']);
@ -218,13 +275,22 @@ trait Files
}
}
if (!isset($res['ext'])) {
$res['ext'] = $this->get_extension_from_location($res['InputFileLocation'], $this->get_extension_from_mime($res['mime']));
$res['ext'] = $this->get_extension_from_location($res['InputFileLocation'], $this->get_extension_from_mime(isset($res['mime']) ? $res['mime'] : 'image/jpeg'));
}
if (!isset($res['mime'])) {
$res['mime'] = $this->get_mime_from_extension($res['ext'], 'image/jpeg');
}
if (!isset($res['name'])) {
$res['name'] = $message_media['file']['access_hash'];
}
return $res;
// Wallpapers
case 'wallPaper':
$photo = end($message_media['sizes']);
return array_merge($res, $this->get_download_info($photo));
// Photos
case 'photo':
case 'messageMediaPhoto':
if ($message_media['_'] == 'photo') {
@ -234,32 +300,45 @@ trait Files
$res['MessageMedia'] = $message_media;
$photo = end($message_media['photo']['sizes']);
}
$res['name'] = $photo['location']['volume_id'].'_'.$photo['location']['local_id'];
$res['InputFileLocation'] = ['_' => 'inputFileLocation', 'volume_id' => $photo['location']['volume_id'], 'local_id' => $photo['location']['local_id'], 'secret' => $photo['location']['secret'], 'dc_id' => $photo['location']['dc_id']];
$res['ext'] = $this->get_extension_from_location($res['InputFileLocation'], '.jpg');
$res['mime'] = 'image/jpeg';
if (isset($photo['location']['size'])) {
$res['size'] = $photo['location']['size'];
}
if (isset($photo['location']['bytes'])) {
$res['size'] = strlen($photo['location']['bytes']);
return array_merge($res, $this->get_download_info($photo));
case 'userProfilePhoto':
case 'chatPhoto':
return array_merge($res, $this->get_download_info($message_media['photo_big']));
case 'photoCachedSize':
$res['size'] = strlen($message_media['bytes']);
$res['data'] = $message_media['bytes'];
if ($message_media['location']['_'] === 'fileLocationUnavailable') {
$res['name'] = $message_media['volume_id'].'_'.$message_media['local_id'];
$res['mime'] = $this->get_mime_from_buffer($res['data']);
$res['ext'] = $this->get_extension_from_mime($res['mime']);
} else {
$res = array_merge($res, $this->get_download_info($message_media['location']));
}
return $res;
case 'photoSize':
case 'photoCachedSize':
$res['name'] = $message_media['location']['volume_id'].'_'.$message_media['location']['local_id'];
$res['InputFileLocation'] = ['_' => 'inputFileLocation', 'volume_id' => $message_media['location']['volume_id'], 'local_id' => $message_media['location']['local_id'], 'secret' => $message_media['location']['secret'], 'dc_id' => $message_media['location']['dc_id']];
$res['ext'] = $this->get_extension_from_location($res['InputFileLocation'], '.jpg');
$res['mime'] = 'image/jpeg';
if (isset($photo['location']['size'])) {
$res['size'] = $photo['location']['size'];
}
if (isset($photo['location']['bytes'])) {
$res['size'] = strlen($photo['location']['bytes']);
$res = $this->get_download_info($message_media['location']);
if (isset($message_media['size'])) {
$res['size'] = $message_media['size'];
}
return $res;
case 'fileLocationUnavailable':
throw new \danog\MadelineProto\Exception('File location unavailable');
case 'fileLocation':
$res['name'] = $message_media['volume_id'].'_'.$message_media['local_id'];
$res['InputFileLocation'] = ['_' => 'inputFileLocation', 'volume_id' => $message_media['volume_id'], 'local_id' => $message_media['local_id'], 'secret' => $message_media['secret'], 'dc_id' => $message_media['dc_id'], 'file_reference' => $this->wait($this->referenceDatabase->getReference(ReferenceDatabase::PHOTO_LOCATION_LOCATION, $message_media))];
$res['ext'] = $this->get_extension_from_location($res['InputFileLocation'], '.jpg');
$res['mime'] = $this->get_mime_from_extension($res['ext'], 'image/jpeg');
return $res;
// Documents
case 'decryptedMessageMediaExternalDocument':
case 'document':
$message_media = ['_' => 'messageMediaDocument', 'ttl_seconds' => 0, 'document' => $message_media];
@ -286,7 +365,7 @@ trait Files
$res['name'] .= ' - '.$audio['performer'];
}
}
$res['InputFileLocation'] = ['_' => 'inputDocumentFileLocation', 'id' => $message_media['document']['id'], 'access_hash' => $message_media['document']['access_hash'], 'version' => isset($message_media['document']['version']) ? $message_media['document']['version'] : 0, 'dc_id' => $message_media['document']['dc_id']];
$res['InputFileLocation'] = ['_' => 'inputDocumentFileLocation', 'id' => $message_media['document']['id'], 'access_hash' => $message_media['document']['access_hash'], 'version' => isset($message_media['document']['version']) ? $message_media['document']['version'] : 0, 'dc_id' => $message_media['document']['dc_id'], 'file_reference' => $this->wait($this->referenceDatabase->getReference(ReferenceDatabase::DOCUMENT_LOCATION_LOCATION, $message_media['document']))];
if (!isset($res['ext'])) {
$res['ext'] = $this->get_extension_from_location($res['InputFileLocation'], $this->get_extension_from_mime($message_media['document']['mime_type']));
}
@ -330,11 +409,12 @@ trait Files
$file = realpath($file);
$message_media = $this->get_download_info($message_media);
$stream = fopen($file, 'r+b');
$size = fstat($stream)['size'];
$this->logger->logger('Waiting for lock of file to download...');
flock($stream, LOCK_EX);
try {
$this->download_to_stream($message_media, $stream, $cb, filesize($file), -1);
$this->download_to_stream($message_media, $stream, $cb, $size, -1);
} finally {
flock($stream, LOCK_UN);
fclose($stream);
@ -372,7 +452,10 @@ trait Files
$size = $end - $offset;
$part_size = $this->settings['download']['part_size'];
$percent = 0;
$datacenter = isset($message_media['InputFileLocation']['dc_id']) ? $message_media['InputFileLocation']['dc_id'] : $this->datacenter->curdc;
$datacenter = isset($message_media['InputFileLocation']['dc_id']) ? $message_media['InputFileLocation']['dc_id'] : $this->settings['connection_settings']['default_dc'];
if (isset($this->datacenter->sockets[$datacenter.'_media'])) {
$datacenter .= '_media';
}
if (isset($message_media['key'])) {
$digest = hash('md5', $message_media['key'].$message_media['iv'], true);
$fingerprint = $this->unpack_signed_int(substr($digest, 0, 4) ^ substr($digest, 4, 4));
@ -396,7 +479,16 @@ trait Files
$res = $cdn ? $this->method_call('upload.getCdnFile', ['file_token' => $message_media['file_token'], 'offset' => $offset, 'limit' => $part_size], ['heavy' => true, 'file' => true, 'datacenter' => $datacenter]) : $this->method_call('upload.getFile', ['location' => $message_media['InputFileLocation'], 'offset' => $offset, 'limit' => $part_size], ['heavy' => true, 'file' => true, 'datacenter' => &$datacenter]);
} catch (\danog\MadelineProto\RPCErrorException $e) {
if (strpos($e->rpc, 'FLOOD_WAIT_') === 0) {
//if (isset($message_media['MessageMedia']) && !$this->get_self()['bot']) $this->method_call('messages.sendMedia', ['peer' => '@danogentili', 'media' => $message_media['MessageMedia'], 'message' => 'This is broken'], ['datacenter' => $this->datacenter->curdc]);
if (isset($message_media['MessageMedia']) && !$this->authorization['user']['bot'] && $this->settings['download']['report_broken_media']) {
try {
$this->method_call('messages.sendMedia', ['peer' => 'support', 'media' => $message_media['MessageMedia'], 'message' => "I can't download this file, could you please help?"], ['datacenter' => $this->datacenter->curdc]);
} catch (RPCErrorException $e) {
$this->logger->logger('An error occurred while reporting the broken file: '.$e->rpc, Logger::FATAL_ERROR);
} catch (Exception $e) {
$this->logger->logger('An error occurred while reporting the broken file: '.$e->getMessage(), Logger::FATAL_ERROR);
}
}
throw new \danog\MadelineProto\Exception('The media server where this file is hosted is offline/overloaded, please try again later. Send the media to the telegram devs or to @danogentili to fix this.');
}
switch ($e->rpc) {

View File

@ -1,161 +0,0 @@
<?php
/*
Copyright 2016-2018 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\MTProtoTools;
/**
* Manages packing and unpacking of messages, and the list of sent and received messages.
*/
trait MessageHandler
{
public function send_unencrypted_message($type, $message_data, $message_id, $datacenter)
{
$this->logger->logger("Sending $type as unencrypted message to DC $datacenter", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$message_data = "\0\0\0\0\0\0\0\0".$message_id.$this->pack_unsigned_int(strlen($message_data)).$message_data;
$this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id] = ['response' => -1];
$this->datacenter->sockets[$datacenter]->send_message($message_data);
}
public function send_messages($datacenter)
{
//$has_ack = false;
if (count($this->datacenter->sockets[$datacenter]->object_queue) > 1) {
$messages = [];
$this->logger->logger("Sending msg_container as encrypted message to DC $datacenter", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
foreach ($this->datacenter->sockets[$datacenter]->object_queue as $message) {
$message['seqno'] = $this->generate_out_seq_no($datacenter, $message['content_related']);
$message['bytes'] = strlen($message['body']);
//$has_ack = $has_ack || $message['_'] === 'msgs_ack';
$this->logger->logger("Inside of msg_container, sending {$message['_']} as encrypted message to DC $datacenter", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$message['_'] = 'MTmessage';
$messages[] = $message;
$this->datacenter->sockets[$datacenter]->outgoing_messages[$message['msg_id']] = ['seq_no' => $message['seqno'], 'response' => -1]; //, 'content' => $this->deserialize($message['body'], ['type' => '', 'datacenter' => $datacenter])];
}
$message_data = $this->serialize_object(['type' => ''], ['_' => 'msg_container', 'messages' => $messages], 'lol');
$message_id = $this->generate_message_id($datacenter);
$seq_no = $this->generate_out_seq_no($datacenter, false);
} elseif (count($this->datacenter->sockets[$datacenter]->object_queue)) {
$message = array_shift($this->datacenter->sockets[$datacenter]->object_queue);
$this->logger->logger("Sending {$message['_']} as encrypted message to DC $datacenter", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$message_data = $message['body'];
$message_id = $message['msg_id'];
$seq_no = $this->generate_out_seq_no($datacenter, $message['content_related']);
} else {
return;
}
$plaintext = $this->datacenter->sockets[$datacenter]->temp_auth_key['server_salt'].$this->datacenter->sockets[$datacenter]->session_id.$message_id.pack('VV', $seq_no, strlen($message_data)).$message_data;
$padding = $this->posmod(-strlen($plaintext), 16);
if ($padding < 12) {
$padding += 16;
}
$padding = $this->random($padding);
$message_key = substr(hash('sha256', substr($this->datacenter->sockets[$datacenter]->temp_auth_key['auth_key'], 88, 32).$plaintext.$padding, true), 8, 16);
list($aes_key, $aes_iv) = $this->aes_calculate($message_key, $this->datacenter->sockets[$datacenter]->temp_auth_key['auth_key']);
$message = $this->datacenter->sockets[$datacenter]->temp_auth_key['id'].$message_key.$this->ige_encrypt($plaintext.$padding, $aes_key, $aes_iv);
$this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id] = ['seq_no' => $seq_no, 'response' => -1];
$this->datacenter->sockets[$datacenter]->send_message($message);
$this->datacenter->sockets[$datacenter]->object_queue = [];
/*if ($has_ack) {
foreach ($this->datacenter->sockets[$datacenter]->ack_queue as $msg_id) {
$this->datacenter->sockets[$datacenter]->incoming_messages[$msg_id]['ack'] = true;
}
$this->datacenter->sockets[$datacenter]->ack_queue = [];
}*/
}
/**
* Reading connection and receiving message from server.
*/
public function recv_message($datacenter)
{
if ($this->datacenter->sockets[$datacenter]->must_open) {
$this->logger->logger('Trying to read from closed socket, sending initial ping');
if ($this->is_http($datacenter)) {
$this->method_call('http_wait', ['max_wait' => 500, 'wait_after' => 150, 'max_delay' => 500], ['datacenter' => $datacenter]);
} elseif (isset($this->datacenter->sockets[$datacenter]->temp_auth_key['connection_inited']) && $this->datacenter->sockets[$datacenter]->temp_auth_key['connection_inited']) {
$this->method_call('ping', ['ping_id' => 0], ['datacenter' => $datacenter]);
} else {
throw new \danog\MadelineProto\Exception('Resend query');
}
}
$payload = $this->datacenter->sockets[$datacenter]->read_message();
if (strlen($payload) === 4) {
$payload = $this->unpack_signed_int($payload);
$this->logger->logger("Received $payload from DC $datacenter", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
return $payload;
}
$auth_key_id = substr($payload, 0, 8);
if ($auth_key_id === "\0\0\0\0\0\0\0\0") {
$message_id = substr($payload, 8, 8);
$this->check_message_id($message_id, ['outgoing' => false, 'datacenter' => $datacenter, 'container' => false]);
$message_length = unpack('V', substr($payload, 16, 4))[1];
$message_data = substr($payload, 20, $message_length);
$this->datacenter->sockets[$datacenter]->incoming_messages[$message_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->sockets[$datacenter]->temp_auth_key['auth_key'], false);
$decrypted_data = $this->ige_decrypt($encrypted_data, $aes_key, $aes_iv);
/*
$server_salt = substr($decrypted_data, 0, 8);
if ($server_salt != $this->datacenter->sockets[$datacenter]->temp_auth_key['server_salt']) {
$this->logger->logger('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->sockets[$datacenter]->session_id) {
throw new \danog\MadelineProto\Exception('Session id mismatch.');
}
$message_id = substr($decrypted_data, 16, 8);
$this->check_message_id($message_id, ['outgoing' => false, 'datacenter' => $datacenter, 'container' => false]);
$seq_no = unpack('V', substr($decrypted_data, 24, 4))[1];
// Dunno how to handle any incorrect sequence numbers
$message_data_length = unpack('V', substr($decrypted_data, 28, 4))[1];
if ($message_data_length > strlen($decrypted_data)) {
throw new \danog\MadelineProto\SecurityException('message_data_length is too big');
}
if (strlen($decrypted_data) - 32 - $message_data_length < 12) {
throw new \danog\MadelineProto\SecurityException('padding is too small');
}
if (strlen($decrypted_data) - 32 - $message_data_length > 1024) {
throw new \danog\MadelineProto\SecurityException('padding is too big');
}
if ($message_data_length < 0) {
throw new \danog\MadelineProto\SecurityException('message_data_length not positive');
}
if ($message_data_length % 4 != 0) {
throw new \danog\MadelineProto\SecurityException('message_data_length not divisible by 4');
}
$message_data = substr($decrypted_data, 32, $message_data_length);
if ($message_key != substr(hash('sha256', substr($this->datacenter->sockets[$datacenter]->temp_auth_key['auth_key'], 96, 32).$decrypted_data, true), 8, 16)) {
throw new \danog\MadelineProto\SecurityException('msg_key mismatch');
}
$this->datacenter->sockets[$datacenter]->incoming_messages[$message_id] = ['seq_no' => $seq_no];
} else {
$this->close_and_reopen($datacenter);
throw new \danog\MadelineProto\Exception('Got unknown auth_key id');
}
$deserialized = $this->deserialize($message_data, ['type' => '', 'datacenter' => $datacenter]);
$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;
$this->datacenter->sockets[$datacenter]->last_recv = time();
return true;
}
}

View File

@ -1,95 +0,0 @@
<?php
/*
Copyright 2016-2018 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\MTProtoTools;
/**
* Manages message ids.
*/
trait MsgIdHandler
{
public function check_message_id($new_message_id, $aargs)
{
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) {
$this->logger->logger('Given message id ('.$new_message_id.') is too old compared to the min value ('.$min_message_id.').', \danog\MadelineProto\Logger::WARNING);
}
$max_message_id = (new \phpseclib\Math\BigInteger(time() + $this->datacenter->sockets[$aargs['datacenter']]->time_delta + 30))->bitwise_leftShift(32);
if ($max_message_id->compare($new_message_id) < 0) {
throw new \danog\MadelineProto\Exception('Given message id ('.$new_message_id.') is too new compared to the max value ('.$max_message_id.'). Consider syncing your date.');
}
if ($aargs['outgoing']) {
if (!$new_message_id->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$zero)) {
throw new \danog\MadelineProto\Exception('Given message id ('.$new_message_id.') is not divisible by 4. Consider syncing your date.');
}
if (!\danog\MadelineProto\Magic::$has_thread && $new_message_id->compare($key = $this->get_max_id($aargs['datacenter'], false)) <= 0) {
throw new \danog\MadelineProto\Exception('Given message id ('.$new_message_id.') is lower than or equal to the current limit ('.$key.'). Consider syncing your date.', 1);
}
if (count($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages) > $this->settings['msg_array_limit']['outgoing']) {
reset($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages);
$key = key($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages);
unset($this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[$key]);
}
$this->datacenter->sockets[$aargs['datacenter']]->max_outgoing_id = $new_message_id;
$this->datacenter->sockets[$aargs['datacenter']]->outgoing_messages[strrev($new_message_id->toBytes())] = [];
} else {
if (!$new_message_id->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$one) && !$new_message_id->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$three)) {
throw new \danog\MadelineProto\Exception('message id mod 4 != 1 or 3');
}
$key = $this->get_max_id($aargs['datacenter'], true);
if ($aargs['container']) {
if ($new_message_id->compare($key = $this->get_max_id($aargs['datacenter'], true)) >= 0) {
$this->logger->logger('WARNING: Given message id ('.$new_message_id.') is bigger than or equal to the current limit ('.$key.'). Consider syncing your date.', \danog\MadelineProto\Logger::WARNING);
}
} else {
if ($new_message_id->compare($key = $this->get_max_id($aargs['datacenter'], true)) <= 0) {
$this->logger->logger('WARNING: Given message id ('.$new_message_id.') is lower than or equal to the current limit ('.$key.'). Consider syncing your date.', \danog\MadelineProto\Logger::WARNING);
}
}
if (count($this->datacenter->sockets[$aargs['datacenter']]->incoming_messages) > $this->settings['msg_array_limit']['incoming']) {
reset($this->datacenter->sockets[$aargs['datacenter']]->incoming_messages);
$key = key($this->datacenter->sockets[$aargs['datacenter']]->incoming_messages);
if ($key[0] === "\0") {
$key = $key;
}
unset($this->datacenter->sockets[$aargs['datacenter']]->incoming_messages[$key]);
}
$this->datacenter->sockets[$aargs['datacenter']]->max_incoming_id = $new_message_id;
$this->datacenter->sockets[$aargs['datacenter']]->incoming_messages[strrev($new_message_id->toBytes())] = [];
}
}
public function generate_message_id($datacenter)
{
$message_id = (new \phpseclib\Math\BigInteger(time() + $this->datacenter->sockets[$datacenter]->time_delta))->bitwise_leftShift(32);
if ($message_id->compare($key = $this->get_max_id($datacenter, false)) <= 0) {
$message_id = $key->add(\danog\MadelineProto\Magic::$four);
}
$this->check_message_id($message_id, ['outgoing' => true, 'datacenter' => $datacenter, 'container' => false]);
return strrev($message_id->toBytes());
}
public function get_max_id($datacenter, $incoming)
{
$incoming = $incoming ? 'incoming' : 'outgoing';
if (isset($this->datacenter->sockets[$datacenter]->{'max_'.$incoming.'_id'}) && is_object($this->datacenter->sockets[$datacenter]->{'max_'.$incoming.'_id'})) {
return $this->datacenter->sockets[$datacenter]->{'max_'.$incoming.'_id'};
}
return \danog\MadelineProto\Magic::$zero;
}
}

View File

@ -0,0 +1,202 @@
<?php
/**
* Password calculator module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\MTProtoTools;
use danog\MadelineProto\Exception;
use danog\MadelineProto\Magic;
use danog\MadelineProto\SecurityException;
use danog\MadelineProto\Tools;
use phpseclib\Math\BigInteger;
/**
* Manages password calculation.
*/
class PasswordCalculator
{
use AuthKeyHandler;
use Tools;
private $new_algo;
private $secure_random = '';
private $current_algo;
private $srp_B;
private $srp_BForHash;
private $srp_id;
public function __construct($logger)
{
$this->logger = $logger;
}
public function addInfo(array $object)
{
if ($object['_'] !== 'account.password') {
throw new Exception('Wrong constructor');
}
if ($object['has_secure_values']) {
throw new Exception('Cannot parse secure values');
}
if ($object['has_password']) {
switch ($object['current_algo']['_']) {
case 'passwordKdfAlgoUnknown':
throw new Exception('Update your client to continue');
case 'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow':
$object['current_algo']['g'] = new BigInteger($object['current_algo']['g']);
$object['current_algo']['p'] = new BigInteger((string) $object['current_algo']['p'], 256);
$this->check_p_g($object['current_algo']['p'], $object['current_algo']['g']);
$object['current_algo']['gForHash'] = str_pad($object['current_algo']['g']->toBytes(), 256, chr(0), \STR_PAD_LEFT);
$object['current_algo']['pForHash'] = str_pad($object['current_algo']['p']->toBytes(), 256, chr(0), \STR_PAD_LEFT);
break;
default:
throw new Exception("Unknown KDF algo {$object['current_algo']['_']}");
}
$this->current_algo = $object['current_algo'];
$object['srp_B'] = new BigInteger((string) $object['srp_B'], 256);
if ($object['srp_B']->compare(\danog\MadelineProto\Magic::$zero) < 0) {
throw new SecurityException('srp_B < 0');
}
if ($object['srp_B']->compare($object['current_algo']['p']) > 0) {
throw new SecurityException('srp_B > p');
}
$this->srp_B = $object['srp_B'];
$this->srp_BForHash = str_pad($object['srp_B']->toBytes(), 256, chr(0), \STR_PAD_LEFT);
$this->srp_id = $object['srp_id'];
} else {
$this->current_algo = null;
$this->srp_B = null;
$this->srp_BForHash = null;
$this->srp_id = null;
}
switch ($object['new_algo']['_']) {
case 'passwordKdfAlgoUnknown':
throw new Exception('Update your client to continue');
case 'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow':
$object['new_algo']['g'] = new BigInteger($object['new_algo']['g']);
$object['new_algo']['p'] = new BigInteger((string) $object['new_algo']['p'], 256);
$this->check_p_g($object['new_algo']['p'], $object['new_algo']['g']);
$object['new_algo']['gForHash'] = str_pad($object['new_algo']['g']->toBytes(), 256, chr(0), \STR_PAD_LEFT);
$object['new_algo']['pForHash'] = str_pad($object['new_algo']['p']->toBytes(), 256, chr(0), \STR_PAD_LEFT);
break;
default:
throw new Exception("Unknown KDF algo {$object['new_algo']['_']}");
}
$this->new_algo = $object['new_algo'];
$this->secure_random = $object['secure_random'];
}
public function createSalt(string $prefix = ''): string
{
return $prefix.$this->random(32);
}
public function hashSha256(string $data, string $salt): string
{
return hash('sha256', $salt.$data.$salt, true);
}
public function hashPassword(string $password, string $client_salt, string $server_salt): string
{
$buf = $this->hashSha256($password, $client_salt);
$buf = $this->hashSha256($buf, $server_salt);
$hash = hash_pbkdf2('sha512', $buf, $client_salt, 100000, 0, true);
return $this->hashSha256($hash, $server_salt);
}
public function getCheckPassword(string $password): array
{
if ($password === '') {
return ['_' => 'inputCheckPasswordEmpty'];
}
$client_salt = $this->current_algo['salt1'];
$server_salt = $this->current_algo['salt2'];
$g = $this->current_algo['g'];
$gForHash = $this->current_algo['gForHash'];
$p = $this->current_algo['p'];
$pForHash = $this->current_algo['pForHash'];
$B = $this->srp_B;
$BForHash = $this->srp_BForHash;
$id = $this->srp_id;
$x = new BigInteger($this->hashPassword($password, $client_salt, $server_salt), 256);
$g_x = $g->powMod($x, $p);
$k = new BigInteger(hash('sha256', $pForHash.$gForHash, true), 256);
$kg_x = $k->multiply($g_x)->powMod(Magic::$one, $p);
$a = new BigInteger($this->random(2048 / 8), 256);
$A = $g->powMod($a, $p);
$this->check_G($A, $p);
$AForHash = str_pad($A->toBytes(), 256, chr(0), \STR_PAD_LEFT);
$b_kg_x = $B->powMod(Magic::$one, $p)->subtract($kg_x);
$u = new BigInteger(hash('sha256', $AForHash.$BForHash, true), 256);
$ux = $u->multiply($x);
$a_ux = $a->add($ux);
$S = $b_kg_x->powMod($a_ux, $p);
$SForHash = str_pad($S->toBytes(), 256, chr(0), \STR_PAD_LEFT);
$K = hash('sha256', $SForHash, true);
$h1 = hash('sha256', $pForHash, true);
$h2 = hash('sha256', $gForHash, true);
$h1 ^= $h2;
$M1 = hash('sha256', $h1.hash('sha256', $client_salt, true).hash('sha256', $server_salt, true).$AForHash.$BForHash.$K, true);
return ['_' => 'inputCheckPasswordSRP', 'srp_id' => $id, 'A' => $AForHash, 'M1' => $M1];
}
public function getPassword(array $params): array
{
$return = ['password' => $this->getCheckPassword(isset($params['password']) ? $params['password'] : ''), 'new_settings' => ['_' => 'account.passwordInputSettings', 'new_algo' => ['_' => 'passwordKdfAlgoUnknown'], 'new_password_hash' => '', 'hint' => '']];
$new_settings = &$return['new_settings'];
if (isset($params['new_password']) && $params['new_password'] !== '') {
$client_salt = $this->createSalt($this->new_algo['salt1']);
$server_salt = $this->new_algo['salt2'];
$g = $this->new_algo['g'];
$p = $this->new_algo['p'];
$pForHash = $this->new_algo['pForHash'];
$x = new BigInteger($this->hashPassword($params['new_password'], $client_salt, $server_salt), 256);
$v = $g->powMod($x, $p);
$vForHash = str_pad($v->toBytes(), 256, chr(0), \STR_PAD_LEFT);
$new_settings['new_algo'] = [
'_' => 'passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow',
'salt1' => $client_salt,
'salt2' => $server_salt,
'g' => (int) $g->toString(),
'p' => $pForHash,
];
$new_settings['new_password_hash'] = $vForHash;
$new_settings['hint'] = $params['hint'];
if (isset($params['email'])) {
$new_settings['email'] = $params['email'];
}
}
return $return;
}
}

View File

@ -1,18 +1,26 @@
<?php
/*
Copyright 2016-2018 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/>.
/**
* PeerHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\MTProtoTools;
use Amp\Loop;
/**
* Manages peers.
*/
@ -23,6 +31,11 @@ trait PeerHandler
return -($id + pow(10, (int) floor(log($id, 10) + 3)));
}
public function from_supergroup($id)
{
return -$id - pow(10, (int) floor(log(-$id, 10)));
}
public function is_supergroup($id)
{
$log = log(-$id, 10);
@ -55,116 +68,99 @@ trait PeerHandler
}
}
public function add_support($support)
{
$this->supportUser = $support['user']['id'];
}
public function add_users($users)
{
foreach ($users as $key => $user) {
if (!isset($user['access_hash'])) {
if (isset($user['username']) && !isset($this->chats[$user['id']])) {
if ($this->postpone_pwrchat) {
$this->pending_pwrchat[$user['username']] = [false, true];
} else {
try {
$this->get_pwr_chat($user['username'], false, true);
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
}
continue;
}
switch ($user['_']) {
case 'user':
if (!isset($this->chats[$user['id']]) || $this->chats[$user['id']] !== $user) {
$this->chats[$user['id']] = $user;
foreach ($users as $user) {
$this->add_user($user);
}
}
if ($this->postpone_pwrchat) {
$this->pending_pwrchat[$user['id']] = [false, true];
} else {
try {
$this->get_pwr_chat($user['id'], false, true);
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
}
case 'userEmpty':
break;
default:
throw new \danog\MadelineProto\Exception('Invalid user provided at key '.$key.': '.var_export($user, true));
break;
public function add_user($user)
{
if (!isset($user['access_hash'])) {
if (isset($user['username']) && !isset($this->chats[$user['id']])) {
$this->cache_pwr_chat($user['username'], false, true);
}
return;
}
switch ($user['_']) {
case 'user':
if (!isset($this->chats[$user['id']]) || $this->chats[$user['id']] !== $user) {
$this->chats[$user['id']] = $user;
$this->cache_pwr_chat($user['id'], false, true);
}
case 'userEmpty':
break;
default:
throw new \danog\MadelineProto\Exception('Invalid user provided', $user);
break;
}
}
public function add_chats($chats)
{
foreach ($chats as $key => $chat) {
switch ($chat['_']) {
case 'chat':
case 'chatEmpty':
case 'chatForbidden':
if (!isset($this->chats[-$chat['id']]) || $this->chats[-$chat['id']] !== $chat) {
$this->chats[-$chat['id']] = $chat;
foreach ($chats as $chat) {
$this->add_chat($chat);
}
}
if ($this->postpone_pwrchat) {
$this->pending_pwrchat[-$chat['id']] = [$this->settings['peer']['full_fetch'], true];
} else {
try {
$this->get_pwr_chat(-$chat['id'], $this->settings['peer']['full_fetch'], true);
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
public function add_chat($chat)
{
switch ($chat['_']) {
case 'chat':
case 'chatEmpty':
case 'chatForbidden':
if (!isset($this->chats[-$chat['id']]) || $this->chats[-$chat['id']] !== $chat) {
$this->chats[-$chat['id']] = $chat;
$this->cache_pwr_chat(-$chat['id'], $this->settings['peer']['full_fetch'], true);
}
break;
case 'channelEmpty':
break;
case 'channel':
case 'channelForbidden':
$bot_api_id = $this->to_supergroup($chat['id']);
if (!isset($chat['access_hash'])) {
if (isset($chat['username']) && !isset($this->chats[$bot_api_id])) {
$this->cache_pwr_chat($chat['username'], $this->settings['peer']['full_fetch'], true);
}
case 'channelEmpty':
break;
case 'channel':
case 'channelForbidden':
$bot_api_id = $this->to_supergroup($chat['id']);
if (!isset($chat['access_hash'])) {
if (isset($chat['username']) && !isset($this->chats[$bot_api_id])) {
if ($this->postpone_pwrchat) {
$this->pending_pwrchat[$chat['username']] = [$this->settings['peer']['full_fetch'], true];
} else {
try {
$this->get_pwr_chat($chat['username'], $this->settings['peer']['full_fetch'], true);
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
}
continue;
}
if (!isset($this->chats[$bot_api_id]) || $this->chats[$bot_api_id] !== $chat) {
$this->chats[$bot_api_id] = $chat;
try {
if ($this->settings['peer']['full_fetch'] && (!isset($this->full_chats[$bot_api_id]) || $this->full_chats[$bot_api_id]['full']['participants_count'] !== $this->get_full_info($bot_api_id)['full']['participants_count'])) {
if ($this->postpone_pwrchat) {
$this->pending_pwrchat[$this->to_supergroup($chat['id'])] = [$this->settings['peer']['full_fetch'], true];
} else {
$this->get_pwr_chat($this->to_supergroup($chat['id']), $this->settings['peer']['full_fetch'], true);
}
}
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
return;
}
if (!isset($this->chats[$bot_api_id]) || $this->chats[$bot_api_id] !== $chat) {
$this->chats[$bot_api_id] = $chat;
if ($this->settings['peer']['full_fetch'] && (!isset($this->full_chats[$bot_api_id]) || $this->full_chats[$bot_api_id]['full']['participants_count'] !== $this->get_full_info($bot_api_id)['full']['participants_count'])) {
$this->cache_pwr_chat($bot_api_id, $this->settings['peer']['full_fetch'], true);
}
break;
default:
throw new \danog\MadelineProto\Exception('Invalid chat provided at key '.$key.': '.var_export($chat, true));
break;
}
}
break;
default:
throw new \danog\MadelineProto\Exception('Invalid chat provided at key '.$key.': '.var_export($chat, true));
break;
}
}
public function cache_pwr_chat($id, $full_fetch, $send)
{
if ($this->postpone_pwrchat) {
$this->pending_pwrchat[$id] = [$full_fetch, $send];
} else {
Loop::defer(function () use ($id, $full_fetch, $send) {
try {
$this->get_pwr_chat($id, $full_fetch, $send);
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
});
}
}
@ -355,8 +351,22 @@ trait PeerHandler
if (is_string($id)) {
$id = \danog\MadelineProto\Magic::$bigint ? (float) $id : (int) $id;
}
if (!isset($this->chats[$id]) && $id < 0 && !$this->is_supergroup($id)) {
$this->method_call('messages.getFullChat', ['chat_id' => -$id], ['datacenter' => $this->datacenter->curdc]);
if (!isset($this->chats[$id])) {
try {
if ($id < 0) {
if ($this->is_supergroup($id)) {
$this->method_call('channels.getChannels', ['id' => [['access_hash' => 0, 'channel_id' => $this->from_supergroup($id), '_' => 'inputChannel']]], ['datacenter' => $this->datacenter->curdc]);
} else {
$this->method_call('messages.getFullChat', ['chat_id' => -$id], ['datacenter' => $this->datacenter->curdc]);
}
} else {
$this->method_call('users.getUsers', ['id' => [['access_hash' => 0, 'user_id' => $id, '_' => 'inputUser']]], ['datacenter' => $this->datacenter->curdc]);
}
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
}
if (isset($this->chats[$id])) {
try {
@ -396,6 +406,13 @@ trait PeerHandler
if ($id === 'me') {
return $this->get_info($this->authorization['user']['id']);
}
if ($id === 'support') {
if (!$this->supportUser) {
$this->method_call('help.getSupport', [], ['datacenter' => $this->settings['connection_settings']['default_dc']]);
}
return $this->get_info($this->supportUser);
}
foreach ($this->chats as $chat) {
if (isset($chat['username']) && strtolower($chat['username']) === $id) {
return $this->gen_all($chat);
@ -462,10 +479,8 @@ trait PeerHandler
break;
case 'channelForbidden':
throw new \danog\MadelineProto\RPCErrorException('CHAT_FORBIDDEN');
break;
default:
throw new \danog\MadelineProto\Exception('Invalid constructor given '.var_export($constructor, true));
break;
}
return $res;
@ -518,23 +533,13 @@ trait PeerHandler
$res[$key] = $full['User'][$key];
}
}
if (isset($full['full']['about'])) {
$res['about'] = $full['full']['about'];
}
if (isset($full['full']['bot_info'])) {
$res['bot_info'] = $full['full']['bot_info'];
}
if (isset($full['full']['phone_calls_available'])) {
$res['phone_calls_available'] = $full['full']['phone_calls_available'];
}
if (isset($full['full']['phone_calls_private'])) {
$res['phone_calls_private'] = $full['full']['phone_calls_private'];
}
if (isset($full['full']['common_chats_count'])) {
$res['common_chats_count'] = $full['full']['common_chats_count'];
foreach (['about', 'bot_info', 'phone_calls_available', 'phone_calls_private', 'common_chats_count', 'can_pin_message', 'pinned_msg_id', 'notify_settings'] as $key) {
if (isset($full['full'][$key])) {
$res[$key] = $full['full'][$key];
}
}
if (isset($full['full']['profile_photo']['sizes'])) {
$res['photo'] = $this->photosize_to_botapi(end($full['full']['profile_photo']['sizes']), []);
$res['photo'] = $this->photosize_to_botapi(end($full['full']['profile_photo']['sizes']), $full['full']['profile_photo']);
}
/*$bio = '';
if ($full['type'] === 'user' && isset($res['username']) && !isset($res['about']) && $fullfetch) {
@ -554,11 +559,16 @@ trait PeerHandler
$res[$key] = $full['Chat'][$key];
}
}
foreach (['bot_info', 'pinned_msg_id', 'notify_settings'] as $key) {
if (isset($full['full'][$key])) {
$res[$key] = $full['full'][$key];
}
}
if (isset($res['admins_enabled'])) {
$res['all_members_are_administrators'] = $res['admins_enabled'];
$res['all_members_are_administrators'] = !$res['admins_enabled'];
}
if (isset($full['full']['chat_photo']['sizes'])) {
$res['photo'] = $this->photosize_to_botapi(end($full['full']['chat_photo']['sizes']), []);
$res['photo'] = $this->photosize_to_botapi(end($full['full']['chat_photo']['sizes']), $full['full']['chat_photo']);
}
if (isset($full['full']['exported_invite']['link'])) {
$res['invite'] = $full['full']['exported_invite']['link'];
@ -574,13 +584,13 @@ trait PeerHandler
$res[$key] = $full['Chat'][$key];
}
}
foreach (['can_set_stickers', 'stickerset', 'can_view_participants', 'can_set_username', 'participants_count', 'admins_count', 'kicked_count', 'banned_count', 'migrated_from_chat_id', 'migrated_from_max_id', 'pinned_msg_id', 'about', 'hidden_prehistory', 'available_min_id'] as $key) {
foreach (['read_inbox_max_id', 'read_outbox_max_id', 'hidden_prehistory', 'bot_info', 'notify_settings', 'can_set_stickers', 'stickerset', 'can_view_participants', 'can_set_username', 'participants_count', 'admins_count', 'kicked_count', 'banned_count', 'migrated_from_chat_id', 'migrated_from_max_id', 'pinned_msg_id', 'about', 'hidden_prehistory', 'available_min_id', 'can_view_stats', 'online_count'] as $key) {
if (isset($full['full'][$key])) {
$res[$key] = $full['full'][$key];
}
}
if (isset($full['full']['chat_photo']['sizes'])) {
$res['photo'] = $this->photosize_to_botapi(end($full['full']['chat_photo']['sizes']), []);
$res['photo'] = $this->photosize_to_botapi(end($full['full']['chat_photo']['sizes']), $full['full']['chat_photo']);
}
if (isset($full['full']['exported_invite']['link'])) {
$res['invite'] = $full['full']['exported_invite']['link'];
@ -632,7 +642,7 @@ trait PeerHandler
$res['participants'][$key] = $newres;
}
}
if (!isset($res['participants']) && isset($res['can_view_participants']) && $res['can_view_participants'] && $fullfetch) {
if (!isset($res['participants']) && $fullfetch && in_array($res['type'], ['supergroup', 'channel'])) {
$total_count = (isset($res['participants_count']) ? $res['participants_count'] : 0) + (isset($res['admins_count']) ? $res['admins_count'] : 0) + (isset($res['kicked_count']) ? $res['kicked_count'] : 0) + (isset($res['banned_count']) ? $res['banned_count'] : 0);
$res['participants'] = [];
$limit = 200;

View File

@ -0,0 +1,593 @@
<?php
/**
* Files module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\MTProtoTools;
use Amp\Deferred;
use Amp\Promise;
use Amp\Success;
use danog\MadelineProto\Exception;
use danog\MadelineProto\TL\TLCallback;
use danog\MadelineProto\Tools;
use function Amp\call;
/**
* Manages upload and download of files.
*/
class ReferenceDatabase implements TLCallback
{
use Tools;
// Reference from a document
const DOCUMENT_LOCATION = 0;
// Reference from a photo
const PHOTO_LOCATION = 1;
// Reference from a location (can only be photo location)
const PHOTO_LOCATION_LOCATION = 2;
// Reference from a location (can only be document location)
const DOCUMENT_LOCATION_LOCATION = 0;
// Peer + photo ID
const USER_PHOTO_ORIGIN = 0;
// Peer (default photo ID)
const PEER_PHOTO_ORIGIN = 1;
// set ID
const STICKER_SET_ID_ORIGIN = 2;
// Peer + msg ID
const MESSAGE_ORIGIN = 3;
//
const SAVED_GIFS_ORIGIN = 4;
//
const STICKER_SET_RECENT_ORIGIN = 5;
//
const STICKER_SET_FAVED_ORIGIN = 6;
// emoticon
const STICKER_SET_EMOTICON_ORIGIN = 8;
//
const WALLPAPER_ORIGIN = 9;
const LOCATION_CONTEXT = [
'inputFileLocation' => self::PHOTO_LOCATION_LOCATION,
'inputDocumentFileLocation' => self::DOCUMENT_LOCATION_LOCATION,
'inputPhoto' => self::PHOTO_LOCATION,
'inputDocument' => self::DOCUMENT_LOCATION,
];
const METHOD_CONTEXT = [
'photos.updateProfilePhoto' => self::USER_PHOTO_ORIGIN,
'photos.getUserPhotos' => self::USER_PHOTO_ORIGIN,
'photos.uploadProfilePhoto' => self::USER_PHOTO_ORIGIN,
'messages.getStickers' => self::STICKER_SET_EMOTICON_ORIGIN,
];
const CONSTRUCTOR_CONTEXT = [
'message' => self::MESSAGE_ORIGIN,
'messageService' => self::MESSAGE_ORIGIN,
'chatFull' => self::PEER_PHOTO_ORIGIN,
'channelFull' => self::PEER_PHOTO_ORIGIN,
'chat' => self::PEER_PHOTO_ORIGIN,
'channel' => self::PEER_PHOTO_ORIGIN,
'updateUserPhoto' => self::USER_PHOTO_ORIGIN,
'user' => self::USER_PHOTO_ORIGIN,
'userFull' => self::USER_PHOTO_ORIGIN,
'message' => self::MESSAGE_ORIGIN,
'messageService' => self::MESSAGE_ORIGIN,
'wallPaper' => self::WALLPAPER_ORIGIN,
'messages.savedGifs' => self::SAVED_GIFS_ORIGIN,
'messages.recentStickers' => self::STICKER_SET_RECENT_ORIGIN,
'messages.favedStickers' => self::STICKER_SET_FAVED_ORIGIN,
'messages.stickerSet' => self::STICKER_SET_ID_ORIGIN,
'document' => self::STICKER_SET_ID_ORIGIN,
];
/**
* References indexed by location.
*
* @var array
*/
private $db = [];
private $cache = [];
private $cacheContexts = [];
private $refreshed = [];
private $API;
private $refresh = false;
private $refreshCount = 0;
public function __construct($API)
{
$this->API = $API;
$this->init();
}
public function __wakeup()
{
$this->init();
}
public function __sleep()
{
return ['db', 'API'];
}
public function init()
{
}
public function getMethodCallbacks(): array
{
return array_fill_keys(array_keys(self::METHOD_CONTEXT), [[$this, 'addOriginMethod']]);
}
public function getMethodBeforeCallbacks(): array
{
return array_fill_keys(array_keys(self::METHOD_CONTEXT), [[$this, 'addOriginMethodContext']]);
}
public function getConstructorCallbacks(): array
{
return array_merge(
array_fill_keys(['document', 'photo', 'fileLocation'], [[$this, 'addReference']]),
array_fill_keys(array_keys(self::CONSTRUCTOR_CONTEXT), [[$this, 'addOrigin']]),
['document' => [[$this, 'addReference'], [$this, 'addOrigin']]]
);
}
public function getConstructorBeforeCallbacks(): array
{
return array_fill_keys(array_keys(self::CONSTRUCTOR_CONTEXT), [[$this, 'addOriginContext']]);
}
public function getConstructorSerializeCallbacks(): array
{
return array_fill_keys(array_keys(self::LOCATION_CONTEXT), [$this, 'populateReferenceSync']);
}
public function getTypeMismatchCallbacks(): array
{
return [];
}
public function reset()
{
if ($this->cacheContexts) {
$this->API->logger->logger('Found '.count($this->cacheContexts).' pending contexts', \danog\MadelineProto\Logger::ERROR);
$this->cacheContexts = [];
}
if ($this->cache) {
$this->API->logger->logger('Found pending locations', \danog\MadelineProto\Logger::ERROR);
$this->cache = [];
}
}
public function addReference(array $location)
{
if (!$this->cacheContexts) {
$this->API->logger->logger('Trying to add reference out of context, report the following message to @danogentili!', \danog\MadelineProto\Logger::ERROR);
$frames = [];
$previous = '';
foreach (debug_backtrace(0) as $k => $frame) {
if (isset($frame['function']) && $frame['function'] === 'deserialize') {
if (isset($frame['args'][1]['subtype'])) {
if ($frame['args'][1]['subtype'] === $previous) {
continue;
}
$frames[] = $frame['args'][1]['subtype'];
$previous = $frame['args'][1]['subtype'];
} elseif (isset($frame['args'][1]['type'])) {
if ($frame['args'][1]['type'] === '') {
break;
}
if ($frame['args'][1]['type'] === $previous) {
continue;
}
$frames[] = $frame['args'][1]['type'];
$previous = $frame['args'][1]['type'];
}
}
}
$frames = array_reverse($frames);
$tl_trace = array_shift($frames);
foreach ($frames as $frame) {
$tl_trace .= "['".$frame."']";
}
$this->API->logger->logger($tl_trace, \danog\MadelineProto\Logger::ERROR);
return false;
}
$key = count($this->cacheContexts) - 1;
switch ($location['_']) {
case 'document':
$locationType = self::DOCUMENT_LOCATION;
break;
case 'photo':
$locationType = self::PHOTO_LOCATION;
break;
case 'fileLocation':
$locationType = self::PHOTO_LOCATION_LOCATION;
break;
default:
throw new Exception('Unknown location type provided: '.$location['_']);
}
$this->API->logger->logger("Caching reference from location of type $locationType from {$location['_']}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
if (!isset($this->cache[$key])) {
$this->cache[$key] = [];
}
$this->cache[$key][$this->serializeLocation($locationType, $location)] = (string) $location['file_reference'];
return true;
}
public function addOriginContext(string $type)
{
if (!isset(self::CONSTRUCTOR_CONTEXT[$type])) {
throw new \danog\MadelineProto\Exception("Unknown origin type provided: $type");
}
$originContext = self::CONSTRUCTOR_CONTEXT[$type];
$this->API->logger->logger("Adding origin context $originContext for {$type}!", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->cacheContexts[] = $originContext;
}
public function addOrigin(array $data = [])
{
$key = count($this->cacheContexts) - 1;
if ($key === -1) {
throw new \danog\MadelineProto\Exception('Trying to add origin with no origin context set');
}
$originType = array_pop($this->cacheContexts);
if (!isset($this->cache[$key])) {
$this->API->logger->logger("Removing origin context $originType for {$data['_']}, nothing in the reference cache!", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
return;
}
$cache = $this->cache[$key];
unset($this->cache[$key]);
$origin = [];
switch ($data['_']) {
case 'message':
case 'messageService':
$origin['peer'] = $data;
$origin['msg_id'] = $data['id'];
break;
case 'messages.savedGifs':
case 'messages.recentStickers':
case 'messages.favedStickers':
case 'wallPaper':
break;
case 'user':
$origin['max_id'] = $data['photo']['photo_id'];
$origin['offset'] = -1;
$origin['limit'] = 1;
$origin['user_id'] = $data['id'];
break;
case 'updateUserPhoto':
$origin['max_id'] = $data['photo']['photo_id'];
$origin['offset'] = -1;
$origin['limit'] = 1;
$origin['user_id'] = $data['user_id'];
break;
case 'userFull':
$origin['max_id'] = $data['profile_photo']['id'];
$origin['offset'] = -1;
$origin['limit'] = 1;
$origin['user_id'] = $data['user']['id'];
break;
case 'chatFull':
case 'chat':
$origin['peer'] = -$data['id'];
break;
case 'channelFull':
case 'channel':
$origin['peer'] = $this->API->to_supergroup($data['id']);
break;
case 'document':
foreach ($data['attributes'] as $attribute) {
if ($attribute['_'] === 'documentAttributeSticker' && $attribute['stickerset']['_'] !== 'inputStickerSetEmpty') {
$origin['stickerset'] = $attribute['stickerset'];
}
}
if (!isset($origin['stickerset'])) {
$key = count($this->cacheContexts) - 1;
if (!isset($this->cache[$key])) {
$this->cache[$key] = [];
}
foreach ($cache as $location => $reference) {
$this->cache[$key][$location] = $reference;
}
$this->API->logger->logger("Skipped origin $originType ({$data['_']}) for ".count($cache).' references', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
return;
}
break;
case 'messages.stickerSet':
$origin['stickerset'] = ['_' => 'inputStickerSetID', 'id' => $data['set']['id'], 'access_hash' => $data['set']['access_hash']];
break;
default:
throw new \danog\MadelineProto\Exception("Unknown origin type provided: {$data['_']}");
}
foreach ($cache as $location => $reference) {
$this->storeReference($location, $reference, $originType, $origin);
}
$this->API->logger->logger("Added origin $originType ({$data['_']}) to ".count($cache).' references', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
}
public function addOriginMethodContext(string $type)
{
if (!isset(self::METHOD_CONTEXT[$type])) {
throw new \danog\MadelineProto\Exception("Unknown origin type provided: {$type}");
}
$originContext = self::METHOD_CONTEXT[$type];
$this->API->logger->logger("Adding origin context $originContext for {$type}!", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->cacheContexts[] = $originContext;
}
public function addOriginMethod(array $data, array $res)
{
$key = count($this->cacheContexts) - 1;
if ($key === -1) {
throw new \danog\MadelineProto\Exception('Trying to add origin with no origin context set');
}
$originType = array_pop($this->cacheContexts);
if (!isset($this->cache[$key])) {
$this->API->logger->logger("Removing origin context $originType for {$data['_']}, nothing in the reference cache!", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
return;
}
$cache = $this->cache[$key];
unset($this->cache[$key]);
$origin = [];
$body = $data['body'];
switch ($data['_']) {
case 'photos.updateProfilePhoto':
$origin['max_id'] = $res['photo_id'];
$origin['offset'] = -1;
$origin['limit'] = 1;
$origin['user_id'] = $this->API->authorization['user']['id'];
break;
case 'photos.uploadProfilePhoto':
$origin['max_id'] = $res['photo']['id'];
$origin['offset'] = -1;
$origin['limit'] = 1;
$origin['user_id'] = $this->API->authorization['user']['id'];
break;
case 'photos.getUserPhotos':
$origin['user_id'] = $body['user_id'];
$origin['offset'] = -1;
$origin['limit'] = 1;
$count = 0;
foreach ($res['photos'] as $photo) {
$origin['max_id'] = $photo['id'];
$location = $this->serializeLocation(self::PHOTO_LOCATION, $photo);
if (isset($cache[$location])) {
$reference = $cache[$location];
unset($cache[$location]);
$this->storeReference($location, $reference, $originType, $origin);
$count++;
}
if (isset($photo['sizes'])) {
foreach ($photo['sizes'] as $size) {
if (isset($size['location'])) {
$location = $this->serializeLocation(self::PHOTO_LOCATION_LOCATION, $size['location']);
if (isset($cache[$location])) {
$reference = $cache[$location];
unset($cache[$location]);
$this->storeReference($location, $reference, $originType, $origin);
$count++;
}
}
}
}
}
$this->API->logger->logger("Added origin $originType ({$data['_']}) to $count references", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
return;
case 'messages.getStickers':
$origin['emoticon'] = $body['emoticon'];
break;
default:
throw new \danog\MadelineProto\Exception("Unknown origin type provided: {$data['_']}");
}
foreach ($cache as $location => $reference) {
$this->storeReference($location, $reference, $originType, $origin);
}
$this->API->logger->logger("Added origin $originType ({$data['_']}) to ".count($cache).' references', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
}
public function storeReference(string $location, string $reference, int $originType, array $origin)
{
if (!isset($this->db[$location])) {
$this->db[$location] = ['origins' => []];
}
$this->db[$location]['reference'] = $reference;
$this->db[$location]['origins'][$originType] = $origin;
if ($this->refresh) {
$this->refreshed[$location] = true;
}
$key = count($this->cacheContexts) - 1;
if ($key >= 0) {
$this->cache[$key][$location] = $reference;
}
}
public function refreshNext($refresh = false)
{
if ($this->refreshCount === 1 && !$refresh) {
$this->refreshed = [];
$this->refreshCount--;
$this->refresh = false;
} elseif ($this->refreshCount === 0 && $refresh) {
$this->refreshed = [];
$this->refreshCount++;
$this->refresh = true;
} elseif ($this->refreshCount === 0 && !$refresh) {
} elseif ($refresh) {
$this->refreshCount++;
} elseif (!$refresh) {
$this->refreshCount--;
}
}
public function refreshReference(int $locationType, array $location): Promise
{
return $this->refreshReferenceInternal($this->serializeLocation($locationType, $location));
}
public function refreshReferenceInternal(string $location): Promise
{
if (isset($this->refreshed[$location])) {
$this->API->logger->logger('Reference already refreshed!', \danog\MadelineProto\Logger::VERBOSE);
return new Success($this->db[$location]['reference']);
}
return call([$this, 'refreshReferenceInternalGenerator'], $location);
}
public function refreshReferenceInternalGenerator(string $location): \Generator
{
ksort($this->db[$location]['origins']);
$count = 0;
foreach ($this->db[$location]['origins'] as $originType => &$origin) {
$count++;
$this->API->logger->logger("Try {$count} refreshing file reference with origin type {$originType}", \danog\MadelineProto\Logger::VERBOSE);
switch ($originType) {
// Peer + msg ID
case self::MESSAGE_ORIGIN:
if (is_array($origin['peer'])) {
$origin['peer'] = $this->API->get_info($origin['peer'])['bot_api_id'];
}
if ($origin['peer'] < 0) {
yield $this->API->method_call_async_read('channels.getMessages', ['channel' => $origin['peer'], 'id' => [$origin['msg_id']]], ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
break;
}
yield $this->API->method_call_async_read('messages.getMessages', ['id' => [$origin['msg_id']]], ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
break;
// Peer + photo ID
case self::PEER_PHOTO_ORIGIN:
if (isset($this->API->full_chats[$origin['peer']]['last_update'])) {
$this->API->full_chats[$origin['peer']]['last_update'] = 0;
}
$this->API->get_full_info($origin['peer']);
yield new Success(0);
break;
// Peer (default photo ID)
case self::USER_PHOTO_ORIGIN:
yield $this->API->method_call_async_read('photos.getUserPhotos', $origin, ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
break;
case self::SAVED_GIFS_ORIGIN:
yield $this->API->method_call_async_read('messages.getSavedGifs', $origin, ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
break;
case self::STICKER_SET_ID_ORIGIN:
yield $this->API->method_call_async_read('messages.getStickerSet', $origin, ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
break;
case self::STICKER_SET_RECENT_ORIGIN:
yield $this->API->method_call_async_read('messages.getRecentStickers', $origin, ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
break;
case self::STICKER_SET_FAVED_ORIGIN:
yield $this->API->method_call_async_read('messages.getFavedStickers', $origin, ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
break;
case self::STICKER_SET_EMOTICON_ORIGIN:
yield $this->API->method_call_async_read('messages.getStickers', $origin, ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
break;
case self::WALLPAPER_ORIGIN:
yield $this->API->method_call_async_read('account.getWallPapers', $origin, ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
break;
default:
throw new \danog\MadelineProto\Exception("Unknown origin type $originType");
}
if (isset($this->refreshed[$location])) {
return $this->db[$location]['reference'];
}
}
throw new Exception('Did not refresh reference');
}
public function populateReferenceSync(array $object): array
{
return $this->wait($this->populateReference($object));
}
public function populateReference(array $object): Promise
{
$deferred = new Deferred();
$this->getReference(self::LOCATION_CONTEXT[$object['_']], $object)->onResolve(function ($e, $res) use ($deferred, $object) {
if ($e) {
throw $e;
}
$object['file_reference'] = $res;
$deferred->resolve($object);
});
return $deferred->promise();
}
public function getReference(int $locationType, array $location): Promise
{
$locationString = $this->serializeLocation($locationType, $location);
if (!isset($this->db[$locationString]['reference'])) {
if (isset($location['file_reference'])) {
$this->API->logger->logger("Using outdated file reference for location of type $locationType object {$location['_']}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
return new Success($location['file_reference']);
}
throw new \danog\MadelineProto\Exception("Could not find file reference for location of type $locationType object {$location['_']}");
}
$this->API->logger->logger("Getting file reference for location of type $locationType object {$location['_']}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
if ($this->refresh) {
return $this->refreshReferenceInternal($locationString);
}
return new Success($this->db[$locationString]['reference']);
}
private function serializeLocation(int $locationType, array $location)
{
switch ($locationType) {
case self::DOCUMENT_LOCATION:
case self::DOCUMENT_LOCATION_LOCATION:
case self::PHOTO_LOCATION:
return $locationType.(is_int($location['id']) ? $this->pack_signed_long($location['id']) : $location['id']);
case self::PHOTO_LOCATION_LOCATION:
$dc_id = $this->pack_signed_int($location['dc_id']);
$volume_id = is_int($location['volume_id']) ? $this->pack_signed_long($location['volume_id']) : $location['volume_id'];
$local_id = $this->pack_signed_int($location['local_id']);
return $locationType.$dc_id.$volume_id.$local_id;
}
}
public function __debugInfo()
{
return ['ReferenceDatabase instance '.spl_object_hash($this)];
}
}

View File

@ -1,18 +1,26 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* ResponseHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\MTProtoTools;
use Amp\Loop;
/**
* Manages responses.
*/
@ -20,6 +28,7 @@ trait ResponseHandler
{
public function send_msgs_state_info($req_msg_id, $msg_ids, $datacenter)
{
// TODO REWRITE
$info = '';
foreach ($msg_ids as $msg_id) {
$cur_info = 0;
@ -43,19 +52,24 @@ trait ResponseHandler
$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($datacenter)
public $n = 0;
public function handle_messages($datacenter, $actual_datacenter = null)
{
if ($actual_datacenter) {
$datacenter = $actual_datacenter;
}
//$n = $this->n++;
$only_updates = true;
foreach ($this->datacenter->sockets[$datacenter]->new_incoming as $current_msg_id) {
$unset = false;
//var_dump($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]);
$this->logger->logger((isset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['from_container']) ? 'Inside of container, received ' : 'Received ').$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['_'].' from DC '.$datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE);
//var_dump($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content'], \danog\MadelineProto\Logger::ULTRA_VERBOSE);
switch ($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['_']) {
case 'msgs_ack':
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$only_updates = false;
foreach ($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['msg_ids'] as $msg_id) {
$this->ack_outgoing_message_id($msg_id, $datacenter);
@ -64,90 +78,67 @@ trait ResponseHandler
break;
case 'rpc_result':
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->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->logger->logger($this->datacenter->sockets[$datacenter]->outgoing_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['req_msg_id']]);
//$this->logger->logger($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']);
$this->datacenter->sockets[$datacenter]->outgoing_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['req_msg_id']]['response'] = $current_msg_id;
$req_msg_id = $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'];
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$this->handle_response($req_msg_id, $current_msg_id, $datacenter);
$only_updates = false;
break;
case 'future_salts':
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->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$only_updates = false;
$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;
$this->handle_response($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['req_msg_id'], $current_msg_id, $datacenter);
break;
case 'bad_server_salt':
case 'bad_msg_notification':
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$only_updates = false;
$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
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']]);
switch ($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['error_code']) {
case 48:
$this->datacenter->sockets[$datacenter]->temp_auth_key['server_salt'] = $this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['new_server_salt'];
throw new \danog\MadelineProto\Exception('Got bad message notification');
case 16:
case 17:
$this->logger->logger('Received bad_msg_notification: '.self::BAD_MSG_ERROR_CODES[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['error_code']], \danog\MadelineProto\Logger::WARNING);
$this->datacenter->sockets[$datacenter]->time_delta = (int) (new \phpseclib\Math\BigInteger(strrev($current_msg_id), 256))->bitwise_rightShift(32)->subtract(new \phpseclib\Math\BigInteger(time()))->toString();
$this->logger->logger('Set time delta to '.$this->datacenter->sockets[$datacenter]->time_delta, \danog\MadelineProto\Logger::WARNING);
$this->reset_session();
$this->datacenter->sockets[$datacenter]->temp_auth_key = null;
$this->init_authorization();
throw new \danog\MadelineProto\Exception('Got bad message notification');
}
$this->datacenter->sockets[$datacenter]->outgoing_messages[$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['bad_msg_id']]['response'] = $current_msg_id;
$this->handle_response($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['bad_msg_id'], $current_msg_id, $datacenter);
break;
case 'pong':
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$only_updates = false;
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']['msg_id']]);
$this->ack_outgoing_message_id($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['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']['msg_id']]['response'] = $current_msg_id;
$this->handle_response($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['msg_id'], $current_msg_id, $datacenter);
break;
case 'new_session_created':
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$only_updates = false;
$this->datacenter->sockets[$datacenter]->temp_auth_key['server_salt'] = $this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['server_salt'];
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
$this->ack_incoming_message_id($current_msg_id, $datacenter);
// Acknowledge that I received the server's response
if ($this->authorized === self::LOGGED_IN && !$this->initing_authorization && $this->datacenter->sockets[$this->datacenter->curdc]->temp_auth_key !== null) {
$this->get_updates_difference();
Loop::defer([$this, 'get_updates_difference']);
}
$unset = true;
//foreach ($this->datacenter->sockets[$datacenter]->new_outgoing as $message_id) {
// $this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id]['sent'] = 0;
//}
// Loop::defer([$this->datacenter->sockets[$datacenter]->checker, 'resume']);
unset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']);
break;
case 'msg_container':
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]->check_message_id($message['msg_id'], ['outgoing' => false, 'container' => true]);
$this->datacenter->sockets[$datacenter]->incoming_messages[$message['msg_id']] = ['seq_no' => $message['seqno'], 'content' => $message['body'], 'from_container' => true];
$this->datacenter->sockets[$datacenter]->new_incoming[$message['msg_id']] = $message['msg_id'];
$this->handle_messages($datacenter);
}
$unset = true;
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$only_updates = false;
unset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']);
break;
case 'msg_copy':
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$only_updates = false;
$this->ack_incoming_message_id($current_msg_id, $datacenter);
// Acknowledge that I received the server's response
@ -155,44 +146,42 @@ trait ResponseHandler
$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'], ['outgoing' => false, 'datacenter' => $datacenter, 'container' => true]);
$this->datacenter->sockets[$datacenter]->check_message_id($message['orig_message']['msg_id'], ['outgoing' => false, '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($datacenter);
}
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
$unset = true;
unset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']);
break;
case 'http_wait':
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$only_updates = false;
$this->logger->logger($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;
unset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']);
break;
case 'msgs_state_info':
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$only_updates = false;
$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;
$this->handle_response($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['req_msg_id'], $current_msg_id, $datacenter);
break;
case 'msgs_state_req':
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$only_updates = false;
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':
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$only_updates = false;
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_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 (($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['info'][$key] & 4) !== 0) {
$this->ack_outgoing_message_id($msg_id, $datacenter);
$this->got_response_for_outgoing_message_id($msg_id, $datacenter);
}
foreach (self::MSGS_INFO_FLAGS as $flag => $description) {
if (($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['info'][$key] & $flag) !== 0) {
@ -203,7 +192,7 @@ trait ResponseHandler
}
break;
case 'msg_detailed_info':
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$only_updates = false;
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']])) {
@ -214,7 +203,7 @@ trait ResponseHandler
}
}
case 'msg_new_detailed_info':
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$only_updates = false;
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);
@ -224,7 +213,7 @@ trait ResponseHandler
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
break;
case 'msg_resend_req':
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$only_updates = false;
$ok = true;
unset($this->datacenter->sockets[$datacenter]->new_incoming[$current_msg_id]);
@ -242,7 +231,7 @@ trait ResponseHandler
}
break;
case 'msg_resend_ans_req':
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$only_updates = false;
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);
@ -253,45 +242,34 @@ trait ResponseHandler
}
break;
default:
$this->check_in_seq_no($datacenter, $current_msg_id);
$this->datacenter->sockets[$datacenter]->check_in_seq_no($current_msg_id);
$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->sockets[$datacenter]->new_incoming[$current_msg_id]);
$unset = true;
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($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 (strpos($datacenter, 'cdn') === false) {
$this->handle_updates($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']);
Loop::defer([$this, 'handle_updates'], $this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']);
}
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['users'])) {
unset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['users']);
}
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['chats'])) {
unset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['chats']);
}
unset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']);
$only_updates = true && $only_updates;
break;
default:
$only_updates = false;
$this->logger->logger('Trying to assign a response of type '.$response_type.' to its request...', \danog\MadelineProto\Logger::VERBOSE);
foreach ($this->datacenter->sockets[$datacenter]->new_outgoing as $key => $expecting) {
foreach ($this->datacenter->sockets[$datacenter]->new_outgoing as $key => $expecting_msg_id) {
$expecting = $this->datacenter->sockets[$datacenter]->outgoing_messages[$expecting_msg_id];
$this->logger->logger('Does the request of return type '.$expecting['type'].' match?', \danog\MadelineProto\Logger::VERBOSE);
if ($response_type === $expecting['type']) {
$this->logger->logger('Yes', \danog\MadelineProto\Logger::VERBOSE);
$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]);
$this->handle_response($expecting_msg_id, $current_msg_id, $datacenter);
break 2;
}
$this->logger->logger('No', \danog\MadelineProto\Logger::VERBOSE);
@ -302,128 +280,259 @@ trait ResponseHandler
}
break;
}
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($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($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['user'])) {
$this->add_users([$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['user']]);
}
if (isset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['chat'])) {
$this->add_chats([$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['chat']]);
}
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($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($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($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['result']);
break;
}
}
if ($unset) {
unset($this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]);
}
}
//$this->n--;
return $only_updates;
}
public function handle_rpc_error($server_answer, &$aargs)
public function handle_reject($datacenter, &$request, $data)
{
if (in_array($server_answer['error_message'], ['PERSISTENT_TIMESTAMP_EMPTY', 'PERSISTENT_TIMESTAMP_OUTDATED', 'PERSISTENT_TIMESTAMP_INVALID'])) {
throw new \danog\MadelineProto\PTSException($server_answer['error_message']);
if (isset($request['promise']) && is_object($request['promise'])) {
Loop::defer(function () use (&$request, $data) {
$request['promise']->fail($data);
unset($request['promise']);
});
} elseif (isset($request['container'])) {
foreach ($request['container'] as $message_id) {
$this->handle_reject($datacenter, $this->datacenter->sockets[$datacenter]->outgoing_messages[$message_id], $data);
}
} else {
$this->logger->logger('Rejecting: already got response for '.(isset($request['_']) ? $request['_'] : '-'));
$this->logger->logger("Rejecting: $data");
}
switch ($server_answer['error_code']) {
case 500:
if ($server_answer['error_message'] === 'MSG_WAIT_FAILED') {
throw new \danog\MadelineProto\RPCErrorException($server_answer['error_message'], $server_answer['error_code']);
}
}
throw new \danog\MadelineProto\Exception('Re-executing query after server error...');
case 303:
$this->datacenter->curdc = $aargs['datacenter'] = (int) preg_replace('/[^0-9]+/', '', $server_answer['error_message']);
public function handle_response($request_id, $response_id, $datacenter)
{
//var_dumP("Response ".bin2hex($request_id));
$response = &$this->datacenter->sockets[$datacenter]->incoming_messages[$response_id]['content'];
unset($this->datacenter->sockets[$datacenter]->incoming_messages[$response_id]['content']);
$request = &$this->datacenter->sockets[$datacenter]->outgoing_messages[$request_id];
if (isset($aargs['file']) && $aargs['file'] && isset($this->datacenter->sockets[$aargs['datacenter'].'_media'])) {
\danog\MadelineProto\Logger::log('Using media DC');
$aargs['datacenter'] .= '_media';
}
if (isset($request['method']) && $request['method'] && $request['_'] !== 'auth.bindTempAuthKey' && $this->datacenter->sockets[$datacenter]->temp_auth_key !== null && (!isset($this->datacenter->sockets[$datacenter]->temp_auth_key['connection_inited']) || $this->datacenter->sockets[$datacenter]->temp_auth_key['connection_inited'] === false)) {
$this->datacenter->sockets[$datacenter]->temp_auth_key['connection_inited'] = true;
}
if (isset($response['_'])) {
switch ($response['_']) {
case 'rpc_error':
if (in_array($response['error_message'], ['PERSISTENT_TIMESTAMP_EMPTY', 'PERSISTENT_TIMESTAMP_OUTDATED', 'PERSISTENT_TIMESTAMP_INVALID'])) {
$this->got_response_for_outgoing_message_id($request_id, $datacenter);
$this->handle_reject($datacenter, $request, new \danog\MadelineProto\PTSException($response['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->logger->logger($server_answer['error_message'], \danog\MadelineProto\Logger::FATAL_ERROR);
foreach ($this->datacenter->sockets as $socket) {
$socket->temp_auth_key = null;
$socket->auth_key = null;
$socket->authorized = false;
return;
}
if (strpos($response['error_message'], 'FILE_REFERENCE_') === 0) {
$this->logger->logger("Got {$response['error_message']}, refreshing file reference and repeating method call...");
$request['refresh_references'] = true;
if (isset($request['serialized_body'])) {
unset($request['serialized_body']);
}
$this->authorized = self::NOT_LOGGED_IN;
$this->authorization = null;
$this->init_authorization();
throw new \danog\MadelineProto\RPCErrorException($server_answer['error_message'], $server_answer['error_code']);
case 'AUTH_KEY_UNREGISTERED':
case 'AUTH_KEY_INVALID':
if ($this->authorized !== self::LOGGED_IN) {
throw new \danog\MadelineProto\RPCErrorException($server_answer['error_message'], $server_answer['error_code']);
}
$this->logger->logger('Auth key not registered, resetting temporary and permanent auth keys...', \danog\MadelineProto\Logger::ERROR);
Loop::defer([$this, 'method_recall'], ['message_id' => $request_id, 'datacenter' => $datacenter]);
$this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key = null;
$this->datacenter->sockets[$aargs['datacenter']]->auth_key = null;
$this->datacenter->sockets[$aargs['datacenter']]->authorized = false;
if ($this->authorized_dc === $aargs['datacenter'] && $this->authorized === self::LOGGED_IN) {
$this->logger->logger('Permanent auth key was main authorized key, logging out...', \danog\MadelineProto\Logger::FATAL_ERROR);
foreach ($this->datacenter->sockets as $socket) {
$socket->temp_auth_key = null;
$socket->auth_key = null;
$socket->authorized = false;
return;
}
switch ($response['error_code']) {
case 500:
if ($response['error_message'] === 'MSG_WAIT_FAILED') {
$this->datacenter->sockets[$datacenter]->call_queue[$request['queue']] = [];
}
$this->authorized = self::NOT_LOGGED_IN;
$this->authorization = null;
$this->init_authorization();
throw new \danog\MadelineProto\RPCErrorException($server_answer['error_message'], $server_answer['error_code']);
}
$this->init_authorization();
Loop::defer([$this, 'method_recall'], ['message_id' => $request_id, 'datacenter' => $datacenter]);
throw new \danog\MadelineProto\Exception('I had to recreate the temporary authorization key');
case 'AUTH_KEY_PERM_EMPTY':
if ($this->authorized !== self::LOGGED_IN) {
throw new \danog\MadelineProto\RPCErrorException($server_answer['error_message'], $server_answer['error_code']);
}
$this->logger->logger('Temporary auth key not bound, resetting temporary auth key...', \danog\MadelineProto\Logger::ERROR);
return;
case 303:
$old_datacenter = $datacenter;
$this->datacenter->curdc = $datacenter = (int) preg_replace('/[^0-9]+/', '', $response['error_message']);
$this->datacenter->sockets[$aargs['datacenter']]->temp_auth_key = null;
$this->init_authorization();
// idk
throw new \danog\MadelineProto\Exception('I had to recreate the temporary authorization key');
}
case 420:
$seconds = preg_replace('/[^0-9]+/', '', $server_answer['error_message']);
$limit = isset($aargs['FloodWaitLimit']) ? $aargs['FloodWaitLimit'] : $this->settings['flood_timeout']['wait_if_lt'];
if (is_numeric($seconds) && $seconds < $limit) {
$this->logger->logger('Flood, waiting '.$seconds.' seconds...', \danog\MadelineProto\Logger::NOTICE);
sleep($seconds);
if (isset($request['file']) && $request['file'] && isset($this->datacenter->sockets[$datacenter.'_media'])) {
\danog\MadelineProto\Logger::log('Using media DC');
$datacenter .= '_media';
}
throw new \danog\MadelineProto\Exception('Re-executing query...');
}
if (isset($request['user_related']) && $request['user_related']) {
$this->settings['connection_settings']['default_dc'] = $this->authorized_dc = $this->datacenter->curdc;
}
default:
throw new \danog\MadelineProto\RPCErrorException($server_answer['error_message'], $server_answer['error_code']);
Loop::defer([$this, 'method_recall'], ['message_id' => $request_id, 'datacenter' => $datacenter, 'old_datacenter' => $old_datacenter]);
return;
case 401:
switch ($response['error_message']) {
case 'USER_DEACTIVATED':
case 'SESSION_REVOKED':
case 'SESSION_EXPIRED':
$this->got_response_for_outgoing_message_id($request_id, $datacenter);
$this->logger->logger($response['error_message'], \danog\MadelineProto\Logger::FATAL_ERROR);
foreach ($this->datacenter->sockets as $socket) {
$socket->temp_auth_key = null;
$socket->session_id = null;
$socket->auth_key = null;
$socket->authorized = false;
}
if ($response['error_message'] === 'USER_DEACTIVATED') {
$this->logger->logger('!!!!!!! WARNING !!!!!!!', \danog\MadelineProto\Logger::FATAL_ERROR);
$this->logger->logger("Telegram's flood prevention system suspended this account.", \danog\MadelineProto\Logger::ERROR);
$this->logger->logger('To continue, manual verification is required.', \danog\MadelineProto\Logger::FATAL_ERROR);
$phone = isset($this->authorization['user']['phone']) ? '+'.$this->authorization['user']['phone'] : 'you are currently using';
$this->logger->logger('Send an email to recover@telegram.org, asking to unban the phone number '.$phone.', and quickly describe what will you do with this phone number.', \danog\MadelineProto\Logger::FATAL_ERROR);
$this->logger->logger('Then login again.', \danog\MadelineProto\Logger::FATAL_ERROR);
$this->logger->logger('If you intentionally deleted this account, ignore this message.', \danog\MadelineProto\Logger::FATAL_ERROR);
}
$this->authorized = self::NOT_LOGGED_IN;
$this->authorization = null;
Loop::defer(function () use ($datacenter, &$request, &$response) {
$this->init_authorization();
$this->handle_reject($datacenter, $request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code']));
});
return;
case 'AUTH_KEY_UNREGISTERED':
case 'AUTH_KEY_INVALID':
if ($this->authorized !== self::LOGGED_IN) {
$this->got_response_for_outgoing_message_id($request_id, $datacenter);
Loop::defer(function () use ($datacenter, &$request, &$response) {
$this->init_authorization();
$this->handle_reject($datacenter, $request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code']));
});
return;
}
$this->datacenter->sockets[$datacenter]->session_id = null;
$this->datacenter->sockets[$datacenter]->temp_auth_key = null;
$this->datacenter->sockets[$datacenter]->auth_key = null;
$this->datacenter->sockets[$datacenter]->authorized = false;
$this->logger->logger('Auth key not registered, resetting temporary and permanent auth keys...', \danog\MadelineProto\Logger::ERROR);
if ($this->authorized_dc === $datacenter && $this->authorized === self::LOGGED_IN) {
$this->got_response_for_outgoing_message_id($request_id, $datacenter);
$this->logger->logger('Permanent auth key was main authorized key, logging out...', \danog\MadelineProto\Logger::FATAL_ERROR);
foreach ($this->datacenter->sockets as $socket) {
$socket->temp_auth_key = null;
$socket->auth_key = null;
$socket->authorized = false;
}
$this->logger->logger('!!!!!!! WARNING !!!!!!!', \danog\MadelineProto\Logger::FATAL_ERROR);
$this->logger->logger("Telegram's flood prevention system suspended this account.", \danog\MadelineProto\Logger::ERROR);
$this->logger->logger('To continue, manual verification is required.', \danog\MadelineProto\Logger::FATAL_ERROR);
$phone = isset($this->authorization['user']['phone']) ? '+'.$this->authorization['user']['phone'] : 'you are currently using';
$this->logger->logger('Send an email to recover@telegram.org, asking to unban the phone number '.$phone.', and quickly describe what will you do with this phone number.', \danog\MadelineProto\Logger::FATAL_ERROR);
$this->logger->logger('Then login again.', \danog\MadelineProto\Logger::FATAL_ERROR);
$this->logger->logger('If you intentionally deleted this account, ignore this message.', \danog\MadelineProto\Logger::FATAL_ERROR);
$this->authorized = self::NOT_LOGGED_IN;
$this->authorization = null;
Loop::defer(function () use ($datacenter, &$request, &$response) {
$this->init_authorization();
$this->handle_reject($datacenter, $request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code']));
});
return;
}
Loop::defer(function () use ($request_id, $datacenter) {
$this->init_authorization();
$this->method_recall('', ['message_id' => $request_id, 'datacenter' => $datacenter]);
});
return;
case 'AUTH_KEY_PERM_EMPTY':
$this->logger->logger('Temporary auth key not bound, resetting temporary auth key...', \danog\MadelineProto\Logger::ERROR);
$this->datacenter->sockets[$datacenter]->temp_auth_key = null;
Loop::defer(function () use ($request_id, $datacenter) {
$this->init_authorization();
$this->method_recall('', ['message_id' => $request_id, 'datacenter' => $datacenter]);
});
return;
}
$this->got_response_for_outgoing_message_id($request_id, $datacenter);
$this->handle_reject($datacenter, $request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code']));
return;
case 420:
$seconds = preg_replace('/[^0-9]+/', '', $response['error_message']);
$limit = isset($aargs['FloodWaitLimit']) ? $aargs['FloodWaitLimit'] : $this->settings['flood_timeout']['wait_if_lt'];
if (is_numeric($seconds) && $seconds < $limit) {
$this->logger->logger('Flood, waiting '.$seconds.' seconds before repeating async call...', \danog\MadelineProto\Logger::NOTICE);
Loop::delay($seconds * 1000, [$this, 'method_recall'], ['message_id' => $request_id, 'datacenter' => $datacenter]);
return;
}
// no break
default:
$this->got_response_for_outgoing_message_id($request_id, $datacenter);
$this->handle_reject($datacenter, $request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code']));
return;
}
return;
case 'boolTrue':
case 'boolFalse':
$response = $response['_'] === 'boolTrue';
break;
case 'bad_server_salt':
case 'bad_msg_notification':
$this->logger->logger('Received bad_msg_notification: '.self::BAD_MSG_ERROR_CODES[$response['error_code']], \danog\MadelineProto\Logger::WARNING);
switch ($response['error_code']) {
case 48:
$this->datacenter->sockets[$datacenter]->temp_auth_key['server_salt'] = $response['new_server_salt'];
Loop::defer([$this, 'method_recall'], ['message_id' => $request_id, 'datacenter' => $datacenter]);
return;
case 16:
case 17:
$this->datacenter->sockets[$datacenter]->time_delta = (int) (new \phpseclib\Math\BigInteger(strrev($response_id), 256))->bitwise_rightShift(32)->subtract(new \phpseclib\Math\BigInteger(time()))->toString();
$this->logger->logger('Set time delta to '.$this->datacenter->sockets[$datacenter]->time_delta, \danog\MadelineProto\Logger::WARNING);
$this->reset_session();
$this->datacenter->sockets[$datacenter]->temp_auth_key = null;
Loop::defer(function () use ($request_id, $datacenter) {
$this->init_authorization();
$this->method_recall('', ['message_id' => $request_id, 'datacenter' => $datacenter]);
});
return;
}
$this->got_response_for_outgoing_message_id($request_id, $datacenter);
$this->handle_reject($datacenter, $request, new \danog\MadelineProto\RPCErrorException('Received bad_msg_notification: '.self::BAD_MSG_ERROR_CODES[$response['error_code']], $response['error_code']));
return;
}
}
if (!isset($request['promise'])) {
$this->logger->logger('Response: already got response for '.(isset($request['_']) ? $request['_'] : '-').' with message ID '.$this->unpack_signed_long($request_id));
return;
}
if (isset($request['botAPI']) && $request['botAPI']) {
$response = $this->MTProto_to_botAPI($response);
}
unset($request);
$this->got_response_for_outgoing_message_id($request_id, $datacenter);
Loop::defer(function () use ($request_id, $response, $datacenter) {
$this->datacenter->sockets[$datacenter]->outgoing_messages[$request_id]['promise']->resolve($response);
unset($this->datacenter->sockets[$datacenter]->outgoing_messages[$request_id]['promise']);
});
}
public function handle_pending_updates()
@ -443,11 +552,15 @@ trait ResponseHandler
}
}
public function handle_updates($updates)
public function handle_updates($updates, $actual_updates = null)
{
if (!$this->settings['updates']['handle_updates']) {
return;
}
if ($actual_updates) {
$updates = $actual_updates;
}
if ($this->postpone_updates) {
$this->logger->logger('Postpone update handling', \danog\MadelineProto\Logger::VERBOSE);
$this->pending_updates[] = $updates;
@ -467,53 +580,59 @@ trait ResponseHandler
}
}
switch ($updates['_']) {
case 'updates':
case 'updatesCombined':
foreach ($updates['updates'] as $update) {
$this->handle_update($update, $opts);
}
break;
case 'updateShort':
$this->handle_update($updates['update'], $opts);
break;
case 'updateShortMessage':
case 'updateShortChatMessage':
$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->authorization['user']['id']);
if (!$this->peer_isset($from_id) || !$this->peer_isset($to_id) || isset($updates['via_bot_id']) && !$this->peer_isset($updates['via_bot_id']) || isset($updates['entities']) && !$this->entities_peer_isset($updates['entities']) || isset($updates['fwd_from']) && !$this->fwd_peer_isset($updates['fwd_from'])) {
$this->logger->logger('getDifference: good - getting user for updateShortMessage', \danog\MadelineProto\Logger::VERBOSE);
$this->get_updates_difference();
}
$message = $updates;
$message['_'] = 'message';
$message['from_id'] = $from_id;
case 'updates':
case 'updatesCombined':
foreach ($updates['updates'] as $update) {
$this->handle_update($update, $opts);
}
break;
case 'updateShort':
$this->handle_update($updates['update'], $opts);
break;
case 'updateShortMessage':
case 'updateShortChatMessage':
$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->authorization['user']['id']);
if (!$this->peer_isset($from_id) || !$this->peer_isset($to_id) || isset($updates['via_bot_id']) && !$this->peer_isset($updates['via_bot_id']) || isset($updates['entities']) && !$this->entities_peer_isset($updates['entities']) || isset($updates['fwd_from']) && !$this->fwd_peer_isset($updates['fwd_from'])) {
$this->logger->logger('getDifference: good - getting user for updateShortMessage', \danog\MadelineProto\Logger::VERBOSE);
$this->get_updates_difference();
}
$message = $updates;
$message['_'] = 'message';
$message['from_id'] = $from_id;
try {
$message['to_id'] = $this->get_info($to_id)['Peer'];
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger('Still did not get user in database, postponing update', \danog\MadelineProto\Logger::ERROR);
//$this->pending_updates[] = $updates;
try {
$message['to_id'] = $this->get_info($to_id)['Peer'];
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger('Still did not get user in database, postponing update', \danog\MadelineProto\Logger::ERROR);
//$this->pending_updates[] = $updates;
break;
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger('Still did not get user in database, postponing update', \danog\MadelineProto\Logger::ERROR);
//$this->pending_updates[] = $updates;
break;
}
$update = ['_' => 'updateNewMessage', 'message' => $message, 'pts' => $updates['pts'], 'pts_count' => $updates['pts_count']];
$this->handle_update($update, $opts);
break;
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger('Still did not get user in database, postponing update', \danog\MadelineProto\Logger::ERROR);
//$this->pending_updates[] = $updates;
case 'updateShortSentMessage':
//$this->set_update_state(['date' => $updates['date']]);
break;
}
$update = ['_' => 'updateNewMessage', 'message' => $message, 'pts' => $updates['pts'], 'pts_count' => $updates['pts_count']];
$this->handle_update($update, $opts);
break;
case 'updateShortSentMessage':
//$this->set_update_state(['date' => $updates['date']]);
break;
case 'updatesTooLong':
$this->get_updates_difference();
break;
default:
throw new \danog\MadelineProto\ResponseException('Unrecognized update received: '.var_export($updates, true));
break;
}
case 'updatesTooLong':
$this->get_updates_difference();
break;
default:
throw new \danog\MadelineProto\ResponseException('Unrecognized update received: '.var_export($updates, true));
break;
}
} finally {
$this->postpone_updates = false;
}
if ($this->updates && $this->update_deferred) {
$d = $this->update_deferred;
$this->update_deferred = null;
$d->resolve();
}
}
}

View File

@ -1,39 +0,0 @@
<?php
/*
Copyright 2016-2018 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\MTProtoTools;
/**
* Manages message ids.
*/
trait SaltHandler
{
public function add_salts($salts)
{
foreach ($salts as $salt) {
$this->add_salt($salt['valid_since'], $salt['valid_until'], $salt['salt']);
}
}
public function add_salt($valid_since, $valid_until, $salt)
{
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];
}
}
public function handle_future_salts($salt)
{
$this->method_call('messages.sendMessage', ['peer' => $salt, 'message' => base64_decode('UG93ZXJlZCBieSBATWFkZWxpbmVQcm90bw==')], ['datacenter' => $this->datacenter->curdc]);
}
}

View File

@ -1,81 +1,35 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* SeqNoHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\MTProtoTools;
use danog\MadelineProto\MTProto;
/**
* Manages sequence number.
*/
trait SeqNoHandler
{
public function generate_out_seq_no($datacenter, $content_related)
{
$in = $content_related ? 1 : 0;
$value = $this->datacenter->sockets[$datacenter]->session_out_seq_no;
$this->datacenter->sockets[$datacenter]->session_out_seq_no += $in;
//$this->logger->logger("OUT $datacenter: $value + $in = ".$this->datacenter->sockets[$datacenter]->session_out_seq_no);
return $value * 2 + $in;
}
public function check_in_seq_no($datacenter, $current_msg_id)
{
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']) {
//$this->logger->logger('SECURITY WARNING: Seqno mismatch (should be '.$seq_no.', is '.$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['seq_no'].', '.$this->datacenter->sockets[$datacenter]->incoming_messages[$current_msg_id]['content']['_'].')', \danog\MadelineProto\Logger::ERROR);
}
}
public function generate_in_seq_no($datacenter, $content_related)
{
$in = $content_related ? 1 : 0;
$value = $this->datacenter->sockets[$datacenter]->session_in_seq_no;
$this->datacenter->sockets[$datacenter]->session_in_seq_no += $in;
//$this->logger->logger("IN $datacenter: $value + $in = ".$this->datacenter->sockets[$datacenter]->session_in_seq_no);
return $value * 2 + $in;
}
public function content_related($method)
{
return isset($method['_']) ? !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',
]) : true;
$method = is_array($method) && isset($method['_']) ? $method['_'] : $method;
return is_string($method) ? !in_array($method, MTProto::NOT_CONTENT_RELATED) : true;
}
}

View File

@ -1,18 +1,28 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* UpdateHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\MTProtoTools;
use Amp\Deferred;
use Amp\Delayed;
use function Amp\Promise\any;
/**
* Manages updates.
*/
@ -45,71 +55,35 @@ trait UpdateHandler
return;
}
$this->updates[$this->updates_key++] = $update;
//$this->logger->logger(['Stored ', $update);
$this->logger->logger('Stored ');
}
public function get_updates($params = [])
public function get_updates_async($params = [])
{
if (!$this->settings['updates']['handle_updates']) {
$this->settings['updates']['handle_updates'] = true;
$this->datacenter->sockets[$this->settings['connection_settings']['default_dc']]->updater->start();
}
if (!$this->settings['updates']['run_callback']) {
$this->settings['updates']['run_callback'] = true;
}
array_walk($this->calls, function ($controller, $id) {
if ($controller->getCallState() === \danog\MadelineProto\VoIP::CALL_STATE_ENDED) {
$controller->discard();
}
});
$time = microtime(true);
try {
if (!$this->is_http($this->datacenter->curdc) && !$this->altervista) {
try {
$waiting = $this->datacenter->select();
if (count($waiting)) {
$tries = 10;
while (count($waiting) && $tries--) {
$dc = $waiting[0];
if (($error = $this->recv_message($dc)) !== true) {
if ($error === -404) {
if ($this->datacenter->sockets[$dc]->temp_auth_key !== null) {
$this->logger->logger('WARNING: Resetting auth key...', \danog\MadelineProto\Logger::WARNING);
$this->datacenter->sockets[$dc]->temp_auth_key = null;
$this->init_authorization();
$params = array_merge(self::DEFAULT_GETUPDATES_PARAMS, $params);
throw new \danog\MadelineProto\Exception('I had to recreate the temporary authorization key');
}
}
throw new \danog\MadelineProto\RPCErrorException($error, $error);
}
$only_updates = $this->handle_messages($dc);
$waiting = $this->datacenter->select(true);
}
} else {
$this->get_updates_difference();
}
} catch (\danog\MadelineProto\NothingInTheSocketException $e) {
$this->get_updates_difference();
}
} else {
$this->get_updates_difference();
}
if (time() - $this->last_getdifference > $this->settings['updates']['getdifference_interval']) {
$this->get_updates_difference();
}
} catch (\danog\MadelineProto\RPCErrorException $e) {
if ($e->rpc !== 'RPC_CALL_FAIL') {
throw $e;
}
} catch (\danog\MadelineProto\Exception $e) {
$this->connect_to_all_dcs();
if (empty($this->updates)) {
$this->update_deferred = new Deferred();
yield any([$this->update_deferred->promise(), new Delayed($params['timeout'] * 1000)]);
}
$default_params = ['offset' => 0, 'limit' => null, 'timeout' => 0];
$params = array_merge($default_params, $params);
$params['timeout'] = (int) ($params['timeout'] * 1000000 - (microtime(true) - $time));
usleep($params['timeout'] > 0 ? $params['timeout'] : 0);
if (empty($this->updates)) {
return [];
}
if ($params['offset'] < 0) {
$params['offset'] = array_reverse(array_keys((array) $this->updates))[abs($params['offset']) - 1];
}
@ -127,6 +101,10 @@ trait UpdateHandler
}
}
if (empty($this->updates)) {
$this->updates_key = 0;
}
return $updates;
}
@ -287,7 +265,7 @@ trait UpdateHandler
return $this->updates_state;
}
public function get_updates_difference()
public function get_updates_difference($w = null)
{
if (!$this->settings['updates']['handle_updates']) {
return;
@ -315,6 +293,7 @@ trait UpdateHandler
$this->postpone_updates = true;
$this->updates_state['sync_loading'] = true;
$this->last_getdifference = time();
$this->datacenter->sockets[$this->settings['connection_settings']['default_dc']]->updater->resume();
try {
switch ($difference['_']) {
@ -348,6 +327,15 @@ trait UpdateHandler
$this->updates_state['sync_loading'] = false;
}
$this->handle_pending_updates();
if ($this->updates && $this->update_deferred) {
$d = $this->update_deferred;
$this->update_deferred = null;
$d->resolve();
}
return true;
}
public function get_updates_state()
@ -558,7 +546,7 @@ trait UpdateHandler
return;
}
return $this->calls[$update['phone_call']['id']]->discard(['_' => 'phoneCallDiscardReasonHangup'], [], $update['phone_call']['need_debug']);
return $this->calls[$update['phone_call']['id']]->discard($update['phone_call']['reason'], [], $update['phone_call']['need_debug']);
}
}
if ($update['_'] === 'updateNewEncryptedMessage' && !isset($update['message']['decrypted_message'])) {
@ -629,7 +617,7 @@ trait UpdateHandler
$this->logger->logger('Saving an update of type '.$update['_'].'...', \danog\MadelineProto\Logger::VERBOSE);
if (isset($this->settings['pwr']['strict']) && $this->settings['pwr']['strict'] && isset($this->settings['pwr']['update_handler'])) {
$this->pwr_update_handler($update);
} else {
} elseif ($this->settings['updates']['run_callback']) {
$this->get_updates_update_handler($update);
}
}

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,20 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* MyTelegramOrgWrapper module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
@ -18,6 +24,7 @@ namespace danog\MadelineProto;
class MyTelegramOrgWrapper
{
private $logged = false;
private $hash = '';
public function __construct($number)
{
@ -140,10 +147,11 @@ class MyTelegramOrgWrapper
curl_close($ch);
$title = explode('</title>', explode('<title>', $result)[1])[0];
switch ($title) {
case 'App configuration': return true;
case 'Create new application': $this->creation_hash = explode('"/>', explode('<input type="hidden" name="hash" value="', $result)[1])[0];
case 'App configuration':return true;
case 'Create new application':
$this->creation_hash = explode('"/>', explode('<input type="hidden" name="hash" value="', $result)[1])[0];
return false;
return false;
}
throw new Exception($title);
@ -180,13 +188,13 @@ return false;
$cose = explode('<label for="app_id" class="col-md-4 text-right control-label">App api_id:</label>
<div class="col-md-7">
<span class="form-control input-xlarge uneditable-input" onclick="this.select();"><strong>', $result);
$asd = explode('</strong></span>', $cose['1']);
$api_id = $asd['0'];
$asd = explode('</strong></span>', $cose[1]);
$api_id = $asd[0];
$cose = explode('<label for="app_hash" class="col-md-4 text-right control-label">App api_hash:</label>
<div class="col-md-7">
<span class="form-control input-xlarge uneditable-input" onclick="this.select();">', $result);
$asd = explode('</span>', $cose['1']);
$api_hash = $asd['0'];
$asd = explode('</span>', $cose[1]);
$api_hash = $asd[0];
return ['api_id' => (int) $api_id, 'api_hash' => $api_hash];
}
@ -199,6 +207,7 @@ return false;
if ($this->has_app()) {
throw new Exception('The app was already created!');
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://my.telegram.org/apps/create');
@ -227,6 +236,10 @@ return false;
}
curl_close($ch);
if ($result) {
throw new Exception($result);
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://my.telegram.org/apps');
@ -254,6 +267,13 @@ return false;
}
curl_close($ch);
$title = explode('</title>', explode('<title>', $result)[1])[0];
if ($title === 'Create new application') {
$this->creation_hash = explode('"/>', explode('<input type="hidden" name="hash" value="', $result)[1])[0];
throw new \danog\MadelineProto\Exception('App creation failed');
}
$cose = explode('<label for="app_id" class="col-md-4 text-right control-label">App api_id:</label>
<div class="col-md-7">
<span class="form-control input-xlarge uneditable-input" onclick="this.select();"><strong>', $result);

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* NothingInTheSocketException module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* PTSException module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -0,0 +1,36 @@
<?php
/**
* PayloadStream.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
use Amp\ByteStream\InputStream;
use Amp\Promise;
/**
* PayloadStream.
*
* Represents an MTProto payload
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class PayloadStream implements InputStream
{
public function read(): Promise
{
}
}

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* Proxy module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* RPCErrorException module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
@ -33,7 +39,11 @@ class RPCErrorException extends \Exception
public function __toString()
{
return sprintf(\danog\MadelineProto\Lang::$current_lang['rpc_tg_error'], $this->getMess(), $this->rpc, $this->file, $this->line.PHP_EOL.PHP_EOL).PHP_EOL.'Revision: '.@file_get_contents(__DIR__.'/../../../.git/refs/heads/master').PHP_EOL.$this->getTLTrace().PHP_EOL;
$result = sprintf(\danog\MadelineProto\Lang::$current_lang['rpc_tg_error'], $this->getMess(), $this->rpc, $this->file, $this->line.PHP_EOL.PHP_EOL).PHP_EOL.\danog\MadelineProto\Magic::$revision.PHP_EOL.$this->getTLTrace().PHP_EOL;
if (php_sapi_name() !== 'cli') {
$result = str_replace(PHP_EOL, '<br>'.PHP_EOL, $result);
}
return $result;
}
public function __construct($message = null, $code = 0, Exception $previous = null)

View File

@ -1,15 +1,20 @@
<?php
/*
Copyright 2016-2018 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 the MadelineProto.
If not, see <http://www.gnu.org/licenses/>.
*/
/**
* RSA module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* ResponseException module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* AuthKeyHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\SecretChats;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* MessageHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\SecretChats;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* ResponseHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\SecretChats;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* SeqNoHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\SecretChats;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* SecurityException module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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/>.
*/
/**
* Serialization module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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.
The PWRTelegram API 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/>.
*/
/**
* Server module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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.
The PWRTelegram API 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/>.
*/
/**
* Handler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Server;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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.
The PWRTelegram API 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/>.
*/
/**
* Proxy module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Server;

View File

@ -1,15 +1,21 @@
<?php
/*
Copyright 2016-2018 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.
The PWRTelegram API 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/>.
*/
/**
* Stream module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Server;

View File

@ -0,0 +1,42 @@
<?php
/**
* Buffer helper trait.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream\Async;
use Amp\Promise;
use function Amp\call;
/**
* Buffer helper trait.
*
* Wraps the asynchronous generator methods with asynchronous promise-based methods
*
* @author Daniil Gentili <daniil@daniil.it>
*/
trait Buffer
{
public function bufferRead(int $length): Promise
{
return call([$this, 'bufferReadAsync'], $length);
}
public function bufferWrite(string $data): Promise
{
return call([$this, 'bufferWriteAsync'], $data);
}
}

View File

@ -0,0 +1,74 @@
<?php
/**
* Buffered stream helper trait.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream\Async;
use Amp\Coroutine;
use Amp\Failure;
use Amp\Promise;
use Amp\Success;
use function Amp\call;
/**
* Buffered stream helper trait.
*
* Wraps the asynchronous generator methods with asynchronous promise-based methods
*
* @author Daniil Gentili <daniil@daniil.it>
*/
trait BufferedStream
{
use Stream;
/**
* Get read buffer asynchronously.
*
* @param int $length Length of payload, as detected by this layer
*
* @return Promise
*/
public function getReadBuffer(&$length): Promise
{
try {
$result = $this->getReadBufferAsync($length);
} catch (\Throwable $exception) {
return new Failure($exception);
}
if ($result instanceof \Generator) {
return new Coroutine($result);
}
if ($result instanceof Promise) {
return $result;
}
return new Success($result);
}
/**
* Get write buffer asynchronously.
*
* @param int $length Total length of data that is going to be piped in the buffer
* @param string $append Data to append after entire buffer is written
*
* @return Promise
*/
public function getWriteBuffer(int $length, string $append = ''): Promise
{
return call([$this, 'getWriteBufferAsync'], $length, $append);
}
}

View File

@ -0,0 +1,49 @@
<?php
/**
* Raw stream helper trait.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream\Async;
use Amp\Promise;
use function Amp\call;
/**
* Raw stream helper trait.
*
* Wraps the asynchronous generator methods with asynchronous promise-based methods
*
* @author Daniil Gentili <daniil@daniil.it>
*/
trait RawStream
{
use Stream;
public function read(): Promise
{
return call([$this, 'readAsync']);
}
public function write(string $data): Promise
{
return call([$this, 'writeAsync'], $data);
}
public function end(string $finalData = ''): Promise
{
return call([$this, 'endAsync'], $finalData);
}
}

View File

@ -0,0 +1,38 @@
<?php
/**
* Generic stream helper trait.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream\Async;
use Amp\Promise;
use danog\MadelineProto\Stream\ConnectionContext;
use function Amp\call;
/**
* Generic stream helper trait.
*
* Wraps the asynchronous generator methods with asynchronous promise-based methods
*
* @author Daniil Gentili <daniil@daniil.it>
*/
trait Stream
{
public function connect(ConnectionContext $ctx, string $header = ''): Promise
{
return call([$this, 'connectAsync'], $ctx, $header);
}
}

View File

@ -0,0 +1,47 @@
<?php
/**
* Buffer interface.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream;
use Amp\Promise;
/**
* Buffer interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
interface BufferInterface
{
/**
* Read data asynchronously.
*
* @param int $length How much data to read
*
* @return Promise
*/
public function bufferRead(int $length): Promise;
/**
* Write data asynchronously.
*
* @param string $data Data to write
*
* @return Promise
*/
public function bufferWrite(string $data): Promise;
}

View File

@ -0,0 +1,28 @@
<?php
/**
* Buffered proxy stream interface.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream;
/**
* Buffered proxy stream interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
interface BufferedProxyStreamInterface extends BufferedStreamInterface, ProxyStreamInterface
{
}

View File

@ -0,0 +1,56 @@
<?php
/**
* Buffered stream interface.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream;
use Amp\Promise;
/**
* Buffered stream interface.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
interface BufferedStreamInterface extends StreamInterface
{
/**
* Get read buffer asynchronously.
*
* @param int $length Length of payload, as detected by this layer
*
* @return Promise
*/
public function getReadBuffer(&$length): Promise;
/**
* Get write buffer asynchronously.
*
* @param int $length Total length of data that is going to be piped in the buffer
*
* @return Promise
*/
public function getWriteBuffer(int $length, string $append = ''): Promise;
/**
* Get stream name.
*
* Is supposed to return __CLASS__
*
* @return string
*/
public static function getName(): string;
}

View File

@ -0,0 +1,225 @@
<?php
/**
* Buffered raw stream.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream\Common;
use Amp\Promise;
use Amp\Success;
use danog\MadelineProto\Exception;
use danog\MadelineProto\Stream\Async\RawStream;
use danog\MadelineProto\Stream\ConnectionContext;
use function Amp\call;
use function Amp\Socket\connect;
/**
* Buffered raw stream.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class BufferedRawStream implements \danog\MadelineProto\Stream\BufferedStreamInterface, \danog\MadelineProto\Stream\BufferInterface, \danog\MadelineProto\Stream\RawStreamInterface
{
use RawStream;
const MAX_SIZE = 10 * 1024 * 1024;
protected $stream;
protected $memory_stream;
private $append = '';
private $append_after = 0;
/**
* Asynchronously connect to a TCP/TLS server.
*
* @param ConnectionContext $ctx Connection context
*
* @return \Generator
*/
public function connectAsync(ConnectionContext $ctx, string $header = ''): \Generator
{
$this->stream = yield $ctx->getStream($header);
$this->memory_stream = fopen('php://memory', 'r+');
return true;
}
/**
* Async chunked read.
*
* @return Promise
*/
public function read(): Promise
{
return $this->stream->read();
}
/**
* Async write.
*
* @param string $data Data to write
*
* @return Promise
*/
public function write(string $data): Promise
{
return $this->stream->write($data);
}
/**
* Async close.
*
* @return Generator
*/
public function disconnect()
{
if ($this->memory_stream) {
fclose($this->memory_stream);
$this->memory_stream = null;
}
if ($this->stream) {
$this->stream->disconnect();
$this->stream = null;
}
}
/**
* Get read buffer asynchronously.
*
* @param int $length Length of payload, as detected by this layer
*
* @return Promise
*/
public function getReadBuffer(&$length): Promise
{
$size = fstat($this->memory_stream)['size'];
$offset = ftell($this->memory_stream);
$length = $size - $offset;
if ($length === 0 || $size > self::MAX_SIZE) {
$new_memory_stream = fopen('php://memory', 'r+');
if ($length) {
fwrite($new_memory_stream, fread($this->memory_stream, $length));
fseek($new_memory_stream, 0);
}
fclose($this->memory_stream);
$this->memory_stream = $new_memory_stream;
}
return new \Amp\Success($this);
}
/**
* Get write buffer asynchronously.
*
* @param int $length Total length of data that is going to be piped in the buffer
*
* @return Promise
*/
public function getWriteBuffer(int $length, string $append = ''): Promise
{
if (strlen($append)) {
$this->append = $append;
$this->append_after = $length - strlen($append);
}
return new \Amp\Success($this);
}
/**
* Read data asynchronously.
*
* @param int $length Amount of data to read
*
* @return Promise
*/
public function bufferRead(int $length): Promise
{
$size = fstat($this->memory_stream)['size'];
$offset = ftell($this->memory_stream);
$buffer_length = $size - $offset;
if ($buffer_length >= $length) {
return new Success(fread($this->memory_stream, $length));
}
return call([$this, 'bufferReadAsync'], $length);
}
/**
* Read data asynchronously.
*
* @param int $length Amount of data to read
*
* @return \Generator
*/
public function bufferReadAsync(int $length): \Generator
{
$size = fstat($this->memory_stream)['size'];
$offset = ftell($this->memory_stream);
$buffer_length = $size - $offset;
if ($buffer_length < $length && $buffer_length) {
fseek($this->memory_stream, $offset + $buffer_length);
}
while ($buffer_length < $length) {
$chunk = yield $this->read();
if ($chunk === null) {
$this->disconnect();
throw new \danog\MadelineProto\NothingInTheSocketException();
}
fwrite($this->memory_stream, $chunk);
$buffer_length += strlen($chunk);
}
fseek($this->memory_stream, $offset);
return fread($this->memory_stream, $length);
}
/**
* Async write.
*
* @param string $data Data to write
*
* @return Promise
*/
public function bufferWrite(string $data): Promise
{
if ($this->append_after) {
$this->append_after -= strlen($data);
if ($this->append_after === 0) {
$data .= $this->append;
$this->append = '';
} elseif ($this->append_after < 0) {
$this->append_after = 0;
$this->append = '';
throw new Exception('Tried to send too much out of frame data, cannot append');
}
}
return $this->write($data);
}
/**
* Get class name.
*
* @return string
*/
public static function getName(): string
{
return __CLASS__;
}
}

View File

@ -0,0 +1,319 @@
<?php
/**
* Hash stream wrapper.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream\Common;
use Amp\Promise;
use danog\MadelineProto\Stream\Async\BufferedStream;
use danog\MadelineProto\Stream\Async\Stream;
use danog\MadelineProto\Stream\BufferedProxyStreamInterface;
use danog\MadelineProto\Stream\BufferInterface;
use danog\MadelineProto\Stream\ConnectionContext;
use function Amp\call;
/**
* Hash stream wrapper.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterface
{
use BufferedStream;
private $hash_name;
private $read_hash;
private $write_hash;
private $write_buffer;
private $write_check_after = 0;
private $write_check_pos = 0;
private $read_buffer;
private $read_check_after = 0;
private $read_check_pos = 0;
private $stream;
private $rev = false;
/**
* Enable read hashing.
*
* @return void
*/
public function startReadHash()
{
$this->read_hash = hash_init($this->hash_name);
}
/**
* Check the read hash after N bytes are read.
*
* @param int $after The number of bytes to read before checking the hash
*
* @return void
*/
public function checkReadHash(int $after)
{
$this->read_check_after = $after;
}
/**
* Stop read hashing and get final hash.
*
* @return string
*/
public function getReadHash(): string
{
$hash = hash_final($this->read_hash, true);
if ($this->rev) {
$hash = strrev($hash);
}
$this->read_hash = null;
$this->read_check_after = 0;
$this->read_check_pos = 0;
return $hash;
}
/**
* Check if we are read hashing.
*
* @return bool
*/
public function hasReadHash(): bool
{
return $this->read_hash !== null;
}
/**
* Enable write hashing.
*
* @return void
*/
public function startWriteHash()
{
$this->write_hash = hash_init($this->hash_name);
}
/**
* Write the write hash after N bytes are read.
*
* @param int $after The number of bytes to read before writing the hash
*
* @return void
*/
public function checkWriteHash(int $after)
{
$this->write_check_after = $after;
}
/**
* Stop write hashing and get final hash.
*
* @return string
*/
public function getWriteHash(): string
{
$hash = hash_final($this->write_hash, true);
if ($this->rev) {
$hash = strrev($hash);
}
$this->write_hash = null;
$this->write_check_after = 0;
$this->write_check_pos = 0;
return $hash;
}
/**
* Check if we are write hashing.
*
* @return bool
*/
public function hasWriteHash(): bool
{
return $this->write_hash !== null;
}
/**
* Hashes read data asynchronously.
*
* @param int $length Read and hash $length bytes
*
* @throws PendingReadError Thrown if another read operation is still pending.
*
* @return Generator That resolves with a string when the provided promise is resolved and the data is added to the hashing context
*/
public function bufferReadAsync(int $length): \Generator
{
if ($this->read_check_after && $length + $this->read_check_pos >= $this->read_check_after) {
if ($length + $this->read_check_pos > $this->read_check_after) {
throw new \danog\MadelineProto\Exception('Tried to read too much out of frame data');
}
$data = yield $this->read_buffer->bufferRead($length);
hash_update($this->read_hash, $data);
$hash = $this->getReadHash();
if ($hash !== yield $this->read_buffer->bufferRead(strlen($hash))) {
throw new \danog\MadelineProto\Exception('Hash mismatch');
}
return $data;
}
$data = yield $this->read_buffer->bufferRead($length);
hash_update($this->read_hash, $data);
if ($this->read_check_after) {
$this->read_check_pos += $length;
}
return $data;
}
/**
* Set the hash algorithm.
*
* @param string $hash Algorithm name
*
* @return void
*/
public function setExtra($hash)
{
$rev = strpos($hash, '_rev');
$this->rev = false;
if ($rev !== false) {
$hash = substr($hash, 0, $rev);
$this->rev = true;
}
$this->hash_name = $hash;
}
/**
* Connect to stream.
*
* @param ConnectionContext $ctx The connection context
*
* @return \Generator
*/
public function connectAsync(ConnectionContext $ctx): \Generator
{
$this->write_hash = null;
$this->write_check_after = 0;
$this->write_check_pos = 0;
$this->read_hash = null;
$this->read_check_after = 0;
$this->read_check_pos = 0;
$this->stream = yield $ctx->getStream();
}
/**
* Async close.
*
* @return Promise
*/
public function disconnect()
{
return $this->stream->disconnect();
}
/**
* Get read buffer asynchronously.
*
* @param int $length Length of payload, as detected by this layer
*
* @return Generator
*/
public function getReadBufferAsync(&$length): \Generator
{
if ($this->read_hash) {
$this->read_buffer = yield $this->stream->getReadBuffer($length);
return $this;
}
return yield $this->stream->getReadBuffer($length);
}
/**
* Get write buffer asynchronously.
*
* @param int $length Length of data that is going to be written to the write buffer
*
* @return Generator
*/
public function getWriteBufferAsync(int $length, string $append = ''): \Generator
{
if ($this->write_hash) {
$this->write_buffer = yield $this->stream->getWriteBuffer($length, $append);
return $this;
}
return yield $this->stream->getWriteBuffer($length, $append);
}
/**
* Reads data from the stream.
*
* @throws PendingReadError Thrown if another read operation is still pending.
*
* @return Promise Resolves with a string when new data is available or `null` if the stream has closed.
*/
public function bufferRead(int $length): Promise
{
if ($this->read_hash === null) {
return $this->read_buffer->bufferRead($length);
}
return call([$this, 'bufferReadAsync'], $length);
}
/**
* Writes data to the stream.
*
* @param string $data Bytes to write.
*
* @throws ClosedException If the stream has already been closed.
*
* @return Promise Succeeds once the data has been successfully written to the stream.
*/
public function bufferWrite(string $data): Promise
{
if ($this->write_hash === null) {
return $this->write_buffer->bufferWrite($length);
}
$length = strlen($data);
if ($this->write_check_after && $length + $this->write_check_pos >= $this->write_check_after) {
if ($length + $this->write_check_pos > $this->write_check_after) {
throw new \danog\MadelineProto\Exception('Too much out of frame data was sent, cannot check hash');
}
hash_update($this->write_hash, $data);
return $this->write_buffer->bufferWrite($data.$this->getWriteHash());
}
if ($this->write_check_after) {
$this->write_check_pos += $length;
}
if ($this->write_hash) {
hash_update($this->write_hash, $data);
}
return $this->write_buffer->bufferWrite($data);
}
public static function getName(): string
{
return __CLASS__;
}
}

View File

@ -0,0 +1,391 @@
<?php
/**
* Connection context.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream;
use Amp\CancellationToken;
use Amp\Promise;
use Amp\Socket\ClientConnectContext;
use Amp\Uri\Uri;
use function Amp\call;
/**
* Connection context class.
*
* Is responsible for maintaining state about a certain connection to a DC.
* That includes the Stream chain that is required to use the connection, the connection URI, and other connection-related data.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class ConnectionContext
{
/**
* Whether to use a secure socket.
*
* @var bool
*/
private $secure = false;
/**
* Whether to use test servers.
*
* @var bool
*/
private $test = false;
/**
* The connection URI.
*
* @var \Amp\Uri\Uri
*/
private $uri;
/**
* Socket context.
*
* @var \Amp\Socket\ClientConnectionContext
*/
private $socketContext;
/**
* Cancellation token.
*
* @var \Amp\CancellationToken
*/
private $cancellationToken;
/**
* The telegram DC ID.
*
* @var int
*/
private $dc = 0;
/**
* Whether to use IPv6.
*
* @var bool
*/
private $ipv6 = false;
/**
* An array of arrays containing an array with the stream name and the extra parameter to pass to it.
*
* @var array<array<string, any>>
*/
private $nextStreams = [];
/**
* The current stream key.
*
* @var int
*/
private $key = 0;
/**
* Set the socket context.
*
* @param ClientConnectContext $socketContext
*
* @return self
*/
public function setSocketContext(ClientConnectContext $socketContext): self
{
$this->socketContext = $socketContext;
return $this;
}
/**
* Get the socket context.
*
* @return ClientConnectContext
*/
public function getSocketContext(): ClientConnectContext
{
return $this->socketContext;
}
/**
* Set the connection URI.
*
* @param string|\Amp\Uri\Uri $uri
*
* @return self
*/
public function setUri($uri): self
{
$this->uri = $uri instanceof Uri ? $uri : new Uri($uri);
return $this;
}
/**
* Get the URI as a string.
*
* @return string
*/
public function getStringUri(): string
{
return (string) $this->uri;
}
/**
* Get the URI.
*
* @return \Amp\Uri\Uri
*/
public function getUri(): Uri
{
return $this->uri;
}
/**
* Set the cancellation token.
*
* @param CancellationToken $cancellationToken
*
* @return self
*/
public function setCancellationToken($cancellationToken): self
{
$this->cancellationToken = $cancellationToken;
return $this;
}
/**
* Get the cancellation token.
*
* @return CancellationToken
*/
public function getCancellationToken()
{
return $this->cancellationToken;
}
/**
* Set the secure boolean.
*
* @param bool $secure
*
* @return self
*/
public function setTest(bool $test): self
{
$this->test = $test;
return $this;
}
/**
* Whether to use TLS with socket connections.
*
* @return bool
*/
public function isTest(): bool
{
return $this->test;
}
/**
* Set the secure boolean.
*
* @param bool $secure
*
* @return self
*/
public function secure(bool $secure): self
{
$this->secure = $secure;
return $this;
}
/**
* Whether to use TLS with socket connections.
*
* @return bool
*/
public function isSecure(): bool
{
return $this->secure;
}
/**
* Set the DC ID.
*
* @param string|int $dc
*
* @return self
*/
public function setDc($dc): self
{
$this->dc = $dc;
return $this;
}
/**
* Get the DC ID.
*
* @return string|int
*/
public function getDc()
{
return $this->dc;
}
/**
* Get the int DC ID.
*
* @return string|int
*/
public function getIntDc()
{
$dc = intval($this->dc);
if ($this->test) {
$dc += 10000;
}
if (strpos($this->dc, '_media')) {
$dc = -$dc;
}
return $dc;
}
/**
* Whether to use ipv6.
*
* @param bool $ipv6
*
* @return self
*/
public function setIpv6(bool $ipv6): self
{
$this->ipv6 = $ipv6;
return $this;
}
/**
* Whether to use ipv6.
*
* @return bool
*/
public function getIpv6(): bool
{
return $this->ipv6;
}
/**
* Set the ipv6 boolean.
*
* @return self
*/
public function getCtx(): self
{
return clone $this;
}
/**
* Add a stream to the stream chain.
*
* @param string $streamName
* @param any $extra
*
* @return self
*/
public function addStream(string $streamName, $extra = null): self
{
$this->nextStreams[] = [$streamName, $extra];
$this->key = count($this->nextStreams) - 1;
return $this;
}
/**
* Get the current stream name from the stream chain.
*
* @return string
*/
public function getStreamName(): string
{
return $this->nextStreams[$this->key][0];
}
/**
* Get a stream from the stream chain.
*
* @return Promise
*/
public function getStream(string $buffer = ''): Promise
{
return call([$this, 'getStreamAsync'], $buffer);
}
/**
* Get a stream from the stream chain.
*
* @internal Generator func
*
* @return \Generator
*/
public function getStreamAsync(string $buffer = ''): \Generator
{
list($clazz, $extra) = $this->nextStreams[$this->key--];
$obj = new $clazz();
if ($obj instanceof ProxyStreamInterface) {
$obj->setExtra($extra);
}
yield $obj->connect($this, $buffer);
return $obj;
}
/**
* Get a description "name" of the context.
*
* @return string
*/
public function getName(): string
{
$string = $this->getStringUri();
if ($this->isSecure()) {
$string .= ' (TLS)';
}
$string .= ' DC ';
$string .= $this->getDc();
$string .= ', via ';
$string .= $this->getIpv6() ? 'ipv6' : 'ipv4';
$string .= ' using ';
foreach (array_reverse($this->nextStreams) as $k => $stream) {
if ($k) {
$string .= ' => ';
}
$string .= preg_replace('/.*\\\\/', '', $stream[0]);
if ($stream[1]) {
$string .= ' ('.json_encode($stream[1]).')';
}
}
return $string;
}
/**
* Returns a representation of the context.
*
* @return string
*/
public function __toString()
{
return $this->getName();
}
}

View File

@ -0,0 +1,28 @@
<?php
/**
* MTProto buffer interface.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream;
/**
* MTProto buffer interface, for reading transport MTProto header info.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
interface MTProtoBufferInterface
{
}

View File

@ -0,0 +1,102 @@
<?php
/**
* MsgIdHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream\MTProtoTools;
/**
* Manages message ids.
*/
trait MsgIdHandler
{
public function check_message_id($new_message_id, $aargs)
{
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->time_delta - 300))->bitwise_leftShift(32);
if ($min_message_id->compare($new_message_id) > 0) {
$this->API->logger->logger('Given message id ('.$new_message_id.') is too old compared to the min value ('.$min_message_id.').', \danog\MadelineProto\Logger::WARNING);
}
$max_message_id = (new \phpseclib\Math\BigInteger(time() + $this->time_delta + 30))->bitwise_leftShift(32);
if ($max_message_id->compare($new_message_id) < 0) {
throw new \danog\MadelineProto\Exception('Given message id ('.$new_message_id.') is too new compared to the max value ('.$max_message_id.'). Consider syncing your date.');
}
if ($aargs['outgoing']) {
if (!$new_message_id->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$zero)) {
throw new \danog\MadelineProto\Exception('Given message id ('.$new_message_id.') is not divisible by 4. Consider syncing your date.');
}
if (!\danog\MadelineProto\Magic::$has_thread && $new_message_id->compare($key = $this->get_max_id($incoming = false)) <= 0) {
throw new \danog\MadelineProto\Exception('Given message id ('.$new_message_id.') is lower than or equal to the current limit ('.$key.'). Consider syncing your date.', 1);
}
if (count($this->outgoing_messages) > $this->API->settings['msg_array_limit']['outgoing']) {
reset($this->outgoing_messages);
$key = key($this->outgoing_messages);
if (!isset($this->outgoing_messages[$key]['promise'])) {
unset($this->outgoing_messages[$key]);
}
}
$this->max_outgoing_id = $new_message_id;
$this->outgoing_messages[strrev($new_message_id->toBytes())] = [];
} else {
if (!$new_message_id->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$one) && !$new_message_id->divide(\danog\MadelineProto\Magic::$four)[1]->equals(\danog\MadelineProto\Magic::$three)) {
throw new \danog\MadelineProto\Exception('message id mod 4 != 1 or 3');
}
$key = $this->get_max_id($incoming = true);
if ($aargs['container']) {
if ($new_message_id->compare($key = $this->get_max_id($incoming = true)) >= 0) {
$this->API->logger->logger('WARNING: Given message id ('.$new_message_id.') is bigger than or equal to the current limit ('.$key.'). Consider syncing your date.', \danog\MadelineProto\Logger::WARNING);
}
} else {
if ($new_message_id->compare($key = $this->get_max_id($incoming = true)) <= 0) {
$this->API->logger->logger('WARNING: Given message id ('.$new_message_id.') is lower than or equal to the current limit ('.$key.'). Consider syncing your date.', \danog\MadelineProto\Logger::WARNING);
}
}
if (count($this->incoming_messages) > $this->API->settings['msg_array_limit']['incoming']) {
reset($this->incoming_messages);
$key = key($this->incoming_messages);
if (!isset($this->incoming_messages[$key]['promise'])) {
unset($this->incoming_messages[$key]);
}
}
$this->max_incoming_id = $new_message_id;
$this->incoming_messages[strrev($new_message_id->toBytes())] = [];
}
}
public function generate_message_id()
{
$message_id = (new \phpseclib\Math\BigInteger(time() + $this->time_delta))->bitwise_leftShift(32);
if ($message_id->compare($key = $this->get_max_id($incoming = false)) <= 0) {
$message_id = $key->add(\danog\MadelineProto\Magic::$four);
}
$this->check_message_id($message_id, ['outgoing' => true, 'container' => false]);
return strrev($message_id->toBytes());
}
public function get_max_id($incoming)
{
$incoming = $incoming ? 'incoming' : 'outgoing';
if (isset($this->{'max_'.$incoming.'_id'}) && is_object($this->{'max_'.$incoming.'_id'})) {
return $this->{'max_'.$incoming.'_id'};
}
return \danog\MadelineProto\Magic::$zero;
}
}

View File

@ -0,0 +1,45 @@
<?php
/**
* SaltHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream\MTProtoTools;
/**
* Manages message ids.
*/
trait SaltHandler
{
public function add_salts($salts)
{
foreach ($salts as $salt) {
$this->add_salt($salt['valid_since'], $salt['valid_until'], $salt['salt']);
}
}
public function add_salt($valid_since, $valid_until, $salt)
{
if (!isset($this->temp_auth_key['salts'][$salt])) {
$this->temp_auth_key['salts'][$salt] = ['valid_since' => $valid_since, 'valid_until' => $valid_until];
}
}
public function handle_future_salts($salt)
{
$this->method_call('messages.sendMessage', ['peer' => $salt, 'message' => base64_decode('UG93ZXJlZCBieSBATWFkZWxpbmVQcm90bw==')], ['datacenter' => $this->datacenter->curdc]);
}
}

View File

@ -0,0 +1,56 @@
<?php
/**
* SeqNoHandler module.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream\MTProtoTools;
/**
* Manages sequence number.
*/
trait SeqNoHandler
{
use \danog\MadelineProto\MTProtoTools\SeqNoHandler;
public function generate_out_seq_no($content_related)
{
$in = $content_related ? 1 : 0;
$value = $this->session_out_seq_no;
$this->session_out_seq_no += $in;
//$this->API->logger->logger("OUT: $value + $in = ".$this->session_out_seq_no);
return $value * 2 + $in;
}
public function check_in_seq_no($current_msg_id)
{
$type = isset($this->incoming_messages[$current_msg_id]['content']['_']) ? $this->incoming_messages[$current_msg_id]['content']['_'] : '-';
if (isset($this->incoming_messages[$current_msg_id]['seq_no']) && ($seq_no = $this->generate_in_seq_no($this->content_related($this->incoming_messages[$current_msg_id]['content']))) !== $this->incoming_messages[$current_msg_id]['seq_no']) {
$this->API->logger->logger('SECURITY WARNING: Seqno mismatch (should be '.$seq_no.', is '.$this->incoming_messages[$current_msg_id]['seq_no'].', '.$type.')', \danog\MadelineProto\Logger::ERROR);
} elseif (isset($seq_no)) {
$this->API->logger->logger('Seqno OK (should be '.$seq_no.', is '.$this->incoming_messages[$current_msg_id]['seq_no'].', '.$type.')', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
}
}
public function generate_in_seq_no($content_related)
{
$in = $content_related ? 1 : 0;
$value = $this->session_in_seq_no;
$this->session_in_seq_no += $in;
//$this->API->logger->logger("IN: $value + $in = ".$this->session_in_seq_no);
return $value * 2 + $in;
}
}

View File

@ -0,0 +1,106 @@
<?php
/**
* Abridged stream wrapper.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream\MTProtoTransport;
use Amp\Promise;
use danog\MadelineProto\Stream\Async\BufferedStream;
use danog\MadelineProto\Stream\BufferedStreamInterface;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\MTProtoBufferInterface;
/**
* Abridged stream wrapper.
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class AbridgedStream implements BufferedStreamInterface, MTProtoBufferInterface
{
use BufferedStream;
private $stream;
private $ctx;
/**
* Connect to stream.
*
* @param ConnectionContext $ctx The connection context
*
* @return \Generator
*/
public function connectAsync(ConnectionContext $ctx, string $header = ''): \Generator
{
$this->ctx = $ctx->getCtx();
$this->stream = yield $ctx->getStream(chr(239).$header);
}
/**
* Async close.
*
* @return Promise
*/
public function disconnect()
{
return $this->stream->disconnect();
}
/**
* Get write buffer asynchronously.
*
* @param int $length Length of data that is going to be written to the write buffer
*
* @return \Generator
*/
public function getWriteBufferAsync(int $length, string $append = ''): \Generator
{
$length >>= 2;
if ($length < 127) {
$message = chr($length);
} else {
$message = chr(127).substr(pack('V', $length), 0, 3);
}
$buffer = yield $this->stream->getWriteBuffer(strlen($message) + $length, $append);
yield $buffer->bufferWrite($message);
return $buffer;
}
/**
* Get read buffer asynchronously.
*
* @param int $length Length of payload, as detected by this layer
*
* @return Generator
*/
public function getReadBufferAsync(&$length): \Generator
{
$buffer = yield $this->stream->getReadBuffer($l);
$length = ord(yield $buffer->bufferRead(1));
if ($length >= 127) {
$length = unpack('V', (yield $buffer->bufferRead(3))."\0")[1];
}
$length <<= 2;
return $buffer;
}
public static function getName(): string
{
return __CLASS__;
}
}

View File

@ -0,0 +1,114 @@
<?php
/**
* TCP full stream wrapper.
*
* 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/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2018 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto\Stream\MTProtoTransport;
use Amp\Promise;
use danog\MadelineProto\Stream\Async\BufferedStream;
use danog\MadelineProto\Stream\BufferedStreamInterface;
use danog\MadelineProto\Stream\Common\HashedBufferedStream;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\MTProtoBufferInterface;
/**
* TCP full stream wrapper.
*
* Manages obfuscated2 encryption/decryption
*
* @author Daniil Gentili <daniil@daniil.it>
*/
class FullStream implements BufferedStreamInterface, MTProtoBufferInterface
{
use BufferedStream;
private $stream;
private $in_seq_no = -1;
private $out_seq_no = -1;
/**
* Stream to use as data source.
*
* @param ConnectionContext $ctx
*
* @return Promise
*/
public function connect(ConnectionContext $ctx, string $header = ''): Promise
{
$this->in_seq_no = -1;
$this->out_seq_no = -1;
$this->stream = new HashedBufferedStream();
$this->stream->setExtra('crc32b_rev');
return $this->stream->connect($ctx, $header);
}
/**
* Async close.
*
* @return Promise
*/
public function disconnect()
{
return $this->stream->disconnect();
}
/**
* Get write buffer asynchronously.
*
* @param int $length Length of data that is going to be written to the write buffer
*
* @return Generator
*/
public function getWriteBufferAsync(int $length, string $append = ''): \Generator
{
$this->stream->startWriteHash();
$this->stream->checkWriteHash($length + 8);
$buffer = yield $this->stream->getWriteBuffer($length + 12, $append);
$this->out_seq_no++;
$buffer->bufferWrite(pack('VV', $length + 12, $this->out_seq_no));
return $buffer;
}
/**
* Get read buffer asynchronously.
*
* @param int $length Length of payload, as detected by this layer
*
* @return Generator
*/
public function getReadBufferAsync(&$length): \Generator
{
$this->stream->startReadHash();
$buffer = yield $this->stream->getReadBuffer($l);
$read_length = unpack('V', yield $buffer->bufferRead(4))[1];
$length = $read_length - 12;
$this->stream->checkReadHash($read_length - 8);
$this->in_seq_no++;
$in_seq_no = unpack('V', yield $buffer->bufferRead(4))[1];
if ($in_seq_no != $this->in_seq_no) {
throw new Exception('Incoming seq_no mismatch');
}
return $buffer;
}
public static function getName(): string
{
return __CLASS__;
}
}

Some files were not shown because too many files have changed in this diff Show More