Yield from all the things

This commit is contained in:
Daniil Gentili 2020-01-31 19:29:43 +01:00
parent b842c8f7e5
commit 000839a1b5
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
141 changed files with 1432 additions and 4057 deletions

View File

@ -19,7 +19,7 @@
namespace phpseclib\Math;
if (PHP_MAJOR_VERSION < 7 && !((\class_exists(\Phar::class) && \Phar::running()) || \defined('TESTING_VERSIONS'))) {
if (PHP_MAJOR_VERSION < 7 && !(\class_exists(\Phar::class) && \Phar::running() || \defined('TESTING_VERSIONS'))) {
throw new \Exception('MadelineProto requires php 7 to run natively, use phar.madelineproto.xyz to run on PHP 5.6');
}
if (\defined('HHVM_VERSION')) {
@ -32,7 +32,6 @@ if (\defined('HHVM_VERSION')) {
}
}
}
class BigIntegor
{
}

View File

@ -22,7 +22,8 @@ if (!\class_exists('ReflectionGenerator')) {
}
public function getFunction(): ReflectionFunctionAbstract
{
return new ReflectionFunction(function () {});
return new ReflectionFunction(function () {
});
}
public function getThis(): object
{

View File

@ -1,4 +1,5 @@
<?php
/**
* Yield return value PHP5 polyfill.
*
@ -18,12 +19,10 @@
class YieldReturnValue
{
private $value;
public function __construct($value)
{
$this->value = $value;
}
public function getReturn()
{
return $this->value;

View File

@ -21,7 +21,6 @@ namespace danog\MadelineProto;
use Amp\Deferred;
use Amp\Promise;
use function Amp\File\exists;
use function Amp\File\get;
use function Amp\File\put;
@ -67,7 +66,6 @@ class API extends InternalDoc
public $asyncAPIPromise;
private $oldInstance = false;
private $destructing = false;
/**
* Magic constructor function.
*
@ -94,7 +92,6 @@ class API extends InternalDoc
}
}
}
/**
* Async constructor function.
*
@ -108,29 +105,25 @@ class API extends InternalDoc
{
if (\is_string($params)) {
Logger::constructorFromSettings($settings);
$realpaths = Serialization::realpaths($params);
$this->session = $realpaths['file'];
if (yield exists($realpaths['file'])) {
Logger::log('Waiting for shared lock of serialization lockfile...');
$unlock = yield Tools::flock($realpaths['lockfile'], LOCK_SH);
Logger::log('Shared lock acquired, deserializing...');
try {
$tounserialize = yield get($realpaths['file']);
} finally {
$unlock();
}
\danog\MadelineProto\Magic::classExists();
try {
$unserialized = \unserialize($tounserialize);
} catch (\danog\MadelineProto\Bug74586Exception $e) {
\class_exists('\\Volatile');
$tounserialize = \str_replace('O:26:"danog\\MadelineProto\\Button":', 'O:35:"danog\\MadelineProto\\TL\\Types\\Button":', $tounserialize);
foreach (['RSA', 'TL\\TLMethods', 'TL\\TLConstructors', 'MTProto', 'API', 'DataCenter', 'Connection', 'TL\\Types\\Button', 'TL\\Types\\Bytes', 'APIFactory'] as $class) {
\class_exists('\\danog\\MadelineProto\\'.$class);
\class_exists('\\danog\\MadelineProto\\' . $class);
}
$unserialized = \danog\Serialization::unserialize($tounserialize);
} catch (\danog\MadelineProto\Exception $e) {
@ -142,7 +135,7 @@ class API extends InternalDoc
}
\class_exists('\\Volatile');
foreach (['RSA', 'TL\\TLMethods', 'TL\\TLConstructors', 'MTProto', 'API', 'DataCenter', 'Connection', 'TL\\Types\\Button', 'TL\\Types\\Bytes', 'APIFactory'] as $class) {
\class_exists('\\danog\\MadelineProto\\'.$class);
\class_exists('\\danog\\MadelineProto\\' . $class);
}
$changed = false;
if (\strpos($tounserialize, 'O:26:"danog\\MadelineProto\\Button":') !== false) {
@ -165,12 +158,10 @@ class API extends InternalDoc
$tounserialize = \str_replace('C:26:"phpseclib3\\Math\\BigInteger"', 'C:24:"tgseclib\\Math\\BigInteger"', $tounserialize);
$changed = true;
}
Logger::log((string) $e, Logger::ERROR);
if (!$changed) {
throw $e;
}
try {
$unserialized = \danog\Serialization::unserialize($tounserialize);
} catch (\Throwable $e) {
@ -189,7 +180,6 @@ class API extends InternalDoc
$this->web_api_template = $unserialized->web_api_template;
$this->my_telegram_org_wrapper = $unserialized->my_telegram_org_wrapper;
$this->getting_api_id = $unserialized->getting_api_id;
if (isset($unserialized->API)) {
$this->API = $unserialized->API;
$this->APIFactory();
@ -208,7 +198,6 @@ class API extends InternalDoc
$params = $settings;
}
Logger::constructorFromSettings($settings);
if (!isset($params['app_info']['api_id']) || !$params['app_info']['api_id']) {
$app = yield $this->APIStart($params);
$params['app_info']['api_id'] = $app['api_id'];
@ -226,7 +215,6 @@ class API extends InternalDoc
//\danog\MadelineProto\Logger::log('Pong: '.$pong['ping_id'], Logger::ULTRA_VERBOSE);
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
}
/**
* Enable or disable async.
*
@ -237,14 +225,12 @@ class API extends InternalDoc
public function async(bool $async): void
{
$this->async = $async;
if ($this->API) {
if ($this->API->event_handler && \class_exists($this->API->event_handler) && \is_subclass_of($this->API->event_handler, '\danog\MadelineProto\EventHandler')) {
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);
}
}
}
/**
* Destruct function.
*
@ -270,7 +256,6 @@ class API extends InternalDoc
}
//restore_error_handler();
}
/**
* Sleep function.
*
@ -282,8 +267,6 @@ class API extends InternalDoc
{
return ['API', 'web_api_template', 'getting_api_id', 'my_telegram_org_wrapper'];
}
/**
* Custom fast getSelf.
*
@ -295,7 +278,6 @@ class API extends InternalDoc
{
return isset($this->API) && isset($this->API->authorization['user']) ? $this->API->authorization['user'] : false;
}
/**
* Init API wrapper.
*
@ -322,7 +304,6 @@ class API extends InternalDoc
$this->methods = [];
foreach ($methods as $method) {
$actual_method = $method;
if ($method == 'methodCallAsyncRead') {
$method = 'methodCall';
} elseif (\stripos($method, 'async') !== false) {
@ -340,14 +321,12 @@ class API extends InternalDoc
$this->methods[\strtolower(Tools::fromCamelCase($method))] = $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')) {
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);
}
}
}
/**
* Get full list of MTProto and API methods.
*
@ -362,10 +341,8 @@ class API extends InternalDoc
foreach ($this->API->methods->by_id as $method) {
$methods[] = $method['method'];
}
return \array_merge($methods, \get_class_methods($this->API));
}
/**
* Serialize session.
*
@ -377,12 +354,11 @@ class API extends InternalDoc
*/
public function serialize(string $filename = ''): Promise
{
return Tools::callFork((function () use ($filename) {
return Tools::callFork((function () use ($filename): \Generator {
if (empty($filename)) {
$filename = $this->session;
}
//Logger::log(\danog\MadelineProto\Lang::$current_lang['serializing_madelineproto']);
if ($filename == '') {
return;
}
@ -399,11 +375,8 @@ class API extends InternalDoc
$this->serialized = \time();
$realpaths = Serialization::realpaths($filename);
//Logger::log('Waiting for exclusive lock of serialization lockfile...');
$unlock = yield Tools::flock($realpaths['lockfile'], LOCK_EX);
//Logger::log('Lock acquired, serializing');
try {
if (!$this->getting_api_id) {
$update_closure = $this->API->settings['updates']['callback'];
@ -425,7 +398,6 @@ class API extends InternalDoc
$unlock();
}
//Logger::log('Done serializing');
return $wrote;
})());
}

View File

@ -26,10 +26,9 @@ class Absolute
{
public static function absolute($file)
{
if (($file[0] !== '/') && ($file[1] !== ':') && !\in_array(\substr($file, 0, 4), ['phar', 'http'])) {
$file = Magic::getcwd().'/'.$file;
if ($file[0] !== '/' && $file[1] !== ':' && !\in_array(\substr($file, 0, 4), ['phar', 'http'])) {
$file = Magic::getcwd() . '/' . $file;
}
return $file;
}
}

View File

@ -61,21 +61,18 @@ abstract class AbstractAPIFactory extends AsyncConstruct
* @var Promise
*/
public $asyncAPIPromise;
/**
* Method list.
*
* @var string[]
*/
protected $methods = [];
public function __construct($namespace, &$API, &$async)
{
$this->namespace = $namespace.'.';
$this->API = &$API;
$this->async = &$async;
$this->namespace = $namespace . '.';
$this->API =& $API;
$this->async =& $async;
}
/**
* Enable or disable async.
*
@ -87,7 +84,6 @@ abstract class AbstractAPIFactory extends AsyncConstruct
{
$this->async = $async;
}
/**
* Call async wrapper function.
*
@ -101,25 +97,21 @@ abstract class AbstractAPIFactory extends AsyncConstruct
public function __call(string $name, array $arguments)
{
$yielded = Tools::call($this->__call_async($name, $arguments));
$async = $this->lua === false && (\is_array(\end($arguments)) && isset(\end($arguments)['async']) ? \end($arguments)['async'] : ($this->async && $name !== 'loop'));
$async = $this->lua === false && (\is_array(\end($arguments)) && isset(\end($arguments)['async']) ? \end($arguments)['async'] : $this->async && $name !== 'loop');
if ($async) {
return $yielded;
}
if (!$this->lua) {
return Tools::wait($yielded);
}
try {
$yielded = Tools::wait($yielded);
Lua::convertObjects($yielded);
return $yielded;
} catch (\Throwable $e) {
return ['error_code' => $e->getCode(), 'error' => $e->getMessage()];
}
}
/**
* Call async wrapper function.
*
@ -159,20 +151,17 @@ abstract class AbstractAPIFactory extends AsyncConstruct
yield $this->API->initAsynchronously();
$this->API->logger->logger('Finished init asynchronously');
}
$lower_name = \strtolower($name);
if ($this->namespace !== '' || !isset($this->methods[$lower_name])) {
$name = $this->namespace.$name;
$name = $this->namespace . $name;
$aargs = isset($arguments[1]) && \is_array($arguments[1]) ? $arguments[1] : [];
$aargs['apifactory'] = true;
$aargs['datacenter'] = $this->API->datacenter->curdc;
$args = isset($arguments[0]) && \is_array($arguments[0]) ? $arguments[0] : [];
return yield $this->API->methodCallAsyncRead($name, $args, $aargs);
}
return yield $this->methods[$lower_name](...$arguments);
}
/**
* Get attribute.
*
@ -189,16 +178,13 @@ abstract class AbstractAPIFactory extends AsyncConstruct
}
if ($name === 'settings') {
$this->API->flushSettings = true;
return $this->API->settings;
}
if ($name === 'logger') {
return $this->API->logger;
}
return $this->API->storage[$name];
}
/**
* Set an attribute.
*
@ -218,13 +204,10 @@ abstract class AbstractAPIFactory extends AsyncConstruct
if ($this->API->asyncInitPromise) {
$this->API->init();
}
return $this->API->__construct(\array_replace_recursive($this->API->settings, $value));
}
return $this->API->storage[$name] = $value;
}
/**
* Whether an attribute exists.
*
@ -237,10 +220,8 @@ abstract class AbstractAPIFactory extends AsyncConstruct
if ($this->asyncAPIPromise) {
Tools::wait($this->asyncAPIPromise);
}
return isset($this->API->storage[$name]);
}
/**
* Unset attribute.
*

View File

@ -27,7 +27,6 @@ use phpDocumentor\Reflection\DocBlockFactory;
class AnnotationsBuilder
{
use Tools;
public function __construct(Logger $logger, array $settings, string $output, array $reflectionClasses, string $namespace)
{
$this->reflectionClasses = $reflectionClasses;
@ -43,14 +42,12 @@ class AnnotationsBuilder
$this->settings = $settings;
$this->output = $output;
}
public function mkAnnotations()
{
\danog\MadelineProto\Logger::log('Generating annotations...', \danog\MadelineProto\Logger::NOTICE);
$this->setProperties();
$this->createInternalClasses();
}
/**
* Open file of class APIFactory
* Insert properties
@ -68,16 +65,15 @@ class AnnotationsBuilder
if ($raw_docblock = $property->getDocComment()) {
$docblock = $fixture->create($raw_docblock);
if ($docblock->hasTag('internal')) {
$content = \str_replace("\n ".$raw_docblock."\n public \$".$property->getName().';', '', $content);
$content = \str_replace("\n " . $raw_docblock . "\n public \$" . $property->getName() . ';', '', $content);
}
}
}
foreach ($this->TL->getMethodNamespaces() as $namespace) {
$content = \preg_replace('/(class( \\w+[,]?){0,}\\n{\\n)/', '${1}'." /**\n"." * @internal this is a internal property generated by build_docs.php, don't change manually\n"." *\n"." * @var {$namespace}\n"." */\n"." public \${$namespace};\n", $content);
$content = \preg_replace('/(class( \\w+[,]?){0,}\\n{\\n)/', '${1}' . " /**\n" . " * @internal this is a internal property generated by build_docs.php, don't change manually\n" . " *\n" . " * @var {$namespace}\n" . " */\n" . " public \${$namespace};\n", $content);
}
\file_put_contents($filename, $content);
}
/**
* Create internalDoc.
*
@ -88,23 +84,19 @@ class AnnotationsBuilder
\danog\MadelineProto\Logger::log('Creating internal classes...', \danog\MadelineProto\Logger::NOTICE);
$handle = \fopen($this->output, 'w');
\fwrite($handle, "<?php namespace {$this->namespace}; class InternalDoc extends APIFactory {}");
$class = new \ReflectionClass($this->reflectionClasses['API']);
$methods = $class->getMethods(\ReflectionMethod::IS_STATIC | \ReflectionMethod::IS_PUBLIC);
$ignoreMethods = ['fetchserializableobject'];
foreach ($methods as $method) {
$ignoreMethods[$method->getName()] = $method->getName();
}
$class = new \ReflectionClass(TLCallback::class);
$methods = $class->getMethods(\ReflectionMethod::IS_STATIC | \ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) {
$ignoreMethods[$method->getName()] = $method->getName();
}
\fclose($handle);
$handle = \fopen($this->output, 'w');
$internalDoc = [];
foreach ($this->TL->getMethods()->by_id as $id => $data) {
if (!\strpos($data['method'], '.')) {
@ -115,7 +107,6 @@ class AnnotationsBuilder
continue;
}
$internalDoc[$namespace][$method]['title'] = \str_replace(['](../', '.md'], ['](https://docs.madelineproto.xyz/API_docs/', '.html'], Lang::$current_lang["method_{$data['method']}"] ?? '');
$type = \str_ireplace(['vector<', '>'], [' of ', '[]'], $data['type']);
foreach ($data['params'] as $param) {
if (\in_array($param['name'], ['flags', 'random_id', 'random_bytes'])) {
@ -133,31 +124,25 @@ class AnnotationsBuilder
$param['type'] = 'Vector t';
$param['subtype'] = 'int';
}
$stype = 'type';
if (isset($param['subtype'])) {
$stype = 'subtype';
}
$ptype = $param[$stype];
switch ($ptype) {
case 'true':
case 'false':
$ptype = 'boolean';
}
$ptype = $stype === 'type' ? $ptype : "[$ptype]";
$opt = ($param['pow'] ?? false) ? 'Optional: ' : '';
$internalDoc[$namespace][$method]['attr'][$param['name']] = [
'type' => $ptype,
'description' => \str_replace(['](../', '.md'], ['](https://docs.madelineproto.xyz/API_docs/', '.html'], $opt.(Lang::$current_lang["method_{$data['method']}_param_{$param['name']}_type_{$param['type']}"] ?? ''))
];
$ptype = $stype === 'type' ? $ptype : "[{$ptype}]";
$opt = $param['pow'] ?? false ? 'Optional: ' : '';
$internalDoc[$namespace][$method]['attr'][$param['name']] = ['type' => $ptype, 'description' => \str_replace(['](../', '.md'], ['](https://docs.madelineproto.xyz/API_docs/', '.html'], $opt . (Lang::$current_lang["method_{$data['method']}_param_{$param['name']}_type_{$param['type']}"] ?? ''))];
}
if ($type === 'Bool') {
$type = \strtolower($type);
}
$internalDoc[$namespace][$method]['return'] = $type;
}
$class = new \ReflectionClass($this->reflectionClasses['MTProto']);
$methods = $class->getMethods(\ReflectionMethod::IS_STATIC | \ReflectionMethod::IS_PUBLIC);
foreach ($methods as $key => $method) {
@ -183,15 +168,13 @@ class AnnotationsBuilder
continue;
}
$static = $method->isStatic();
if (!$static) {
$code = \file_get_contents($method->getFileName());
$code = \implode("\n", \array_slice(\explode("\n", $code), $method->getStartLine(), $method->getEndLine() - $method->getStartLine()));
if (\strpos($code, '$this') === false) {
Logger::log("$name should be STATIC!", Logger::FATAL_ERROR);
Logger::log("{$name} should be STATIC!", Logger::FATAL_ERROR);
}
}
if ($name == 'methodCallAsyncRead') {
$name = 'methodCall';
} elseif (\stripos($name, 'async') !== false) {
@ -203,10 +186,8 @@ class AnnotationsBuilder
}
$name = Tools::fromSnakeCase($name);
$name = \str_ireplace(['mtproto', 'api'], ['MTProto', 'API'], $name);
$doc = 'public function ';
$doc .= $name;
$doc .= '(';
$paramList = '';
$hasVariadic = false;
@ -224,7 +205,7 @@ class AnnotationsBuilder
$doc .= $type->getName();
$doc .= ' ';
} else {
Logger::log($name.'.'.$param->getName()." has no type!", Logger::WARNING);
Logger::log($name . '.' . $param->getName() . " has no type!", Logger::WARNING);
}
if ($param->isVariadic()) {
$doc .= '...';
@ -237,19 +218,17 @@ class AnnotationsBuilder
if ($param->isOptional() && !$param->isVariadic()) {
$doc .= ' = ';
if ($param->isDefaultValueConstant()) {
$doc .= '\\'.\str_replace(['NULL', 'self'], ['null', 'danog\\MadelineProto\\MTProto'], $param->getDefaultValueConstantName());
$doc .= '\\' . \str_replace(['NULL', 'self'], ['null', 'danog\\MadelineProto\\MTProto'], $param->getDefaultValueConstantName());
} else {
$doc .= \str_replace('NULL', 'null', \var_export($param->getDefaultValue(), true));
}
}
$doc .= ', ';
if ($param->isVariadic()) {
$hasVariadic = true;
$paramList .= '...';
}
$paramList .= '$'.$param->getName().', ';
$paramList .= '$' . $param->getName() . ', ';
}
$hasReturnValue = ($type = $method->getReturnType()) && !\in_array($type->getName(), [\Generator::class, Promise::class]);
if (!$hasVariadic && !$static && !$hasReturnValue) {
@ -271,35 +250,29 @@ class AnnotationsBuilder
$doc .= $type->getName() === 'self' ? $this->reflectionClasses['API'] : $type->getName();
$async = false;
}
$finalParamList = $hasVariadic ? "Tools::arr($paramList)" : "[$paramList]";
$finalParamList = $hasVariadic ? "Tools::arr({$paramList})" : "[{$paramList}]";
$ret = $type && \in_array($type->getName(), ['self', 'void']) ? '' : 'return';
$doc .= "\n{\n";
if ($async) {
$doc .= " $ret \$this->__call(__FUNCTION__, $finalParamList);\n";
$doc .= " {$ret} \$this->__call(__FUNCTION__, {$finalParamList});\n";
} elseif (!$static) {
$doc .= " $ret \$this->API->$name($paramList);\n";
$doc .= " {$ret} \$this->API->{$name}({$paramList});\n";
} else {
$doc .= " $ret \\".$method->getDeclaringClass()->getName()."::".$name."($paramList);\n";
$doc .= " {$ret} \\" . $method->getDeclaringClass()->getName() . "::" . $name . "({$paramList});\n";
}
if (!$ret && $type->getName() === 'self') {
$doc .= " return \$this;\n";
}
$doc .= "}\n";
if (!$method->getDocComment()) {
Logger::log("$name has no PHPDOC!", Logger::FATAL_ERROR);
Logger::log("{$name} has no PHPDOC!", Logger::FATAL_ERROR);
}
if (!$type) {
Logger::log("$name has no return type!", Logger::FATAL_ERROR);
Logger::log("{$name} has no return type!", Logger::FATAL_ERROR);
}
$internalDoc['InternalDoc'][$name]['method'] = $method->getDocComment() ?? '';
$internalDoc['InternalDoc'][$name]['method'] .= "\n ".\implode("\n ", \explode("\n", $doc));
$internalDoc['InternalDoc'][$name]['method'] .= "\n " . \implode("\n ", \explode("\n", $doc));
}
\fwrite($handle, "<?php\n");
\fwrite($handle, "/**\n");
\fwrite($handle, " * This file is automatic generated by build_docs.php file\n");
@ -331,8 +304,8 @@ class AnnotationsBuilder
$longest[2] = \max($longest[2], \strlen($param['description']));
}
foreach ($properties['attr'] as $name => $param) {
$param['type'] = \str_pad('`'.$param['type'].'`', $longest[0]+2);
$name = \str_pad('**'.$name.'**', $longest[1]+4);
$param['type'] = \str_pad('`' . $param['type'] . '`', $longest[0] + 2);
$name = \str_pad('**' . $name . '**', $longest[1] + 4);
$param['description'] = \str_pad($param['description'], $longest[2]);
\fwrite($handle, " * * {$param['type']} {$name} - {$param['description']}\n");
}

View File

@ -1,4 +1,5 @@
<?php
/**
* Async constructor abstract class.
*
@ -35,7 +36,6 @@ class AsyncConstruct
* @var Promise
*/
public $asyncInitPromise;
/**
* Blockingly init.
*
@ -47,7 +47,6 @@ class AsyncConstruct
Tools::wait($this->asyncInitPromise);
}
}
/**
* Asynchronously init.
*
@ -59,7 +58,6 @@ class AsyncConstruct
yield $this->asyncInitPromise;
}
}
/**
* Set init promise.
*

View File

@ -1,4 +1,5 @@
<?php
/**
* Async parameters class.
*
@ -33,7 +34,6 @@ class AsyncParameters
* @var callable
*/
private $callable;
/**
* Create async parameters.
*
@ -43,8 +43,6 @@ class AsyncParameters
{
$this->callable = $callable;
}
/**
* Create async parameters.
*
@ -54,7 +52,6 @@ class AsyncParameters
{
$this->callable = $callable;
}
/**
* Get parameters asynchronously.
*
@ -63,7 +60,6 @@ class AsyncParameters
public function getParameters()
{
$callable = $this->callable;
return $callable();
}
}

View File

@ -33,19 +33,15 @@ class CombinedAPI
public $serialization_interval = 30;
public $serialized = 0;
protected $async;
public function __magic_construct($session, $paths = [])
{
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
\danog\MadelineProto\Magic::classExists();
$realpaths = Serialization::realpaths($session);
$this->session = $realpaths['file'];
foreach ($paths as $path => $settings) {
$this->addInstance($path, $settings);
}
if (\file_exists($realpaths['file'])) {
if (!\file_exists($realpaths['lockfile'])) {
\touch($realpaths['lockfile']);
@ -55,7 +51,6 @@ class CombinedAPI
\danog\MadelineProto\Logger::log('Waiting for shared lock of serialization lockfile...');
\flock($realpaths['lockfile'], LOCK_SH);
\danog\MadelineProto\Logger::log('Shared lock acquired, deserializing...');
try {
$tounserialize = \file_get_contents($realpaths['file']);
} finally {
@ -63,14 +58,11 @@ class CombinedAPI
\fclose($realpaths['lockfile']);
}
$deserialized = \unserialize($tounserialize);
/*foreach ($deserialized['instance_paths'] as $path) {
$this->addInstance($path, isset($paths[$path]) ? $paths[$path] : []);
}*/
$this->addInstance($path, isset($paths[$path]) ? $paths[$path] : []);
}*/
$this->event_handler = $deserialized['event_handler'];
$this->event_handler_instance = $deserialized['event_handler_instance'];
if ($this->event_handler !== null) {
$this->setEventHandler($this->event_handler);
}
@ -79,29 +71,23 @@ class CombinedAPI
$this->addInstance($path, $settings);
}
}
public function addInstance($path, $settings = [])
{
if (isset($this->instances[$path]) && isset($this->instance_paths[$path])) {
if (isset($this->event_handler_instance)) {
$this->event_handler_instance->referenceInstance($path);
}
return;
}
\danog\MadelineProto\Logger::constructor(3);
\danog\MadelineProto\Logger::log("INSTANTIATING $path...");
\danog\MadelineProto\Logger::log("INSTANTIATING {$path}...");
$instance = new \danog\MadelineProto\API($path, $settings);
$this->instance_paths[$path] = $path;
$this->instances[$path] = $instance;
if (isset($this->event_handler_instance)) {
$this->event_handler_instance->referenceInstance($path);
}
}
public function removeInstance($path)
{
if (isset($this->instance_paths[$path])) {
@ -110,27 +96,22 @@ class CombinedAPI
if (isset($this->instances[$path])) {
unset($this->instances[$path]);
}
if (isset($this->event_handler_instance)) {
$this->event_handler_instance->removeInstance($path);
}
}
public function __destruct()
{
if (\danog\MadelineProto\Magic::$has_thread && \is_object(\Thread::getCurrentThread()) || Magic::isFork()) {
return;
}
$this->serialize();
}
public function serialize($filename = '')
{
/*foreach ($this->instances as $instance) {
$instance->serialize();
}*/
$instance->serialize();
}*/
if (\is_null($this->session)) {
return;
}
@ -147,7 +128,6 @@ class CombinedAPI
\danog\MadelineProto\Logger::log('Waiting for exclusive lock of serialization lockfile...');
\flock($realpaths['lockfile'], LOCK_EX);
\danog\MadelineProto\Logger::log('Lock acquired, serializing');
try {
$wrote = \file_put_contents($realpaths['tempfile'], \serialize(['event_handler' => $this->event_handler, 'event_handler_instance' => $this->event_handler_instance, 'instance_paths' => $this->instance_paths]));
\rename($realpaths['tempfile'], $realpaths['file']);
@ -155,12 +135,9 @@ class CombinedAPI
\flock($realpaths['lockfile'], LOCK_UN);
\fclose($realpaths['lockfile']);
}
$this->serialized = \time();
return $wrote;
}
public $event_handler;
private $event_handler_instance;
private $event_handler_methods = [];
@ -170,20 +147,17 @@ class CombinedAPI
}
public function setEventHandler($event_handler)
{
if (!\class_exists($event_handler) || !\is_subclass_of($event_handler, '\danog\MadelineProto\CombinedEventHandler')) {
if (!\class_exists($event_handler) || !\is_subclass_of($event_handler, '\\danog\\MadelineProto\\CombinedEventHandler')) {
throw new \danog\MadelineProto\Exception('Wrong event handler was defined');
}
$this->event_handler = $event_handler;
if (!($this->event_handler_instance instanceof $this->event_handler)) {
if (!$this->event_handler_instance instanceof $this->event_handler) {
$class_name = $this->event_handler;
$this->event_handler_instance = new $class_name($this);
} else {
$this->event_handler_instance->__construct($this);
}
$this->event_handler_methods = [];
foreach (\get_class_methods($this->event_handler) as $method) {
if ($method === 'onLoop') {
$this->loop_callback = [$this->event_handler_instance, 'onLoop'];
@ -199,16 +173,13 @@ class CombinedAPI
}
}
}
public function eventUpdateHandler($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;
@ -216,22 +187,18 @@ class CombinedAPI
$instance->async($async);
}
}
public function setLoopCallback($callback)
{
$this->loop_callback = $callback;
}
public function getUpdates($params = [])
{
}
public function loop($max_forks = 0)
{
if (\is_callable($max_forks)) {
return \danog\MadelineProto\Tools::wait($max_forks());
}
$loops = [];
foreach ($this->instances as $path => $instance) {
\danog\MadelineProto\Tools::wait($instance->initAsynchronously());
@ -250,12 +217,10 @@ class CombinedAPI
}
$loops[] = \danog\MadelineProto\Tools::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);
\danog\MadelineProto\Tools::wait(all($loops));
}

View File

@ -22,7 +22,6 @@ namespace danog\MadelineProto;
abstract class CombinedEventHandler
{
private $CombinedAPI;
public function __construct($CombinedAPI)
{
$this->CombinedAPI = $CombinedAPI;
@ -30,7 +29,6 @@ abstract class CombinedEventHandler
$this->referenceInstance($path);
}
}
final public function __sleep()
{
$keys = \method_exists($this, '__magic_sleep') ? $this->__magic_sleep() : \get_object_vars($this);
@ -46,15 +44,12 @@ abstract class CombinedEventHandler
}
}
}
return \array_keys($keys);
}
final public function referenceInstance($path)
{
$this->{$path} = $this->CombinedAPI->instances[$path];
}
final public function removeInstance($path)
{
if (isset($this->{$path})) {

View File

@ -1,4 +1,5 @@
<?php
/**
* Connection module.
*
@ -44,7 +45,6 @@ class Connection extends Session
{
use \danog\Serializable;
use Tools;
/**
* Writer loop.
*
@ -87,7 +87,6 @@ class Connection extends Session
* @var ConnectionContext
*/
private $ctx;
/**
* HTTP request count.
*
@ -100,14 +99,12 @@ class Connection extends Session
* @var integer
*/
private $httpResCount = 0;
/**
* Date of last chunk received.
*
* @var integer
*/
private $lastChunk = 0;
/**
* Logger instance.
*
@ -126,28 +123,24 @@ class Connection extends Session
* @var DataCenterConnection
*/
protected $shared;
/**
* DC ID.
*
* @var string
*/
protected $datacenter;
/**
* Connection ID.
*
* @var int
*/
private $id = 0;
/**
* DC ID and connection ID concatenated.
*
* @var
*/
private $datacenterId = '';
/**
* Whether this socket has to be reconnected.
*
@ -214,7 +207,6 @@ class Connection extends Session
{
$this->shared->reading($reading, $this->id);
}
/**
* Tell the class that we have read a chunk of data from the socket.
*
@ -233,7 +225,6 @@ class Connection extends Session
{
return $this->lastChunk;
}
/**
* Indicate a received HTTP response.
*
@ -270,7 +261,6 @@ class Connection extends Session
{
return $this->httpReqCount;
}
/**
* Get connection ID.
*
@ -280,7 +270,6 @@ class Connection extends Session
{
return $this->id;
}
/**
* Get datacenter concatenated with connection ID.
*
@ -290,7 +279,6 @@ class Connection extends Session
{
return $this->datacenterId;
}
/**
* Get connection context.
*
@ -300,7 +288,6 @@ class Connection extends Session
{
return $this->ctx;
}
/**
* Check if is an HTTP connection.
*
@ -310,7 +297,6 @@ class Connection extends Session
{
return \in_array($this->ctx->getStreamName(), [HttpStream::getName(), HttpsStream::getName()]);
}
/**
* Check if is a media connection.
*
@ -320,7 +306,6 @@ class Connection extends Session
{
return $this->ctx->isMedia();
}
/**
* Check if is a CDN connection.
*
@ -330,7 +315,6 @@ class Connection extends Session
{
return $this->ctx->isCDN();
}
/**
* Connects to a telegram DC using the specified protocol, proxy and connection parameters.
*
@ -342,18 +326,15 @@ class Connection extends Session
{
$this->ctx = $ctx->getCtx();
$this->datacenter = $ctx->getDc();
$this->datacenterId = $this->datacenter.'.'.$this->id;
$this->datacenterId = $this->datacenter . '.' . $this->id;
$this->API->logger->logger("Connecting to DC {$this->datacenterId}", \danog\MadelineProto\Logger::WARNING);
$ctx->setReadCallback([$this, 'haveRead']);
$this->stream = yield $ctx->getStream();
if ($this->needsReconnect) {
$this->needsReconnect = false;
}
$this->httpReqCount = 0;
$this->httpResCount = 0;
if (!isset($this->writer)) {
$this->writer = new WriteLoop($this);
}
@ -378,7 +359,6 @@ class Connection extends Session
unset($this->new_outgoing[$message_id], $this->outgoing_messages[$message_id]);
}
}
$this->writer->start();
$this->reader->start();
if (!$this->checker->start()) {
@ -389,7 +369,6 @@ class Connection extends Session
$this->pinger->start();
}
}
/**
* Send an MTProto message.
*
@ -427,17 +406,13 @@ class Connection extends Session
public function sendMessage(array $message, bool $flush = true): \Generator
{
$deferred = new Deferred();
if (!isset($message['serialized_body'])) {
$body = \is_object($message['body']) ? yield $message['body'] : $message['body'];
$refreshNext = isset($message['refreshNext']) && $message['refreshNext'];
//$refreshNext = true;
if ($refreshNext) {
$this->API->referenceDatabase->refreshNext(true);
}
if ($message['method']) {
$body = yield $this->API->getTL()->serializeMethod($message['_'], $body);
} else {
@ -450,16 +425,13 @@ class Connection extends Session
$message['serialized_body'] = $body;
unset($body);
}
$message['send_promise'] = $deferred;
$this->pending_outgoing[$this->pending_outgoing_key++] = $message;
if ($flush && isset($this->writer)) {
$this->writer->resume();
}
return $deferred->promise();
}
/**
* Flush pending packets.
*
@ -500,7 +472,6 @@ class Connection extends Session
$this->API = $extra->getExtra();
$this->logger = $this->API->logger;
}
/**
* Get main instance.
*
@ -510,7 +481,6 @@ class Connection extends Session
{
return $this->API;
}
/**
* Get shared connection instance.
*
@ -520,7 +490,6 @@ class Connection extends Session
{
return $this->shared;
}
/**
* Disconnect from DC.
*
@ -549,7 +518,6 @@ class Connection extends Session
}
$this->API->logger->logger("Disconnected from DC {$this->datacenterId}");
}
/**
* Reconnect to DC.
*
@ -561,7 +529,6 @@ class Connection extends Session
$this->disconnect(true);
yield $this->API->datacenter->dcConnect($this->ctx->getDc(), $this->id);
}
/**
* Get name.
*

View File

@ -37,16 +37,14 @@ class ContextConnector implements Connector
$this->fromDns = $fromDns;
$this->logger = $dataCenter->getAPI()->getLogger();
}
public function connect(string $uri, ?ConnectContext $ctx = null, ?CancellationToken $token = null): Promise
{
return Tools::call((function () use ($uri, $ctx, $token) {
$ctx = $ctx ?? new ConnectContext;
$token = $token ?? new NullCancellationToken;
return Tools::call((function () use ($uri, $ctx, $token): \Generator {
$ctx = $ctx ?? new ConnectContext();
$token = $token ?? new NullCancellationToken();
$ctxs = $this->dataCenter->generateContexts(0, $uri, $ctx);
if (empty($ctxs)) {
throw new Exception("No contexts for raw connection to URI $uri");
throw new Exception("No contexts for raw connection to URI {$uri}");
}
foreach ($ctxs as $ctx) {
/* @var $ctx \danog\MadelineProto\Stream\ConnectionContext */
@ -55,22 +53,20 @@ class ContextConnector implements Connector
$ctx->setCancellationToken($token);
$result = yield $ctx->getStream();
$this->logger->logger('OK!', \danog\MadelineProto\Logger::WARNING);
return $result->getSocket();
} catch (\Throwable $e) {
if (\MADELINEPROTO_TEST === 'pony') {
throw $e;
}
$this->logger->logger('Connection failed: '.$e, \danog\MadelineProto\Logger::ERROR);
$this->logger->logger('Connection failed: ' . $e, \danog\MadelineProto\Logger::ERROR);
if ($e instanceof MultiReasonException) {
foreach ($e->getReasons() as $reason) {
$this->logger->logger('Multireason: '.$reason, \danog\MadelineProto\Logger::ERROR);
$this->logger->logger('Multireason: ' . $reason, \danog\MadelineProto\Logger::ERROR);
}
}
}
}
throw new \danog\MadelineProto\Exception("Could not connect to URI $uri");
throw new \danog\MadelineProto\Exception("Could not connect to URI {$uri}");
})());
}
}

View File

@ -1,4 +1,5 @@
<?php
/**
* Coroutine (modified version of AMP Coroutine).
*
@ -57,24 +58,20 @@ final class Coroutine implements Promise, \ArrayAccess
private $exception;
/** @var mixed Promise success value when executing next coroutine step, null at all other times. */
private $value;
/** @var ?self Reference to coroutine that started this coroutine */
private $parentCoroutine;
/**
* @param \Generator $generator
*/
public function __construct(\Generator $generator)
{
$this->generator = $generator;
try {
$yielded = $this->generator->current();
while (!$yielded instanceof Promise) {
if ($yielded instanceof \YieldReturnValue) {
$this->resolve($yielded->getReturn());
$this->generator->next();
return;
}
if (!$this->generator->valid()) {
@ -83,7 +80,6 @@ final class Coroutine implements Promise, \ArrayAccess
} else {
$this->resolve(null);
}
return;
}
if ($yielded instanceof \Generator) {
@ -97,7 +93,6 @@ final class Coroutine implements Promise, \ArrayAccess
}
} catch (\Throwable $exception) {
$this->fail($exception);
return;
}
/*
@ -109,10 +104,8 @@ final class Coroutine implements Promise, \ArrayAccess
$this->value = $value;
if (!$this->immediate) {
$this->immediate = true;
return;
}
try {
do {
if ($this->exception) {
@ -127,10 +120,8 @@ final class Coroutine implements Promise, \ArrayAccess
$this->resolve($yielded->getReturn());
$this->onResolve = null;
$this->generator->next();
return;
}
if (!$this->generator->valid()) {
if (PHP_MAJOR_VERSION >= 7) {
$this->resolve($this->generator->getReturn());
@ -138,7 +129,6 @@ final class Coroutine implements Promise, \ArrayAccess
$this->resolve(null);
}
$this->onResolve = null;
return;
}
if ($yielded instanceof \Generator) {
@ -164,20 +154,19 @@ final class Coroutine implements Promise, \ArrayAccess
};
$yielded->onResolve($this->onResolve);
}
/**
* Throw exception into the generator
* Throw exception into the generator.
*
* @param \Throwable $reason Exception
*
*
* @internal
*
*
* @return void
*/
public function throw(\Throwable $reason)
{
if (!isset($reason->yieldedFrames)) {
if (method_exists($reason, 'updateTLTrace')) {
if (\method_exists($reason, 'updateTLTrace')) {
$reason->updateTLTrace($this->getTrace());
} else {
$reason->yieldedFrames = $this->getTrace();
@ -185,7 +174,6 @@ final class Coroutine implements Promise, \ArrayAccess
}
$this->generator->throw($reason);
}
/**
* @param \Throwable $reason Failure reason.
*/
@ -193,7 +181,6 @@ final class Coroutine implements Promise, \ArrayAccess
{
$this->resolve(new Failure($reason));
}
public function offsetExists($offset): bool
{
throw new Exception('Not supported!');
@ -207,14 +194,14 @@ final class Coroutine implements Promise, \ArrayAccess
*/
public function offsetGet($offset)
{
return Tools::call((function () use ($offset) {
return Tools::call((function () use ($offset): \Generator {
$result = yield $this;
return $result[$offset];
})());
}
public function offsetSet($offset, $value)
{
return Tools::call((function () use ($offset, $value) {
return Tools::call((function () use ($offset, $value): \Generator {
$result = yield $this;
if ($offset === null) {
return $result[] = $value;
@ -224,12 +211,11 @@ final class Coroutine implements Promise, \ArrayAccess
}
public function offsetUnset($offset)
{
return Tools::call((function () use ($offset) {
return Tools::call((function () use ($offset): \Generator {
$result = yield $this;
unset($result[$offset]);
})());
}
/**
* Get an attribute asynchronously.
*
@ -239,19 +225,18 @@ final class Coroutine implements Promise, \ArrayAccess
*/
public function __get(string $offset)
{
return Tools::call((function () use ($offset) {
return Tools::call((function () use ($offset): \Generator {
$result = yield $this;
return $result->{$offset};
})());
}
public function __call(string $name, array $arguments)
{
return Tools::call((function () use ($name, $arguments) {
return Tools::call((function () use ($name, $arguments): \Generator {
$result = yield $this;
return $result->{$name}($arguments);
})());
}
/**
* Get current stack trace for running coroutine.
*
@ -263,13 +248,7 @@ final class Coroutine implements Promise, \ArrayAccess
try {
$reflector = new ReflectionGenerator($this->generator);
$frames = $reflector->getTrace();
$frames []= \array_merge(
$this->parentCoroutine ? $this->parentCoroutine->getFrame() : [],
[
'function' => $reflector->getFunction()->getName(),
'args' => []
]
);
$frames[] = \array_merge($this->parentCoroutine ? $this->parentCoroutine->getFrame() : [], ['function' => $reflector->getFunction()->getName(), 'args' => []]);
} catch (\Throwable $e) {
}
if ($this->parentCoroutine) {
@ -277,9 +256,8 @@ final class Coroutine implements Promise, \ArrayAccess
}
return $frames;
}
/**
* Get current execution frame
* Get current execution frame.
*
* @return array
*/
@ -287,10 +265,7 @@ final class Coroutine implements Promise, \ArrayAccess
{
try {
$reflector = new ReflectionGenerator($this->generator);
return [
'file' => $reflector->getExecutingFile(),
'line' => $reflector->getExecutingLine(),
];
return ['file' => $reflector->getExecutingFile(), 'line' => $reflector->getExecutingLine()];
} catch (\Throwable $e) {
}
return [];

View File

@ -114,12 +114,10 @@ class DataCenter
* @var \Amp\Http\Client\Cookie\CookieJar
*/
private $CookieJar;
public function __sleep()
{
return ['sockets', 'curdc', 'dclist', 'settings'];
}
public function __wakeup()
{
$array = [];
@ -136,7 +134,6 @@ class DataCenter
}
$this->setDataCenterConnections($array);
}
/**
* Set auth key information from saved auth array.
*
@ -147,7 +144,7 @@ class DataCenter
public function setDataCenterConnections(array $saved)
{
foreach ($saved as $id => $data) {
$connection = $this->sockets[$id] = new DataCenterConnection;
$connection = $this->sockets[$id] = new DataCenterConnection();
if (isset($data['permAuthKey'])) {
$connection->setPermAuthKey(new PermAuthKey($data['permAuthKey']));
}
@ -187,12 +184,11 @@ class DataCenter
public function __magic_construct($API, array $dclist, array $settings, bool $reconnectAll = true, CookieJar $jar = null)
{
$this->API = $API;
$changed = [];
$changedSettings = $this->settings !== $settings;
if (!$reconnectAll) {
$changed = [];
$test = ($API->getCachedConfig()['test_mode'] ?? false) ? 'test' : 'main';
$test = $API->getCachedConfig()['test_mode'] ?? false ? 'test' : 'main';
foreach ($dclist[$test] as $ipv6 => $dcs) {
foreach ($dcs as $id => $dc) {
if ($dc !== ($this->dclist[$test][$ipv6][$id] ?? [])) {
@ -201,7 +197,6 @@ class DataCenter
}
}
}
$this->dclist = $dclist;
$this->settings = $settings;
foreach ($this->sockets as $key => $socket) {
@ -217,96 +212,57 @@ class DataCenter
unset($this->sockets[$key]);
}
}
if ($reconnectAll || $changedSettings || !$this->CookieJar) {
$this->CookieJar = $jar ?? new InMemoryCookieJar;
$this->HTTPClient = (new HttpClientBuilder)
->interceptNetwork(new CookieInterceptor($this->CookieJar))
->usingPool(new UnlimitedConnectionPool(new DefaultConnectionFactory(new ContextConnector($this))))
->build();
$DoHHTTPClient = (new HttpClientBuilder)
->interceptNetwork(new CookieInterceptor($this->CookieJar))
->usingPool(new UnlimitedConnectionPool(new DefaultConnectionFactory(new ContextConnector($this, true))))
->build();
$DoHConfig = new DoHConfig(
[
new Nameserver('https://mozilla.cloudflare-dns.com/dns-query'),
new Nameserver('https://dns.google/resolve'),
],
$DoHHTTPClient
);
$nonProxiedDoHConfig = new DoHConfig(
[
new Nameserver('https://mozilla.cloudflare-dns.com/dns-query'),
new Nameserver('https://dns.google/resolve'),
]
);
$this->DoHClient = Magic::$altervista || Magic::$zerowebhost ?
new Rfc1035StubResolver() :
new Rfc8484StubResolver($DoHConfig);
$this->nonProxiedDoHClient = Magic::$altervista || Magic::$zerowebhost ?
new Rfc1035StubResolver() :
new Rfc8484StubResolver($nonProxiedDoHConfig);
$this->CookieJar = $jar ?? new InMemoryCookieJar();
$this->HTTPClient = (new HttpClientBuilder())->interceptNetwork(new CookieInterceptor($this->CookieJar))->usingPool(new UnlimitedConnectionPool(new DefaultConnectionFactory(new ContextConnector($this))))->build();
$DoHHTTPClient = (new HttpClientBuilder())->interceptNetwork(new CookieInterceptor($this->CookieJar))->usingPool(new UnlimitedConnectionPool(new DefaultConnectionFactory(new ContextConnector($this, true))))->build();
$DoHConfig = new DoHConfig([new Nameserver('https://mozilla.cloudflare-dns.com/dns-query'), new Nameserver('https://dns.google/resolve')], $DoHHTTPClient);
$nonProxiedDoHConfig = new DoHConfig([new Nameserver('https://mozilla.cloudflare-dns.com/dns-query'), new Nameserver('https://dns.google/resolve')]);
$this->DoHClient = Magic::$altervista || Magic::$zerowebhost ? new Rfc1035StubResolver() : new Rfc8484StubResolver($DoHConfig);
$this->nonProxiedDoHClient = Magic::$altervista || Magic::$zerowebhost ? new Rfc1035StubResolver() : new Rfc8484StubResolver($nonProxiedDoHConfig);
}
}
public function dcConnect(string $dc_number, int $id = -1): \Generator
{
$old = isset($this->sockets[$dc_number]) && (
$this->sockets[$dc_number]->shouldReconnect() ||
(
$id !== -1
&& $this->sockets[$dc_number]->hasConnection($id)
&& $this->sockets[$dc_number]->getConnection($id)->shouldReconnect()
)
);
$old = isset($this->sockets[$dc_number]) && ($this->sockets[$dc_number]->shouldReconnect() || $id !== -1 && $this->sockets[$dc_number]->hasConnection($id) && $this->sockets[$dc_number]->getConnection($id)->shouldReconnect());
if (isset($this->sockets[$dc_number]) && !$old) {
$this->API->logger("Not reconnecting to DC $dc_number ($id)");
$this->API->logger("Not reconnecting to DC {$dc_number} ({$id})");
return false;
}
$ctxs = $this->generateContexts($dc_number);
if (empty($ctxs)) {
return false;
}
foreach ($ctxs as $ctx) {
try {
if ($old) {
$this->API->logger->logger("Reconnecting to DC $dc_number ($id) from existing", \danog\MadelineProto\Logger::WARNING);
$this->API->logger->logger("Reconnecting to DC {$dc_number} ({$id}) from existing", \danog\MadelineProto\Logger::WARNING);
$this->sockets[$dc_number]->setExtra($this->API);
yield $this->sockets[$dc_number]->connect($ctx, $id);
} else {
$this->API->logger->logger("Connecting to DC $dc_number from scratch", \danog\MadelineProto\Logger::WARNING);
$this->API->logger->logger("Connecting to DC {$dc_number} from scratch", \danog\MadelineProto\Logger::WARNING);
$this->sockets[$dc_number] = new DataCenterConnection();
$this->sockets[$dc_number]->setExtra($this->API);
yield $this->sockets[$dc_number]->connect($ctx);
}
$this->API->logger->logger('OK!', \danog\MadelineProto\Logger::WARNING);
return true;
} catch (\Throwable $e) {
if (\MADELINEPROTO_TEST === 'pony') {
throw $e;
}
$this->API->logger->logger('Connection failed: '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);
$this->API->logger->logger('Connection failed: ' . $e->getMessage(), \danog\MadelineProto\Logger::ERROR);
}
}
throw new \danog\MadelineProto\Exception("Could not connect to DC $dc_number");
throw new \danog\MadelineProto\Exception("Could not connect to DC {$dc_number}");
}
public function generateContexts($dc_number = 0, string $uri = '', ConnectContext $context = null)
{
$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 'abridged':
case 'tcp_abridged':
@ -319,7 +275,7 @@ class DataCenter
case 'obfuscated2':
$this->settings[$dc_config_number]['protocol'] = 'tcp_intermediate_padded';
$this->settings[$dc_config_number]['obfuscated'] = true;
// no break
// no break
case 'intermediate_padded':
case 'tcp_intermediate_padded':
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [IntermediatePaddedStream::getName(), []]];
@ -359,13 +315,11 @@ class DataCenter
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []]];
}
$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'])) {
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_iterable($this->settings[$dc_config_number]['proxy'])) {
$proxies = $this->settings[$dc_config_number]['proxy'];
$proxy_extras = $this->settings[$dc_config_number]['proxy_extra'];
@ -423,12 +377,10 @@ class DataCenter
$combo = \array_merge($first, $second);
}
}
\array_unshift($combos, $combo);
//unset($combos[$k]);
}
}
if ($dc_number) {
$combos[] = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [HttpsStream::getName(), []]];
}
@ -436,19 +388,13 @@ class DataCenter
}
/* @var $context \Amp\ConnectContext */
$context = $context ?? (new ConnectContext())->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) {
// This is only for non-MTProto connections
if (!$dc_number) {
/* @var $ctx \danog\MadelineProto\Stream\ConnectionContext */
$ctx = (new ConnectionContext())
->setSocketContext($context)
->setUri($uri)
->setIpv6($ipv6 === 'ipv6');
$ctx = (new ConnectionContext())->setSocketContext($context)->setUri($uri)->setIpv6($ipv6 === 'ipv6');
foreach ($combo as $stream) {
if ($stream[0] === DefaultStream::getName() && $stream[1] === []) {
$stream[1] = new DoHConnector($this, $ctx);
@ -458,59 +404,44 @@ class DataCenter
$ctxs[] = $ctx;
continue;
}
// This is only for MTProto connections
if (!isset($this->dclist[$test][$ipv6][$dc_number]['ip_address'])) {
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)];
$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;
$uri = 'tcp://' . $subdomain . '.web.telegram.org:' . $port . '/' . $path;
} elseif ($stream === HttpStream::getName()) {
$uri = 'tcp://'.$address.':'.$port.'/api';
$uri = 'tcp://' . $address . ':' . $port . '/api';
} else {
$uri = 'tcp://'.$address.':'.$port;
$uri = 'tcp://' . $address . ':' . $port;
}
if ($combo[1][0] === WssStream::getName()) {
$subdomain = $this->dclist['ssl_subdomains'][\preg_replace('/\D+/', '', $dc_number)];
$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://' . $subdomain . '.web.telegram.org:' . $port . '/' . $path;
} elseif ($combo[1][0] === WsStream::getName()) {
$subdomain = $this->dclist['ssl_subdomains'][\preg_replace('/\D+/', '', $dc_number)];
$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;
$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');
$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) {
if ($stream[0] === DefaultStream::getName() && $stream[1] === []) {
$stream[1] = new DoHConnector($this, $ctx);
@ -524,22 +455,17 @@ class DataCenter
}
}
}
if (isset($this->dclist[$test][$ipv6][$dc_number.'_bk']['ip_address'])) {
$ctxs = \array_merge($ctxs, $this->generateContexts($dc_number.'_bk'));
if (isset($this->dclist[$test][$ipv6][$dc_number . '_bk']['ip_address'])) {
$ctxs = \array_merge($ctxs, $this->generateContexts($dc_number . '_bk'));
}
if (empty($ctxs)) {
unset($this->sockets[$dc_number]);
$this->API->logger->logger("No info for DC $dc_number", \danog\MadelineProto\Logger::ERROR);
$this->API->logger->logger("No info for DC {$dc_number}", \danog\MadelineProto\Logger::ERROR);
} elseif (\MADELINEPROTO_TEST === 'pony') {
return [$ctxs[0]];
}
return $ctxs;
}
/**
* Get main API.
*
@ -549,7 +475,6 @@ class DataCenter
{
return $this->API;
}
/**
* Get async HTTP client.
*
@ -559,7 +484,6 @@ class DataCenter
{
return $this->HTTPClient;
}
/**
* Get async HTTP client cookies.
*
@ -587,7 +511,6 @@ class DataCenter
{
return $this->nonProxiedDoHClient;
}
/**
* Get contents of file.
*
@ -599,7 +522,6 @@ class DataCenter
{
return yield (yield $this->getHTTPClient()->request(new Request($url)))->getBody()->buffer();
}
/**
* Get Connection instance for authorization.
*
@ -664,8 +586,6 @@ class DataCenter
{
return isset($this->sockets[$dc]);
}
/**
* Check if connected to datacenter using HTTP.
*
@ -677,7 +597,6 @@ class DataCenter
{
return $this->sockets[$datacenter]->isHttp();
}
/**
* Check if connected to datacenter directly using IP address.
*
@ -689,7 +608,6 @@ class DataCenter
{
return $this->sockets[$datacenter]->byIPAddress();
}
/**
* Get all DC IDs.
*
@ -701,7 +619,6 @@ class DataCenter
{
$test = $this->settings['all']['test_mode'] ? 'test' : 'main';
$ipv6 = $this->settings['all']['ipv6'] ? 'ipv6' : 'ipv4';
return $all ? \array_keys((array) $this->dclist[$test][$ipv6]) : \array_keys((array) $this->sockets);
}
}

View File

@ -1,4 +1,5 @@
<?php
/**
* Connection module handling all connections to a datacenter.
*
@ -36,7 +37,6 @@ class DataCenterConnection implements JsonSerializable
const READ_WEIGHT = 1;
const READ_WEIGHT_MEDIA = 5;
const WRITE_WEIGHT = 10;
/**
* Promise for connection.
*
@ -49,7 +49,6 @@ class DataCenterConnection implements JsonSerializable
* @var Deferred
*/
private $connectionsDeferred;
/**
* Temporary auth key.
*
@ -62,7 +61,6 @@ class DataCenterConnection implements JsonSerializable
* @var PermAuthKey|null
*/
private $permAuthKey;
/**
* Connections open to a certain DC.
*
@ -75,42 +73,36 @@ class DataCenterConnection implements JsonSerializable
* @var array<string, int>
*/
private $availableConnections = [];
/**
* Main API instance.
*
* @var \danog\MadelineProto\MTProto
*/
private $API;
/**
* Connection context.
*
* @var ConnectionContext
*/
private $ctx;
/**
* DC ID.
*
* @var string
*/
private $datacenter;
/**
* Linked DC ID.
*
* @var string
*/
private $linked;
/**
* Loop to keep weights at sane value.
*
* @var \danog\MadelineProto\Loop\Generic\PeriodicLoop
*/
private $robinLoop;
/**
* Decrement roundrobin weight by this value if busy reading.
*
@ -123,14 +115,12 @@ class DataCenterConnection implements JsonSerializable
* @var integer
*/
private $decWrite = 10;
/**
* Backed up messages.
*
* @var array
*/
private $backup = [];
/**
* Whether this socket has to be reconnected.
*
@ -191,7 +181,6 @@ class DataCenterConnection implements JsonSerializable
{
$this->{$temp ? 'tempAuthKey' : 'permAuthKey'} = $key;
}
/**
* Get temporary authorization key.
*
@ -210,7 +199,6 @@ class DataCenterConnection implements JsonSerializable
{
return $this->getAuthKey(false);
}
/**
* Check if has temporary authorization key.
*
@ -220,7 +208,6 @@ class DataCenterConnection implements JsonSerializable
{
return $this->hasAuthKey(true);
}
/**
* Check if has permanent authorization key.
*
@ -230,7 +217,6 @@ class DataCenterConnection implements JsonSerializable
{
return $this->hasAuthKey(false);
}
/**
* Set temporary authorization key.
*
@ -253,7 +239,6 @@ class DataCenterConnection implements JsonSerializable
{
return $this->setAuthKey($key, false);
}
/**
* Bind temporary and permanent auth keys.
*
@ -286,7 +271,6 @@ class DataCenterConnection implements JsonSerializable
{
return $this->hasTempAuthKey() ? $this->getTempAuthKey()->isAuthorized() : false;
}
/**
* Set the authorized boolean.
*
@ -302,7 +286,6 @@ class DataCenterConnection implements JsonSerializable
$this->getTempAuthKey()->authorized($authorized);
}
}
/**
* Link permanent authorization info of main DC to media DC.
*
@ -313,9 +296,8 @@ class DataCenterConnection implements JsonSerializable
public function link(string $dc)
{
$this->linked = $dc;
$this->permAuthKey = &$this->API->datacenter->getDataCenterConnection($dc)->permAuthKey;
$this->permAuthKey =& $this->API->datacenter->getDataCenterConnection($dc)->permAuthKey;
}
/**
* Reset MTProto sessions.
*
@ -349,7 +331,6 @@ class DataCenterConnection implements JsonSerializable
$socket->flush();
}
}
/**
* Get connection context.
*
@ -359,7 +340,6 @@ class DataCenterConnection implements JsonSerializable
{
return $this->ctx;
}
/**
* Has connection context?
*
@ -369,7 +349,6 @@ class DataCenterConnection implements JsonSerializable
{
return isset($this->ctx);
}
/**
* Connect function.
*
@ -380,32 +359,26 @@ class DataCenterConnection implements JsonSerializable
*/
public function connect(ConnectionContext $ctx, int $id = -1): \Generator
{
$this->API->logger->logger("Trying shared connection via $ctx ($id)");
$this->API->logger->logger("Trying shared connection via {$ctx} ({$id})");
$this->ctx = $ctx->getCtx();
$this->datacenter = $ctx->getDc();
$media = $ctx->isMedia() || $ctx->isCDN();
$count = $media ? $this->API->settings['connection_settings']['media_socket_count']['min'] : 1;
if ($count > 1) {
if (!$this->robinLoop) {
$this->robinLoop = new PeriodicLoop($this->API, [$this, 'even'], "robin loop DC {$this->datacenter}", $this->API->settings['connection_settings']['robin_period']);
}
$this->robinLoop->start();
}
$this->decRead = $media ? self::READ_WEIGHT_MEDIA : self::READ_WEIGHT;
$this->decWrite = self::WRITE_WEIGHT;
if ($id === -1 || !isset($this->connections[$id])) {
if ($this->connections) {
$this->API->logger("Already connected!", Logger::WARNING);
return;
}
yield $this->connectMore($count);
yield from $this->connectMore($count);
yield $this->restoreBackup();
$this->connectionsPromise = new Success();
if ($this->connectionsDeferred) {
$connectionsDeferred = $this->connectionsDeferred;
@ -414,10 +387,9 @@ class DataCenterConnection implements JsonSerializable
}
} else {
$this->availableConnections[$id] = 0;
yield $this->connections[$id]->connect($ctx);
yield from $this->connections[$id]->connect($ctx);
}
}
/**
* Connect to the DC using count more sockets.
*
@ -425,21 +397,19 @@ class DataCenterConnection implements JsonSerializable
*
* @return void
*/
private function connectMore(int $count)
private function connectMore(int $count): \Generator
{
$ctx = $this->ctx->getCtx();
$count += $previousCount = \count($this->connections);
for ($x = $previousCount; $x < $count; $x++) {
$connection = new Connection();
$connection->setExtra($this, $x);
yield $connection->connect($ctx);
yield from $connection->connect($ctx);
$this->connections[$x] = $connection;
$this->availableConnections[$x] = 0;
$ctx = $this->ctx->getCtx();
}
}
/**
* Signal that a connection ID disconnected.
*
@ -459,12 +429,10 @@ class DataCenterConnection implements JsonSerializable
$list .= $message['_'] ?? '-';
$list .= ', ';
}
$this->API->logger->logger("Backed up $list from DC {$this->datacenter}.$id");
$this->API->logger->logger("Backed up {$list} from DC {$this->datacenter}.{$id}");
$this->backup = \array_merge($this->backup, $backup);
unset($this->connections[$id], $this->availableConnections[$id]);
}
/**
* Close all connections to DC.
*
@ -472,9 +440,8 @@ class DataCenterConnection implements JsonSerializable
*/
public function disconnect()
{
$this->connectionsDeferred = new Deferred;
$this->connectionsDeferred = new Deferred();
$this->connectionsPromise = $this->connectionsDeferred->promise();
$this->API->logger->logger("Disconnecting from shared DC {$this->datacenter}");
if ($this->robinLoop) {
$this->robinLoop->signal(true);
@ -485,12 +452,10 @@ class DataCenterConnection implements JsonSerializable
$connection->disconnect();
}
$count = \count($this->backup) - $before;
$this->API->logger->logger("Backed up $count, added to $before existing messages) from DC {$this->datacenter}");
$this->API->logger->logger("Backed up {$count}, added to {$before} existing messages) from DC {$this->datacenter}");
$this->connections = [];
$this->availableConnections = [];
}
/**
* Reconnect to DC.
*
@ -500,9 +465,8 @@ class DataCenterConnection implements JsonSerializable
{
$this->API->logger->logger("Reconnecting shared DC {$this->datacenter}");
$this->disconnect();
yield $this->connect($this->ctx);
yield from $this->connect($this->ctx);
}
/**
* Restore backed up messages.
*
@ -513,7 +477,7 @@ class DataCenterConnection implements JsonSerializable
$backup = $this->backup;
$this->backup = [];
$count = \count($backup);
$this->API->logger->logger("Restoring $count messages to DC {$this->datacenter}");
$this->API->logger->logger("Restoring {$count} messages to DC {$this->datacenter}");
foreach ($backup as $message) {
Tools::callFork($this->getConnection()->sendMessage($message, false));
}
@ -528,7 +492,6 @@ class DataCenterConnection implements JsonSerializable
{
return $this->connections[0];
}
/**
* Check if any connection is available.
*
@ -548,12 +511,10 @@ class DataCenterConnection implements JsonSerializable
public function waitGetConnection(): Promise
{
if (empty($this->availableConnections)) {
$deferred = new Deferred;
$this->connectionsPromise->onResolve(
function ($e, $v) use ($deferred) {
$deferred->resolve($this->getConnection());
}
);
$deferred = new Deferred();
$this->connectionsPromise->onResolve(function ($e, $v) use ($deferred) {
$deferred->resolve($this->getConnection());
});
return $deferred->promise();
}
return new Success($this->getConnection());
@ -575,12 +536,10 @@ class DataCenterConnection implements JsonSerializable
}
$max = \max($this->availableConnections);
$key = \array_search($max, $this->availableConnections);
// Decrease to implement round robin
$this->availableConnections[$key]--;
return $this->connections[$key];
}
/**
* Even out round robin values.
*
@ -607,7 +566,6 @@ class DataCenterConnection implements JsonSerializable
}
}
}
/**
* Indicate that one of the sockets is busy reading.
*
@ -632,8 +590,6 @@ class DataCenterConnection implements JsonSerializable
{
$this->availableConnections[$x] += $writing ? -$this->decWrite : $this->decWrite;
}
/**
* Set main instance.
*
@ -645,7 +601,6 @@ class DataCenterConnection implements JsonSerializable
{
$this->API = $API;
}
/**
* Get main instance.
*
@ -655,7 +610,6 @@ class DataCenterConnection implements JsonSerializable
{
return $this->API;
}
/**
* Check if is an HTTP connection.
*
@ -665,7 +619,6 @@ class DataCenterConnection implements JsonSerializable
{
return \in_array($this->ctx->getStreamName(), [HttpStream::getName(), HttpsStream::getName()]);
}
/**
* Check if is connected directly by IP address.
*
@ -675,7 +628,6 @@ class DataCenterConnection implements JsonSerializable
{
return !$this->ctx->hasStreamName(WssStream::getName()) && !$this->ctx->hasStreamName(HttpsStream::getName());
}
/**
* Check if is a media connection.
*
@ -685,7 +637,6 @@ class DataCenterConnection implements JsonSerializable
{
return $this->ctx->isMedia();
}
/**
* Check if is a CDN connection.
*
@ -695,7 +646,6 @@ class DataCenterConnection implements JsonSerializable
{
return $this->ctx->isCDN();
}
/**
* Get DC-specific settings.
*
@ -706,7 +656,6 @@ class DataCenterConnection implements JsonSerializable
$dc_config_number = isset($this->API->settings['connection_settings'][$this->datacenter]) ? $this->datacenter : 'all';
return $this->API->settings['connection_settings'][$dc_config_number];
}
/**
* JSON serialize function.
*
@ -714,15 +663,7 @@ class DataCenterConnection implements JsonSerializable
*/
public function jsonSerialize(): array
{
return $this->linked ?
[
'linked' => $this->linked,
'tempAuthKey' => $this->tempAuthKey
] :
[
'permAuthKey' => $this->permAuthKey,
'tempAuthKey' => $this->tempAuthKey
];
return $this->linked ? ['linked' => $this->linked, 'tempAuthKey' => $this->tempAuthKey] : ['permAuthKey' => $this->permAuthKey, 'tempAuthKey' => $this->tempAuthKey];
}
/**
* Sleep function.

View File

@ -31,7 +31,6 @@ use Amp\Socket\ConnectException;
use Amp\Socket\Connector;
use Amp\Socket\ResourceSocket;
use danog\MadelineProto\Stream\ConnectionContext;
use function Amp\Socket\Internal\parseUri;
class DoHConnector implements Connector
@ -53,13 +52,11 @@ class DoHConnector implements Connector
$this->dataCenter = $dataCenter;
$this->ctx = $ctx;
}
public function connect(string $uri, ?ConnectContext $socketContext = null, ?CancellationToken $token = null): Promise
{
return Tools::call((function () use ($uri, $socketContext, $token) {
$socketContext = $socketContext ?? new ConnectContext;
$token = $token ?? new NullCancellationToken;
return Tools::call((function () use ($uri, $socketContext, $token): \Generator {
$socketContext = $socketContext ?? new ConnectContext();
$token = $token ?? new NullCancellationToken();
$attempt = 0;
$uris = [];
$failures = [];
@ -105,7 +102,6 @@ class DoHConnector implements Connector
if ($this->ctx->getIpv6()) {
$records = \array_reverse($records);
}
foreach ($records as $record) {
/** @var Record $record */
if ($record->getType() === Record::AAAA) {
@ -115,34 +111,23 @@ class DoHConnector implements Connector
}
}
}
$flags = \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT;
$timeout = $socketContext->getConnectTimeout();
foreach ($uris as $builtUri) {
try {
$streamContext = \stream_context_create($socketContext->withoutTlsContext()->toStreamContextArray());
if (!$socket = @\stream_socket_client($builtUri, $errno, $errstr, null, $flags, $streamContext)) {
throw new ConnectException(\sprintf(
'Connection to %s failed: [Error #%d] %s%s',
$uri,
$errno,
$errstr,
$failures ? '; previous attempts: ' . \implode($failures) : ''
), $errno);
if (!($socket = @\stream_socket_client($builtUri, $errno, $errstr, null, $flags, $streamContext))) {
throw new ConnectException(\sprintf('Connection to %s failed: [Error #%d] %s%s', $uri, $errno, $errstr, $failures ? '; previous attempts: ' . \implode($failures) : ''), $errno);
}
\stream_set_blocking($socket, false);
$deferred = new Deferred;
$deferred = new Deferred();
$watcher = Loop::onWritable($socket, [$deferred, 'resolve']);
$id = $token->subscribe([$deferred, 'fail']);
try {
yield Promise\timeout($deferred->promise(), $timeout);
} catch (TimeoutException $e) {
throw new ConnectException(\sprintf(
'Connecting to %s failed: timeout exceeded (%d ms)%s',
$uri,
$timeout,
$failures ? '; previous attempts: ' . \implode($failures) : ''
), 110); // See ETIMEDOUT in http://www.virtsync.com/c-error-codes-include-errno
throw new ConnectException(\sprintf('Connecting to %s failed: timeout exceeded (%d ms)%s', $uri, $timeout, $failures ? '; previous attempts: ' . \implode($failures) : ''), 110);
// See ETIMEDOUT in http://www.virtsync.com/c-error-codes-include-errno
} finally {
Loop::cancel($watcher);
$token->unsubscribe($id);
@ -150,26 +135,21 @@ class DoHConnector implements Connector
// The following hack looks like the only way to detect connection refused errors with PHP's stream sockets.
if (\stream_socket_get_name($socket, true) === false) {
\fclose($socket);
throw new ConnectException(\sprintf(
'Connection to %s refused%s',
$uri,
$failures ? '; previous attempts: ' . \implode($failures) : ''
), 111); // See ECONNREFUSED in http://www.virtsync.com/c-error-codes-include-errno
throw new ConnectException(\sprintf('Connection to %s refused%s', $uri, $failures ? '; previous attempts: ' . \implode($failures) : ''), 111);
// See ECONNREFUSED in http://www.virtsync.com/c-error-codes-include-errno
}
} catch (ConnectException $e) {
// Includes only error codes used in this file, as error codes on other OS families might be different.
// In fact, this might show a confusing error message on OS families that return 110 or 111 by itself.
$knownReasons = [
110 => 'connection timeout',
111 => 'connection refused',
];
$knownReasons = [110 => 'connection timeout', 111 => 'connection refused'];
$code = $e->getCode();
$reason = $knownReasons[$code] ?? ('Error #' . $code);
$reason = $knownReasons[$code] ?? 'Error #' . $code;
if (++$attempt === $socketContext->getMaxAttempts()) {
break;
}
$failures[] = "{$uri} ({$reason})";
continue; // Could not connect to host, try next host in the list.
continue;
// Could not connect to host, try next host in the list.
}
return ResourceSocket::fromClientSocket($socket, $socketContext->getTlsContext());
}

View File

@ -28,7 +28,6 @@ class DocsBuilder
use \danog\MadelineProto\DocsBuilder\Constructors;
use Tools;
public $td = false;
public function __construct($logger, $settings)
{
$this->logger = $logger;
@ -50,19 +49,17 @@ class DocsBuilder
\chdir($this->settings['output_dir']);
$this->index = $settings['readme'] ? 'README.md' : 'index.md';
}
public $types = [];
public $any = '*';
public function mkDocs()
{
\danog\MadelineProto\Logger::log('Generating documentation index...', \danog\MadelineProto\Logger::NOTICE);
\file_put_contents($this->index, '---
title: '.$this->settings['title'].'
description: '.$this->settings['description'].'
title: ' . $this->settings['title'] . '
description: ' . $this->settings['description'] . '
image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png
---
# '.$this->settings['description'].'
# ' . $this->settings['description'] . '
[Back to main documentation](..)
@ -90,14 +87,14 @@ image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png
//$br = $new_namespace != $last_namespace ? '***<br><br>' : '';
$type = \str_replace(['<', '>'], ['_of_', ''], $otype);
$type = \preg_replace('/.*_of_/', '', $type);
$index .= '['.\str_replace('_', '\\_', $type).']('.$type.'.md)<a name="'.$type.'"></a>
$index .= '[' . \str_replace('_', '\\_', $type) . '](' . $type . '.md)<a name="' . $type . '"></a>
';
$constructors = '';
foreach ($keys['constructors'] as $data) {
$predicate = $data['predicate'].(isset($data['layer']) && $data['layer'] !== '' ? '_'.$data['layer'] : '');
$predicate = $data['predicate'] . (isset($data['layer']) && $data['layer'] !== '' ? '_' . $data['layer'] : '');
$md_predicate = \str_replace('_', '\\_', $predicate);
$constructors .= '['.$md_predicate.'](../constructors/'.$predicate.'.md)
$constructors .= '[' . $md_predicate . '](../constructors/' . $predicate . '.md)
';
}
@ -105,26 +102,25 @@ image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png
foreach ($keys['methods'] as $data) {
$name = $data['method'];
$md_name = \str_replace(['.', '_'], ['->', '\\_'], $name);
$methods .= '[$MadelineProto->'.$md_name.'](../methods/'.$name.'.md)
$methods .= '[$MadelineProto->' . $md_name . '](../methods/' . $name . '.md)
';
}
$description = isset($this->td_descriptions['types'][$otype]) ? $this->td_descriptions['types'][$otype] : 'constructors and methods of type '.$type;
$description = isset($this->td_descriptions['types'][$otype]) ? $this->td_descriptions['types'][$otype] : 'constructors and methods of type ' . $type;
$symFile = \str_replace('.', '_', $type);
$redir = $symFile !== $type ? "\nredirect_from: /API_docs/types/$symFile.html" : '';
$redir = $symFile !== $type ? "\nredirect_from: /API_docs/types/{$symFile}.html" : '';
$header = '---
title: '.$type.'
description: constructors and methods of type '.$type.'
image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png'.$redir.'
title: ' . $type . '
description: constructors and methods of type ' . $type . '
image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png' . $redir . '
---
# Type: '.\str_replace('_', '\\_', $type).'
# Type: ' . \str_replace('_', '\\_', $type) . '
[Back to types index](index.md)
';
$header .= isset($this->td_descriptions['types'][$otype]) ? $this->td_descriptions['types'][$otype].PHP_EOL.PHP_EOL : '';
$header .= isset($this->td_descriptions['types'][$otype]) ? $this->td_descriptions['types'][$otype] . PHP_EOL . PHP_EOL : '';
if (!isset($this->settings['td'])) {
if (\in_array($type, ['User', 'InputUser', 'Chat', 'InputChannel', 'Peer', 'InputDialogPeer', 'DialogPeer', 'InputPeer', 'NotifyPeer', 'InputNotifyPeer'])) {
$header .= 'You can directly provide the [Update](Update.md) or [Message](Message.md) object here, MadelineProto will automatically extract the destination chat id.
@ -132,23 +128,11 @@ image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png'.$redi
The following syntaxes can also be used:
```
$'.$type." = '@username'; // Username
$' . $type . " = '@username'; // Username\n\n\$" . $type . " = 'me'; // The currently logged-in user\n\n\$" . $type . ' = 44700; // bot API id (users)
$' . $type . ' = -492772765; // bot API id (chats)
$' . $type . ' = -10038575794; // bot API id (channels)
\$".$type." = 'me'; // The currently logged-in user
\$".$type.' = 44700; // bot API id (users)
$'.$type.' = -492772765; // bot API id (chats)
$'.$type.' = -10038575794; // bot API id (channels)
$'.$type." = 'https://t.me/danogentili'; // t.me URLs
\$".$type." = 'https://t.me/joinchat/asfln1-21fa_'; // t.me invite links
\$".$type." = 'user#44700'; // tg-cli style id (users)
\$".$type." = 'chat#492772765'; // tg-cli style id (chats)
\$".$type." = 'channel#38575794'; // tg-cli style id (channels)
```
A [Chat](Chat.md), a [User](User.md), an [InputPeer](InputPeer.md), an [InputDialogPeer](InputDialogPeer.md), an [InputNotifyPeer](InputNotifyPeer.md), an [InputUser](InputUser.md), an [InputChannel](InputChannel.md), a [Peer](Peer.md), an [DialogPeer](DialogPeer.md), [NotifyPeer](NotifyPeer.md), or a [Chat](Chat.md) object can also be used.\n\n\n";
$' . $type . " = 'https://t.me/danogentili'; // t.me URLs\n\$" . $type . " = 'https://t.me/joinchat/asfln1-21fa_'; // t.me invite links\n\n\$" . $type . " = 'user#44700'; // tg-cli style id (users)\n\$" . $type . " = 'chat#492772765'; // tg-cli style id (chats)\n\$" . $type . " = 'channel#38575794'; // tg-cli style id (channels)\n```\n\nA [Chat](Chat.md), a [User](User.md), an [InputPeer](InputPeer.md), an [InputDialogPeer](InputDialogPeer.md), an [InputNotifyPeer](InputNotifyPeer.md), an [InputUser](InputUser.md), an [InputChannel](InputChannel.md), a [Peer](Peer.md), an [DialogPeer](DialogPeer.md), [NotifyPeer](NotifyPeer.md), or a [Chat](Chat.md) object can also be used.\n\n\n";
}
if (\in_array($type, ['InputEncryptedChat'])) {
$header .= 'You can directly provide the [Update](Update.md) or [EncryptedMessage](EncryptedMessage.md) object here, MadelineProto will automatically extract the destination chat id.
@ -156,7 +140,7 @@ A [Chat](Chat.md), a [User](User.md), an [InputPeer](InputPeer.md), an [InputDia
The following syntax can also be used:
```
$'.$type.' = -147286699; // Numeric chat id returned by requestSecretChat, can be positive or negative
$' . $type . ' = -147286699; // Numeric chat id returned by requestSecretChat, can be positive or negative
```
@ -166,7 +150,7 @@ $'.$type.' = -147286699; // Numeric chat id returned by requestSecretChat, can b
$header .= 'The following syntax can also be used:
```
$'.$type.' = \'filename.mp4\'; // The file path can also be used
$' . $type . ' = \'filename.mp4\'; // The file path can also be used
```
@ -191,7 +175,7 @@ $'.$type.' = \'filename.mp4\'; // The file path can also be used
$header .= 'The following syntax can also be used:
```
$'.$type.' = 142; // Numeric message ID
$' . $type . ' = 142; // Numeric message ID
```
@ -203,7 +187,7 @@ $'.$type.' = 142; // Numeric message ID
To click these buttons simply run the `click` method:
```
$result = $'.$type.'->click();
$result = $' . $type . '->click();
```
`$result` can be one of the following:
@ -224,12 +208,12 @@ You can also access the properties of the constructor as a normal array, for exa
}
$constructors = '### Possible values (constructors):
'.$constructors.'
' . $constructors . '
';
$methods = '### Methods that return an object of this type (methods):
'.$methods.'
' . $methods . '
';
if (!isset($this->settings['td'])) {
@ -389,7 +373,7 @@ Easy as pie:
```
$call->storage["pony"] = "fluttershy";
\danog\MadelineProto\Logger::log($call->storage["pony"]); // fluttershy
\\danog\\MadelineProto\\Logger::log($call->storage["pony"]); // fluttershy
```
Note: when modifying this property, *never* overwrite the previous values. Always either modify the values of the array separately like showed above, or use array_merge.
@ -416,14 +400,14 @@ After modifying it, you must always parse the new configuration with a call to `
';
}
}
if (\file_exists('types/'.$type.'.md')) {
if (\file_exists('types/' . $type . '.md')) {
\danog\MadelineProto\Logger::log($type);
}
\file_put_contents('types/'.$type.'.md', $header.$constructors.$methods);
\file_put_contents('types/' . $type . '.md', $header . $constructors . $methods);
$last_namespace = $new_namespace;
}
\danog\MadelineProto\Logger::log('Generating types index...', \danog\MadelineProto\Logger::NOTICE);
\file_put_contents('types/'.$this->index, '---
\file_put_contents('types/' . $this->index, '---
title: Types
description: List of types
image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png
@ -432,7 +416,7 @@ image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png
[Back to API documentation index](..)
'.$index);
' . $index);
\danog\MadelineProto\Logger::log('Generating additional types...', \danog\MadelineProto\Logger::NOTICE);
\file_put_contents('types/string.md', '---
title: string
@ -452,7 +436,7 @@ image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png
## Type: bytes
[Back to constructor index](index.md)
An object of type `\danog\MadelineProto\TL\Types\Bytes`.
An object of type `\\danog\\MadelineProto\\TL\\Types\\Bytes`.
When casted to string, turns into a string of bytes of variable length, with length smaller than or equal to 16777215.
When JSON-serialized, turns into an array of the following format:
```
@ -604,7 +588,6 @@ Any json-encodable data.
');
\danog\MadelineProto\Logger::log('Done!', \danog\MadelineProto\Logger::NOTICE);
}
public static $template = '<?php
/**
* Lang module
@ -622,7 +605,7 @@ Any json-encodable data.
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
namespace danog\\MadelineProto;
class Lang
{
@ -631,12 +614,11 @@ class Lang
// THIS WILL BE OVERWRITTEN BY $lang["en"]
public static $current_lang = %s;
}';
public static function addToLang(string $key, string $value = '', bool $force = false)
{
if (!isset(\danog\MadelineProto\Lang::$lang['en'][$key]) || $force) {
\danog\MadelineProto\Lang::$lang['en'][$key] = $value;
\file_put_contents(__DIR__.'/Lang.php', \sprintf(self::$template, \var_export(\danog\MadelineProto\Lang::$lang, true), \var_export(\danog\MadelineProto\Lang::$lang['en'], true)));
\file_put_contents(__DIR__ . '/Lang.php', \sprintf(self::$template, \var_export(\danog\MadelineProto\Lang::$lang, true), \var_export(\danog\MadelineProto\Lang::$lang['en'], true)));
}
}
}

View File

@ -25,7 +25,7 @@ trait Constructors
{
public function mkConstructors()
{
foreach (\glob('constructors/'.$this->any) as $unlink) {
foreach (\glob('constructors/' . $this->any) as $unlink) {
\unlink($unlink);
}
if (\file_exists('constructors')) {
@ -42,10 +42,10 @@ trait Constructors
}
$got[$id] = '';
/*
if (preg_match('/%/', $type)) {
$type = $this->TL->getConstructors($this->td)->findByType(str_replace('%', '', $type))['predicate'];
}*/
$layer = isset($data['layer']) && $data['layer'] !== '' ? '_'.$data['layer'] : '';
if (preg_match('/%/', $type)) {
$type = $this->TL->getConstructors($this->td)->findByType(str_replace('%', '', $type))['predicate'];
}*/
$layer = isset($data['layer']) && $data['layer'] !== '' ? '_' . $data['layer'] : '';
$type = \str_replace(['<', '>'], ['_of_', ''], $data['type']);
$php_type = \preg_replace('/.*_of_/', '', $type);
$constructor = \str_replace(['<', '>'], ['_of_', ''], $data['predicate']);
@ -74,12 +74,12 @@ trait Constructors
if (\substr($param[$type_or_subtype], -1) === '>') {
$param[$type_or_subtype] = \substr($param[$type_or_subtype], 0, -1);
}
$params .= "'".$param['name']."' => ";
$param[$type_or_subtype] = '['.Tools::markdownEscape($param[$type_or_subtype]).'](../'.$type_or_bare_type.'/'.$param[$type_or_subtype].'.md)';
$params .= (isset($param['subtype']) ? '\\['.$param[$type_or_subtype].'\\]' : $param[$type_or_subtype]).', ';
$params .= "'" . $param['name'] . "' => ";
$param[$type_or_subtype] = '[' . Tools::markdownEscape($param[$type_or_subtype]) . '](../' . $type_or_bare_type . '/' . $param[$type_or_subtype] . '.md)';
$params .= (isset($param['subtype']) ? '\\[' . $param[$type_or_subtype] . '\\]' : $param[$type_or_subtype]) . ', ';
}
$md_constructor = \str_replace('_', '\\_', $constructor.$layer);
$this->docs_constructors[$constructor] = '[$'.$md_constructor.'](../constructors/'.$php_constructor.$layer.'.md) = \\['.$params.'\\];<a name="'.$constructor.$layer.'"></a>
$md_constructor = \str_replace('_', '\\_', $constructor . $layer);
$this->docs_constructors[$constructor] = '[$' . $md_constructor . '](../constructors/' . $php_constructor . $layer . '.md) = \\[' . $params . '\\];<a name="' . $constructor . $layer . '"></a>
';
$table = empty($data['params']) ? '' : '### Attributes:
@ -88,12 +88,11 @@ trait Constructors
|----------|---------------|----------|
';
if (!isset($this->TL->getDescriptions()['constructors'][$data['predicate']])) {
$this->addToLang('object_'.$data['predicate']);
if (\danog\MadelineProto\Lang::$lang['en']['object_'.$data['predicate']] !== '') {
$this->TL->getDescriptions()['constructors'][$data['predicate']]['description'] = \danog\MadelineProto\Lang::$lang['en']['object_'.$data['predicate']];
$this->addToLang('object_' . $data['predicate']);
if (\danog\MadelineProto\Lang::$lang['en']['object_' . $data['predicate']] !== '') {
$this->TL->getDescriptions()['constructors'][$data['predicate']]['description'] = \danog\MadelineProto\Lang::$lang['en']['object_' . $data['predicate']];
}
}
if (isset($this->TL->getDescriptions()['constructors'][$data['predicate']]) && !empty($data['params'])) {
$table = '### Attributes:
@ -115,7 +114,7 @@ trait Constructors
$param['type'] = 'DecryptedMessage';
}
if ($type === 'DecryptedMessageMedia' && \in_array($param['name'], ['key', 'iv'])) {
unset(\danog\MadelineProto\Lang::$lang['en']['object_'.$data['predicate'].'_param_'.$param['name'].'_type_'.$param['type']]);
unset(\danog\MadelineProto\Lang::$lang['en']['object_' . $data['predicate'] . '_param_' . $param['name'] . '_type_' . $param['type']]);
continue;
}
$ptype = $param[isset($param['subtype']) ? 'subtype' : 'type'];
@ -139,59 +138,58 @@ trait Constructors
}
$human_ptype = $ptype;
if (\strpos($type, 'Input') === 0 && \in_array($ptype, ['User', 'InputUser', 'Chat', 'InputChannel', 'Peer', 'InputDialogPeer', 'DialogPeer', 'NotifyPeer', 'InputNotifyPeer', 'InputPeer']) && !isset($this->settings['td'])) {
$human_ptype = 'Username, chat ID, Update, Message or '.$ptype;
$human_ptype = 'Username, chat ID, Update, Message or ' . $ptype;
}
if (\strpos($type, 'Input') === 0 && \in_array($ptype, ['InputMedia', 'InputDocument', 'InputPhoto']) && !isset($this->settings['td'])) {
$human_ptype = 'MessageMedia, Message, Update or '.$ptype;
$human_ptype = 'MessageMedia, Message, Update or ' . $ptype;
}
if (\in_array($ptype, ['InputMessage']) && !isset($this->settings['td'])) {
$human_ptype = 'Message ID or '.$ptype;
$human_ptype = 'Message ID or ' . $ptype;
}
if (\in_array($ptype, ['InputEncryptedChat']) && !isset($this->settings['td'])) {
$human_ptype = 'Secret chat ID, Update, EncryptedMessage or '.$ptype;
$human_ptype = 'Secret chat ID, Update, EncryptedMessage or ' . $ptype;
}
if (\in_array($ptype, ['InputFile']) && !isset($this->settings['td'])) {
$human_ptype = 'File path or '.$ptype;
$human_ptype = 'File path or ' . $ptype;
}
if (\in_array($ptype, ['InputEncryptedFile']) && !isset($this->settings['td'])) {
$human_ptype = 'File path or '.$ptype;
$human_ptype = 'File path or ' . $ptype;
}
$table .= '|'.\str_replace('_', '\\_', $param['name']).'|'.(isset($param['subtype']) ? 'Array of ' : '').'['.\str_replace('_', '\\_', $human_ptype).'](../'.$type_or_bare_type.'/'.$ptype.'.md) | '.(isset($param['pow']) || $this->TL->getConstructors($this->td)->findByPredicate(\lcfirst($param['type']).'Empty') || ($data['type'] === 'InputMedia' && $param['name'] === 'mime_type') || ($data['type'] === 'DocumentAttribute' && \in_array($param['name'], ['w', 'h', 'duration'])) ? 'Optional' : 'Yes').'|';
$table .= '|' . \str_replace('_', '\\_', $param['name']) . '|' . (isset($param['subtype']) ? 'Array of ' : '') . '[' . \str_replace('_', '\\_', $human_ptype) . '](../' . $type_or_bare_type . '/' . $ptype . '.md) | ' . (isset($param['pow']) || $this->TL->getConstructors($this->td)->findByPredicate(\lcfirst($param['type']) . 'Empty') || $data['type'] === 'InputMedia' && $param['name'] === 'mime_type' || $data['type'] === 'DocumentAttribute' && \in_array($param['name'], ['w', 'h', 'duration']) ? 'Optional' : 'Yes') . '|';
if (!isset($this->TL->getDescriptions()['constructors'][$data['predicate']]['params'][$param['name']])) {
$this->addToLang('object_'.$data['predicate'].'_param_'.$param['name'].'_type_'.$param['type']);
$this->addToLang('object_' . $data['predicate'] . '_param_' . $param['name'] . '_type_' . $param['type']);
if (isset($this->TL->getDescriptions()['constructors'][$data['predicate']]['description'])) {
$this->TL->getDescriptions()['constructors'][$data['predicate']]['params'][$param['name']] = \danog\MadelineProto\Lang::$lang['en']['object_'.$data['predicate'].'_param_'.$param['name'].'_type_'.$param['type']];
$this->TL->getDescriptions()['constructors'][$data['predicate']]['params'][$param['name']] = \danog\MadelineProto\Lang::$lang['en']['object_' . $data['predicate'] . '_param_' . $param['name'] . '_type_' . $param['type']];
}
}
if (isset($this->TL->getDescriptions()['constructors'][$data['predicate']]['params'][$param['name']])) {
$table .= $this->TL->getDescriptions()['constructors'][$data['predicate']]['params'][$param['name']].'|';
$table .= $this->TL->getDescriptions()['constructors'][$data['predicate']]['params'][$param['name']] . '|';
}
$table .= PHP_EOL;
$pptype = \in_array($ptype, ['string', 'bytes']) ? "'".$ptype."'" : $ptype;
$ppptype = \in_array($ptype, ['string']) ? '"'.$ptype.'"' : $ptype;
$ppptype = \in_array($ptype, ['bytes']) ? '{"_": "bytes", "bytes":"base64 encoded '.$ptype.'"}' : $ppptype;
$params .= ", '".$param['name']."' => ";
$params .= isset($param['subtype']) ? '['.$pptype.', '.$pptype.']' : $pptype;
$lua_params .= ', '.$param['name'].'=';
$lua_params .= isset($param['subtype']) ? '{'.$pptype.'}' : $pptype;
$pwr_params .= ', "'.$param['name'].'": '.(isset($param['subtype']) ? '['.$ppptype.']' : $ppptype);
$pptype = \in_array($ptype, ['string', 'bytes']) ? "'" . $ptype . "'" : $ptype;
$ppptype = \in_array($ptype, ['string']) ? '"' . $ptype . '"' : $ptype;
$ppptype = \in_array($ptype, ['bytes']) ? '{"_": "bytes", "bytes":"base64 encoded ' . $ptype . '"}' : $ppptype;
$params .= ", '" . $param['name'] . "' => ";
$params .= isset($param['subtype']) ? '[' . $pptype . ', ' . $pptype . ']' : $pptype;
$lua_params .= ', ' . $param['name'] . '=';
$lua_params .= isset($param['subtype']) ? '{' . $pptype . '}' : $pptype;
$pwr_params .= ', "' . $param['name'] . '": ' . (isset($param['subtype']) ? '[' . $ppptype . ']' : $ppptype);
if ($param['name'] === 'reply_markup') {
$hasreplymarkup = true;
}
}
$params = "['_' => '".$data['predicate']."'".$params.']';
$lua_params = "{_='".$data['predicate']."'".$lua_params.'}';
$pwr_params = '{"_": "'.$data['predicate'].'"'.$pwr_params.'}';
$description = isset($this->TL->getDescriptions()['constructors'][$data['predicate']]) ? $this->TL->getDescriptions()['constructors'][$data['predicate']]['description'] : $constructor.' attributes, type and example';
$symFile = \str_replace('.', '_', $constructor.$layer);
$redir = $symFile !== $constructor.$layer ? "\nredirect_from: /API_docs/constructors/$symFile.html" : '';
$params = "['_' => '" . $data['predicate'] . "'" . $params . ']';
$lua_params = "{_='" . $data['predicate'] . "'" . $lua_params . '}';
$pwr_params = '{"_": "' . $data['predicate'] . '"' . $pwr_params . '}';
$description = isset($this->TL->getDescriptions()['constructors'][$data['predicate']]) ? $this->TL->getDescriptions()['constructors'][$data['predicate']]['description'] : $constructor . ' attributes, type and example';
$symFile = \str_replace('.', '_', $constructor . $layer);
$redir = $symFile !== $constructor . $layer ? "\nredirect_from: /API_docs/constructors/{$symFile}.html" : '';
$header = '---
title: '.$data['predicate'].'
description: '.$description.'
image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png'.$redir.'
title: ' . $data['predicate'] . '
description: ' . $description . '
image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png' . $redir . '
---
# Constructor: '.\str_replace('_', '\\_', $data['predicate'].$layer).'
# Constructor: ' . \str_replace('_', '\\_', $data['predicate'] . $layer) . '
[Back to constructors index](index.md)
@ -202,9 +200,9 @@ image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png'.$redi
';
if (isset($this->TL->getDescriptions()['constructors'][$data['predicate']])) {
$header .= $this->TL->getDescriptions()['constructors'][$data['predicate']]['description'].PHP_EOL.PHP_EOL;
$header .= $this->TL->getDescriptions()['constructors'][$data['predicate']]['description'] . PHP_EOL . PHP_EOL;
}
$type = '### Type: ['.\str_replace('_', '\\_', $php_type).'](../types/'.$php_type.'.md)
$type = '### Type: [' . \str_replace('_', '\\_', $php_type) . '](../types/' . $php_type . '.md)
';
@ -213,14 +211,14 @@ image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png'.$redi
$example = '### Example:
```php
$'.$constructor.$layer.' = '.$params.';
$' . $constructor . $layer . ' = ' . $params . ';
```
Or, if you\'re into Lua:
```lua
'.$constructor.$layer.'='.$lua_params.'
' . $constructor . $layer . '=' . $lua_params . '
```
@ -270,7 +268,7 @@ MadelineProto supports all html entities supported by [html_entity_decode](http:
';
}
}
\file_put_contents('constructors/'.$constructor.$layer.'.md', $header.$table.$type.$example);
\file_put_contents('constructors/' . $constructor . $layer . '.md', $header . $table . $type . $example);
}
$this->logger->logger('Generating constructors index...', \danog\MadelineProto\Logger::NOTICE);
\ksort($this->docs_constructors);
@ -279,10 +277,10 @@ MadelineProto supports all html entities supported by [html_entity_decode](http:
$new_namespace = \preg_replace('/_.*/', '', $constructor);
$br = $new_namespace != $last_namespace ? '***
<br><br>' : '';
$value = $br.$value;
$value = $br . $value;
$last_namespace = $new_namespace;
}
\file_put_contents('constructors/'.$this->index, '---
\file_put_contents('constructors/' . $this->index, '---
title: Constructors
description: List of constructors
image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png
@ -290,6 +288,6 @@ image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png
# Constructors
[Back to API documentation index](..)
'.\implode('', $this->docs_constructors));
' . \implode('', $this->docs_constructors));
}
}

View File

@ -44,7 +44,7 @@ trait Methods
}
}
}
foreach (\glob('methods/'.$this->any) as $unlink) {
foreach (\glob('methods/' . $this->any) as $unlink) {
\unlink($unlink);
}
if (\file_exists('methods')) {
@ -80,31 +80,28 @@ trait Methods
$type_or_subtype = isset($param['subtype']) ? 'subtype' : 'type';
$type_or_bare_type = \ctype_upper(Tools::end(\explode('.', $param[$type_or_subtype]))[0]) || \in_array($param[$type_or_subtype], ['!X', 'X', 'bytes', 'true', 'false', 'double', 'string', 'Bool', 'int', 'long', 'int128', 'int256', 'int512', 'int53']) ? 'types' : 'constructors';
$param[$type_or_subtype] = \str_replace(['true', 'false'], ['Bool', 'Bool'], $param[$type_or_subtype]);
$param[$type_or_subtype] = '['.Tools::markdownEscape($param[$type_or_subtype]).'](../'.$type_or_bare_type.'/'.$param[$type_or_subtype].'.md)';
$params .= "'".$param['name']."' => ".(isset($param['subtype']) ? '\\['.$param[$type_or_subtype].'\\]' : $param[$type_or_subtype]).', ';
$param[$type_or_subtype] = '[' . Tools::markdownEscape($param[$type_or_subtype]) . '](../' . $type_or_bare_type . '/' . $param[$type_or_subtype] . '.md)';
$params .= "'" . $param['name'] . "' => " . (isset($param['subtype']) ? '\\[' . $param[$type_or_subtype] . '\\]' : $param[$type_or_subtype]) . ', ';
}
if (!isset($this->td_descriptions['methods'][$data['method']])) {
$this->addToLang('method_'.$data['method']);
if (\danog\MadelineProto\Lang::$lang['en']['method_'.$data['method']] !== '') {
$this->td_descriptions['methods'][$data['method']]['description'] = \danog\MadelineProto\Lang::$lang['en']['method_'.$data['method']];
$this->addToLang('method_' . $data['method']);
if (\danog\MadelineProto\Lang::$lang['en']['method_' . $data['method']] !== '') {
$this->td_descriptions['methods'][$data['method']]['description'] = \danog\MadelineProto\Lang::$lang['en']['method_' . $data['method']];
}
}
$md_method = '['.$php_method.']('.$method.'.md)';
$this->docs_methods[$method] = '$MadelineProto->'.$md_method.'(\\['.$params.'\\]) === [$'.\str_replace('_', '\\_', $type).'](../types/'.$php_type.'.md)<a name="'.$method.'"></a>
$md_method = '[' . $php_method . '](' . $method . '.md)';
$this->docs_methods[$method] = '$MadelineProto->' . $md_method . '(\\[' . $params . '\\]) === [$' . \str_replace('_', '\\_', $type) . '](../types/' . $php_type . '.md)<a name="' . $method . '"></a>
';
if (isset($this->td_descriptions['methods'][$data['method']])) {
$desc = \Parsedown::instance()->line(\trim(\explode("\n", $this->td_descriptions['methods'][$data['method']]['description'])[0], '.'));
$dom = new \DOMDocument();
$dom->loadHTML(\mb_convert_encoding($desc, 'HTML-ENTITIES', 'UTF-8'));
$desc = $dom->textContent;
$this->human_docs_methods[$this->td_descriptions['methods'][$data['method']]['description'].': '.$data['method']] = '* <a href="'.$method.'.html" name="'.$method.'">'.$desc.': '.$data['method'].'</a>
$this->human_docs_methods[$this->td_descriptions['methods'][$data['method']]['description'] . ': ' . $data['method']] = '* <a href="' . $method . '.html" name="' . $method . '">' . $desc . ': ' . $data['method'] . '</a>
';
}
$params = '';
$lua_params = '';
$pwr_params = '';
@ -148,47 +145,45 @@ trait Methods
}
$human_ptype = $ptype;
if (\in_array($ptype, ['InputDialogPeer', 'DialogPeer', 'NotifyPeer', 'InputNotifyPeer', 'User', 'InputUser', 'Chat', 'InputChannel', 'Peer', 'InputPeer']) && !isset($this->settings['td'])) {
$human_ptype = 'Username, chat ID, Update, Message or '.$ptype;
$human_ptype = 'Username, chat ID, Update, Message or ' . $ptype;
}
if (\in_array($ptype, ['InputMedia', 'InputPhoto', 'InputDocument']) && !isset($this->settings['td'])) {
$human_ptype = 'MessageMedia, Update, Message or '.$ptype;
$human_ptype = 'MessageMedia, Update, Message or ' . $ptype;
}
if (\in_array($ptype, ['InputMessage']) && !isset($this->settings['td'])) {
$human_ptype = 'Message ID or '.$ptype;
$human_ptype = 'Message ID or ' . $ptype;
}
if (\in_array($ptype, ['InputEncryptedChat']) && !isset($this->settings['td'])) {
$human_ptype = 'Secret chat ID, Update, EncryptedMessage or '.$ptype;
$human_ptype = 'Secret chat ID, Update, EncryptedMessage or ' . $ptype;
}
if (\in_array($ptype, ['InputFile']) && !isset($this->settings['td'])) {
$human_ptype = 'File path or '.$ptype;
$human_ptype = 'File path or ' . $ptype;
}
if (\in_array($ptype, ['InputEncryptedFile']) && !isset($this->settings['td'])) {
$human_ptype = 'File path or '.$ptype;
$human_ptype = 'File path or ' . $ptype;
}
$type_or_bare_type = \ctype_upper(Tools::end(\explode('.', $param[$type_or_subtype]))[0]) || \in_array($param[$type_or_subtype], ['!X', 'X', 'bytes', 'true', 'false', 'double', 'string', 'Bool', 'int', 'long', 'int128', 'int256', 'int512', 'int53']) ? 'types' : 'constructors';
if (!isset($this->td_descriptions['methods'][$data['method']]['params'][$param['name']])) {
$this->addToLang('method_'.$data['method'].'_param_'.$param['name'].'_type_'.$param['type']);
$this->addToLang('method_' . $data['method'] . '_param_' . $param['name'] . '_type_' . $param['type']);
if (isset($this->td_descriptions['methods'][$data['method']]['description'])) {
$this->td_descriptions['methods'][$data['method']]['params'][$param['name']] = \danog\MadelineProto\Lang::$lang['en']['method_'.$data['method'].'_param_'.$param['name'].'_type_'.$param['type']];
$this->td_descriptions['methods'][$data['method']]['params'][$param['name']] = \danog\MadelineProto\Lang::$lang['en']['method_' . $data['method'] . '_param_' . $param['name'] . '_type_' . $param['type']];
}
}
if (isset($this->td_descriptions['methods'][$data['method']])) {
$table .= '|'.\str_replace('_', '\\_', $param['name']).'|'.(isset($param['subtype']) ? 'Array of ' : '').'['.\str_replace('_', '\\_', $human_ptype).'](../'.$type_or_bare_type.'/'.$ptype.'.md) | '.$this->td_descriptions['methods'][$data['method']]['params'][$param['name']].' | '.(isset($param['pow']) || (($id = $this->TL->getConstructors($this->td)->findByPredicate(\lcfirst($param['type']).'Empty')) && $id['type'] === $param['type']) || (($id = $this->TL->getConstructors($this->td)->findByPredicate('input'.$param['type'].'Empty')) && $id['type'] === $param['type']) ? 'Optional' : 'Yes').'|';
$table .= '|' . \str_replace('_', '\\_', $param['name']) . '|' . (isset($param['subtype']) ? 'Array of ' : '') . '[' . \str_replace('_', '\\_', $human_ptype) . '](../' . $type_or_bare_type . '/' . $ptype . '.md) | ' . $this->td_descriptions['methods'][$data['method']]['params'][$param['name']] . ' | ' . (isset($param['pow']) || ($id = $this->TL->getConstructors($this->td)->findByPredicate(\lcfirst($param['type']) . 'Empty')) && $id['type'] === $param['type'] || ($id = $this->TL->getConstructors($this->td)->findByPredicate('input' . $param['type'] . 'Empty')) && $id['type'] === $param['type'] ? 'Optional' : 'Yes') . '|';
} else {
$table .= '|'.\str_replace('_', '\\_', $param['name']).'|'.(isset($param['subtype']) ? 'Array of ' : '').'['.\str_replace('_', '\\_', $human_ptype).'](../'.$type_or_bare_type.'/'.$ptype.'.md) | '.(isset($param['pow']) || (($id = $this->TL->getConstructors($this->td)->findByPredicate(\lcfirst($param['type']).'Empty')) && $id['type'] === $param['type']) || (($id = $this->TL->getConstructors($this->td)->findByPredicate('input'.$param['type'].'Empty')) && $id['type'] === $param['type']) ? 'Optional' : 'Yes').'|';
$table .= '|' . \str_replace('_', '\\_', $param['name']) . '|' . (isset($param['subtype']) ? 'Array of ' : '') . '[' . \str_replace('_', '\\_', $human_ptype) . '](../' . $type_or_bare_type . '/' . $ptype . '.md) | ' . (isset($param['pow']) || ($id = $this->TL->getConstructors($this->td)->findByPredicate(\lcfirst($param['type']) . 'Empty')) && $id['type'] === $param['type'] || ($id = $this->TL->getConstructors($this->td)->findByPredicate('input' . $param['type'] . 'Empty')) && $id['type'] === $param['type'] ? 'Optional' : 'Yes') . '|';
}
$table .= PHP_EOL;
$pptype = \in_array($ptype, ['string', 'bytes']) ? "'".$ptype."'" : $ptype;
$ppptype = \in_array($ptype, ['string']) ? '"'.$ptype.'"' : $ptype;
$ppptype = \in_array($ptype, ['bytes']) ? '{"_": "bytes", "bytes":"base64 encoded '.$ptype.'"}' : $ppptype;
$params .= "'".$param['name']."' => ";
$params .= (isset($param['subtype']) ? '['.$pptype.', '.$pptype.']' : $pptype).', ';
$json_params .= '"'.$param['name'].'": '.(isset($param['subtype']) ? '['.$ppptype.']' : $ppptype).', ';
$pwr_params .= $param['name'].' - Json encoded '.(isset($param['subtype']) ? ' array of '.$ptype : $ptype)."\n\n";
$lua_params .= $param['name'].'=';
$lua_params .= (isset($param['subtype']) ? '{'.$pptype.'}' : $pptype).', ';
$pptype = \in_array($ptype, ['string', 'bytes']) ? "'" . $ptype . "'" : $ptype;
$ppptype = \in_array($ptype, ['string']) ? '"' . $ptype . '"' : $ptype;
$ppptype = \in_array($ptype, ['bytes']) ? '{"_": "bytes", "bytes":"base64 encoded ' . $ptype . '"}' : $ppptype;
$params .= "'" . $param['name'] . "' => ";
$params .= (isset($param['subtype']) ? '[' . $pptype . ', ' . $pptype . ']' : $pptype) . ', ';
$json_params .= '"' . $param['name'] . '": ' . (isset($param['subtype']) ? '[' . $ppptype . ']' : $ppptype) . ', ';
$pwr_params .= $param['name'] . ' - Json encoded ' . (isset($param['subtype']) ? ' array of ' . $ptype : $ptype) . "\n\n";
$lua_params .= $param['name'] . '=';
$lua_params .= (isset($param['subtype']) ? '{' . $pptype . '}' : $pptype) . ', ';
if ($param['name'] === 'reply_markup') {
$hasreplymarkup = true;
}
@ -205,42 +200,42 @@ trait Methods
$pwr_params = "parseMode - string\n";
}
}
$description = isset($this->td_descriptions['methods'][$data['method']]) ? $this->td_descriptions['methods'][$data['method']]['description'] : $data['method'].' parameters, return type and example';
$description = isset($this->td_descriptions['methods'][$data['method']]) ? $this->td_descriptions['methods'][$data['method']]['description'] : $data['method'] . ' parameters, return type and example';
$symFile = \str_replace('.', '_', $method);
$redir = $symFile !== $method ? "\nredirect_from: /API_docs/methods/$symFile.html" : '';
$redir = $symFile !== $method ? "\nredirect_from: /API_docs/methods/{$symFile}.html" : '';
$header = '---
title: '.$data['method'].'
description: '.$description.'
image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png'.$redir.'
title: ' . $data['method'] . '
description: ' . $description . '
image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png' . $redir . '
---
# Method: '.\str_replace('_', '\\_', $data['method']).'
# Method: ' . \str_replace('_', '\\_', $data['method']) . '
[Back to methods index](index.md)
';
/*
if (isset(\danog\MadelineProto\MTProto::DISALLOWED_METHODS[$data['method']])) {
$header .= '**'.\danog\MadelineProto\MTProto::DISALLOWED_METHODS[$data['method']]."**\n\n\n\n\n";
file_put_contents('methods/'.$method.'.md', $header);
continue;
}*/
if (isset(\danog\MadelineProto\MTProto::DISALLOWED_METHODS[$data['method']])) {
$header .= '**'.\danog\MadelineProto\MTProto::DISALLOWED_METHODS[$data['method']]."**\n\n\n\n\n";
file_put_contents('methods/'.$method.'.md', $header);
continue;
}*/
if ($this->td) {
$header .= 'YOU CANNOT USE THIS METHOD IN MADELINEPROTO
';
}
$header .= isset($this->td_descriptions['methods'][$data['method']]) ? $this->td_descriptions['methods'][$data['method']]['description'].PHP_EOL.PHP_EOL : '';
$header .= isset($this->td_descriptions['methods'][$data['method']]) ? $this->td_descriptions['methods'][$data['method']]['description'] . PHP_EOL . PHP_EOL : '';
$table .= '
';
$return = '### Return type: ['.\str_replace('_', '\\_', $type).'](../types/'.$php_type.'.md)
$return = '### Return type: [' . \str_replace('_', '\\_', $type) . '](../types/' . $php_type . '.md)
';
$bot = !\in_array($data['method'], $bots);
$example = '';
if (!isset($this->settings['td'])) {
$example .= '### Can bots use this method: **'.($bot ? 'YES' : 'NO')."**\n\n\n";
$example .= '### Can bots use this method: **' . ($bot ? 'YES' : 'NO') . "**\n\n\n";
$example .= \str_replace('[]', '', '### MadelineProto Example ([now async for huge speed and parallelism!](https://docs.madelineproto.xyz/docs/ASYNC.html)):
@ -250,16 +245,16 @@ if (!file_exists(\'madeline.php\')) {
}
include \'madeline.php\';
$MadelineProto = new \danog\MadelineProto\API(\'session.madeline\');
$MadelineProto = new \\danog\\MadelineProto\\API(\'session.madeline\');
$MadelineProto->start();
$'.$type.' = $MadelineProto->'.$php_method.'(['.$params.']);
$' . $type . ' = $MadelineProto->' . $php_method . '([' . $params . ']);
```
Or, if you\'re into Lua:
```lua
'.$type.' = '.$data['method'].'({'.$lua_params.'})
' . $type . ' = ' . $data['method'] . '({' . $lua_params . '})
```
');
@ -276,7 +271,7 @@ You can provide bot API reply_markup objects here.
$example .= '
## Return value
If the length of the provided message is bigger than 4096, the message will be split in chunks and the method will be called multiple times, with the same parameters (except for the message), and an array of ['.\str_replace('_', '\\_', $type).'](../types/'.$php_type.'.md) will be returned instead.
If the length of the provided message is bigger than 4096, the message will be split in chunks and the method will be called multiple times, with the same parameters (except for the message), and an array of [' . \str_replace('_', '\\_', $type) . '](../types/' . $php_type . '.md) will be returned instead.
';
@ -328,12 +323,12 @@ MadelineProto supports all html entities supported by [html_entity_decode](http:
';
foreach ($new['result'][$data['method']] as $error) {
[$error, $code] = $error;
$example .= "|$code|$error|".$errors['human_result'][$error][0].'|'."\n";
$example .= "|{$code}|{$error}|" . $errors['human_result'][$error][0] . '|' . "\n";
}
$example .= "\n\n";
}
}
\file_put_contents('methods/'.$method.'.md', $header.$table.$return.$example);
\file_put_contents('methods/' . $method . '.md', $header . $table . $return . $example);
}
$this->logger->logger('Generating methods index...', \danog\MadelineProto\Logger::NOTICE);
\ksort($this->docs_methods);
@ -344,10 +339,10 @@ MadelineProto supports all html entities supported by [html_entity_decode](http:
$br = $new_namespace != $last_namespace ? '***
<br><br>
' : '';
$value = $br.$value;
$value = $br . $value;
$last_namespace = $new_namespace;
}
\file_put_contents('methods/api_'.$this->index, '---
\file_put_contents('methods/api_' . $this->index, '---
title: Methods
description: List of methods
image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png
@ -355,7 +350,7 @@ image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png
# Methods
[Back to API documentation index](..)
[Go to the new description-version method index]('.$this->index.')
[Go to the new description-version method index](' . $this->index . ')
$MadelineProto->[logout](https://docs.madelineproto.xyz/logout.html)();
@ -383,9 +378,8 @@ $MadelineProto->[requestCall](https://docs.madelineproto.xyz/requestCall.html)($
$MadelineProto->[requestSecretChat](https://docs.madelineproto.xyz/requestSecretChat.html)($id);
'.\implode('', $this->docs_methods));
\file_put_contents('methods/'.$this->index, '---
' . \implode('', $this->docs_methods));
\file_put_contents('methods/' . $this->index, '---
title: Methods
description: What do you want to do?
image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png
@ -393,7 +387,7 @@ image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png
# What do you want to do?
[Go back to API documentation index](..)
[Go to the old code-version method index](api_'.$this->index.')
[Go to the old code-version method index](api_' . $this->index . ')
* [Logout](https://docs.madelineproto.xyz/logout.html)
@ -417,6 +411,6 @@ image: https://docs.madelineproto.xyz/favicons/android-chrome-256x256.png
* [Create a secret chat bot](https://docs.madelineproto.xyz/docs/SECRET_CHATS.html)
'.\implode('', $this->human_docs_methods));
' . \implode('', $this->human_docs_methods));
}
}

View File

@ -35,8 +35,8 @@ class EventHandler extends InternalDoc
return;
}
$this->API = $MadelineProto->API;
$this->async = &$MadelineProto->async;
$this->methods = &$MadelineProto->methods;
$this->async =& $MadelineProto->async;
$this->methods =& $MadelineProto->methods;
foreach ($this->API->getMethodNamespaces() as $namespace) {
$this->{$namespace} = new APIFactory($namespace, $this->API, $this->async);
}

View File

@ -23,12 +23,10 @@ class Exception extends \Exception
{
use TL\PrettyException;
public static $rollbar = true;
public function __toString()
{
return $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:'.PHP_EOL.$this->getTLTrace();
return $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:' . PHP_EOL . $this->getTLTrace();
}
public function __construct($message = null, $code = 0, self $previous = null, $file = null, $line = null)
{
$this->prettifyTL();
@ -40,9 +38,8 @@ class Exception extends \Exception
}
parent::__construct($message, $code, $previous);
if (\strpos($message, 'socket_accept') === false) {
\danog\MadelineProto\Logger::log($message.' in '.\basename($this->file).':'.$this->line, \danog\MadelineProto\Logger::FATAL_ERROR);
\danog\MadelineProto\Logger::log($message . ' in ' . \basename($this->file) . ':' . $this->line, \danog\MadelineProto\Logger::FATAL_ERROR);
}
if (\in_array($message, ['The session is corrupted!', 'Re-executing query...', 'I had to recreate the temporary authorization key', 'This peer is not present in the internal peer database', "Couldn't get response", 'Chat forbidden', 'The php-libtgvoip extension is required to accept and manage calls. See daniil.it/MadelineProto for more info.', 'File does not exist', 'Please install this fork of phpseclib: https://github.com/danog/phpseclib'])) {
return;
}
@ -53,18 +50,17 @@ class Exception extends \Exception
\Rollbar\Rollbar::log(\Rollbar\Payload\Level::error(), $this, \debug_backtrace(0));
}
}
public static function extension(string $extensionName)
{
$additional = 'Try running sudo apt-get install php'.PHP_MAJOR_VERSION.'.'.PHP_MINOR_VERSION.'-'.$extensionName.'.';
$additional = 'Try running sudo apt-get install php' . PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION . '-' . $extensionName . '.';
if ($extensionName === 'libtgvoip') {
$additional = 'Follow the instructions @ https://voip.madelineproto.xyz to install it.';
} elseif ($extensionName === 'prime') {
$additional = 'Follow the instructions @ https://prime.madelineproto.xyz to install it.';
}
$message = 'MadelineProto requires the '.$extensionName.' extension to run. '.$additional;
$message = 'MadelineProto requires the ' . $extensionName . ' extension to run. ' . $additional;
if (PHP_SAPI !== 'cli') {
echo $message.'<br>';
echo $message . '<br>';
}
$file = 'MadelineProto';
$line = 1;
@ -78,17 +74,11 @@ 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
|| \strpos($errstr, 'headers already sent')
|| ($errfile
&& (\strpos($errfile, 'vendor/amphp') !== false || \strpos($errfile, 'vendor/league') !== false))
) {
if (\error_reporting() === 0 || \strpos($errstr, 'headers already sent') || $errfile && (\strpos($errfile, 'vendor/amphp') !== false || \strpos($errfile, 'vendor/league') !== false)) {
return false;
}
throw new self($errstr, $errno, null, $errfile, $errline);
}
/**
* ExceptionErrorHandler.
*

View File

@ -36,7 +36,6 @@ class FileCallback implements FileCallbackInterface
* @var callable
*/
private $callback;
/**
* Construct file callback.
*
@ -48,7 +47,6 @@ class FileCallback implements FileCallbackInterface
$this->file = $file;
$this->callback = $callback;
}
/**
* Get file.
*
@ -58,7 +56,6 @@ class FileCallback implements FileCallbackInterface
{
return $this->file;
}
/**
* Invoke callback.
*
@ -71,7 +68,6 @@ class FileCallback implements FileCallbackInterface
public function __invoke($percent, $speed, $time)
{
$callback = $this->callback;
return $callback($percent, $speed, $time);
}
}

View File

@ -30,7 +30,6 @@ interface FileCallbackInterface
* @return mixed
*/
public function getFile();
/**
* Invoke callback.
*

View File

@ -5128,7 +5128,7 @@ class InternalDoc extends APIFactory
* @param string $file File to lock
* @param integer $operation Locking mode
* @param float $polling Polling interval
*
*
* @return Promise
*/
public function flock(string $file, int $operation, float $polling = 0.1)

View File

@ -12,7 +12,7 @@
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2019 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
* @link https://docs.madelineproto.xyz MadelineProto documentation
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;

View File

@ -30,12 +30,10 @@ use function Amp\ByteStream\getStdout;
class Logger
{
use Tools;
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];
const BACKGROUND = ['default' => 49, 'black' => 40, 'red' => 41, 'magenta' => 45, 'yellow' => 43, 'green' => 42, 'blue' => 44, 'cyan' => 46, 'light_gray' => 47, 'dark_gray' => 100, 'light_red' => 101, 'light_green' => 102, 'light_yellow' => 103, 'light_blue' => 104, 'light_magenta' => 105, 'light_cyan' => 106, 'white' => 107];
const SET = ['bold' => 1, 'dim' => 2, 'underlined' => 3, 'blink' => 4, 'reverse' => 5, 'hidden' => 6];
const RESET = ['all' => 0, 'bold' => 21, 'dim' => 22, 'underlined' => 24, 'blink' => 25, 'reverse' => 26, 'hidden' => 28];
/**
* Logging mode.
*
@ -72,7 +70,6 @@ class Logger
* @var string
*/
public $newline = "\n";
/**
* Default logger instance.
*
@ -85,20 +82,17 @@ class Logger
* @var boolean
*/
public static $printed = false;
const ULTRA_VERBOSE = 5;
const VERBOSE = 4;
const NOTICE = 3;
const WARNING = 2;
const ERROR = 1;
const FATAL_ERROR = 0;
const NO_LOGGER = 0;
const DEFAULT_LOGGER = 1;
const FILE_LOGGER = 2;
const ECHO_LOGGER = 3;
const CALLABLE_LOGGER = 4;
/**
* Construct global static logger from MadelineProto settings.
*
@ -133,28 +127,24 @@ class Logger
$settings['logger']['logger_param'] = $settings['logger']['param'];
}
if (PHP_SAPI !== 'cli' && isset($settings['logger']['logger_param']) && $settings['logger']['logger_param'] === 'MadelineProto.log') {
$settings['logger']['logger_param'] = Magic::$script_cwd.'/MadelineProto.log';
$settings['logger']['logger_param'] = Magic::$script_cwd . '/MadelineProto.log';
}
$logger = new self($settings['logger']['logger'], $settings['logger']['logger_param'] ?? '', $prefix, $settings['logger']['logger_level'] ?? Logger::VERBOSE, $settings['logger']['max_size'] ?? 100 * 1024 * 1024);
if (!self::$default) {
self::$default = $logger;
}
if (PHP_SAPI !== 'cli') {
try {
\error_reporting(E_ALL);
\ini_set('log_errors', 1);
\ini_set('error_log', $settings['logger']['logger'] === self::FILE_LOGGER ? $settings['logger']['logger_param'] : Magic::$script_cwd.'/MadelineProto.log');
\ini_set('error_log', $settings['logger']['logger'] === self::FILE_LOGGER ? $settings['logger']['logger_param'] : Magic::$script_cwd . '/MadelineProto.log');
\error_log('Enabled PHP logging');
} catch (\danog\MadelineProto\Exception $e) {
$logger->logger('Could not enable PHP logging');
}
}
return $logger;
}
/**
* Construct global logger.
*
@ -170,7 +160,6 @@ class Logger
{
self::$default = new self($mode, $optional, $prefix, $level, $max_size);
}
/**
* Construct global logger.
*
@ -189,21 +178,17 @@ class Logger
}
$this->mode = $mode;
$this->optional = $mode == 2 ? Absolute::absolute($optional) : $optional;
$this->prefix = $prefix === '' ? '' : ', '.$prefix;
$this->prefix = $prefix === '' ? '' : ', ' . $prefix;
$this->level = $level;
if ($this->mode === 2 && !\file_exists(\pathinfo($this->optional, PATHINFO_DIRNAME))) {
$this->optional = Magic::$script_cwd.'/MadelineProto.log';
$this->optional = Magic::$script_cwd . '/MadelineProto.log';
}
if ($this->mode === 2 && !\preg_match('/\.log$/', $this->optional)) {
if ($this->mode === 2 && !\preg_match('/\\.log$/', $this->optional)) {
$this->optional .= '.log';
}
if ($mode === 2 && $max_size !== -1 && \file_exists($this->optional) && \filesize($this->optional) > $max_size) {
\unlink($this->optional);
}
$this->colors[self::ULTRA_VERBOSE] = \implode(';', [self::FOREGROUND['light_gray'], self::SET['dim']]);
$this->colors[self::VERBOSE] = \implode(';', [self::FOREGROUND['green'], self::SET['bold']]);
$this->colors[self::NOTICE] = \implode(';', [self::FOREGROUND['yellow'], self::SET['bold']]);
@ -211,11 +196,10 @@ class Logger
$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 = getStdout();
if (PHP_SAPI !== 'cli') {
$this->newline = '<br>'.$this->newline;
$this->newline = '<br>' . $this->newline;
}
} elseif ($this->mode === 2) {
$this->stdout = new ResourceOutputStream(\fopen($this->optional, 'a+'));
@ -230,7 +214,6 @@ class Logger
}
}
}
/**
* Log a message.
*
@ -244,10 +227,9 @@ class Logger
if (!\is_null(self::$default)) {
self::$default->logger($param, $level, \basename(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php'));
} else {
echo $param.PHP_EOL;
echo $param . PHP_EOL;
}
}
/**
* Log a message.
*
@ -265,12 +247,10 @@ class Logger
if (!self::$printed) {
self::$printed = true;
$this->colors[self::NOTICE] = \implode(';', [self::FOREGROUND['light_gray'], self::SET['bold'], self::BACKGROUND['blue']]);
$this->logger('MadelineProto');
$this->logger('Copyright (C) 2016-2019 Daniil Gentili');
$this->logger('Licensed under AGPLv3');
$this->logger('https://github.com/danog/MadelineProto');
$this->colors[self::NOTICE] = \implode(';', [self::FOREGROUND['yellow'], self::SET['bold']]);
}
if ($this->mode === 4) {
@ -289,22 +269,26 @@ class Logger
if (empty($file)) {
$file = \basename(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php');
}
$param = \str_pad($file.$prefix.': ', 16 + \strlen($prefix))."\t".$param;
$param = \str_pad($file . $prefix . ': ', 16 + \strlen($prefix)) . "\t" . $param;
switch ($this->mode) {
case 1:
if ($this->stdout->write($param.$this->newline) instanceof Failure) {
\error_log($param);
case 1:
if ($this->stdout->write($param . $this->newline) instanceof Failure) {
\error_log($param);
}
break;
default:
$param = Magic::$isatty ? "\33[" . $this->colors[$level] . 'm' . $param . "\33[0m" . $this->newline : $param . $this->newline;
if ($this->stdout->write($param) instanceof Failure) {
switch ($this->mode) {
case 3:
echo $param;
break;
case 2:
\file_put_contents($this->optional, $param, FILE_APPEND);
break;
}
break;
default:
$param = Magic::$isatty ? "\33[".$this->colors[$level].'m'.$param."\33[0m".$this->newline : $param.$this->newline;
if ($this->stdout->write($param) instanceof Failure) {
switch ($this->mode) {
case 3: echo $param; break;
case 2: \file_put_contents($this->optional, $param, FILE_APPEND); break;
}
}
break;
}
}
break;
}
}
}

View File

@ -1,4 +1,5 @@
<?php
/**
* RPC call status check loop.
*
@ -43,7 +44,6 @@ class CheckLoop extends ResumableSignalLoop
* @var string
*/
protected $datacenter;
/**
* DataCenterConnection instance.
*
@ -57,106 +57,98 @@ class CheckLoop extends ResumableSignalLoop
$this->datacenter = $connection->getDatacenterID();
$this->datacenterConnection = $connection->getShared();
}
public function loop()
public function loop(): \Generator
{
$API = $this->API;
$datacenter = $this->datacenter;
$connection = $this->connection;
$shared = $this->datacenterConnection;
$timeout = $shared->getSettings()['timeout'];
$timeoutResend = $timeout * $timeout; // Typically 25 seconds, good enough
$timeoutResend = $timeout * $timeout;
// Typically 25 seconds, good enough
while (true) {
while (empty($connection->new_outgoing)) {
if (yield $this->waitSignal($this->pause())) {
return;
}
}
if ($connection->hasPendingCalls()) {
$last_msgid = $connection->getMaxId(true);
$last_chunk = $connection->getLastChunk();
if ($shared->hasTempAuthKey()) {
$full_message_ids = $connection->getPendingCalls(); //array_values($connection->new_outgoing);
$full_message_ids = $connection->getPendingCalls();
//array_values($connection->new_outgoing);
foreach (\array_chunk($full_message_ids, 8192) as $message_ids) {
$deferred = new Deferred();
$deferred->promise()->onResolve(
function ($e, $result) use ($message_ids, $API, $connection, $datacenter, $timeoutResend) {
if ($e) {
$API->logger("Got exception in check loop for DC $datacenter");
$API->logger((string) $e);
return;
}
$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') {
$connection->gotResponseForOutgoingMessageId($message_id);
break;
}
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.($message_id).' not received by server, resending...', \danog\MadelineProto\Logger::ERROR);
$connection->methodRecall('watcherId', ['message_id' => $message_id, 'postpone' => true]);
break;
case 4:
if ($chr & 32) {
if ($connection->outgoing_messages[$message_id]['sent'] + $timeoutResend < \time()) {
$API->logger->logger('Message '.$connection->outgoing_messages[$message_id]['_'].' with message ID '.($message_id).' received by server and is being processed for way too long, resending request...', \danog\MadelineProto\Logger::ERROR);
$connection->methodRecall('', ['message_id' => $message_id, 'postpone' => true]);
} else {
$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) {
\danog\MadelineProto\Tools::callFork($connection->objectCall('msg_resend_ans_req', ['msg_ids' => $reply], ['postpone' => true]));
}
$connection->flush();
$deferred->promise()->onResolve(function ($e, $result) use ($message_ids, $API, $connection, $datacenter, $timeoutResend) {
if ($e) {
$API->logger("Got exception in check loop for DC {$datacenter}");
$API->logger((string) $e);
return;
}
);
$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') {
$connection->gotResponseForOutgoingMessageId($message_id);
break;
}
$API->logger->logger('Message ' . $connection->outgoing_messages[$message_id]['_'] . ' with message ID ' . $message_id . ' not received by server, resending...', \danog\MadelineProto\Logger::ERROR);
$connection->methodRecall('watcherId', ['message_id' => $message_id, 'postpone' => true]);
break;
case 4:
if ($chr & 32) {
if ($connection->outgoing_messages[$message_id]['sent'] + $timeoutResend < \time()) {
$API->logger->logger('Message ' . $connection->outgoing_messages[$message_id]['_'] . ' with message ID ' . $message_id . ' received by server and is being processed for way too long, resending request...', \danog\MadelineProto\Logger::ERROR);
$connection->methodRecall('', ['message_id' => $message_id, 'postpone' => true]);
} else {
$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) {
\danog\MadelineProto\Tools::callFork($connection->objectCall('msg_resend_ans_req', ['msg_ids' => $reply], ['postpone' => true]));
}
$connection->flush();
});
$list = '';
// Don't edit this here pls
foreach ($message_ids as $message_id) {
$list .= $connection->outgoing_messages[$message_id]['_'].', ';
$list .= $connection->outgoing_messages[$message_id]['_'] . ', ';
}
$API->logger->logger("Still missing $list on DC $datacenter, sending state request", \danog\MadelineProto\Logger::ERROR);
$API->logger->logger("Still missing {$list} on DC {$datacenter}, sending state request", \danog\MadelineProto\Logger::ERROR);
yield $connection->objectCall('msgs_state_req', ['msg_ids' => $message_ids], ['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);
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);
$connection->methodRecall('', ['message_id' => $message_id, 'postpone' => true]);
}
}
@ -165,12 +157,10 @@ class CheckLoop extends ResumableSignalLoop
if (yield $this->waitSignal($this->pause($timeout))) {
return;
}
if ($connection->getMaxId(true) === $last_msgid && $connection->getLastChunk() === $last_chunk) {
$API->logger->logger("We did not receive a response for $timeout seconds: reconnecting and exiting check loop on DC $datacenter");
$API->logger->logger("We did not receive a response for {$timeout} seconds: reconnecting and exiting check loop on DC {$datacenter}");
//$this->exitedLoop();
Tools::callForkDefer($connection->reconnect());
return;
}
} else {
@ -180,7 +170,6 @@ class CheckLoop extends ResumableSignalLoop
}
}
}
public function __toString(): string
{
return "check loop in DC {$this->datacenter}";

View File

@ -1,4 +1,5 @@
<?php
/**
* HttpWait loop.
*
@ -40,14 +41,12 @@ class HttpWaitLoop extends ResumableSignalLoop
* @var string
*/
protected $datacenter;
/**
* DataCenterConnection instance.
*
* @var \danog\MadelineProto\DataCenterConnection
*/
protected $datacenterConnection;
public function __construct(Connection $connection)
{
$this->connection = $connection;
@ -55,18 +54,15 @@ class HttpWaitLoop extends ResumableSignalLoop
$this->datacenter = $connection->getDatacenterID();
$this->datacenterConnection = $connection->getShared();
}
public function loop()
public function loop(): \Generator
{
$API = $this->API;
$datacenter = $this->datacenter;
$connection = $this->connection;
$shared = $this->datacenterConnection;
if (!$shared->isHttp()) {
return;
}
while (true) {
if (yield $this->waitSignal($this->pause())) {
return;
@ -79,14 +75,13 @@ class HttpWaitLoop extends ResumableSignalLoop
return;
}
}
$API->logger->logger("DC $datacenter: request {$connection->countHttpSent()}, response {$connection->countHttpReceived()}");
if ($connection->countHttpSent() === $connection->countHttpReceived() && (!empty($connection->pending_outgoing) || (!empty($connection->new_outgoing) && !$connection->hasPendingCalls()))) {
$API->logger->logger("DC {$datacenter}: request {$connection->countHttpSent()}, response {$connection->countHttpReceived()}");
if ($connection->countHttpSent() === $connection->countHttpReceived() && (!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], 'contentRelated' => true, 'unencrypted' => false, 'method' => false]);
}
$API->logger->logger("DC $datacenter: request {$connection->countHttpSent()}, response {$connection->countHttpReceived()}");
$API->logger->logger("DC {$datacenter}: request {$connection->countHttpSent()}, response {$connection->countHttpReceived()}");
}
}
public function __toString(): string
{
return "HTTP wait loop in DC {$this->datacenter}";

View File

@ -1,4 +1,5 @@
<?php
/**
* Ping loop.
*
@ -40,14 +41,12 @@ class PingLoop extends ResumableSignalLoop
* @var string
*/
protected $datacenter;
/**
* DataCenterConnection instance.
*
* @var \danog\MadelineProto\DataCenterConnection
*/
protected $datacenterConnection;
public function __construct(Connection $connection)
{
$this->connection = $connection;
@ -55,14 +54,12 @@ class PingLoop extends ResumableSignalLoop
$this->datacenter = $connection->getDatacenterID();
$this->datacenterConnection = $connection->getShared();
}
public function loop()
public function loop(): \Generator
{
$API = $this->API;
$datacenter = $this->datacenter;
$connection = $this->connection;
$shared = $this->datacenterConnection;
$timeout = $shared->getSettings()['timeout'];
while (true) {
while (!$shared->hasTempAuthKey()) {
@ -74,17 +71,16 @@ class PingLoop extends ResumableSignalLoop
return;
}
if (\time() - $connection->getLastChunk() >= $timeout) {
$API->logger->logger("Ping DC $datacenter");
$API->logger->logger("Ping DC {$datacenter}");
try {
yield $connection->methodCallAsyncRead('ping', ['ping_id' => \random_bytes(8)]);
} catch (\Throwable $e) {
$API->logger->logger("Error while pinging DC $datacenter");
$API->logger->logger("Error while pinging DC {$datacenter}");
$API->logger->logger((string) $e);
}
}
}
}
public function __toString(): string
{
return "Ping loop in DC {$this->datacenter}";

View File

@ -1,4 +1,5 @@
<?php
/**
* Socket read loop.
*
@ -38,7 +39,6 @@ class ReadLoop extends SignalLoop
{
use Tools;
use Crypt;
/**
* Connection instance.
*
@ -57,7 +57,6 @@ class ReadLoop extends SignalLoop
* @var string
*/
protected $datacenter;
public function __construct(Connection $connection)
{
$this->connection = $connection;
@ -65,33 +64,29 @@ class ReadLoop extends SignalLoop
$this->datacenter = $connection->getDatacenterID();
$this->datacenterConnection = $connection->getShared();
}
public function loop()
public function loop(): \Generator
{
$API = $this->API;
$datacenter = $this->datacenter;
$connection = $this->connection;
$shared = $this->datacenterConnection;
while (true) {
try {
$error = yield $this->waitSignal($this->readMessage());
} catch (NothingInTheSocketException | StreamException | PendingReadError | \Error $e) {
} catch (NothingInTheSocketException|StreamException|PendingReadError|\Error $e) {
if ($connection->shouldReconnect()) {
return;
}
Tools::callForkDefer((function () use ($API, $connection, $datacenter, $e) {
Tools::callForkDefer((function () use ($API, $connection, $datacenter, $e): \Generator {
$API->logger->logger($e);
$API->logger->logger("Got nothing in the socket in DC {$datacenter}, reconnecting...", Logger::ERROR);
yield $connection->reconnect();
})());
return;
}
if (\is_int($error)) {
//$this->exitedLoop();
Tools::callForkDefer((function () use ($error, $shared, $connection, $datacenter, $API) {
Tools::callForkDefer((function () use ($error, $shared, $connection, $datacenter, $API): \Generator {
if ($error === -404) {
if ($shared->hasTempAuthKey()) {
$API->logger->logger("WARNING: Resetting auth key in DC {$datacenter}...", \danog\MadelineProto\Logger::WARNING);
@ -117,70 +112,53 @@ class ReadLoop extends SignalLoop
yield $connection->reconnect();
} else {
yield $connection->reconnect();
throw new \danog\MadelineProto\RPCErrorException($error, $error);
}
})());
return;
}
$connection->httpReceived();
Loop::defer([$connection, 'handleMessages']);
if ($shared->isHttp()) {
Loop::defer([$connection, 'pingHttpWaiter']);
}
}
}
public function readMessage()
public function readMessage(): \Generator
{
$API = $this->API;
$datacenter = $this->datacenter;
$connection = $this->connection;
$shared = $this->datacenterConnection;
if ($connection->shouldReconnect()) {
$API->logger->logger('Not reading because connection is old');
throw new NothingInTheSocketException();
}
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::ERROR);
$API->logger->logger("Received {$payload} from DC " . $datacenter, \danog\MadelineProto\Logger::ERROR);
return $payload;
}
throw $e;
}
if ($payload_length === 4) {
$payload = \danog\MadelineProto\Tools::unpackSignedInt(yield $buffer->bufferRead(4));
$API->logger->logger("Received $payload from DC ".$datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$API->logger->logger("Received {$payload} from DC " . $datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE);
return $payload;
}
$connection->reading(true);
try {
$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->checkMessageId($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) {
@ -195,18 +173,17 @@ class ReadLoop extends SignalLoop
$message_key = yield $buffer->bufferRead(16);
list($aes_key, $aes_iv) = $this->aesCalculate($message_key, $shared->getTempAuthKey()->getAuthKey(), 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->igeDecrypt($encrypted_data, $aes_key, $aes_iv);
/*
$server_salt = substr($decrypted_data, 0, 8);
if ($server_salt != $shared->getTempAuthKey()->getServerSalt()) {
$API->logger->logger('WARNING: Server salt mismatch (my server salt '.$shared->getTempAuthKey()->getServerSalt().' is not equal to server server salt '.$server_salt.').', \danog\MadelineProto\Logger::WARNING);
}
*/
$server_salt = substr($decrypted_data, 0, 8);
if ($server_salt != $shared->getTempAuthKey()->getServerSalt()) {
$API->logger->logger('WARNING: Server salt mismatch (my server salt '.$shared->getTempAuthKey()->getServerSalt().' 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) {
$API->logger->logger("Session ID mismatch", Logger::FATAL_ERROR);
@ -216,7 +193,6 @@ class ReadLoop extends SignalLoop
$message_id = \substr($decrypted_data, 16, 8);
$connection->checkMessageId($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');
@ -234,30 +210,26 @@ class ReadLoop extends SignalLoop
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($shared->getTempAuthKey()->getAuthKey(), 96, 32).$decrypted_data, true), 8, 16)) {
if ($message_key != \substr(\hash('sha256', \substr($shared->getTempAuthKey()->getAuthKey(), 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 {
$API->logger->logger('Got unknown auth_key id', \danog\MadelineProto\Logger::ERROR);
return -404;
}
$deserialized = $API->getTL()->deserialize($message_data, ['type' => '', 'connection' => $connection]);
$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_http_wait = 0;
$API->logger->logger('Received payload from DC '.$datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$API->logger->logger('Received payload from DC ' . $datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE);
} finally {
$connection->reading(false);
}
return true;
}
public function __toString(): string
{
return "read loop in DC {$this->datacenter}";

View File

@ -1,4 +1,5 @@
<?php
/**
* Socket write loop.
*
@ -34,7 +35,6 @@ class WriteLoop extends ResumableSignalLoop
{
use Crypt;
use Tools;
/**
* Connection instance.
*
@ -53,7 +53,6 @@ class WriteLoop extends ResumableSignalLoop
* @var string
*/
protected $datacenter;
public function __construct(Connection $connection)
{
$this->connection = $connection;
@ -62,14 +61,12 @@ class WriteLoop extends ResumableSignalLoop
$ctx = $connection->getCtx();
$this->datacenter = $connection->getDatacenterID();
}
public function loop(): \Generator
{
$API = $this->API;
$connection = $this->connection;
$shared = $this->datacenterConnection;
$datacenter = $this->datacenter;
$please_wait = false;
while (true) {
while (empty($connection->pending_outgoing) || $please_wait) {
@ -77,21 +74,18 @@ class WriteLoop extends ResumableSignalLoop
$API->logger->logger('Not writing because connection is old');
return;
}
$please_wait = false;
$API->logger->logger("Waiting in $this", Logger::ULTRA_VERBOSE);
$API->logger->logger("Waiting in {$this}", Logger::ULTRA_VERBOSE);
if (yield $this->waitSignal($this->pause())) {
$API->logger->logger("Exiting $this", Logger::ULTRA_VERBOSE);
$API->logger->logger("Exiting {$this}", Logger::ULTRA_VERBOSE);
return;
}
$API->logger->logger("Done waiting in $this", Logger::ULTRA_VERBOSE);
$API->logger->logger("Done waiting in {$this}", Logger::ULTRA_VERBOSE);
if ($connection->shouldReconnect()) {
$API->logger->logger('Not writing because connection is old');
return;
}
}
$connection->writing(true);
try {
$please_wait = yield $this->{$shared->hasTempAuthKey() ? 'encryptedWriteLoop' : 'unencryptedWriteLoop'}();
@ -99,7 +93,7 @@ class WriteLoop extends ResumableSignalLoop
if ($connection->shouldReconnect()) {
return;
}
Tools::callForkDefer((function () use ($API, $connection, $datacenter, $e) {
Tools::callForkDefer((function () use ($API, $connection, $datacenter, $e): \Generator {
$API->logger->logger($e);
$API->logger->logger("Got nothing in the socket in DC {$datacenter}, reconnecting...", Logger::ERROR);
yield $connection->reconnect();
@ -108,18 +102,15 @@ class WriteLoop extends ResumableSignalLoop
} finally {
$connection->writing(false);
}
//$connection->waiter->resume();
}
}
public function unencryptedWriteLoop()
public function unencryptedWriteLoop(): \Generator
{
$API = $this->API;
$datacenter = $this->datacenter;
$connection = $this->connection;
$shared = $this->datacenterConnection;
while ($connection->pending_outgoing) {
$skipped_all = true;
foreach ($connection->pending_outgoing as $k => $message) {
@ -130,20 +121,14 @@ class WriteLoop extends ResumableSignalLoop
continue;
}
$skipped_all = false;
$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->generateMessageId();
$length = \strlen($message['serialized_body']);
$pad_length = -$length & 15;
$pad_length += 16 * \danog\MadelineProto\Tools::randomInt($modulus = 16);
$pad = \danog\MadelineProto\Tools::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.\danog\MadelineProto\Tools::packUnsignedInt($length).$message['serialized_body'].$pad);
yield $buffer->bufferWrite("\0\0\0\0\0\0\0\0" . $message_id . \danog\MadelineProto\Tools::packUnsignedInt($length) . $message['serialized_body'] . $pad);
//var_dump("plain ".bin2hex($message_id));
$connection->httpSent();
$connection->outgoing_messages[$message_id] = $message;
@ -151,11 +136,8 @@ class WriteLoop extends ResumableSignalLoop
$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);
unset($message['send_promise']);
}
@ -164,14 +146,12 @@ class WriteLoop extends ResumableSignalLoop
}
}
}
public function encryptedWriteLoop(): \Generator
{
$API = $this->API;
$datacenter = $this->datacenter;
$connection = $this->connection;
$shared = $this->datacenterConnection;
do {
if (!$shared->hasTempAuthKey()) {
return;
@ -182,13 +162,12 @@ class WriteLoop extends ResumableSignalLoop
$temporary_keys = [];
if (\count($to_ack = $connection->ack_queue)) {
foreach (\array_chunk($connection->ack_queue, 8192) as $acks) {
$connection->pending_outgoing[$connection->pending_outgoing_key] = ['_' => 'msgs_ack', 'serialized_body' => yield $this->API->getTL()->serializeObject(['type' => ''], ['_' => 'msgs_ack','msg_ids' => $acks], 'msgs_ack'), 'contentRelated' => false, 'unencrypted' => false, 'method' => false];
$connection->pending_outgoing[$connection->pending_outgoing_key] = ['_' => 'msgs_ack', 'serialized_body' => yield $this->API->getTL()->serializeObject(['type' => ''], ['_' => 'msgs_ack', 'msg_ids' => $acks], 'msgs_ack'), 'contentRelated' => false, 'unencrypted' => false, 'method' => false];
$temporary_keys[$connection->pending_outgoing_key] = true;
$API->logger->logger("Adding msgs_ack {$connection->pending_outgoing_key}", Logger::ULTRA_VERBOSE);
$connection->pending_outgoing_key++;
}
}
$has_http_wait = false;
$messages = [];
$keys = [];
@ -206,7 +185,6 @@ class WriteLoop extends ResumableSignalLoop
$connection->pending_outgoing_key++;
}
}
$total_length = 0;
$count = 0;
\ksort($connection->pending_outgoing);
@ -231,45 +209,20 @@ class WriteLoop extends ResumableSignalLoop
$API->logger->logger('Length overflow, postponing part of payload', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
break;
}
$message_id = isset($message['msg_id']) ? $message['msg_id'] : $connection->generateMessageId();
$API->logger->logger("Sending {$message['_']} as encrypted message to DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$MTmessage = ['_' => 'MTmessage', 'msg_id' => $message_id, 'body' => $message['serialized_body'], 'seqno' => $connection->generateOutSeqNo($message['contentRelated'])];
if (isset($message['method']) && $message['method'] && $message['_'] !== 'http_wait') {
if (!$shared->getTempAuthKey()->isInited() && $message['_'] !== 'auth.bindTempAuthKey' && !$inited) {
$inited = true;
$API->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['write_client_info'], $message['_']), \danog\MadelineProto\Logger::NOTICE);
$MTmessage['body'] = yield $API->getTL()->serializeMethod(
'invokeWithLayer',
[
'layer' => $API->settings['tl_schema']['layer'],
'query' => yield $API->getTL()->serializeMethod(
'initConnection',
[
'api_id' => $API->settings['app_info']['api_id'],
'api_hash' => $API->settings['app_info']['api_hash'],
'device_model' => !$connection->isCDN() ? $API->settings['app_info']['device_model'] : 'n/a',
'system_version' => !$connection->isCDN() ? $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'],
'proxy' => $connection->getCtx()->getInputClientProxy(),
'query' => $MTmessage['body'],
]
),
]
);
$MTmessage['body'] = yield $API->getTL()->serializeMethod('invokeWithLayer', ['layer' => $API->settings['tl_schema']['layer'], 'query' => yield $API->getTL()->serializeMethod('initConnection', ['api_id' => $API->settings['app_info']['api_id'], 'api_hash' => $API->settings['app_info']['api_hash'], 'device_model' => !$connection->isCDN() ? $API->settings['app_info']['device_model'] : 'n/a', 'system_version' => !$connection->isCDN() ? $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'], 'proxy' => $connection->getCtx()->getInputClientProxy(), 'query' => $MTmessage['body']])]);
} else {
if (isset($message['queue'])) {
if (!isset($connection->call_queue[$message['queue']])) {
$connection->call_queue[$message['queue']] = [];
}
$MTmessage['body'] = yield $API->getTL()->serializeMethod('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']]);
@ -279,12 +232,12 @@ class WriteLoop extends ResumableSignalLoop
}
// TODO
/* 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'] = yield $API->getTL()->serializeObject(['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);
}*/
if (($g = strlen($gzipped = gzencode($MTmessage['body']))) < $l) {
$MTmessage['body'] = yield $API->getTL()->serializeObject(['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']);
@ -295,32 +248,25 @@ class WriteLoop extends ResumableSignalLoop
}
$count++;
$total_length += $actual_length;
$MTmessage['bytes'] = $body_length;
$messages[] = $MTmessage;
$keys[$k] = $message_id;
}
if ($shared->isHttp() && $skipped && $count === \count($temporary_keys)) {
foreach ($temporary_keys as $key => $true) {
$API->logger->logger("Removing temporary {$connection->pending_outgoing[$key]['_']} by $key", Logger::ULTRA_VERBOSE);
$API->logger->logger("Removing temporary {$connection->pending_outgoing[$key]['_']} by {$key}", Logger::ULTRA_VERBOSE);
unset($connection->pending_outgoing[$key]);
$count--;
}
}
$MTmessage = null;
if ($count > 1) {
$API->logger->logger("Wrapping in msg_container ($count messages of total size $total_length) as encrypted message for DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$API->logger->logger("Wrapping in msg_container ({$count} messages of total size {$total_length}) as encrypted message for DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$message_id = $connection->generateMessageId();
$connection->pending_outgoing[$connection->pending_outgoing_key] = ['_' => 'msg_container', 'container' => \array_values($keys), 'contentRelated' => false, 'method' => false, 'unencrypted' => false];
//var_dumP("container ".bin2hex($message_id));
$keys[$connection->pending_outgoing_key++] = $message_id;
$message_data = yield $API->getTL()->serializeObject(['type' => ''], ['_' => 'msg_container', 'messages' => $messages], 'container');
$message_data_length = \strlen($message_data);
$seq_no = $connection->generateOutSeqNo(false);
} elseif ($count) {
@ -330,44 +276,30 @@ class WriteLoop extends ResumableSignalLoop
$message_id = $message['msg_id'];
$seq_no = $message['seqno'];
} else {
$API->logger->logger("NO MESSAGE SENT in DC $datacenter", \danog\MadelineProto\Logger::WARNING);
$API->logger->logger("NO MESSAGE SENT in DC {$datacenter}", \danog\MadelineProto\Logger::WARNING);
return true;
}
unset($messages);
$plaintext = $shared->getTempAuthKey()->getServerSalt().$connection->session_id.$message_id.\pack('VV', $seq_no, $message_data_length).$message_data;
$plaintext = $shared->getTempAuthKey()->getServerSalt() . $connection->session_id . $message_id . \pack('VV', $seq_no, $message_data_length) . $message_data;
$padding = \danog\MadelineProto\Tools::posmod(-\strlen($plaintext), 16);
if ($padding < 12) {
$padding += 16;
}
$padding = \danog\MadelineProto\Tools::random($padding);
$message_key = \substr(\hash('sha256', \substr($shared->getTempAuthKey()->getAuthKey(), 88, 32).$plaintext.$padding, true), 8, 16);
$message_key = \substr(\hash('sha256', \substr($shared->getTempAuthKey()->getAuthKey(), 88, 32) . $plaintext . $padding, true), 8, 16);
list($aes_key, $aes_iv) = $this->aesCalculate($message_key, $shared->getTempAuthKey()->getAuthKey());
$message = $shared->getTempAuthKey()->getID().$message_key.$this->igeEncrypt($plaintext.$padding, $aes_key, $aes_iv);
$message = $shared->getTempAuthKey()->getID() . $message_key . $this->igeEncrypt($plaintext . $padding, $aes_key, $aes_iv);
$buffer = yield $connection->stream->getWriteBuffer($len = \strlen($message));
//$t = \microtime(true);
yield $buffer->bufferWrite($message);
$connection->httpSent();
$API->logger->logger("Sent encrypted payload to DC {$datacenter}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$sent = \time();
if ($to_ack) {
$connection->ack_queue = [];
}
foreach ($keys as $key => $message_id) {
$connection->outgoing_messages[$message_id] = &$connection->pending_outgoing[$key];
$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;
@ -380,17 +312,13 @@ class WriteLoop extends ResumableSignalLoop
//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) && !$skipped);
if (empty($connection->pending_outgoing)) {
$connection->pending_outgoing_key = 'a';
}
return $skipped;
}
public function __toString(): string
{
return "write loop in DC {$this->datacenter}";

View File

@ -1,4 +1,5 @@
<?php
/**
* Generic loop.
*
@ -30,10 +31,8 @@ class GenericLoop extends ResumableSignalLoop
const STOP = -1;
const PAUSE = null;
const CONTINUE = 0;
protected $callback;
protected $name;
/**
* Constructor.
*
@ -54,24 +53,21 @@ class GenericLoop extends ResumableSignalLoop
$this->callback = $callback->bindTo($this);
$this->name = $name;
}
public function loop()
public function loop(): \Generator
{
$callback = $this->callback;
while (true) {
$timeout = yield $callback();
if ($timeout === self::PAUSE) {
$this->API->logger->logger("Pausing $this", \danog\MadelineProto\Logger::VERBOSE);
$this->API->logger->logger("Pausing {$this}", \danog\MadelineProto\Logger::VERBOSE);
} elseif ($timeout > 0) {
$this->API->logger->logger("Pausing $this for $timeout", \danog\MadelineProto\Logger::VERBOSE);
$this->API->logger->logger("Pausing {$this} for {$timeout}", \danog\MadelineProto\Logger::VERBOSE);
}
if ($timeout === self::STOP || yield $this->waitSignal($this->pause($timeout))) {
return;
}
}
}
public function __toString(): string
{
return $this->name;

View File

@ -1,4 +1,5 @@
<?php
/**
* Periodic loop.
*
@ -31,7 +32,6 @@ class PeriodicLoop extends ResumableSignalLoop
private $callback;
private $name;
private $timeout;
/**
* Constructor.
*
@ -47,22 +47,19 @@ class PeriodicLoop extends ResumableSignalLoop
$this->name = $name;
$this->timeout = $timeout;
}
public function loop()
public function loop(): \Generator
{
$callback = $this->callback;
$logger = $this->API->logger;
while (true) {
$result = yield $this->waitSignal($this->pause($this->timeout));
if ($result) {
$logger->logger("Got signal in $this, exiting");
$logger->logger("Got signal in {$this}, exiting");
return;
}
yield $callback();
}
}
public function __toString(): string
{
return $this->name;

View File

@ -1,4 +1,5 @@
<?php
/**
* Loop helper trait.
*
@ -32,59 +33,49 @@ use danog\MadelineProto\Loop\LoopInterface;
abstract class Loop implements LoopInterface
{
use \danog\MadelineProto\Tools;
private $count = 0;
/**
* MTProto instance.
*
* @var \danog\MadelineProto\MTProto
*/
public $API;
public function __construct($API)
{
$this->API = $API;
}
public function start()
{
if ($this->count) {
//$this->API->logger->logger("NOT entering $this with running count {$this->count}", Logger::ERROR);
return false;
}
return \danog\MadelineProto\Tools::callFork($this->loopImpl());
}
private function loopImpl()
private function loopImpl(): \Generator
{
//yield ['my_trace' => debug_backtrace(0, 1)[0], (string) $this];
$this->startedLoop();
$this->API->logger->logger("Entered $this", Logger::ULTRA_VERBOSE);
$this->API->logger->logger("Entered {$this}", Logger::ULTRA_VERBOSE);
try {
yield $this->loop();
} finally {
$this->exitedLoop();
$this->API->logger->logger("Physically exited $this", Logger::ULTRA_VERBOSE);
$this->API->logger->logger("Physically exited {$this}", Logger::ULTRA_VERBOSE);
//return null;
}
}
public function exitedLoop()
{
if ($this->count) {
$this->API->logger->logger("Exited $this", Logger::ULTRA_VERBOSE);
$this->API->logger->logger("Exited {$this}", Logger::ULTRA_VERBOSE);
$this->count--;
}
}
public function startedLoop()
{
$this->count++;
}
public function isRunning()
{
return $this->count;

View File

@ -1,4 +1,5 @@
<?php
/**
* Loop helper trait.
*
@ -36,7 +37,6 @@ abstract class ResumableSignalLoop extends SignalLoop implements ResumableLoopIn
private $resume;
private $pause;
private $resumeWatcher;
public function pause($time = null): Promise
{
if (!\is_null($time)) {
@ -51,16 +51,13 @@ abstract class ResumableSignalLoop extends SignalLoop implements ResumableLoopIn
$this->resumeWatcher = Loop::delay((int) ($time * 1000), [$this, 'resume'], $resume);
}
$this->resume = new Deferred();
$pause = $this->pause;
$this->pause = new Deferred();
if ($pause) {
Loop::defer([$pause, 'resolve']);
}
return $this->resume->promise();
}
public function resume($watcherId = null, $expected = 0)
{
if ($this->resumeWatcher) {
@ -75,15 +72,12 @@ abstract class ResumableSignalLoop extends SignalLoop implements ResumableLoopIn
$resume = $this->resume;
$this->resume = null;
$resume->resolve();
return $this->pause ? $this->pause->promise() : null;
}
}
public function resumeDefer()
{
Loop::defer([$this, 'resume']);
return $this->pause ? $this->pause->promise() : null;
}
}

View File

@ -1,4 +1,5 @@
<?php
/**
* Loop helper trait.
*
@ -31,7 +32,6 @@ use danog\MadelineProto\Loop\SignalLoopInterface;
abstract class SignalLoop extends Loop implements SignalLoopInterface
{
private $signalDeferred;
public function signal($what)
{
if ($this->signalDeferred) {
@ -44,7 +44,6 @@ abstract class SignalLoop extends Loop implements SignalLoopInterface
}
}
}
public function waitSignal($promise): Promise
{
if ($promise instanceof \Generator) {
@ -52,7 +51,6 @@ abstract class SignalLoop extends Loop implements SignalLoopInterface
}
$this->signalDeferred = new Deferred();
$dpromise = $this->signalDeferred->promise();
$promise->onResolve(function () use ($promise) {
if ($this->signalDeferred !== null) {
$deferred = $this->signalDeferred;
@ -60,7 +58,6 @@ abstract class SignalLoop extends Loop implements SignalLoopInterface
$deferred->resolve($promise);
}
});
return $dpromise;
}
}

View File

@ -1,4 +1,5 @@
<?php
/**
* Loop interface.
*
@ -31,14 +32,12 @@ interface LoopInterface
* @return void
*/
public function start();
/**
* The actual loop.
*
* @return void
*/
public function loop();
/**
* Get name of the loop.
*

View File

@ -1,4 +1,5 @@
<?php
/**
* Resumable loop interface.
*
@ -35,7 +36,6 @@ interface ResumableLoopInterface extends LoopInterface
* @return Promise
*/
public function pause($time = null): Promise;
/**
* Resume the loop.
*

View File

@ -1,4 +1,5 @@
<?php
/**
* Signal loop interface.
*
@ -35,7 +36,6 @@ interface SignalLoopInterface extends LoopInterface
* @return Promise
*/
public function waitSignal($promise): Promise;
/**
* Send a signal to the the loop.
*

View File

@ -1,4 +1,5 @@
<?php
/**
* Update feeder loop.
*
@ -38,29 +39,24 @@ class FeedLoop extends ResumableSignalLoop
* @var UpdateLoop
*/
private $updater;
public function __construct($API, $channelId = false)
{
$this->API = $API;
$this->channelId = $channelId;
}
public function loop()
public function loop(): \Generator
{
$API = $this->API;
$this->updater = $API->updaters[$this->channelId];
if (!$this->API->settings['updates']['handle_updates']) {
return false;
}
while (!$this->API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
if (yield $this->waitSignal($this->pause())) {
return;
}
}
$this->state = $this->channelId === false ? (yield $API->loadUpdateState()) : $API->loadChannelState($this->channelId);
$this->state = $this->channelId === false ? yield $API->loadUpdateState() : $API->loadChannelState($this->channelId);
while (true) {
while (!$this->API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
if (yield $this->waitSignal($this->pause())) {
@ -73,11 +69,11 @@ class FeedLoop extends ResumableSignalLoop
if (!$this->API->settings['updates']['handle_updates']) {
return;
}
$API->logger->logger("Resumed $this");
$API->logger->logger("Resumed {$this}");
while ($this->incomingUpdates) {
$updates = $this->incomingUpdates;
$this->incomingUpdates = [];
yield $this->parse($updates);
yield from $this->parse($updates);
$updates = null;
}
while ($this->parsedUpdates) {
@ -91,8 +87,7 @@ class FeedLoop extends ResumableSignalLoop
}
}
}
public function parse($updates)
public function parse($updates): \Generator
{
\reset($updates);
while ($updates) {
@ -102,7 +97,6 @@ class FeedLoop extends ResumableSignalLoop
if ($update['_'] === 'updateChannelTooLong') {
$this->API->logger->logger('Got channel too long update, getting difference...', \danog\MadelineProto\Logger::VERBOSE);
yield $this->updater->resume();
continue;
}
if (isset($update['pts'], $update['pts_count'])) {
@ -111,12 +105,11 @@ class FeedLoop extends ResumableSignalLoop
$mid = isset($update['message']['id']) ? $update['message']['id'] : '-';
$mypts = $this->state->pts();
$computed = $mypts + $pts_count;
$this->API->logger->logger("$msg. My pts: {$mypts}, remote pts: {$update['pts']}, computed pts: $computed, msg id: {$mid}, channel id: {$this->channelId}", \danog\MadelineProto\Logger::ERROR);
$this->API->logger->logger("{$msg}. My pts: {$mypts}, remote pts: {$update['pts']}, computed pts: {$computed}, msg id: {$mid}, channel id: {$this->channelId}", \danog\MadelineProto\Logger::ERROR);
};
$result = $this->state->checkPts($update);
if ($result < 0) {
$logger('PTS duplicate');
continue;
}
if ($result > 0) {
@ -130,19 +123,16 @@ class FeedLoop extends ResumableSignalLoop
if (isset($update['message']['id'], $update['message']['to_id']) && !\in_array($update['_'], ['updateEditMessage', 'updateEditChannelMessage', 'updateMessageID'])) {
if (!$this->API->checkMsgId($update['message'])) {
$logger('MSGID duplicate');
continue;
}
}
$logger('PTS OK');
$this->state->pts($update['pts']);
}
$this->save($update);
}
}
public function feed($updates)
public function feed($updates): \Generator
{
$result = [];
foreach ($updates as $update) {
@ -152,11 +142,9 @@ class FeedLoop extends ResumableSignalLoop
}
$result[$res] = true;
}
return $result;
}
public function feedSingle($update)
public function feedSingle($update): \Generator
{
$channelId = false;
switch ($update['_']) {
@ -178,7 +166,6 @@ class FeedLoop extends ResumableSignalLoop
}
break;
}
if ($channelId && !$this->API->getChannelStates()->has($channelId)) {
$this->API->loadChannelState($channelId, $update);
if (!isset($this->API->feeders[$channelId])) {
@ -190,7 +177,6 @@ class FeedLoop extends ResumableSignalLoop
$this->API->feeders[$channelId]->start();
$this->API->updaters[$channelId]->start();
}
switch ($update['_']) {
case 'updateNewMessage':
case 'updateEditMessage':
@ -200,31 +186,21 @@ class FeedLoop extends ResumableSignalLoop
$from = false;
$via_bot = false;
$entities = false;
if ($update['message']['_'] !== 'messageEmpty' && (
($from = isset($update['message']['from_id']) && !yield $this->API->peerIsset($update['message']['from_id'])) ||
($to = !yield $this->API->peerIsset($update['message']['to_id'])) ||
($via_bot = isset($update['message']['via_bot_id']) && !yield $this->API->peerIsset($update['message']['via_bot_id'])) ||
($entities = isset($update['message']['entities']) && !yield $this->API->entitiesPeerIsset($update['message']['entities'])) // ||
//isset($update['message']['fwd_from']) && !yield $this->fwdPeerIsset($update['message']['fwd_from'])
)) {
if ($update['message']['_'] !== 'messageEmpty' && (($from = isset($update['message']['from_id']) && !yield $this->API->peerIsset($update['message']['from_id'])) || ($to = !yield $this->API->peerIsset($update['message']['to_id'])) || ($via_bot = isset($update['message']['via_bot_id']) && !yield $this->API->peerIsset($update['message']['via_bot_id'])) || ($entities = isset($update['message']['entities']) && !yield $this->API->entitiesPeerIsset($update['message']['entities'])))) {
$log = '';
if ($from) {
$log .= "from_id {$update['message']['from_id']}, ";
}
if ($to) {
$log .= 'to_id '.\json_encode($update['message']['to_id']).', ';
$log .= 'to_id ' . \json_encode($update['message']['to_id']) . ', ';
}
if ($via_bot) {
$log .= "via_bot {$update['message']['via_bot_id']}, ";
}
if ($entities) {
$log .= 'entities '.\json_encode($update['message']['entities']).', ';
$log .= 'entities ' . \json_encode($update['message']['entities']) . ', ';
}
$this->API->logger->logger("Not enough data: for message update $log, getting difference...", \danog\MadelineProto\Logger::VERBOSE);
$this->API->logger->logger("Not enough data: for message update {$log}, getting difference...", \danog\MadelineProto\Logger::VERBOSE);
$update = ['_' => 'updateChannelTooLong'];
if ($channelId && $to) {
$channelId = false;
@ -233,47 +209,39 @@ class FeedLoop extends ResumableSignalLoop
break;
default:
if ($channelId && !yield $this->API->peerIsset($this->API->toSupergroup($channelId))) {
$this->API->logger->logger('Skipping update, I do not have the channel id '.$channelId, \danog\MadelineProto\Logger::ERROR);
$this->API->logger->logger('Skipping update, I do not have the channel id ' . $channelId, \danog\MadelineProto\Logger::ERROR);
return false;
}
break;
}
if ($channelId !== $this->channelId) {
if (isset($this->API->feeders[$channelId])) {
return yield $this->API->feeders[$channelId]->feedSingle($update);
return yield from $this->API->feeders[$channelId]->feedSingle($update);
} elseif ($this->channelId) {
return yield $this->API->feeders[false]->feedSingle($update);
return yield from $this->API->feeders[false]->feedSingle($update);
}
}
$this->API->logger->logger('Was fed an update of type '.$update['_']." in $this...", \danog\MadelineProto\Logger::VERBOSE);
$this->API->logger->logger('Was fed an update of type ' . $update['_'] . " in {$this}...", \danog\MadelineProto\Logger::VERBOSE);
$this->incomingUpdates[] = $update;
return $this->channelId;
}
public function save($update)
{
$this->parsedUpdates[] = $update;
}
public function saveMessages($messages)
{
foreach ($messages as $message) {
if (!$this->API->checkMsgId($message)) {
$this->API->logger->logger("MSGID duplicate ({$message['id']}) in $this");
$this->API->logger->logger("MSGID duplicate ({$message['id']}) in {$this}");
continue;
}
if ($message['_'] !== 'messageEmpty') {
$this->API->logger->logger('Getdiff fed me message of type '.$message['_']." in $this...", \danog\MadelineProto\Logger::VERBOSE);
$this->API->logger->logger('Getdiff fed me message of type ' . $message['_'] . " in {$this}...", \danog\MadelineProto\Logger::VERBOSE);
}
$this->parsedUpdates[] = ['_' => $this->channelId === false ? 'updateNewMessage' : 'updateNewChannelMessage', 'message' => $message, 'pts' => -1, 'pts_count' => -1];
}
}
public function __toString(): string
{
return !$this->channelId ? 'update feed loop generic' : "update feed loop channel {$this->channelId}";

View File

@ -1,4 +1,5 @@
<?php
/**
* Update feeder loop.
*
@ -31,28 +32,23 @@ class SeqLoop extends ResumableSignalLoop
private $incomingUpdates = [];
private $feeder;
private $pendingWakeups = [];
public function __construct($API)
{
$this->API = $API;
}
public function loop()
public function loop(): \Generator
{
$API = $this->API;
$this->feeder = $API->feeders[false];
if (!$this->API->settings['updates']['handle_updates']) {
return false;
}
while (!$this->API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
if (yield $this->waitSignal($this->pause())) {
return;
}
}
$this->state = yield $API->loadUpdateState();
while (true) {
while (!$this->API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
if (yield $this->waitSignal($this->pause())) {
@ -68,7 +64,7 @@ class SeqLoop extends ResumableSignalLoop
while ($this->incomingUpdates) {
$updates = $this->incomingUpdates;
$this->incomingUpdates = [];
yield $this->parse($updates);
yield from $this->parse($updates);
$updates = null;
}
while ($this->pendingWakeups) {
@ -79,8 +75,7 @@ class SeqLoop extends ResumableSignalLoop
}
}
}
public function parse($updates)
public function parse($updates): \Generator
{
\reset($updates);
while ($updates) {
@ -88,53 +83,43 @@ class SeqLoop extends ResumableSignalLoop
$key = \key($updates);
$update = $updates[$key];
unset($updates[$key]);
$options = $update['options'];
$seq_start = $options['seq_start'];
$seq_end = $options['seq_end'];
$result = $this->state->checkSeq($seq_start);
if ($result > 0) {
$this->API->logger->logger('Seq hole. seq_start: '.$seq_start.' != cur seq: '.($this->state->seq() + 1), \danog\MadelineProto\Logger::ERROR);
$this->API->logger->logger('Seq hole. seq_start: ' . $seq_start . ' != cur seq: ' . ($this->state->seq() + 1), \danog\MadelineProto\Logger::ERROR);
yield $this->pause(1.0);
if (!$this->incomingUpdates) {
yield $this->API->updaters[false]->resume();
}
$this->incomingUpdates = \array_merge($this->incomingUpdates, [$update], $updates);
continue;
}
if ($result < 0) {
$this->API->logger->logger('Seq too old. seq_start: '.$seq_start.' != cur seq: '.($this->state->seq() + 1), \danog\MadelineProto\Logger::ERROR);
$this->API->logger->logger('Seq too old. seq_start: ' . $seq_start . ' != cur seq: ' . ($this->state->seq() + 1), \danog\MadelineProto\Logger::ERROR);
continue;
}
$this->state->seq($seq_end);
if (isset($options['date'])) {
$this->state->date($options['date']);
}
yield $this->save($update);
yield from $this->save($update);
}
}
public function feed($updates)
{
$this->API->logger->logger('Was fed updates of type '.$updates['_'].'...', \danog\MadelineProto\Logger::VERBOSE);
$this->API->logger->logger('Was fed updates of type ' . $updates['_'] . '...', \danog\MadelineProto\Logger::VERBOSE);
$this->incomingUpdates[] = $updates;
}
public function save($updates)
public function save($updates): \Generator
{
$this->pendingWakeups += yield $this->feeder->feed($updates['updates']);
}
public function addPendingWakeups($wakeups)
{
$this->pendingWakeups += $wakeups;
}
public function __toString(): string
{
return 'update seq loop';

View File

@ -1,4 +1,5 @@
<?php
/**
* Update loop.
*
@ -31,38 +32,31 @@ use danog\MadelineProto\RPCErrorException;
class UpdateLoop extends ResumableSignalLoop
{
use \danog\MadelineProto\Tools;
private $toPts;
private $channelId;
private $feeder;
public function __construct($API, $channelId)
{
$this->API = $API;
$this->channelId = $channelId;
}
public function loop()
public function loop(): \Generator
{
$API = $this->API;
$feeder = $this->feeder = $API->feeders[$this->channelId];
while (!$API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
if (yield $this->waitSignal($this->pause())) {
$API->logger->logger("Exiting $this due to signal");
$API->logger->logger("Exiting {$this} due to signal");
return;
}
}
$this->state = $state = $this->channelId === false ? (yield $API->loadUpdateState()) : $API->loadChannelState($this->channelId);
$this->state = $state = $this->channelId === false ? yield $API->loadUpdateState() : $API->loadChannelState($this->channelId);
$timeout = $API->settings['updates']['getdifference_interval'];
$first = true;
while (true) {
while (!$API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
if (yield $this->waitSignal($this->pause())) {
$API->logger->logger("Exiting $this due to signal");
$API->logger->logger("Exiting {$this} due to signal");
return;
}
}
@ -71,7 +65,7 @@ class UpdateLoop extends ResumableSignalLoop
$this->toPts = null;
while (true) {
if ($this->channelId) {
$API->logger->logger('Resumed and fetching '.$this->channelId.' difference...', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$API->logger->logger('Resumed and fetching ' . $this->channelId . ' difference...', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
if ($state->pts() <= 1) {
$limit = 10;
} elseif ($API->authorization['user']['bot']) {
@ -80,17 +74,14 @@ class UpdateLoop extends ResumableSignalLoop
$limit = 100;
}
$request_pts = $state->pts();
try {
$difference = yield $API->methodCallAsyncRead('updates.getChannelDifference', ['channel' => 'channel#'.$this->channelId, 'filter' => ['_' => 'channelMessagesFilterEmpty'], 'pts' => $request_pts, 'limit' => $limit, 'force' => true], ['datacenter' => $API->datacenter->curdc, 'postpone' => $first]);
$difference = yield $API->methodCallAsyncRead('updates.getChannelDifference', ['channel' => 'channel#' . $this->channelId, 'filter' => ['_' => 'channelMessagesFilterEmpty'], 'pts' => $request_pts, 'limit' => $limit, 'force' => true], ['datacenter' => $API->datacenter->curdc, 'postpone' => $first]);
} catch (RPCErrorException $e) {
if (\in_array($e->rpc, ['CHANNEL_PRIVATE', 'CHAT_FORBIDDEN', 'CHANNEL_INVALID'])) {
$feeder->signal(true);
unset($API->updaters[$this->channelId], $API->feeders[$this->channelId]);
$API->getChannelStates()->remove($this->channelId);
$API->logger->logger("Channel private, exiting $this");
$API->logger->logger("Channel private, exiting {$this}");
return true;
}
throw $e;
@ -99,9 +90,7 @@ class UpdateLoop extends ResumableSignalLoop
$feeder->signal(true);
$API->getChannelStates()->remove($this->channelId);
unset($API->updaters[$this->channelId], $API->feeders[$this->channelId]);
$API->logger->logger("Channel private, exiting $this");
$API->logger->logger("Channel private, exiting {$this}");
return true;
}
throw $e;
@ -109,8 +98,7 @@ class UpdateLoop extends ResumableSignalLoop
if (isset($difference['timeout'])) {
$timeout = $difference['timeout'];
}
$API->logger->logger('Got '.$difference['_'], \danog\MadelineProto\Logger::VERBOSE);
$API->logger->logger('Got ' . $difference['_'], \danog\MadelineProto\Logger::VERBOSE);
switch ($difference['_']) {
case 'updates.channelDifferenceEmpty':
$state->update($difference);
@ -118,12 +106,11 @@ class UpdateLoop extends ResumableSignalLoop
break 2;
case 'updates.channelDifference':
if ($request_pts >= $difference['pts'] && $request_pts > 1) {
$API->logger->logger("The PTS ({$difference['pts']}) I got with getDifference is smaller than the PTS I requested ".$state->pts().', using '.($state->pts() + 1), \danog\MadelineProto\Logger::VERBOSE);
$API->logger->logger("The PTS ({$difference['pts']}) I got with getDifference is smaller than the PTS I requested " . $state->pts() . ', using ' . ($state->pts() + 1), \danog\MadelineProto\Logger::VERBOSE);
$difference['pts'] = $request_pts + 1;
}
$result += yield $feeder->feed($difference['other_updates']);
$state->update($difference);
$feeder->saveMessages($difference['new_messages']);
if (!$difference['final']) {
if ($difference['pts'] >= $toPts) {
@ -144,13 +131,12 @@ class UpdateLoop extends ResumableSignalLoop
unset($difference);
break;
default:
throw new \danog\MadelineProto\Exception('Unrecognized update difference received: '.\var_export($difference, true));
throw new \danog\MadelineProto\Exception('Unrecognized update difference received: ' . \var_export($difference, true));
}
} else {
$API->logger->logger('Resumed and fetching normal difference...', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$difference = yield $API->methodCallAsyncRead('updates.getDifference', ['pts' => $state->pts(), 'date' => $state->date(), 'qts' => $state->qts()], ['datacenter' => $API->settings['connection_settings']['default_dc']]);
$API->logger->logger('Got '.$difference['_'], \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$API->logger->logger('Got ' . $difference['_'], \danog\MadelineProto\Logger::ULTRA_VERBOSE);
switch ($difference['_']) {
case 'updates.differenceEmpty':
$state->update($difference);
@ -187,32 +173,28 @@ class UpdateLoop extends ResumableSignalLoop
unset($difference);
break;
default:
throw new \danog\MadelineProto\Exception('Unrecognized update difference received: '.\var_export($difference, true));
throw new \danog\MadelineProto\Exception('Unrecognized update difference received: ' . \var_export($difference, true));
}
}
}
$API->logger->logger("Finished parsing updates in $this, now resuming feeders");
$API->logger->logger("Finished parsing updates in {$this}, now resuming feeders");
foreach ($result as $channelId => $boh) {
$API->feeders[$channelId]->resumeDefer();
}
$API->logger->logger("Finished resuming feeders in $this, signaling updates");
$API->logger->logger("Finished resuming feeders in {$this}, signaling updates");
$API->signalUpdate();
$API->logger->logger("Finished signaling updates in $this, pausing");
$API->logger->logger("Finished signaling updates in {$this}, pausing");
$first = false;
if (yield $this->waitSignal($this->pause($timeout))) {
$API->logger->logger("Exiting $this due to signal");
$API->logger->logger("Exiting {$this} due to signal");
return;
}
}
}
public function setLimit($toPts)
{
$this->toPts = $toPts;
}
public function __toString(): string
{
return !$this->channelId ? 'getUpdate loop generic' : "getUpdate loop channel {$this->channelId}";

View File

@ -25,7 +25,6 @@ class Lua
public $MadelineProto;
protected $Lua;
protected $script;
public function __magic_construct($script, $MadelineProto)
{
if (!\file_exists($script)) {
@ -34,16 +33,13 @@ 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();
}
public function __sleep()
{
return ['MadelineProto', 'script'];
}
public function __wakeup()
{
$this->Lua = new \Lua($this->script);
@ -77,7 +73,6 @@ class Lua
$this->MadelineProto->{$namespace}->lua = true;
}
}
public function tdcliFunction($params, $cb = null, $cb_extra = null)
{
$params = $this->MadelineProto->td_to_mtproto($this->MadelineProto->tdcliToTd($params));
@ -88,10 +83,8 @@ class Lua
if (\is_callable($cb)) {
$cb($this->MadelineProto->mtproto_to_td($result), $cb_extra);
}
return $result;
}
public function madelineFunction($params, $cb = null, $cb_extra = null)
{
$result = $this->MadelineProto->API->methodCall($params['_'], $params, ['datacenter' => $this->MadelineProto->API->datacenter->curdc]);
@ -99,15 +92,12 @@ class Lua
$cb($result, $cb_extra);
}
self::convertObjects($result);
return $result;
}
public function tdcliUpdateCallback($update)
{
$this->Lua->tdcliUpdateCallback($this->MadelineProto->mtproto_to_tdcli($update));
}
private function convertArray($array)
{
if (!\is_array($array)) {
@ -119,29 +109,23 @@ class Lua
}, \array_flip($array)));
}
}
private function isSequential(array $arr)
{
if ([] === $arr) {
return false;
}
return isset($arr[0]) && \array_keys($arr) === \range(0, \count($arr) - 1);
}
public function __get($name)
{
if ($name === 'API') {
return $this->MadelineProto->API;
}
return $this->Lua->{$name};
}
public function __call($name, $params)
{
self::convertObjects($params);
try {
return $this->Lua->{$name}(...$params);
} catch (\danog\MadelineProto\RPCErrorException $e) {
@ -160,12 +144,10 @@ class Lua
return ['error_code' => $e->getCode(), 'error' => $e->getMessage()];
}
}
public function __set($name, $value)
{
return $this->Lua->{$name} = $value;
}
public static function convertObjects(&$data)
{
\array_walk_recursive($data, function (&$value, $key) {

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,5 @@
<?php
/**
* MTProto Auth key.
*
@ -43,7 +44,6 @@ abstract class AuthKey implements JsonSerializable
* @var string
*/
protected $serverSalt;
/**
* Constructor function.
*
@ -52,7 +52,7 @@ abstract class AuthKey implements JsonSerializable
public function __construct(array $old = [])
{
if (isset($old['auth_key'])) {
if (\strlen($old['auth_key']) !== 2048/8 && \strpos($old['authkey'], 'pony') === 0) {
if (\strlen($old['auth_key']) !== 2048 / 8 && \strpos($old['authkey'], 'pony') === 0) {
$old['auth_key'] = \base64_decode(\substr($old['auth_key'], 4));
}
$this->setAuthKey($old['auth_key']);
@ -61,8 +61,6 @@ abstract class AuthKey implements JsonSerializable
$this->setServerSalt($old['server_salt']);
}
}
/**
* Set auth key.
*
@ -75,7 +73,6 @@ abstract class AuthKey implements JsonSerializable
$this->authKey = $authKey;
$this->id = \substr(\sha1($authKey, true), -8);
}
/**
* Check if auth key is present.
*
@ -85,7 +82,6 @@ abstract class AuthKey implements JsonSerializable
{
return $this->authKey !== null;
}
/**
* Get auth key.
*
@ -95,7 +91,6 @@ abstract class AuthKey implements JsonSerializable
{
return $this->authKey;
}
/**
* Get auth key ID.
*
@ -105,7 +100,6 @@ abstract class AuthKey implements JsonSerializable
{
return $this->id;
}
/**
* Set server salt.
*
@ -117,7 +111,6 @@ abstract class AuthKey implements JsonSerializable
{
$this->serverSalt = $salt;
}
/**
* Get server salt.
*
@ -127,7 +120,6 @@ abstract class AuthKey implements JsonSerializable
{
return $this->serverSalt;
}
/**
* Check if has server salt.
*
@ -137,14 +129,12 @@ abstract class AuthKey implements JsonSerializable
{
return $this->serverSalt !== null;
}
/**
* Check if we are logged in.
*
* @return boolean
*/
abstract public function isAuthorized(): bool;
/**
* Set the authorized boolean.
*

View File

@ -1,4 +1,5 @@
<?php
/**
* MTProto permanent auth key.
*
@ -29,7 +30,6 @@ class PermAuthKey extends AuthKey
* @var boolean
*/
private $authorized = false;
/**
* Constructor function.
*
@ -51,7 +51,6 @@ class PermAuthKey extends AuthKey
{
return $this->authorized;
}
/**
* Set the authorized boolean.
*
@ -63,8 +62,6 @@ class PermAuthKey extends AuthKey
{
$this->authorized = $authorized;
}
/**
* JSON serialization function.
*
@ -72,11 +69,7 @@ class PermAuthKey extends AuthKey
*/
public function jsonSerialize(): array
{
return [
'auth_key' => 'pony'.\base64_encode($this->authKey),
'server_salt' => $this->serverSalt,
'authorized' => $this->authorized
];
return ['auth_key' => 'pony' . \base64_encode($this->authKey), 'server_salt' => $this->serverSalt, 'authorized' => $this->authorized];
}
/**
* Sleep function.
@ -85,11 +78,6 @@ class PermAuthKey extends AuthKey
*/
public function __sleep()
{
return [
'authKey',
'id',
'serverSalt',
'authorized'
];
return ['authKey', 'id', 'serverSalt', 'authorized'];
}
}

View File

@ -1,4 +1,5 @@
<?php
/**
* MTProto temporary auth key.
*
@ -31,21 +32,18 @@ class TempAuthKey extends AuthKey implements JsonSerializable
* @var PermAuthKey|null
*/
private $bound;
/**
* Expiration date.
*
* @var int
*/
private $expires = 0;
/**
* Whether the connection is inited for this auth key.
*
* @var boolean
*/
protected $inited = false;
/**
* Constructor function.
*
@ -61,7 +59,6 @@ class TempAuthKey extends AuthKey implements JsonSerializable
$this->init($old['connection_inited']);
}
}
/**
* Init or deinit connection for auth key.
*
@ -82,7 +79,6 @@ class TempAuthKey extends AuthKey implements JsonSerializable
{
return $this->inited;
}
/**
* Bind auth key.
*
@ -96,11 +92,10 @@ class TempAuthKey extends AuthKey implements JsonSerializable
$this->bound = $bound;
if (!$pfs) {
foreach (['authKey', 'id', 'serverSalt'] as $key) {
$this->{$key} = &$bound->{$key};
$this->{$key} =& $bound->{$key};
}
}
}
/**
* Check if auth key is bound.
*
@ -110,7 +105,6 @@ class TempAuthKey extends AuthKey implements JsonSerializable
{
return $this->bound !== null;
}
/**
* Check if we are logged in.
*
@ -120,7 +114,6 @@ class TempAuthKey extends AuthKey implements JsonSerializable
{
return $this->bound ? $this->bound->isAuthorized() : false;
}
/**
* Set the authorized boolean.
*
@ -132,7 +125,6 @@ class TempAuthKey extends AuthKey implements JsonSerializable
{
$this->bound->authorized($authorized);
}
/**
* Set expiration date of temporary auth key.
*
@ -144,7 +136,6 @@ class TempAuthKey extends AuthKey implements JsonSerializable
{
$this->expires = $expires;
}
/**
* Check if auth key has expired.
*
@ -154,7 +145,6 @@ class TempAuthKey extends AuthKey implements JsonSerializable
{
return \time() > $this->expires;
}
/**
* JSON serialization function.
*
@ -162,15 +152,8 @@ class TempAuthKey extends AuthKey implements JsonSerializable
*/
public function jsonSerialize(): array
{
return [
'auth_key' => 'pony'.\base64_encode($this->authKey),
'server_salt' => $this->serverSalt,
'bound' => $this->isBound(),
'expires' => $this->expires,
'connection_inited' => $this->inited
];
return ['auth_key' => 'pony' . \base64_encode($this->authKey), 'server_salt' => $this->serverSalt, 'bound' => $this->isBound(), 'expires' => $this->expires, 'connection_inited' => $this->inited];
}
/**
* Sleep function.
*
@ -178,14 +161,7 @@ class TempAuthKey extends AuthKey implements JsonSerializable
*/
public function __sleep()
{
return [
'authKey',
'id',
'serverSalt',
'bound',
'expires',
'inited'
];
return ['authKey', 'id', 'serverSalt', 'bound', 'expires', 'inited'];
}
/**
* Wakeup function.

View File

@ -28,31 +28,27 @@ trait AckHandler
{
// The server acknowledges that it received my message
if (!isset($this->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);
$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;
}
//$this->logger->logger("Ack-ed ".$this->outgoing_messages[$message_id]['_']." with message ID $message_id on DC $datacenter");
/*
if (isset($this->outgoing_messages[$message_id]['body'])) {
unset($this->outgoing_messages[$message_id]['body']);
}
if (isset($this->new_outgoing[$message_id])) {
unset($this->new_outgoing[$message_id]);
}*/
if (isset($this->outgoing_messages[$message_id]['body'])) {
unset($this->outgoing_messages[$message_id]['body']);
}
if (isset($this->new_outgoing[$message_id])) {
unset($this->new_outgoing[$message_id]);
}*/
return true;
}
public function gotResponseForOutgoingMessageId($message_id): bool
{
// The server acknowledges that it received my message
if (isset($this->new_outgoing[$message_id])) {
unset($this->new_outgoing[$message_id]);
}
if (!isset($this->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);
$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->outgoing_messages[$message_id]['body'])) {
@ -61,28 +57,21 @@ trait AckHandler
if (isset($this->outgoing_messages[$message_id]['serialized_body'])) {
unset($this->outgoing_messages[$message_id]['serialized_body']);
}
return true;
}
public function ackIncomingMessageId($message_id): bool
{
// I let the server know that I received its message
if (!isset($this->incoming_messages[$message_id])) {
$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);
$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->temp_auth_key['id'] === null || $this->temp_auth_key['id'] === "\0\0\0\0\0\0\0\0") {
// || (isset($this->incoming_messages[$message_id]['ack']) && $this->incoming_messages[$message_id]['ack'])) {
return;
}*/
// || (isset($this->incoming_messages[$message_id]['ack']) && $this->incoming_messages[$message_id]['ack'])) {
return;
}*/
$this->ack_queue[$message_id] = $message_id;
return true;
}
/**
* Check if there are some pending calls.
*
@ -95,26 +84,17 @@ trait AckHandler
$pfs = $settings['pfs'];
$unencrypted = !$this->shared->hasTempAuthKey();
$notBound = !$this->shared->isBound();
$pfsNotBound = $pfs && $notBound;
foreach ($this->new_outgoing as $message_id) {
if (isset($this->outgoing_messages[$message_id]['sent'])
&& $this->outgoing_messages[$message_id]['sent'] + $timeout < \time()
&& $unencrypted === $this->outgoing_messages[$message_id]['unencrypted']
&& $this->outgoing_messages[$message_id]['_'] !== 'msgs_state_req'
) {
if (isset($this->outgoing_messages[$message_id]['sent']) && $this->outgoing_messages[$message_id]['sent'] + $timeout < \time() && $unencrypted === $this->outgoing_messages[$message_id]['unencrypted'] && $this->outgoing_messages[$message_id]['_'] !== 'msgs_state_req') {
if ($pfsNotBound && $this->outgoing_messages[$message_id]['_'] !== 'auth.bindTempAuthKey') {
continue;
}
return true;
}
}
return false;
}
/**
* Get all pending calls (also clear pending state requests).
*
@ -127,28 +107,20 @@ trait AckHandler
$pfs = $settings['pfs'];
$unencrypted = !$this->shared->hasTempAuthKey();
$notBound = !$this->shared->isBound();
$pfsNotBound = $pfs && $notBound;
$result = [];
foreach ($this->new_outgoing as $k => $message_id) {
if (isset($this->outgoing_messages[$message_id]['sent'])
&& $this->outgoing_messages[$message_id]['sent'] + $timeout < \time()
&& $unencrypted === $this->outgoing_messages[$message_id]['unencrypted']
) {
if (isset($this->outgoing_messages[$message_id]['sent']) && $this->outgoing_messages[$message_id]['sent'] + $timeout < \time() && $unencrypted === $this->outgoing_messages[$message_id]['unencrypted']) {
if ($pfsNotBound && $this->outgoing_messages[$message_id]['_'] !== 'auth.bindTempAuthKey') {
continue;
}
if ($this->outgoing_messages[$message_id]['_'] === 'msgs_state_req') {
unset($this->new_outgoing[$k], $this->outgoing_messages[$message_id]);
continue;
}
$result[] = $message_id;
}
}
return $result;
}
}

View File

@ -24,7 +24,6 @@ use Amp\Promise;
use Amp\Success;
use danog\MadelineProto\Async\AsyncParameters;
use danog\MadelineProto\Tools;
use function Amp\Promise\all;
/**
@ -48,17 +47,13 @@ trait CallHandler
if ($datacenter === $this->datacenter) {
$datacenter = false;
}
$message_ids = $this->outgoing_messages[$message_id]['container'] ?? [$message_id];
foreach ($message_ids as $message_id) {
if (isset($this->outgoing_messages[$message_id]['body'])) {
if ($datacenter) {
$res = $this->API->datacenter->waitGetConnection($datacenter)->onResolve(
function ($e, $r) use ($message_id) {
return $r->sendMessage($this->outgoing_messages[$message_id], false);
}
);
$res = $this->API->datacenter->waitGetConnection($datacenter)->onResolve(function ($e, $r) use ($message_id) {
return $r->sendMessage($this->outgoing_messages[$message_id], false);
});
} else {
$res = $this->sendMessage($this->outgoing_messages[$message_id], false);
}
@ -66,7 +61,7 @@ trait CallHandler
$this->ackOutgoingMessageId($message_id);
$this->gotResponseForOutgoingMessageId($message_id);
} else {
$this->logger->logger('Could not resend '.(isset($this->outgoing_messages[$message_id]['_']) ? $this->outgoing_messages[$message_id]['_'] : $message_id));
$this->logger->logger('Could not resend ' . (isset($this->outgoing_messages[$message_id]['_']) ? $this->outgoing_messages[$message_id]['_'] : $message_id));
}
}
if (!$postpone) {
@ -77,7 +72,6 @@ trait CallHandler
}
}
}
/**
* Call method and wait asynchronously for response.
*
@ -97,22 +91,17 @@ trait CallHandler
$deferred->fail($e);
} else {
if (\is_array($read_deferred)) {
$read_deferred = \array_map(
function ($value) {
return $value->promise();
},
$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 ($aargs['noResponse'] ?? false) ? new Success() : $deferred->promise();
return $aargs['noResponse'] ?? false ? new Success() : $deferred->promise();
}
/**
* Call method and make sure it is asynchronously sent.
*
@ -126,7 +115,6 @@ trait CallHandler
{
return \danog\MadelineProto\Tools::call($this->methodCallAsyncWriteGenerator($method, $args, $aargs));
}
/**
* Call method and make sure it is asynchronously sent (generator).
*
@ -138,24 +126,18 @@ trait CallHandler
*/
public function methodCallAsyncWriteGenerator(string $method, $args = [], array $aargs = ['msg_id' => null]): \Generator
{
if (\is_array($args)
&& isset($args['id']['_'])
&& isset($args['id']['dc_id'])
&& $args['id']['_'] === 'inputBotInlineMessageID'
&& $this->datacenter !== $args['id']['dc_id']
) {
if (\is_array($args) && isset($args['id']['_']) && isset($args['id']['dc_id']) && $args['id']['_'] === 'inputBotInlineMessageID' && $this->datacenter !== $args['id']['dc_id']) {
$aargs['datacenter'] = $args['id']['dc_id'];
return $this->API->methodCallAsyncWriteGenerator($method, $args, $aargs);
}
if (($aargs['file'] ?? false) && !$this->isMedia() && $this->API->datacenter->has($this->datacenter.'_media')) {
if (($aargs['file'] ?? false) && !$this->isMedia() && $this->API->datacenter->has($this->datacenter . '_media')) {
$this->logger->logger('Using media DC');
$aargs['datacenter'] = $this->datacenter.'_media';
$aargs['datacenter'] = $this->datacenter . '_media';
return $this->API->methodCallAsyncWriteGenerator($method, $args, $aargs);
}
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['multiple'])) {
$aargs['multiple'] = true;
@ -170,18 +152,15 @@ trait CallHandler
$new_aargs = $aargs;
$new_aargs['postpone'] = true;
unset($new_aargs['multiple']);
if (isset($args['multiple'])) {
unset($args['multiple']);
}
foreach ($args as $single_args) {
$promises[] = $this->methodCallAsyncWrite($method, $single_args, $new_aargs);
}
if (!isset($aargs['postpone'])) {
$this->writer->resume();
}
return yield all($promises);
}
$args = yield $this->API->botAPIToMTProto($args);
@ -189,37 +168,21 @@ trait CallHandler
$args['ping_id'] = Tools::packSignedLong($args['ping_id']);
}
}
$deferred = new Deferred();
$message = \array_merge(
$aargs,
[
'_' => $method,
'type' => $this->API->getTL()->getMethods()->findByMethod($method)['type'],
'contentRelated' => $this->contentRelated($method),
'promise' => $deferred,
'method' => true,
'unencrypted' => !$this->shared->hasTempAuthKey() && \strpos($method, '.') === false
]
);
$message = \array_merge($aargs, ['_' => $method, 'type' => $this->API->getTL()->getMethods()->findByMethod($method)['type'], 'contentRelated' => $this->contentRelated($method), 'promise' => $deferred, 'method' => true, 'unencrypted' => !$this->shared->hasTempAuthKey() && \strpos($method, '.') === false]);
if (\is_object($args) && $args instanceof AsyncParameters) {
$message['body'] = yield $args->fetchParameters();
} else {
$message['body'] = $args;
}
if (($method === 'users.getUsers' && $args === ['id' => [['_' => 'inputUserSelf']]]) || $method === 'auth.exportAuthorization' || $method === 'updates.getDifference') {
if ($method === 'users.getUsers' && $args === ['id' => [['_' => 'inputUserSelf']]] || $method === 'auth.exportAuthorization' || $method === 'updates.getDifference') {
$message['user_related'] = true;
}
$aargs['postpone'] = $aargs['postpone'] ?? false;
$deferred = yield $this->sendMessage($message, !$aargs['postpone']);
$this->checker->resume();
return $deferred;
}
/**
* Send object and make sure it is asynchronously sent (generator).
*
@ -235,7 +198,6 @@ trait CallHandler
if (isset($aargs['promise'])) {
$message['promise'] = $aargs['promise'];
}
$aargs['postpone'] = $aargs['postpone'] ?? false;
return $this->sendMessage($message, !$aargs['postpone']);
}

View File

@ -26,7 +26,6 @@ trait MsgIdHandler
{
public $max_incoming_id;
public $max_outgoing_id;
public function checkMessageId($new_message_id, $aargs)
{
if (!\is_object($new_message_id)) {
@ -34,18 +33,18 @@ trait MsgIdHandler
}
$min_message_id = (new \tgseclib\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);
$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 \tgseclib\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.');
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.');
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->getMaxId($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);
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);
@ -63,11 +62,11 @@ trait MsgIdHandler
$key = $this->getMaxId($incoming = true);
if ($aargs['container']) {
if ($new_message_id->compare($key = $this->getMaxId($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);
$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->getMaxId($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);
$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']) {
@ -81,7 +80,6 @@ trait MsgIdHandler
$this->incoming_messages[\strrev($new_message_id->toBytes())] = [];
}
}
public function generateMessageId()
{
$message_id = (new \tgseclib\Math\BigInteger(\time() + $this->time_delta))->bitwise_leftShift(32);
@ -89,17 +87,14 @@ trait MsgIdHandler
$message_id = $key->add(\danog\MadelineProto\Magic::$four);
}
$this->checkMessageId($message_id, ['outgoing' => true, 'container' => false]);
return \strrev($message_id->toBytes());
}
public function getMaxId($incoming)
{
$incoming = $incoming ? 'incoming' : 'outgoing';
if (isset($this->{'max_'.$incoming.'_id'}) && \is_object($this->{'max_'.$incoming.'_id'})) {
return $this->{'max_'.$incoming.'_id'};
if (isset($this->{'max_' . $incoming . '_id'}) && \is_object($this->{'max_' . $incoming . '_id'})) {
return $this->{'max_' . $incoming . '_id'};
}
return \danog\MadelineProto\Magic::$zero;
}
}

View File

@ -28,35 +28,33 @@ use danog\MadelineProto\MTProto;
*/
trait ResponseHandler
{
public function sendMsgsStateInfo($req_msg_id, $msg_ids)
public function sendMsgsStateInfo($req_msg_id, $msg_ids): \Generator
{
$this->logger->logger('Sending state info for '.\count($msg_ids).' message IDs');
$this->logger->logger('Sending state info for ' . \count($msg_ids) . ' message IDs');
$info = '';
foreach ($msg_ids as $msg_id) {
$cur_info = 0;
if (!isset($this->incoming_messages[$msg_id])) {
$msg_id = new \tgseclib\Math\BigInteger(\strrev($msg_id), 256);
if ((new \tgseclib\Math\BigInteger(\time() + $this->time_delta + 30))->bitwise_leftShift(32)->compare($msg_id) < 0) {
$this->logger->logger("Do not know anything about $msg_id and it is too small");
$this->logger->logger("Do not know anything about {$msg_id} and it is too small");
$cur_info |= 3;
} elseif ((new \tgseclib\Math\BigInteger(\time() + $this->time_delta - 300))->bitwise_leftShift(32)->compare($msg_id) > 0) {
$this->logger->logger("Do not know anything about $msg_id and it is too big");
$this->logger->logger("Do not know anything about {$msg_id} and it is too big");
$cur_info |= 1;
} else {
$this->logger->logger("Do not know anything about $msg_id");
$this->logger->logger("Do not know anything about {$msg_id}");
$cur_info |= 2;
}
} else {
$this->logger->logger("Know about $msg_id");
$this->logger->logger("Know about {$msg_id}");
$cur_info |= 4;
}
$info .= \chr($cur_info);
}
$this->outgoing_messages[yield $this->objectCall('msgs_state_info', ['req_msg_id' => $req_msg_id, 'info' => $info], ['postpone' => true])]['response'] = $req_msg_id;
}
public $n = 0;
public function handleMessages()
{
$only_updates = true;
@ -67,8 +65,7 @@ trait ResponseHandler
unset($this->new_incoming[$current_msg_id]);
continue;
}
$this->logger->logger((isset($this->incoming_messages[$current_msg_id]['from_container']) ? 'Inside of container, received ' : 'Received ').$this->incoming_messages[$current_msg_id]['content']['_'].' from DC '.$this->datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->logger->logger((isset($this->incoming_messages[$current_msg_id]['from_container']) ? 'Inside of container, received ' : 'Received ') . $this->incoming_messages[$current_msg_id]['content']['_'] . ' from DC ' . $this->datacenter, \danog\MadelineProto\Logger::ULTRA_VERBOSE);
switch ($this->incoming_messages[$current_msg_id]['content']['_']) {
case 'msgs_ack':
unset($this->new_incoming[$current_msg_id]);
@ -78,7 +75,6 @@ trait ResponseHandler
$this->ackOutgoingMessageId($msg_id);
// Acknowledge that the server received my message
}
unset($this->incoming_messages[$current_msg_id]['content']);
break;
case 'rpc_result':
@ -89,47 +85,39 @@ trait ResponseHandler
$req_msg_id = $this->incoming_messages[$current_msg_id]['content']['req_msg_id'];
$this->incoming_messages[$current_msg_id]['content'] = $this->incoming_messages[$current_msg_id]['content']['result'];
$this->checkInSeqNo($current_msg_id);
$this->handleResponse($req_msg_id, $current_msg_id);
break;
case 'future_salts':
case 'msgs_state_info':
$msg_id_type = 'req_msg_id';
// no break
// no break
case 'bad_server_salt':
case 'bad_msg_notification':
$msg_id_type = isset($msg_id_type) ? $msg_id_type : 'bad_msg_id';
// no break
// no break
case 'pong':
$msg_id_type = isset($msg_id_type) ? $msg_id_type : 'msg_id';
unset($this->new_incoming[$current_msg_id]);
$this->checkInSeqNo($current_msg_id);
$only_updates = false;
$this->handleResponse($this->incoming_messages[$current_msg_id]['content'][$msg_id_type], $current_msg_id);
unset($msg_id_type);
break;
case 'new_session_created':
unset($this->new_incoming[$current_msg_id]);
$this->checkInSeqNo($current_msg_id);
$only_updates = false;
$this->shared->getTempAuthKey()->setServerSalt($this->incoming_messages[$current_msg_id]['content']['server_salt']);
$this->ackIncomingMessageId($current_msg_id);
// Acknowledge that I received the server's response
if ($this->API->authorized === MTProto::LOGGED_IN && !$this->API->isInitingAuthorization() && $this->API->datacenter->getDataCenterConnection($this->API->datacenter->curdc)->hasTempAuthKey() && isset($this->API->updaters[false])) {
$this->API->updaters[false]->resumeDefer();
}
unset($this->incoming_messages[$current_msg_id]['content']);
break;
case 'msg_container':
unset($this->new_incoming[$current_msg_id]);
$only_updates = false;
foreach ($this->incoming_messages[$current_msg_id]['content']['messages'] as $message) {
$this->checkMessageId($message['msg_id'], ['outgoing' => false, 'container' => true]);
$this->incoming_messages[$message['msg_id']] = ['seq_no' => $message['seqno'], 'content' => $message['body'], 'from_container' => true];
@ -138,14 +126,12 @@ trait ResponseHandler
\ksort($this->new_incoming);
//$this->handleMessages();
//$this->checkInSeqNo($current_msg_id);
unset($this->incoming_messages[$current_msg_id]['content']);
break;
case 'msg_copy':
unset($this->new_incoming[$current_msg_id]);
$this->checkInSeqNo($current_msg_id);
$only_updates = false;
$this->ackIncomingMessageId($current_msg_id);
// Acknowledge that I received the server's response
if (isset($this->incoming_messages[$this->incoming_messages[$current_msg_id]['content']['orig_message']['msg_id']])) {
@ -157,25 +143,19 @@ trait ResponseHandler
$this->incoming_messages[$message['orig_message']['msg_id']] = ['content' => $this->incoming_messages[$current_msg_id]['content']['orig_message']];
$this->new_incoming[$message['orig_message']['msg_id']] = $message['orig_message']['msg_id'];
}
unset($this->incoming_messages[$current_msg_id]['content']);
break;
case 'http_wait':
unset($this->new_incoming[$current_msg_id]);
$this->checkInSeqNo($current_msg_id);
$only_updates = false;
$this->logger->logger($this->incoming_messages[$current_msg_id]['content'], \danog\MadelineProto\Logger::NOTICE);
unset($this->incoming_messages[$current_msg_id]['content']);
break;
case 'msgs_state_req':
$this->checkInSeqNo($current_msg_id);
$only_updates = false;
unset($this->new_incoming[$current_msg_id]);
\danog\MadelineProto\Tools::callFork($this->sendMsgsStateInfo($current_msg_id, $this->incoming_messages[$current_msg_id]['content']['msg_ids']));
unset($this->incoming_messages[$current_msg_id]['content']);
break;
@ -183,11 +163,10 @@ trait ResponseHandler
$this->checkInSeqNo($current_msg_id);
$only_updates = false;
unset($this->new_incoming[$current_msg_id]);
foreach ($this->incoming_messages[$current_msg_id]['content']['msg_ids'] as $key => $msg_id) {
$info = \ord($this->incoming_messages[$current_msg_id]['content']['info'][$key]);
$msg_id = new \tgseclib\Math\BigInteger(\strrev($msg_id), 256);
$status = 'Status for message id '.$msg_id.': ';
$status = 'Status for message id ' . $msg_id . ': ';
/*if ($info & 4) {
*$this->gotResponseForOutgoingMessageId($msg_id);
*}
@ -203,7 +182,6 @@ trait ResponseHandler
case 'msg_detailed_info':
$this->checkInSeqNo($current_msg_id);
unset($this->new_incoming[$current_msg_id]);
$only_updates = false;
if (isset($this->outgoing_messages[$this->incoming_messages[$current_msg_id]['content']['msg_id']])) {
if (isset($this->incoming_messages[$this->incoming_messages[$current_msg_id]['content']['answer_msg_id']])) {
@ -217,7 +195,6 @@ trait ResponseHandler
$this->checkInSeqNo($current_msg_id);
$only_updates = false;
unset($this->new_incoming[$current_msg_id]);
if (isset($this->incoming_messages[$this->incoming_messages[$current_msg_id]['content']['answer_msg_id']])) {
$this->ackIncomingMessageId($this->incoming_messages[$current_msg_id]['content']['answer_msg_id']);
} else {
@ -228,7 +205,6 @@ trait ResponseHandler
$this->checkInSeqNo($current_msg_id);
$only_updates = false;
unset($this->new_incoming[$current_msg_id]);
$ok = true;
foreach ($this->incoming_messages[$current_msg_id]['content']['msg_ids'] as $msg_id) {
if (!isset($this->outgoing_messages[$msg_id]) || isset($this->incoming_messages[$msg_id])) {
@ -247,7 +223,6 @@ trait ResponseHandler
$this->checkInSeqNo($current_msg_id);
$only_updates = false;
unset($this->new_incoming[$current_msg_id]);
\danog\MadelineProto\Tools::callFork($this->sendMsgsStateInfo($current_msg_id, $this->incoming_messages[$current_msg_id]['content']['msg_ids']));
foreach ($this->incoming_messages[$current_msg_id]['content']['msg_ids'] as $msg_id) {
if (isset($this->incoming_messages[$msg_id]['response']) && isset($this->outgoing_messages[$this->incoming_messages[$msg_id]['response']])) {
@ -260,29 +235,24 @@ trait ResponseHandler
$this->ackIncomingMessageId($current_msg_id);
// Acknowledge that I received the server's response
$response_type = $this->API->getTL()->getConstructors()->findByPredicate($this->incoming_messages[$current_msg_id]['content']['_'])['type'];
switch ($response_type) {
case 'Updates':
unset($this->new_incoming[$current_msg_id]);
if (!$this->isCdn()) {
\danog\MadelineProto\Tools::callForkDefer($this->API->handleUpdates($this->incoming_messages[$current_msg_id]['content']));
}
unset($this->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);
$this->logger->logger('Trying to assign a response of type ' . $response_type . ' to its request...', \danog\MadelineProto\Logger::VERBOSE);
foreach ($this->new_outgoing as $key => $expecting_msg_id) {
$expecting = $this->outgoing_messages[$expecting_msg_id];
if (!isset($expecting['type'])) {
continue;
}
$this->logger->logger('Does the request of return type '.$expecting['type'].' match?', \danog\MadelineProto\Logger::VERBOSE);
$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);
unset($this->new_incoming[$current_msg_id]);
@ -291,8 +261,7 @@ trait ResponseHandler
}
$this->logger->logger('No', \danog\MadelineProto\Logger::VERBOSE);
}
$this->logger->logger('Dunno how to handle '.PHP_EOL.\var_export($this->incoming_messages[$current_msg_id]['content'], true), \danog\MadelineProto\Logger::FATAL_ERROR);
$this->logger->logger('Dunno how to handle ' . PHP_EOL . \var_export($this->incoming_messages[$current_msg_id]['content'], true), \danog\MadelineProto\Logger::FATAL_ERROR);
unset($this->new_incoming[$current_msg_id]);
break;
}
@ -302,12 +271,9 @@ trait ResponseHandler
if ($this->pending_outgoing) {
$this->writer->resume();
}
//$this->n--;
return $only_updates;
}
/**
* Reject request with exception.
*
@ -321,9 +287,8 @@ trait ResponseHandler
if (isset($request['promise']) && \is_object($request['promise'])) {
Loop::defer(function () use (&$request, $data) {
if (isset($request['promise'])) {
$this->logger->logger('Rejecting: '.(isset($request['_']) ? $request['_'] : '-'));
$this->logger->logger("Rejecting: $data");
$this->logger->logger('Rejecting: ' . (isset($request['_']) ? $request['_'] : '-'));
$this->logger->logger("Rejecting: {$data}");
$promise = $request['promise'];
unset($request['promise']);
try {
@ -335,8 +300,8 @@ trait ResponseHandler
$this->logger->logger("Got promise already resolved error", \danog\MadelineProto\Logger::FATAL_ERROR);
}
} else {
$this->logger->logger('Rejecting: already got response for '.(isset($request['_']) ? $request['_'] : '-'));
$this->logger->logger("Rejecting: $data");
$this->logger->logger('Rejecting: already got response for ' . (isset($request['_']) ? $request['_'] : '-'));
$this->logger->logger("Rejecting: {$data}");
}
});
} elseif (isset($request['container'])) {
@ -344,28 +309,24 @@ trait ResponseHandler
$this->handleReject($this->outgoing_messages[$message_id], $data);
}
} else {
$this->logger->logger('Rejecting: already got response for '.(isset($request['_']) ? $request['_'] : '-'));
$this->logger->logger("Rejecting: $data");
$this->logger->logger('Rejecting: already got response for ' . (isset($request['_']) ? $request['_'] : '-'));
$this->logger->logger("Rejecting: {$data}");
}
}
public function handleResponse($request_id, $response_id)
{
$response = &$this->incoming_messages[$response_id]['content'];
$response =& $this->incoming_messages[$response_id]['content'];
unset($this->incoming_messages[$response_id]['content']);
$request = &$this->outgoing_messages[$request_id];
$request =& $this->outgoing_messages[$request_id];
if (isset($response['_'])) {
switch ($response['_']) {
case 'rpc_error':
if (($request['method'] ?? false) && $request['_'] !== 'auth.bindTempAuthKey' && $this->shared->hasTempAuthKey() && !$this->shared->getTempAuthKey()->isInited()) {
$this->shared->getTempAuthKey()->init(true);
}
if (\in_array($response['error_message'], ['PERSISTENT_TIMESTAMP_EMPTY', 'PERSISTENT_TIMESTAMP_INVALID'])) {
$this->gotResponseForOutgoingMessageId($request_id);
$this->handleReject($request, new \danog\MadelineProto\PTSException($response['error_message']));
return;
}
if ($response['error_message'] === 'PERSISTENT_TIMESTAMP_OUTDATED') {
@ -373,14 +334,11 @@ trait ResponseHandler
}
if (\strpos($response['error_message'], 'FILE_REFERENCE_') === 0) {
$this->logger->logger("Got {$response['error_message']}, refreshing file reference and repeating method call...");
$request['refreshReferences'] = true;
if (isset($request['serialized_body'])) {
unset($request['serialized_body']);
}
$this->methodRecall('', ['message_id' => $request_id, 'postpone' => true]);
return;
}
switch ($response['error_code']) {
@ -389,32 +347,26 @@ trait ResponseHandler
if ($response['error_message'] === 'MSG_WAIT_FAILED') {
$this->call_queue[$request['queue']] = [];
$this->methodRecall('', ['message_id' => $request_id, 'postpone' => true]);
return;
}
if (\in_array($response['error_message'], ['MSGID_DECREASE_RETRY', 'HISTORY_GET_FAILED', 'RPC_CALL_FAIL', 'PERSISTENT_TIMESTAMP_OUTDATED', 'RPC_MCGET_FAIL', 'no workers running', 'No workers running'])) {
Loop::delay(1 * 1000, [$this, 'methodRecall'], ['message_id' => $request_id, ]);
Loop::delay(1 * 1000, [$this, 'methodRecall'], ['message_id' => $request_id]);
return;
}
$this->gotResponseForOutgoingMessageId($request_id);
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
return;
case 303:
$this->API->datacenter->curdc = $datacenter = (int) \preg_replace('/[^0-9]+/', '', $response['error_message']);
if (isset($request['file']) && $request['file'] && $this->API->datacenter->has($datacenter.'_media')) {
if (isset($request['file']) && $request['file'] && $this->API->datacenter->has($datacenter . '_media')) {
\danog\MadelineProto\Logger::log('Using media DC');
$datacenter .= '_media';
}
if (isset($request['user_related']) && $request['user_related']) {
$this->API->settings['connection_settings']['default_dc'] = $this->API->authorized_dc = $this->API->datacenter->curdc;
}
Loop::defer([$this, 'methodRecall'], ['message_id' => $request_id, 'datacenter' => $datacenter]);
//$this->API->methodRecall('', ['message_id' => $request_id, 'datacenter' => $datacenter, 'postpone' => true]);
return;
case 401:
switch ($response['error_message']) {
@ -422,123 +374,95 @@ trait ResponseHandler
case 'SESSION_REVOKED':
case 'SESSION_EXPIRED':
$this->gotResponseForOutgoingMessageId($request_id);
$this->logger->logger($response['error_message'], \danog\MadelineProto\Logger::FATAL_ERROR);
foreach ($this->API->datacenter->getDataCenterConnections() as $socket) {
$socket->setTempAuthKey(null);
$socket->setPermAuthKey(null);
$socket->resetSession();
}
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 shortly describe what will you do with this phone number.', \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 shortly 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->API->resetSession();
\danog\MadelineProto\Tools::callFork((function () use (&$request, &$response) {
\danog\MadelineProto\Tools::callFork((function () use (&$request, &$response): \Generator {
yield $this->API->initAuthorization();
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
})());
return;
case 'AUTH_KEY_UNREGISTERED':
case 'AUTH_KEY_INVALID':
if ($this->API->authorized !== MTProto::LOGGED_IN) {
$this->gotResponseForOutgoingMessageId($request_id);
\danog\MadelineProto\Tools::callFork((function () use (&$request, &$response) {
\danog\MadelineProto\Tools::callFork((function () use (&$request, &$response): \Generator {
yield $this->API->initAuthorization();
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
})());
return;
}
$this->session_id = null;
$this->shared->setTempAuthKey(null);
$this->shared->setPermAuthKey(null);
$this->logger->logger('Auth key not registered, resetting temporary and permanent auth keys...', \danog\MadelineProto\Logger::ERROR);
if ($this->API->authorized_dc === $this->datacenter && $this->API->authorized === MTProto::LOGGED_IN) {
$this->gotResponseForOutgoingMessageId($request_id);
$this->logger->logger('Permanent auth key was main authorized key, logging out...', \danog\MadelineProto\Logger::FATAL_ERROR);
foreach ($this->API->datacenter->getDataCenterConnections() as $socket) {
$socket->setTempAuthKey(null);
$socket->setPermAuthKey(null);
}
$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);
$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->API->resetSession();
\danog\MadelineProto\Tools::callFork((function () use (&$request, &$response) {
\danog\MadelineProto\Tools::callFork((function () use (&$request, &$response): \Generator {
yield $this->API->initAuthorization();
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
})());
return;
}
\danog\MadelineProto\Tools::callFork((function () use ($request_id) {
\danog\MadelineProto\Tools::callFork((function () use ($request_id): \Generator {
yield $this->API->initAuthorization();
$this->methodRecall('', ['message_id' => $request_id, ]);
$this->methodRecall('', ['message_id' => $request_id]);
})());
return;
case 'AUTH_KEY_PERM_EMPTY':
$this->logger->logger('Temporary auth key not bound, resetting temporary auth key...', \danog\MadelineProto\Logger::ERROR);
$this->shared->setTempAuthKey(null);
\danog\MadelineProto\Tools::callFork((function () use ($request_id) {
\danog\MadelineProto\Tools::callFork((function () use ($request_id): \Generator {
yield $this->API->initAuthorization();
$this->methodRecall('', ['message_id' => $request_id, ]);
$this->methodRecall('', ['message_id' => $request_id]);
})());
return;
}
$this->gotResponseForOutgoingMessageId($request_id);
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
return;
case 420:
$seconds = \preg_replace('/[^0-9]+/', '', $response['error_message']);
$limit = $request['FloodWaitLimit'] ?? $this->API->settings['flood_timeout']['wait_if_lt'];
if (\is_numeric($seconds) && $seconds < $limit) {
//$this->gotResponseForOutgoingMessageId($request_id);
$this->logger->logger('Flood, waiting '.$seconds.' seconds before repeating async call of '.($request['_'] ?? '').'...', \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger('Flood, waiting ' . $seconds . ' seconds before repeating async call of ' . ($request['_'] ?? '') . '...', \danog\MadelineProto\Logger::NOTICE);
$request['sent'] = ($request['sent'] ?? \time()) + $seconds;
Loop::delay($seconds * 1000, [$this, 'methodRecall'], ['message_id' => $request_id, ]);
Loop::delay($seconds * 1000, [$this, 'methodRecall'], ['message_id' => $request_id]);
return;
}
// no break
default:
$this->gotResponseForOutgoingMessageId($request_id);
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException($response['error_message'], $response['error_code'], $request['_'] ?? ''));
return;
}
return;
case 'boolTrue':
case 'boolFalse':
@ -546,41 +470,35 @@ trait ResponseHandler
break;
case 'bad_server_salt':
case 'bad_msg_notification':
$this->logger->logger('Received bad_msg_notification: '.MTProto::BAD_MSG_ERROR_CODES[$response['error_code']], \danog\MadelineProto\Logger::WARNING);
$this->logger->logger('Received bad_msg_notification: ' . MTProto::BAD_MSG_ERROR_CODES[$response['error_code']], \danog\MadelineProto\Logger::WARNING);
switch ($response['error_code']) {
case 48:
$this->shared->getTempAuthKey()->setServerSalt($response['new_server_salt']);
$this->methodRecall('', ['message_id' => $request_id, 'postpone' => true]);
return;
case 16:
case 17:
$this->time_delta = (int) (new \tgseclib\Math\BigInteger(\strrev($response_id), 256))->bitwise_rightShift(32)->subtract(new \tgseclib\Math\BigInteger(\time()))->toString();
$this->logger->logger('Set time delta to '.$this->time_delta, \danog\MadelineProto\Logger::WARNING);
$this->logger->logger('Set time delta to ' . $this->time_delta, \danog\MadelineProto\Logger::WARNING);
$this->API->resetMTProtoSession();
$this->shared->setTempAuthKey(null);
\danog\MadelineProto\Tools::callFork((function () use ($request_id) {
\danog\MadelineProto\Tools::callFork((function () use ($request_id): \Generator {
yield $this->API->initAuthorization();
$this->methodRecall('', ['message_id' => $request_id, ]);
$this->methodRecall('', ['message_id' => $request_id]);
})());
return;
}
$this->gotResponseForOutgoingMessageId($request_id);
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException('Received bad_msg_notification: '.MTProto::BAD_MSG_ERROR_CODES[$response['error_code']], $response['error_code'], $request['_'] ?? ''));
$this->handleReject($request, new \danog\MadelineProto\RPCErrorException('Received bad_msg_notification: ' . MTProto::BAD_MSG_ERROR_CODES[$response['error_code']], $response['error_code'], $request['_'] ?? ''));
return;
}
}
if (($request['method'] ?? false) && $request['_'] !== 'auth.bindTempAuthKey' && $this->shared->hasTempAuthKey() && !$this->shared->getTempAuthKey()->isInited()) {
$this->shared->getTempAuthKey()->init(true);
}
if (!isset($request['promise'])) {
$this->gotResponseForOutgoingMessageId($request_id);
$this->logger->logger('Response: already got response for '.(isset($request['_']) ? $request['_'] : '-').' with message ID '.$request_id);
$this->logger->logger('Response: already got response for ' . (isset($request['_']) ? $request['_'] : '-') . ' with message ID ' . $request_id);
return;
}
$botAPI = isset($request['botAPI']) && $request['botAPI'];
@ -591,27 +509,26 @@ trait ResponseHandler
unset($request);
$this->gotResponseForOutgoingMessageId($request_id);
$r = isset($response['_']) ? $response['_'] : \json_encode($response);
$this->logger->logger("Defer sending $r to deferred", Logger::ULTRA_VERBOSE);
\danog\MadelineProto\Tools::callFork((
function () use ($request_id, $response, $botAPI) {
$r = isset($response['_']) ? $response['_'] : \json_encode($response);
$this->logger->logger("Deferred: sent $r to deferred", Logger::ULTRA_VERBOSE);
if ($botAPI) {
$response = yield $this->MTProtoToBotAPI($response);
}
if (isset($this->outgoing_messages[$request_id]['promise'])) { // This should not happen but happens, should debug
$promise = $this->outgoing_messages[$request_id]['promise'];
unset($this->outgoing_messages[$request_id]['promise']);
try {
$promise->resolve($response);
} catch (\Error $e) {
if (\strpos($e->getMessage(), "Promise has already been resolved") !== 0) {
throw $e;
}
$this->logger->logger("Got promise already resolved error", \danog\MadelineProto\Logger::FATAL_ERROR);
$this->logger->logger("Defer sending {$r} to deferred", Logger::ULTRA_VERBOSE);
\danog\MadelineProto\Tools::callFork((function () use ($request_id, $response, $botAPI): \Generator {
$r = isset($response['_']) ? $response['_'] : \json_encode($response);
$this->logger->logger("Deferred: sent {$r} to deferred", Logger::ULTRA_VERBOSE);
if ($botAPI) {
$response = yield $this->MTProtoToBotAPI($response);
}
if (isset($this->outgoing_messages[$request_id]['promise'])) {
// This should not happen but happens, should debug
$promise = $this->outgoing_messages[$request_id]['promise'];
unset($this->outgoing_messages[$request_id]['promise']);
try {
$promise->resolve($response);
} catch (\Error $e) {
if (\strpos($e->getMessage(), "Promise has already been resolved") !== 0) {
throw $e;
}
$this->logger->logger("Got promise already resolved error", \danog\MadelineProto\Logger::FATAL_ERROR);
}
}
)());
})());
}
}

View File

@ -28,9 +28,7 @@ trait SeqNoHandler
{
public $session_out_seq_no = 0;
public $session_in_seq_no = 0;
public $session_id;
public function generateOutSeqNo($contentRelated)
{
$in = $contentRelated ? 1 : 0;
@ -39,17 +37,15 @@ trait SeqNoHandler
//$this->API->logger->logger("OUT: $value + $in = ".$this->session_out_seq_no);
return $value * 2 + $in;
}
public function checkInSeqNo($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->generateInSeqNo($this->contentRelated($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::ULTRA_VERBOSE);
$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::ULTRA_VERBOSE);
} 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);
$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 generateInSeqNo($contentRelated)
{
$in = $contentRelated ? 1 : 0;
@ -58,11 +54,9 @@ trait SeqNoHandler
//$this->API->logger->logger("IN: $value + $in = ".$this->session_in_seq_no);
return $value * 2 + $in;
}
public function contentRelated($method)
{
$method = \is_array($method) && isset($method['_']) ? $method['_'] : $method;
return \is_string($method) ? !\in_array($method, MTProto::NOT_CONTENT_RELATED) : true;
}
}

View File

@ -1,4 +1,5 @@
<?php
/**
* Session module.
*
@ -28,20 +29,15 @@ abstract class Session
use ResponseHandler;
use SeqNoHandler;
use CallHandler;
public $incoming_messages = [];
public $outgoing_messages = [];
public $new_incoming = [];
public $new_outgoing = [];
public $pending_outgoing = [];
public $pending_outgoing_key = 'a';
public $time_delta = 0;
public $call_queue = [];
public $ack_queue = [];
/**
* Reset MTProto session.
*
@ -68,7 +64,6 @@ abstract class Session
$this->session_out_seq_no = 0;
}
}
/**
* Backup eventual unsent messages before session deletion.
*

View File

@ -46,7 +46,6 @@ trait AuthKeyHandler
* @var boolean
*/
private $pending_auth = false;
/**
* Create authorization key.
*
@ -62,7 +61,6 @@ trait AuthKeyHandler
$connection = $this->datacenter->getAuthConnection($datacenter);
$cdn = $connection->isCDN();
$req_pq = $cdn ? 'req_pq' : 'req_pq_multi';
for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) {
try {
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['req_pq'], \danog\MadelineProto\Logger::VERBOSE);
@ -128,7 +126,6 @@ trait AuthKeyHandler
list($p, $q) = [$q, $p];
}
}
if (!$pq->equals($p->multiply($q))) {
$this->logger->logger('Automatic factorization failed, trying alt py module', \danog\MadelineProto\Logger::ERROR);
$p = new \tgseclib\Math\BigInteger(\danog\PrimeModule::python_single_alt($pq->__toString()));
@ -138,7 +135,6 @@ trait AuthKeyHandler
list($p, $q) = [$q, $p];
}
}
if (!$pq->equals($p->multiply($q))) {
$this->logger->logger('Automatic factorization failed, trying py module', \danog\MadelineProto\Logger::ERROR);
$p = new \tgseclib\Math\BigInteger(\danog\PrimeModule::python_single($pq->__toString()));
@ -148,7 +144,6 @@ trait AuthKeyHandler
list($p, $q) = [$q, $p];
}
}
if (!$pq->equals($p->multiply($q))) {
$this->logger->logger('Automatic factorization failed, trying native module', \danog\MadelineProto\Logger::ERROR);
$p = new \tgseclib\Math\BigInteger(\danog\PrimeModule::native_single($pq->__toString()));
@ -158,37 +153,32 @@ trait AuthKeyHandler
list($p, $q) = [$q, $p];
}
}
if (!$pq->equals($p->multiply($q))) {
$this->logger->logger('Automatic factorization failed, trying wolfram module', \danog\MadelineProto\Logger::ERROR);
$p = new \tgseclib\Math\BigInteger(yield $this->wolframSingle($pq->__toString()));
$p = new \tgseclib\Math\BigInteger(yield from $this->wolframSingle($pq->__toString()));
if (!$p->equals(\danog\MadelineProto\Magic::$zero)) {
$q = $pq->divide($p)[0];
if ($p->compare($q) > 0) {
list($p, $q) = [$q, $p];
}
}
if (!$pq->equals($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));
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));
}
}
}
}
}
}
$this->logger->logger('Factorization '.$pq.' = '.$p.' * '.$q, \danog\MadelineProto\Logger::VERBOSE);
$this->logger->logger('Factorization ' . $pq . ' = ' . $p . ' * ' . $q, \danog\MadelineProto\Logger::VERBOSE);
/*
* ***********************************************************************
* Serialize object for req_DH_params
*/
$p_bytes = $p->toBytes();
$q_bytes = $q->toBytes();
$new_nonce = \danog\MadelineProto\Tools::random(32);
$data_unserialized = ['_' => 'p_q_inner_data'.($expires_in < 0 ? '' : '_temp'), '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)];
$data_unserialized = ['_' => 'p_q_inner_data' . ($expires_in < 0 ? '' : '_temp'), '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 = yield $this->TL->serializeObject(['type' => ''], $data_unserialized, 'p_q_inner_data');
/*
* ***********************************************************************
@ -196,8 +186,7 @@ trait AuthKeyHandler
*/
$sha_digest = \sha1($p_q_inner_data, true);
$random_bytes = \danog\MadelineProto\Tools::random(255 - \strlen($p_q_inner_data) - \strlen($sha_digest));
$to_encrypt = $sha_digest.$p_q_inner_data.$random_bytes;
$to_encrypt = $sha_digest . $p_q_inner_data . $random_bytes;
$encrypted_data = $key->encrypt($to_encrypt);
$this->logger->logger('Starting Diffie Hellman key exchange', \danog\MadelineProto\Logger::VERBOSE);
/*
@ -248,10 +237,8 @@ trait AuthKeyHandler
* Get key, iv and decrypt answer
*/
$encrypted_answer = $server_dh_params['encrypted_answer'];
$tmp_aes_key = \sha1($new_nonce.$server_nonce, true).\substr(\sha1($server_nonce.$new_nonce, true), 0, 12);
$tmp_aes_iv = \substr(\sha1($server_nonce.$new_nonce, true), 12, 8).\sha1($new_nonce.$new_nonce, true).\substr($new_nonce, 0, 4);
$tmp_aes_key = \sha1($new_nonce . $server_nonce, true) . \substr(\sha1($server_nonce . $new_nonce, true), 0, 12);
$tmp_aes_iv = \substr(\sha1($server_nonce . $new_nonce, true), 12, 8) . \sha1($new_nonce . $new_nonce, true) . \substr($new_nonce, 0, 4);
$answer_with_hash = $this->igeDecrypt($encrypted_answer, $tmp_aes_key, $tmp_aes_iv);
/*
* ***********************************************************************
@ -331,8 +318,8 @@ trait AuthKeyHandler
* ***********************************************************************
* encrypt client_DH_inner_data
*/
$data_with_sha = \sha1($data, true).$data;
$data_with_sha_padded = $data_with_sha.\danog\MadelineProto\Tools::random(\danog\MadelineProto\Tools::posmod(-\strlen($data_with_sha), 16));
$data_with_sha = \sha1($data, true) . $data;
$data_with_sha_padded = $data_with_sha . \danog\MadelineProto\Tools::random(\danog\MadelineProto\Tools::posmod(-\strlen($data_with_sha), 16));
$encrypted_data = $this->igeEncrypt($data_with_sha_padded, $tmp_aes_key, $tmp_aes_iv);
$this->logger->logger('Executing set_client_DH_params...', \danog\MadelineProto\Logger::VERBOSE);
/*
@ -362,9 +349,9 @@ trait AuthKeyHandler
$auth_key_str = $auth_key->toBytes();
$auth_key_sha = \sha1($auth_key_str, true);
$auth_key_aux_hash = \substr($auth_key_sha, 0, 8);
$new_nonce_hash1 = \substr(\sha1($new_nonce.\chr(1).$auth_key_aux_hash, true), -16);
$new_nonce_hash2 = \substr(\sha1($new_nonce.\chr(2).$auth_key_aux_hash, true), -16);
$new_nonce_hash3 = \substr(\sha1($new_nonce.\chr(3).$auth_key_aux_hash, true), -16);
$new_nonce_hash1 = \substr(\sha1($new_nonce . \chr(1) . $auth_key_aux_hash, true), -16);
$new_nonce_hash2 = \substr(\sha1($new_nonce . \chr(2) . $auth_key_aux_hash, true), -16);
$new_nonce_hash3 = \substr(\sha1($new_nonce . \chr(3) . $auth_key_aux_hash, true), -16);
/*
* ***********************************************************************
* Check if the client's nonce and the server's nonce are the same
@ -389,16 +376,13 @@ trait AuthKeyHandler
throw new \danog\MadelineProto\SecurityException('wrong new_nonce_hash1');
}
$this->logger->logger('Diffie Hellman key exchange processed successfully!', \danog\MadelineProto\Logger::VERBOSE);
$key = $expires_in < 0 ? new PermAuthKey() : new TempAuthKey();
if ($expires_in >= 0) {
$key->expires(\time() + $expires_in);
}
$key->setServerSalt(\substr($new_nonce, 0, 8) ^ \substr($server_nonce, 0, 8));
$key->setAuthKey($auth_key_str);
$this->logger->logger('Auth key generated', \danog\MadelineProto\Logger::NOTICE);
return $key;
case 'dh_gen_retry':
if ($Set_client_DH_params_answer['new_nonce_hash2'] != $new_nonce_hash2) {
@ -419,21 +403,20 @@ trait AuthKeyHandler
}
}
} catch (\danog\MadelineProto\SecurityException $e) {
$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);
$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);
} catch (\danog\MadelineProto\Exception $e) {
$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);
$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) {
$this->logger->logger('An RPCErrorException occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING);
$this->logger->logger('An RPCErrorException occurred while generating the authorization key: ' . $e->getMessage() . ' Retrying (try number ' . $retry_id_total . ')...', \danog\MadelineProto\Logger::WARNING);
} catch (\Throwable $e) {
$this->logger->logger('An exception occurred while generating the authorization key: '.$e.PHP_EOL.' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING);
$this->logger->logger('An exception occurred while generating the authorization key: ' . $e . PHP_EOL . ' Retrying (try number ' . $retry_id_total . ')...', \danog\MadelineProto\Logger::WARNING);
}
}
if (!$cdn) {
throw new \danog\MadelineProto\SecurityException('Auth Failed');
}
}
/**
* Check validity of g_a parameters.
*
@ -459,10 +442,8 @@ trait AuthKeyHandler
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 < g_a < p - 2^1984 is false).');
}
return true;
}
/**
* Check validity of p and g parameters.
*
@ -492,11 +473,11 @@ trait AuthKeyHandler
* Almost always fails
*/
/*
$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).");
}
*/
$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).");
}
*/
/*
* ***********************************************************************
* Check validity of p
@ -515,10 +496,8 @@ trait AuthKeyHandler
if ($g->compare(\danog\MadelineProto\Magic::$one) <= 0 || $g->compare($p->subtract(\danog\MadelineProto\Magic::$one)) >= 0) {
throw new \danog\MadelineProto\SecurityException('g is invalid (1 < g < p - 1 is false).');
}
return true;
}
/**
* Get diffie-hellman configuration.
*
@ -531,16 +510,13 @@ trait AuthKeyHandler
$dh_config = yield $this->methodCallAsyncRead('messages.getDhConfig', ['version' => $this->dh_config['version'], 'random_length' => 0], ['datacenter' => $this->datacenter->curdc]);
if ($dh_config['_'] === 'messages.dhConfigNotModified') {
$this->logger->logger('DH configuration not modified', \danog\MadelineProto\Logger::VERBOSE);
return $this->dh_config;
}
$dh_config['p'] = new \tgseclib\Math\BigInteger((string) $dh_config['p'], 256);
$dh_config['g'] = new \tgseclib\Math\BigInteger($dh_config['g']);
$this->checkPG($dh_config['p'], $dh_config['g']);
return $this->dh_config = $dh_config;
}
/**
* Bind temporary and permanent auth keys.
*
@ -555,7 +531,6 @@ trait AuthKeyHandler
{
$datacenterConnection = $this->datacenter->getDataCenterConnection($datacenter);
$connection = $datacenterConnection->getAuthConnection();
for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) {
try {
$this->logger->logger('Binding authorization keys...', \danog\MadelineProto\Logger::VERBOSE);
@ -564,34 +539,31 @@ trait AuthKeyHandler
$temp_auth_key_id = $datacenterConnection->getTempAuthKey()->getID();
$perm_auth_key_id = $datacenterConnection->getPermAuthKey()->getID();
$temp_session_id = $connection->session_id;
$message_data = yield $this->TL->serializeObject(['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], 'bindTempAuthKey_inner');
$message_data = yield $this->TL->serializeObject(['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], 'bindTempAuthKey_inner');
$message_id = $connection->generateMessageId();
$seq_no = 0;
$encrypted_data = \danog\MadelineProto\Tools::random(16).$message_id.\pack('VV', $seq_no, \strlen($message_data)).$message_data;
$encrypted_data = \danog\MadelineProto\Tools::random(16) . $message_id . \pack('VV', $seq_no, \strlen($message_data)) . $message_data;
$message_key = \substr(\sha1($encrypted_data, true), -16);
$padding = \danog\MadelineProto\Tools::random(\danog\MadelineProto\Tools::posmod(-\strlen($encrypted_data), 16));
list($aes_key, $aes_iv) = $this->oldAesCalculate($message_key, $datacenterConnection->getPermAuthKey()->getAuthKey());
$encrypted_message = $datacenterConnection->getPermAuthKey()->getID().$message_key.$this->igeEncrypt($encrypted_data.$padding, $aes_key, $aes_iv);
$encrypted_message = $datacenterConnection->getPermAuthKey()->getID() . $message_key . $this->igeEncrypt($encrypted_data . $padding, $aes_key, $aes_iv);
$res = yield $connection->methodCallAsyncRead('auth.bindTempAuthKey', ['perm_auth_key_id' => $perm_auth_key_id, 'nonce' => $nonce, 'expires_at' => $expires_at, 'encrypted_message' => $encrypted_message], ['msg_id' => $message_id]);
if ($res === true) {
$this->logger->logger('Bound temporary and permanent authorization keys, DC '.$datacenter, \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger('Bound temporary and permanent authorization keys, DC ' . $datacenter, \danog\MadelineProto\Logger::NOTICE);
$datacenterConnection->bind();
$datacenterConnection->flush();
return true;
}
} catch (\danog\MadelineProto\SecurityException $e) {
$this->logger->logger('An exception occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING);
$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\Exception $e) {
$this->logger->logger('An exception occurred while generating the authorization key: '.$e->getMessage().' Retrying (try number '.$retry_id_total.')...', \danog\MadelineProto\Logger::WARNING);
$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);
$this->logger->logger('An RPCErrorException occurred while generating the authorization key: ' . $e->getMessage() . ' Retrying (try number ' . $retry_id_total . ')...', \danog\MadelineProto\Logger::WARNING);
}
}
throw new \danog\MadelineProto\SecurityException('An error occurred while binding temporary and permanent authorization keys.');
}
/**
* Factorize number asynchronously using the wolfram API.
*
@ -602,22 +574,11 @@ trait AuthKeyHandler
private function wolframSingle($what): \Generator
{
$code = yield $this->datacenter->fileGetContents('http://www.wolframalpha.com/api/v1/code');
$query = 'Do prime factorization of '.$what;
$params = [
'async' => true,
'banners' => 'raw',
'debuggingdata' => false,
'format' => 'moutput',
'formattimeout' => 8,
'input' => $query,
'output' => 'JSON',
'proxycode' => \json_decode($code, true)['code'],
];
$url = 'https://www.wolframalpha.com/input/json.jsp?'.\http_build_query($params);
$query = 'Do prime factorization of ' . $what;
$params = ['async' => true, 'banners' => 'raw', 'debuggingdata' => false, 'format' => 'moutput', 'formattimeout' => 8, 'input' => $query, 'output' => 'JSON', 'proxycode' => \json_decode($code, true)['code']];
$url = 'https://www.wolframalpha.com/input/json.jsp?' . \http_build_query($params);
$request = new Request($url);
$request->setHeader('referer', 'https://www.wolframalpha.com/input/?i='.\urlencode($query));
$request->setHeader('referer', 'https://www.wolframalpha.com/input/?i=' . \urlencode($query));
$res = \json_decode(yield (yield $this->datacenter->getHTTPClient()->request($request))->getBody()->buffer(), true);
if (!isset($res['queryresult']['pods'])) {
return false;
@ -625,24 +586,20 @@ trait AuthKeyHandler
$fres = false;
foreach ($res['queryresult']['pods'] as $cur) {
if ($cur['id'] === 'Divisors') {
$fres = \explode(', ', \preg_replace(["/{\d+, /", "/, \d+}$/"], '', $cur['subpods'][0]['moutput']));
$fres = \explode(', ', \preg_replace(["/{\\d+, /", "/, \\d+}\$/"], '', $cur['subpods'][0]['moutput']));
break;
}
}
if (\is_array($fres)) {
$fres = $fres[0];
$newval = \intval($fres);
if (\is_int($newval)) {
$fres = $newval;
}
return $fres;
}
return false;
}
/**
* Asynchronously create, bind and check auth keys for all DCs.
*
@ -658,9 +615,7 @@ trait AuthKeyHandler
}
$this->logger("Initing authorization...");
$initing = $this->initing_authorization;
$this->initing_authorization = true;
try {
$dcs = [];
$postpone = [];
@ -688,26 +643,22 @@ trait AuthKeyHandler
$first = \array_shift($dcs)();
yield $first;
}
foreach ($dcs as $id => &$dc) {
$dc = $dc();
}
yield \danog\MadelineProto\Tools::all($dcs);
foreach ($postpone as $id => $socket) {
yield $this->initAuthorizationSocket($id, $socket);
yield from $this->initAuthorizationSocket($id, $socket);
}
if ($this->pending_auth && empty($this->init_auth_dcs)) {
$this->pending_auth = false;
yield $this->initAuthorization();
yield from $this->initAuthorization();
}
} finally {
$this->pending_auth = false;
$this->initing_authorization = $initing;
}
}
/**
* Init auth keys for single DC.
*
@ -720,20 +671,17 @@ trait AuthKeyHandler
*/
public function initAuthorizationSocket(string $id, DataCenterConnection $socket): \Generator
{
$this->logger("Initing authorization DC $id...");
$this->logger("Initing authorization DC {$id}...");
$this->init_auth_dcs[$id] = true;
$connection = $socket->getAuthConnection();
try {
$socket->createSession();
$cdn = $socket->isCDN();
$media = $socket->isMedia();
if (!$socket->hasTempAuthKey() || !$socket->hasPermAuthKey() || !$socket->isBound()) {
if (!$socket->hasPermAuthKey() && !$cdn && !$media) {
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_perm_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->setPermAuthKey(yield $this->createAuthKey(-1, $id));
$socket->setPermAuthKey(yield from $this->createAuthKey(-1, $id));
//$socket->authorized(false);
}
if ($media) {
@ -745,40 +693,35 @@ trait AuthKeyHandler
if ($this->datacenter->getDataCenterConnection($id)->getSettings()['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->setTempAuthKey(null);
$socket->setTempAuthKey(yield $this->createAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
yield $this->bindTempAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
yield from $this->bindTempAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
$this->config = yield $connection->methodCallAsyncRead('help.getConfig', []);
yield $this->syncAuthorization($id);
yield from $this->syncAuthorization($id);
} elseif (!$socket->hasTempAuthKey()) {
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->setTempAuthKey(yield $this->createAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
}
} else {
if (!$cdn) {
$socket->bind(false);
$this->config = yield $connection->methodCallAsyncRead('help.getConfig', []);
yield $this->syncAuthorization($id);
yield from $this->syncAuthorization($id);
} elseif (!$socket->hasTempAuthKey()) {
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
$socket->setTempAuthKey(yield $this->createAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
}
}
} elseif (!$cdn) {
yield $this->syncAuthorization($id);
yield from $this->syncAuthorization($id);
}
} finally {
$this->logger("Done initing authorization DC $id");
$this->logger("Done initing authorization DC {$id}");
unset($this->init_auth_dcs[$id]);
}
}
/**
* Sync authorization data between DCs.
*
@ -801,15 +744,15 @@ trait AuthKeyHandler
}
if ($authorized_socket->hasTempAuthKey() && $authorized_socket->hasPermAuthKey() && $authorized_socket->isAuthorized() && $this->authorized === self::LOGGED_IN && !$socket->isAuthorized() && !$authorized_socket->isCDN()) {
try {
$this->logger->logger('Trying to copy authorization from dc '.$authorized_dc_id.' to dc '.$id);
$this->logger->logger('Trying to copy authorization from dc ' . $authorized_dc_id . ' to dc ' . $id);
$exported_authorization = yield $this->methodCallAsyncRead('auth.exportAuthorization', ['dc_id' => \preg_replace('|_.*|', '', $id)], ['datacenter' => $authorized_dc_id]);
$authorization = yield $this->methodCallAsyncRead('auth.importAuthorization', $exported_authorization, ['datacenter' => $id]);
$socket->authorized(true);
break;
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger('Failure while syncing authorization from DC '.$authorized_dc_id.' to DC '.$id.': '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);
$this->logger->logger('Failure while syncing authorization from DC ' . $authorized_dc_id . ' to DC ' . $id . ': ' . $e->getMessage(), \danog\MadelineProto\Logger::ERROR);
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger('Failure while syncing authorization from DC '.$authorized_dc_id.' to DC '.$id.': '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);
$this->logger->logger('Failure while syncing authorization from DC ' . $authorized_dc_id . ' to DC ' . $id . ': ' . $e->getMessage(), \danog\MadelineProto\Logger::ERROR);
if ($e->rpc === 'DC_ID_INVALID') {
break;
}

View File

@ -40,7 +40,6 @@ trait CallHandler
{
return \danog\MadelineProto\Tools::wait($this->methodCallAsyncRead($method, $args, $aargs));
}
/**
* Call method and wait asynchronously for response.
*
@ -54,16 +53,13 @@ trait CallHandler
*/
public function methodCallAsyncRead(string $method, $args = [], array $aargs = ['msg_id' => null]): Promise
{
$deferred = new Deferred;
$this->datacenter->waitGetConnection($aargs['datacenter'] ?? $this->datacenter->curdc)->onResolve(
static function ($e, $res) use (&$method, &$args, &$aargs, &$deferred) {
if ($e) {
throw $e;
}
$deferred->resolve($res->methodCallAsyncRead($method, $args, $aargs));
$deferred = new Deferred();
$this->datacenter->waitGetConnection($aargs['datacenter'] ?? $this->datacenter->curdc)->onResolve(static function ($e, $res) use (&$method, &$args, &$aargs, &$deferred) {
if ($e) {
throw $e;
}
);
$deferred->resolve($res->methodCallAsyncRead($method, $args, $aargs));
});
return $deferred->promise();
}
/**
@ -77,16 +73,13 @@ trait CallHandler
*/
public function methodCallAsyncWrite(string $method, $args = [], array $aargs = ['msg_id' => null]): Promise
{
$deferred = new Deferred;
$this->datacenter->waitGetConnection($aargs['datacenter'] ?? $this->datacenter->curdc)->onResolve(
static function ($e, $res) use (&$method, &$args, &$aargs, &$deferred) {
if ($e) {
throw $e;
}
$deferred->resolve($res->methodCallAsyncWrite($method, $args, $aargs));
$deferred = new Deferred();
$this->datacenter->waitGetConnection($aargs['datacenter'] ?? $this->datacenter->curdc)->onResolve(static function ($e, $res) use (&$method, &$args, &$aargs, &$deferred) {
if ($e) {
throw $e;
}
);
$deferred->resolve($res->methodCallAsyncWrite($method, $args, $aargs));
});
return $deferred->promise();
}
}

View File

@ -30,7 +30,6 @@ class CombinedUpdatesState
* @var array<UpdatesState>
*/
private $states = [];
/**
* Constructor function.
*
@ -49,7 +48,6 @@ class CombinedUpdatesState
$this->states[$channel] = $state;
}
}
/**
* Get or update multiple parameters.
*
@ -66,10 +64,8 @@ class CombinedUpdatesState
if (!isset($this->states[$channel])) {
return $this->states[$channel] = new UpdatesState($init, $channel);
}
return $this->states[$channel]->update($init);
}
/**
* Remove update state.
*
@ -83,7 +79,6 @@ class CombinedUpdatesState
unset($this->states[$channel]);
}
}
/**
* Check if update state is present.
*
@ -95,7 +90,6 @@ class CombinedUpdatesState
{
return isset($this->states[$channel]);
}
/**
* Are we currently busy?
*

View File

@ -35,14 +35,12 @@ trait Crypt
public static function aesCalculate(string $msg_key, string $auth_key, bool $to_server = true): array
{
$x = $to_server ? 0 : 8;
$sha256_a = \hash('sha256', $msg_key.\substr($auth_key, $x, 36), true);
$sha256_b = \hash('sha256', \substr($auth_key, 40 + $x, 36).$msg_key, true);
$aes_key = \substr($sha256_a, 0, 8).\substr($sha256_b, 8, 16).\substr($sha256_a, 24, 8);
$aes_iv = \substr($sha256_b, 0, 8).\substr($sha256_a, 8, 16).\substr($sha256_b, 24, 8);
$sha256_a = \hash('sha256', $msg_key . \substr($auth_key, $x, 36), true);
$sha256_b = \hash('sha256', \substr($auth_key, 40 + $x, 36) . $msg_key, true);
$aes_key = \substr($sha256_a, 0, 8) . \substr($sha256_b, 8, 16) . \substr($sha256_a, 24, 8);
$aes_iv = \substr($sha256_b, 0, 8) . \substr($sha256_a, 8, 16) . \substr($sha256_b, 24, 8);
return [$aes_key, $aes_iv];
}
/**
* AES KDF function for MTProto v1.
*
@ -57,16 +55,14 @@ trait Crypt
public static function oldAesCalculate(string $msg_key, string $auth_key, bool $to_server = true): array
{
$x = $to_server ? 0 : 8;
$sha1_a = \sha1($msg_key.\substr($auth_key, $x, 32), true);
$sha1_b = \sha1(\substr($auth_key, 32 + $x, 16).$msg_key.\substr($auth_key, 48 + $x, 16), true);
$sha1_c = \sha1(\substr($auth_key, 64 + $x, 32).$msg_key, true);
$sha1_d = \sha1($msg_key.\substr($auth_key, 96 + $x, 32), true);
$aes_key = \substr($sha1_a, 0, 8).\substr($sha1_b, 8, 12).\substr($sha1_c, 4, 12);
$aes_iv = \substr($sha1_a, 8, 12).\substr($sha1_b, 0, 8).\substr($sha1_c, 16, 4).\substr($sha1_d, 0, 8);
$sha1_a = \sha1($msg_key . \substr($auth_key, $x, 32), true);
$sha1_b = \sha1(\substr($auth_key, 32 + $x, 16) . $msg_key . \substr($auth_key, 48 + $x, 16), true);
$sha1_c = \sha1(\substr($auth_key, 64 + $x, 32) . $msg_key, true);
$sha1_d = \sha1($msg_key . \substr($auth_key, 96 + $x, 32), true);
$aes_key = \substr($sha1_a, 0, 8) . \substr($sha1_b, 8, 12) . \substr($sha1_c, 4, 12);
$aes_iv = \substr($sha1_a, 8, 12) . \substr($sha1_b, 0, 8) . \substr($sha1_c, 16, 4) . \substr($sha1_d, 0, 8);
return [$aes_key, $aes_iv];
}
/**
* CTR encrypt.
*
@ -83,10 +79,8 @@ trait Crypt
$cipher = new \tgseclib\Crypt\AES('ctr');
$cipher->setKey($key);
$cipher->setIV($iv);
return @$cipher->encrypt($message);
}
/**
* IGE encrypt.
*
@ -103,7 +97,6 @@ trait Crypt
$cipher = new \tgseclib\Crypt\AES('ige');
$cipher->setKey($key);
$cipher->setIV($iv);
return @$cipher->encrypt($message);
}
/**
@ -122,7 +115,6 @@ trait Crypt
$cipher = new \tgseclib\Crypt\AES('ige');
$cipher->setKey($key);
$cipher->setIV($iv);
return @$cipher->decrypt($message);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -29,26 +29,8 @@ use danog\MadelineProto\Tools;
class MinDatabase implements TLCallback
{
use Tools;
const SWITCH_CONSTRUCTORS = [
'inputChannel',
'inputUser',
'inputPeerUser',
'inputPeerChannel',
];
const CATCH_PEERS = [
'message',
'messageService',
'peerUser',
'peerChannel',
'messageEntityMentionName',
'messageFwdHeader',
'messageActionChatCreate',
'messageActionChatAddUser',
'messageActionChatDeleteUser',
'messageActionChatJoinedByLink',
];
const SWITCH_CONSTRUCTORS = ['inputChannel', 'inputUser', 'inputPeerUser', 'inputPeerChannel'];
const CATCH_PEERS = ['message', 'messageService', 'peerUser', 'peerChannel', 'messageEntityMentionName', 'messageFwdHeader', 'messageActionChatCreate', 'messageActionChatAddUser', 'messageActionChatDeleteUser', 'messageActionChatJoinedByLink'];
const ORIGINS = ['message', 'messageService'];
/**
* References indexed by location.
@ -68,23 +50,19 @@ class MinDatabase implements TLCallback
* @var \danog\MadelineProto\MTProto
*/
private $API;
public function __construct(MTProto $API)
{
$this->API = $API;
$this->init();
}
public function __wakeup()
{
$this->init();
}
public function __sleep()
{
return ['db', 'API'];
}
public function init()
{
foreach ($this->db as $id => $origin) {
@ -93,48 +71,37 @@ class MinDatabase implements TLCallback
}
}
}
public function getMethodCallbacks(): array
{
return [];
}
public function getMethodBeforeCallbacks(): array
{
return [];
}
public function getConstructorCallbacks(): array
{
return \array_merge(
\array_fill_keys(self::CATCH_PEERS, [[$this, 'addPeer']]),
\array_fill_keys(self::ORIGINS, [[$this, 'addOrigin']])
);
return \array_merge(\array_fill_keys(self::CATCH_PEERS, [[$this, 'addPeer']]), \array_fill_keys(self::ORIGINS, [[$this, 'addOrigin']]));
}
public function getConstructorBeforeCallbacks(): array
{
return \array_fill_keys(self::ORIGINS, [[$this, 'addOriginContext']]);
}
public function getConstructorSerializeCallbacks(): array
{
return \array_fill_keys(self::SWITCH_CONSTRUCTORS, [$this, 'populateFrom']);
}
public function getTypeMismatchCallbacks(): array
{
return [];
}
public function reset()
{
if ($this->cache) {
$this->API->logger->logger('Found '.\count($this->cache).' pending contexts', \danog\MadelineProto\Logger::ERROR);
$this->API->logger->logger('Found ' . \count($this->cache) . ' pending contexts', \danog\MadelineProto\Logger::ERROR);
$this->cache = [];
}
}
public function addPeer(array $location)
{
if (!$this->cache) {
@ -148,18 +115,15 @@ class MinDatabase implements TLCallback
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'];
}
@ -168,10 +132,9 @@ class MinDatabase implements TLCallback
$frames = \array_reverse($frames);
$tl_trace = \array_shift($frames);
foreach ($frames as $frame) {
$tl_trace .= "['".$frame."']";
$tl_trace .= "['" . $frame . "']";
}
$this->API->logger->logger($tl_trace, \danog\MadelineProto\Logger::ERROR);
return false;
}
$peers = [];
@ -204,16 +167,13 @@ class MinDatabase implements TLCallback
foreach ($peers as $id => $true) {
$this->cache[$key][$id] = $id;
}
return true;
}
public function addOriginContext(string $type)
{
$this->API->logger->logger("Adding peer origin context for {$type}!", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->cache[] = [];
}
public function addOrigin(array $data = [])
{
$cache = \array_pop($this->cache);
@ -236,10 +196,9 @@ class MinDatabase implements TLCallback
}
$this->db[$id] = $origin;
}
$this->API->logger->logger("Added origin ({$data['_']}) to ".\count($cache).' peer locations', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->API->logger->logger("Added origin ({$data['_']}) to " . \count($cache) . ' peer locations', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
}
public function populateFrom(array $object)
public function populateFrom(array $object): \Generator
{
if (!($object['min'] ?? false)) {
return $object;
@ -250,17 +209,14 @@ class MinDatabase implements TLCallback
$new['_'] .= 'FromMessage';
$new['peer'] = (yield $this->API->getInfo($new['peer']))['InputPeer'];
if ($new['peer']['min']) {
$this->API->logger->logger("Don't have origin peer subinfo with min peer $id, this may fail");
$this->API->logger->logger("Don't have origin peer subinfo with min peer {$id}, this may fail");
return $object;
}
return $new;
}
$this->API->logger->logger("Don't have origin info with min peer $id, this may fail");
$this->API->logger->logger("Don't have origin info with min peer {$id}, this may fail");
return $object;
}
/**
* Check if location info is available for peer.
*
@ -274,6 +230,6 @@ class MinDatabase implements TLCallback
}
public function __debugInfo()
{
return ['MinDatabase instance '.\spl_object_hash($this)];
return ['MinDatabase instance ' . \spl_object_hash($this)];
}
}

View File

@ -1,4 +1,5 @@
<?php
/**
* Password calculator module.
*
@ -34,7 +35,6 @@ class PasswordCalculator
{
use AuthKeyHandler;
use Tools;
/**
* The algorithm to use for calculating the hash of new passwords (a PasswordKdfAlgo object).
*
@ -47,14 +47,12 @@ class PasswordCalculator
* @var string
*/
private $secure_random = '';
/**
* The algorithm to use for calculatuing the hash of the current password (a PasswordKdfAlgo object).
*
* @var array
*/
private $current_algo;
/**
* SRP b parameter.
*
@ -79,7 +77,6 @@ class PasswordCalculator
* @var \danog\MadelineProto\Logger
*/
public $logger;
/**
* Initialize logger.
*
@ -89,7 +86,6 @@ class PasswordCalculator
{
$this->logger = $logger;
}
/**
* Popupate 2FA configuration.
*
@ -115,7 +111,6 @@ class PasswordCalculator
$this->checkPG($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']['_']}");
@ -146,7 +141,6 @@ class PasswordCalculator
$this->checkPG($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']['_']}");
@ -154,7 +148,6 @@ class PasswordCalculator
$this->new_algo = $object['new_algo'];
$this->secure_random = $object['secure_random'];
}
/**
* Create a random string (eventually prefixed by the specified string).
*
@ -163,9 +156,8 @@ class PasswordCalculator
*/
public function createSalt(string $prefix = ''): string
{
return $prefix.\danog\MadelineProto\Tools::random(32);
return $prefix . \danog\MadelineProto\Tools::random(32);
}
/**
* Hash specified data using the salt with SHA256.
*
@ -177,9 +169,8 @@ class PasswordCalculator
*/
public function hashSha256(string $data, string $salt): string
{
return \hash('sha256', $salt.$data.$salt, true);
return \hash('sha256', $salt . $data . $salt, true);
}
/**
* Hashes the specified password.
*
@ -193,10 +184,8 @@ class PasswordCalculator
$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);
}
/**
* Get the InputCheckPassword object for checking the validity of a password using account.checkPassword.
*
@ -214,42 +203,30 @@ class PasswordCalculator
$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);
$k = new BigInteger(\hash('sha256', $pForHash . $gForHash, true), 256);
$kg_x = $k->multiply($g_x)->powMod(Magic::$one, $p);
$a = new BigInteger(\danog\MadelineProto\Tools::random(2048 / 8), 256);
$A = $g->powMod($a, $p);
$this->checkG($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);
$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);
$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];
}
/**
* Get parameters to be passed to the account.updatePasswordSettings to update/set a 2FA password.
*
@ -261,36 +238,24 @@ class PasswordCalculator
public function getPassword(array $params): array
{
$oldPassword = $this->getCheckPassword($params['password'] ?? '');
$return = ['password' => $oldPassword, 'new_settings' => ['_' => 'account.passwordInputSettings', 'new_algo' => ['_' => 'passwordKdfAlgoUnknown'], 'new_password_hash' => '', 'hint' => '']];
$new_settings = &$return['new_settings'];
$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_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

@ -29,9 +29,7 @@ trait PeerHandler
public $caching_simple = [];
public $caching_simple_username = [];
public $caching_possible_username = [];
public $caching_full_info = [];
/**
* Convert MTProto channel ID to bot API channel ID.
*
@ -43,7 +41,6 @@ trait PeerHandler
{
return -($id + \pow(10, (int) \floor(\log($id, 10) + 3)));
}
/**
* Convert bot API channel ID to MTProto channel ID.
*
@ -55,7 +52,6 @@ trait PeerHandler
{
return -$id - \pow(10, (int) \floor(\log(-$id, 10)));
}
/**
* Check whether provided bot API ID is a channel.
*
@ -66,10 +62,8 @@ trait PeerHandler
public static function isSupergroup($id): bool
{
$log = \log(-$id, 10);
return ($log - \intval($log)) * 1000 < 10;
}
/**
* Set support info.
*
@ -83,7 +77,6 @@ trait PeerHandler
{
$this->supportUser = $support['user']['id'];
}
/**
* Add user info.
*
@ -109,14 +102,12 @@ trait PeerHandler
} else {
$this->logger->logger("No access hash with user {$user['id']}, tried and failed to fetch data...");
}
return;
}
switch ($user['_']) {
case 'user':
if (!isset($this->chats[$user['id']]) || $this->chats[$user['id']] !== $user) {
$this->logger->logger("Updated user {$user['id']}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
if (($user['min'] ?? false) && isset($this->chats[$user['id']]) && !($this->chats[$user['id']]['min'] ?? false)) {
$this->logger->logger("{$user['id']} is min, filling missing fields", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
if (isset($this->chats[$user['id']]['access_hash'])) {
@ -124,7 +115,6 @@ trait PeerHandler
$user['access_hash'] = $this->chats[$user['id']]['access_hash'];
}
}
$this->chats[$user['id']] = $user;
$this->cachePwrChat($user['id'], false, true);
}
@ -135,7 +125,6 @@ trait PeerHandler
throw new \danog\MadelineProto\Exception('Invalid user provided', $user);
}
}
/**
* Add chat to database.
*
@ -168,7 +157,6 @@ trait PeerHandler
if (isset($chat['username']) && !isset($this->caching_simple_username[$chat['username']])) {
$this->caching_possible_username[$bot_api_id] = $chat['username'];
}
$this->cachePwrChat($bot_api_id, false, true);
} elseif (isset($chat['username']) && !isset($this->chats[$bot_api_id]) && !isset($this->caching_simple_username[$chat['username']])) {
$this->logger->logger("No access hash with {$chat['_']} {$bot_api_id}, trying to fetch by username...");
@ -176,14 +164,12 @@ trait PeerHandler
} else {
$this->logger->logger("No access hash with {$chat['_']} {$bot_api_id}, tried and failed to fetch data...");
}
return;
}
if (!isset($this->chats[$bot_api_id]) || $this->chats[$bot_api_id] !== $chat) {
$this->logger->logger("Updated chat $bot_api_id", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->logger->logger("Updated chat {$bot_api_id}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
if (($chat['min'] ?? false) && isset($this->chats[$bot_api_id]) && !($this->chats[$bot_api_id]['min'] ?? false)) {
$this->logger->logger("$bot_api_id is min, filling missing fields", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->logger->logger("{$bot_api_id} is min, filling missing fields", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$newchat = $this->chats[$bot_api_id];
foreach (['title', 'username', 'photo', 'banned_rights', 'megagroup', 'verified'] as $field) {
if (isset($chat[$field])) {
@ -192,33 +178,29 @@ trait PeerHandler
}
$chat = $newchat;
}
$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'] !== (yield $this->getFullInfo($bot_api_id))['full']['participants_count'])) {
if ($this->settings['peer']['full_fetch'] && (!isset($this->full_chats[$bot_api_id]) || $this->full_chats[$bot_api_id]['full']['participants_count'] !== (yield from $this->getFullInfo($bot_api_id))['full']['participants_count'])) {
$this->cachePwrChat($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));
throw new \danog\MadelineProto\Exception('Invalid chat provided at key ' . $key . ': ' . \var_export($chat, true));
break;
}
}
private function cachePwrChat($id, $full_fetch, $send)
{
\danog\MadelineProto\Tools::callFork((function () use ($id, $full_fetch, $send) {
\danog\MadelineProto\Tools::callFork((function () use ($id, $full_fetch, $send): \Generator {
try {
yield $this->getPwrChat($id, $full_fetch, $send);
yield from $this->getPwrChat($id, $full_fetch, $send);
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger('While caching: '.$e->getMessage(), \danog\MadelineProto\Logger::WARNING);
$this->logger->logger('While caching: ' . $e->getMessage(), \danog\MadelineProto\Logger::WARNING);
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger('While caching: '.$e->getMessage(), \danog\MadelineProto\Logger::WARNING);
$this->logger->logger('While caching: ' . $e->getMessage(), \danog\MadelineProto\Logger::WARNING);
}
})());
}
/**
* Check if peer is present in internal peer database.
*
@ -229,7 +211,7 @@ trait PeerHandler
public function peerIsset($id): \Generator
{
try {
return isset($this->chats[(yield $this->getInfo($id))['bot_api_id']]);
return isset($this->chats[(yield from $this->getInfo($id))['bot_api_id']]);
} catch (\danog\MadelineProto\Exception $e) {
return false;
} catch (\danog\MadelineProto\RPCErrorException $e) {
@ -239,11 +221,9 @@ trait PeerHandler
if ($e->rpc === 'CHANNEL_PRIVATE') {
return true;
}
return false;
}
}
/**
* Check if all peer entities are in db.
*
@ -258,7 +238,7 @@ trait PeerHandler
try {
foreach ($entities as $entity) {
if ($entity['_'] === 'messageEntityMentionName' || $entity['_'] === 'inputMessageEntityMentionName') {
if (!yield $this->peerIsset($entity['user_id'])) {
if (!(yield from $this->peerIsset($entity['user_id']))) {
return false;
}
}
@ -266,10 +246,8 @@ trait PeerHandler
} catch (\danog\MadelineProto\Exception $e) {
return false;
}
return true;
}
/**
* Check if fwd peer is set.
*
@ -282,19 +260,17 @@ trait PeerHandler
public function fwdPeerIsset(array $fwd): \Generator
{
try {
if (isset($fwd['user_id']) && !yield $this->peerIsset($fwd['user_id'])) {
if (isset($fwd['user_id']) && !(yield from $this->peerIsset($fwd['user_id']))) {
return false;
}
if (isset($fwd['channel_id']) && !yield $this->peerIsset($this->toSupergroup($fwd['channel_id']))) {
if (isset($fwd['channel_id']) && !(yield from $this->peerIsset($this->toSupergroup($fwd['channel_id'])))) {
return false;
}
} catch (\danog\MadelineProto\Exception $e) {
return false;
}
return true;
}
/**
* Get folder ID from object.
*
@ -327,7 +303,7 @@ trait PeerHandler
case 'updateDialogUnreadMark':
case 'updateNotifySettings':
$id = $id['peer'];
// no break
// no break
case 'updateDraftMessage':
case 'inputDialogPeer':
case 'dialogPeer':
@ -339,15 +315,12 @@ trait PeerHandler
case 'folderPeer':
case 'inputFolderPeer':
return $this->getId($id['peer']);
case 'inputUserFromMessage':
case 'inputPeerUserFromMessage':
return $id['user_id'];
case 'inputChannelFromMessage':
case 'inputPeerChannelFromMessage':
return $this->toSupergroup($id['channel_id']);
case 'inputUserSelf':
case 'inputPeerSelf':
return $this->authorization['user']['id'];
@ -383,9 +356,7 @@ trait PeerHandler
if (!isset($id['from_id']) || $id['to_id']['_'] !== 'peerUser' || $id['to_id']['user_id'] !== $this->authorization['user']['id']) {
return $this->getId($id['to_id']);
}
return $id['from_id'];
case 'updateChannelReadMessagesContents':
case 'updateChannelAvailableMessages':
case 'updateChannel':
@ -399,7 +370,7 @@ trait PeerHandler
return $this->toSupergroup($id['channel_id']);
case 'updateChatParticipants':
$id = $id['participants'];
// no break
// no break
case 'updateChatUserTyping':
case 'updateChatParticipantAdd':
case 'updateChatParticipantDelete':
@ -434,18 +405,18 @@ trait PeerHandler
case 'updateEditChannelMessage':
return $this->getId($id['message']);
default:
throw new \danog\MadelineProto\Exception('Invalid constructor given '.\var_export($id, true));
throw new \danog\MadelineProto\Exception('Invalid constructor given ' . \var_export($id, true));
}
}
if (\is_string($id)) {
if (\strpos($id, '#') !== false) {
if (\preg_match('/^channel#(\d*)/', $id, $matches)) {
if (\preg_match('/^channel#(\\d*)/', $id, $matches)) {
return $this->toSupergroup($matches[1]);
}
if (\preg_match('/^chat#(\d*)/', $id, $matches)) {
$id = '-'.$matches[1];
if (\preg_match('/^chat#(\\d*)/', $id, $matches)) {
$id = '-' . $matches[1];
}
if (\preg_match('/^user#(\d*)/', $id, $matches)) {
if (\preg_match('/^user#(\\d*)/', $id, $matches)) {
return $matches[1];
}
}
@ -454,13 +425,10 @@ trait PeerHandler
if (\is_string($id)) {
$id = \danog\MadelineProto\Magic::$bigint ? (float) $id : (int) $id;
}
return $id;
}
return false;
}
/**
* Get info about peer, returns an Info object.
*
@ -483,14 +451,13 @@ trait PeerHandler
return $this->getSecretChat($id['chat_id']);
case 'updateNewEncryptedMessage':
$id = $id['message'];
// no break
// no break
case 'encryptedMessage':
case 'encryptedMessageService':
$id = $id['chat_id'];
if (!isset($this->secret_chats[$id])) {
throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['sec_peer_not_in_db']);
}
return $this->secret_chats[$id];
}
}
@ -499,13 +466,11 @@ trait PeerHandler
if ($try_id !== false) {
$id = $try_id;
}
$tried_simple = false;
if (\is_numeric($id)) {
if (!isset($this->chats[$id])) {
try {
$this->logger->logger("Try fetching $id with access hash 0");
$this->logger->logger("Try fetching {$id} with access hash 0");
$this->caching_simple[$id] = true;
if ($id < 0) {
if ($this->isSupergroup($id)) {
@ -530,7 +495,7 @@ trait PeerHandler
if (isset($this->chats[$id])) {
if (($this->chats[$id]['min'] ?? false) && $this->minDatabase->hasPeer($id) && !isset($this->caching_full_info[$id])) {
$this->caching_full_info[$id] = true;
$this->logger->logger("Only have min peer for $id in database, trying to fetch full info");
$this->logger->logger("Only have min peer for {$id} in database, trying to fetch full info");
try {
if ($id < 0) {
yield $this->methodCallAsyncRead('channels.getChannels', ['id' => [$this->genAll($this->chats[$id], $folder_id)['InputChannel']]], ['datacenter' => $this->datacenter->curdc]);
@ -545,7 +510,6 @@ trait PeerHandler
unset($this->caching_full_info[$id]);
}
}
try {
return $this->genAll($this->chats[$id], $folder_id);
} catch (\danog\MadelineProto\Exception $e) {
@ -558,56 +522,50 @@ trait PeerHandler
}
if (!isset($this->settings['pwr']['requests']) || $this->settings['pwr']['requests'] === true && $recursive) {
$dbres = [];
try {
$dbres = \json_decode(yield $this->datacenter->fileGetContents('https://id.pwrtelegram.xyz/db/getusername?id='.$id), true);
$dbres = \json_decode(yield $this->datacenter->fileGetContents('https://id.pwrtelegram.xyz/db/getusername?id=' . $id), true);
} catch (\Throwable $e) {
$this->logger->logger($e);
}
if (isset($dbres['ok']) && $dbres['ok']) {
yield $this->resolveUsername('@'.$dbres['result']);
return yield $this->getInfo($id, false);
yield from $this->resolveUsername('@' . $dbres['result']);
return yield from $this->getInfo($id, false);
}
}
if ($tried_simple && isset($this->caching_possible_username[$id])) {
$this->logger->logger("No access hash with $id, trying to fetch by username...");
$this->logger->logger("No access hash with {$id}, trying to fetch by username...");
$user = $this->caching_possible_username[$id];
unset($this->caching_possible_username[$id]);
return yield $this->getInfo($user);
return yield from $this->getInfo($user);
}
throw new \danog\MadelineProto\Exception('This peer is not present in the internal peer database');
}
if (\preg_match('@(?:t|telegram)\.(?:me|dog)/(joinchat/)?([a-z0-9_-]*)@i', $id, $matches)) {
if (\preg_match('@(?:t|telegram)\\.(?:me|dog)/(joinchat/)?([a-z0-9_-]*)@i', $id, $matches)) {
if ($matches[1] === '') {
$id = $matches[2];
} else {
$invite = yield $this->methodCallAsyncRead('messages.checkChatInvite', ['hash' => $matches[2]], ['datacenter' => $this->datacenter->curdc]);
if (isset($invite['chat'])) {
return yield $this->getInfo($invite['chat']);
return yield from $this->getInfo($invite['chat']);
}
throw new \danog\MadelineProto\Exception('You have not joined this chat');
}
}
$id = \strtolower(\str_replace('@', '', $id));
if ($id === 'me') {
return yield $this->getInfo($this->authorization['user']['id']);
return yield from $this->getInfo($this->authorization['user']['id']);
}
if ($id === 'support') {
if (!$this->supportUser) {
yield $this->methodCallAsyncRead('help.getSupport', [], ['datacenter' => $this->settings['connection_settings']['default_dc']]);
}
return yield $this->getInfo($this->supportUser);
return yield from $this->getInfo($this->supportUser);
}
foreach ($this->chats as $bot_api_id => $chat) {
if (isset($chat['username']) && \strtolower($chat['username']) === $id) {
if ($chat['min'] ?? false && !isset($this->caching_full_info[$bot_api_id])) {
$this->caching_full_info[$bot_api_id] = true;
$this->logger->logger("Only have min peer for $bot_api_id in database, trying to fetch full info");
$this->logger->logger("Only have min peer for {$bot_api_id} in database, trying to fetch full info");
try {
if ($bot_api_id < 0) {
yield $this->methodCallAsyncRead('channels.getChannels', ['id' => [$this->genAll($this->chats[$bot_api_id], $folder_id)['InputChannel']]], ['datacenter' => $this->datacenter->curdc]);
@ -622,19 +580,15 @@ trait PeerHandler
unset($this->caching_full_info[$bot_api_id]);
}
}
return $this->genAll($this->chats[$bot_api_id], $folder_id);
}
}
if ($recursive) {
yield $this->resolveUsername($id);
return yield $this->getInfo($id, false);
yield from $this->resolveUsername($id);
return yield from $this->getInfo($id, false);
}
throw new \danog\MadelineProto\Exception('This peer is not present in the internal peer database');
}
private function genAll($constructor, $folder_id = null)
{
$res = [$this->TL->getConstructors()->findByPredicate($constructor['_'])['type'] => $constructor];
@ -656,7 +610,7 @@ trait PeerHandler
$res['InputNotifyPeer'] = ['_' => 'inputNotifyPeer', 'peer' => $res['InputPeer']];
$res['user_id'] = $constructor['id'];
$res['bot_api_id'] = $constructor['id'];
$res['type'] = ($constructor['bot'] ?? false) ? 'bot' : 'user';
$res['type'] = $constructor['bot'] ?? false ? 'bot' : 'user';
break;
case 'chat':
case 'chatForbidden':
@ -683,12 +637,12 @@ trait PeerHandler
$res['InputChannel'] = ['_' => 'inputChannel', 'channel_id' => $constructor['id'], 'access_hash' => $constructor['access_hash'], 'min' => $constructor['min']];
$res['channel_id'] = $constructor['id'];
$res['bot_api_id'] = $this->toSupergroup($constructor['id']);
$res['type'] = ($constructor['megagroup'] ?? false) ? 'supergroup' : 'channel';
$res['type'] = $constructor['megagroup'] ?? false ? 'supergroup' : 'channel';
break;
case 'channelForbidden':
throw new \danog\MadelineProto\Exception('This peer is not present in the internal peer database');
default:
throw new \danog\MadelineProto\Exception('Invalid constructor given '.\var_export($constructor, true));
throw new \danog\MadelineProto\Exception('Invalid constructor given ' . \var_export($constructor, true));
}
if ($folder_id) {
$res['FolderPeer'] = ['_' => 'folderPeer', 'peer' => $res['Peer'], 'folder_id' => $folder_id];
@ -696,7 +650,6 @@ trait PeerHandler
}
return $res;
}
/**
* When were full info for this chat last cached.
*
@ -708,7 +661,6 @@ trait PeerHandler
{
return isset($this->full_chats[$id]['last_update']) ? $this->full_chats[$id]['last_update'] : 0;
}
/**
* Get full info about peer, returns an FullInfo object.
*
@ -720,7 +672,7 @@ trait PeerHandler
*/
public function getFullInfo($id): \Generator
{
$partial = yield $this->getInfo($id);
$partial = (yield from $this->getInfo($id));
if (\time() - $this->fullChatLastUpdated($partial['bot_api_id']) < (isset($this->settings['peer']['full_info_cache_time']) ? $this->settings['peer']['full_info_cache_time'] : 0)) {
return \array_merge($partial, $this->full_chats[$partial['bot_api_id']]);
}
@ -737,17 +689,13 @@ trait PeerHandler
$full = (yield $this->methodCallAsyncRead('channels.getFullChannel', ['channel' => $partial['InputChannel']], ['datacenter' => $this->datacenter->curdc]))['full_chat'];
break;
}
$res = [];
$res['full'] = $full;
$res['last_update'] = \time();
$this->full_chats[$partial['bot_api_id']] = $res;
$partial = yield $this->getInfo($id);
$partial = (yield from $this->getInfo($id));
return \array_merge($partial, $res);
}
/**
* Get full info about peer (including full list of channel members), returns a Chat object.
*
@ -759,7 +707,7 @@ trait PeerHandler
*/
public function getPwrChat($id, $fullfetch = true, $send = true): \Generator
{
$full = $fullfetch ? yield $this->getFullInfo($id) : yield $this->getInfo($id);
$full = $fullfetch ? yield from $this->getFullInfo($id) : (yield from $this->getInfo($id));
$res = ['id' => $full['bot_api_id'], 'type' => $full['type']];
switch ($full['type']) {
case 'user':
@ -828,15 +776,15 @@ trait PeerHandler
if (isset($res['participants']) && $fullfetch) {
foreach ($res['participants'] as $key => $participant) {
$newres = [];
$newres['user'] = yield $this->getPwrChat($participant['user_id'], false, true);
$newres['user'] = (yield from $this->getPwrChat($participant['user_id'], false, true));
if (isset($participant['inviter_id'])) {
$newres['inviter'] = yield $this->getPwrChat($participant['inviter_id'], false, true);
$newres['inviter'] = (yield from $this->getPwrChat($participant['inviter_id'], false, true));
}
if (isset($participant['promoted_by'])) {
$newres['promoted_by'] = yield $this->getPwrChat($participant['promoted_by'], false, true);
$newres['promoted_by'] = (yield from $this->getPwrChat($participant['promoted_by'], false, true));
}
if (isset($participant['kicked_by'])) {
$newres['kicked_by'] = yield $this->getPwrChat($participant['kicked_by'], false, true);
$newres['kicked_by'] = (yield from $this->getPwrChat($participant['kicked_by'], false, true));
}
if (isset($participant['date'])) {
$newres['date'] = $participant['date'];
@ -873,15 +821,14 @@ trait PeerHandler
$limit = 200;
$filters = ['channelParticipantsAdmins', 'channelParticipantsBots'];
foreach ($filters as $filter) {
yield $this->fetchParticipants($full['InputChannel'], $filter, '', $total_count, $res);
yield from $this->fetchParticipants($full['InputChannel'], $filter, '', $total_count, $res);
}
$q = '';
$filters = ['channelParticipantsSearch', 'channelParticipantsKicked', 'channelParticipantsBanned'];
foreach ($filters as $filter) {
yield $this->recurseAlphabetSearchParticipants($full['InputChannel'], $filter, $q, $total_count, $res);
yield from $this->recurseAlphabetSearchParticipants($full['InputChannel'], $filter, $q, $total_count, $res);
}
$this->logger->logger('Fetched '.\count($res['participants'])." out of $total_count");
$this->logger->logger('Fetched ' . \count($res['participants']) . " out of {$total_count}");
$res['participants'] = \array_values($res['participants']);
}
if (!$fullfetch) {
@ -890,64 +837,55 @@ trait PeerHandler
if ($fullfetch || $send) {
$this->storeDb($res);
}
return $res;
}
private function recurseAlphabetSearchParticipants($channel, $filter, $q, $total_count, &$res)
private function recurseAlphabetSearchParticipants($channel, $filter, $q, $total_count, &$res): \Generator
{
if (!yield $this->fetchParticipants($channel, $filter, $q, $total_count, $res)) {
if (!(yield from $this->fetchParticipants($channel, $filter, $q, $total_count, $res))) {
return false;
}
for ($x = 'a'; $x !== 'aa' && $total_count > \count($res['participants']); $x++) {
yield $this->recurseAlphabetSearchParticipants($channel, $filter, $q.$x, $total_count, $res);
yield from $this->recurseAlphabetSearchParticipants($channel, $filter, $q . $x, $total_count, $res);
}
}
private function fetchParticipants($channel, $filter, $q, $total_count, &$res)
private function fetchParticipants($channel, $filter, $q, $total_count, &$res): \Generator
{
$offset = 0;
$limit = 200;
$has_more = false;
$cached = false;
$last_count = -1;
do {
try {
$gres = yield $this->methodCallAsyncRead('channels.getParticipants', ['channel' => $channel, 'filter' => ['_' => $filter, 'q' => $q], 'offset' => $offset, 'limit' => $limit, 'hash' => $hash = $this->getParticipantsHash($channel, $filter, $q, $offset, $limit)], ['datacenter' => $this->datacenter->curdc, 'heavy' => true]);
} catch (\danog\MadelineProto\RPCErrorException $e) {
if ($e->rpc === 'CHAT_ADMIN_REQUIRED') {
$this->logger->logger($e->rpc);
return $has_more;
}
throw $e;
}
if ($cached = $gres['_'] === 'channels.channelParticipantsNotModified') {
$gres = $this->fetchParticipantsCache($channel, $filter, $q, $offset, $limit);
} else {
$this->storeParticipantsCache($gres, $channel, $filter, $q, $offset, $limit);
}
if ($last_count !== -1 && $last_count !== $gres['count']) {
$has_more = true;
} else {
$last_count = $gres['count'];
}
foreach ($gres['participants'] as $participant) {
$newres = [];
$newres['user'] = yield $this->getPwrChat($participant['user_id'], false, true);
$newres['user'] = (yield from $this->getPwrChat($participant['user_id'], false, true));
if (isset($participant['inviter_id'])) {
$newres['inviter'] = yield $this->getPwrChat($participant['inviter_id'], false, true);
$newres['inviter'] = (yield from $this->getPwrChat($participant['inviter_id'], false, true));
}
if (isset($participant['kicked_by'])) {
$newres['kicked_by'] = yield $this->getPwrChat($participant['kicked_by'], false, true);
$newres['kicked_by'] = (yield from $this->getPwrChat($participant['kicked_by'], false, true));
}
if (isset($participant['promoted_by'])) {
$newres['promoted_by'] = yield $this->getPwrChat($participant['promoted_by'], false, true);
$newres['promoted_by'] = (yield from $this->getPwrChat($participant['promoted_by'], false, true));
}
if (isset($participant['date'])) {
$newres['date'] = $participant['date'];
@ -980,22 +918,18 @@ trait PeerHandler
}
$res['participants'][$participant['user_id']] = $newres;
}
$this->logger->logger('Fetched '.\count($gres['participants'])." channel participants with filter $filter, query $q, offset $offset, limit $limit, hash $hash: ".($cached ? 'cached' : 'not cached').', '.($offset + \count($gres['participants'])).' participants out of '.$gres['count'].', in total fetched '.\count($res['participants']).' out of '.$total_count);
$this->logger->logger('Fetched ' . \count($gres['participants']) . " channel participants with filter {$filter}, query {$q}, offset {$offset}, limit {$limit}, hash {$hash}: " . ($cached ? 'cached' : 'not cached') . ', ' . ($offset + \count($gres['participants'])) . ' participants out of ' . $gres['count'] . ', in total fetched ' . \count($res['participants']) . ' out of ' . $total_count);
$offset += \count($gres['participants']);
} while (\count($gres['participants']));
if ($offset === $limit) {
return true;
}
return $has_more;
}
private function fetchParticipantsCache($channel, $filter, $q, $offset, $limit)
{
return $this->channel_participants[$channel['channel_id']][$filter][$q][$offset][$limit];
}
private function storeParticipantsCache($gres, $channel, $filter, $q, $offset, $limit)
{
//return;
@ -1008,13 +942,11 @@ trait PeerHandler
$gres['hash'] = \danog\MadelineProto\Tools::genVectorHash($ids);
$this->channel_participants[$channel['channel_id']][$filter][$q][$offset][$limit] = $gres;
}
private function getParticipantsHash($channel, $filter, $q, $offset, $limit)
{
return isset($this->channel_participants[$channel['channel_id']][$filter][$q][$offset][$limit]) ? $this->channel_participants[$channel['channel_id']][$filter][$q][$offset][$limit]['hash'] : 0;
}
private function storeDb($res, $force = false)
private function storeDb($res, $force = false): \Generator
{
$settings = isset($this->settings['connection_settings'][$this->datacenter->curdc]) ? $this->settings['connection_settings'][$this->datacenter->curdc] : $this->settings['connection_settings']['all'];
if (!isset($this->settings['pwr']) || $this->settings['pwr']['pwr'] === false || $settings['test_mode']) {
@ -1033,29 +965,22 @@ trait PeerHandler
if (empty($this->qres)) {
return false;
}
try {
$payload = \json_encode($this->qres);
//$path = '/tmp/ids'.hash('sha256', $payload);
//file_put_contents($path, $payload);
$id = isset($this->authorization['user']['username']) ? $this->authorization['user']['username'] : $this->authorization['user']['id'];
$request = new Request('https://id.pwrtelegram.xyz/db'.$this->settings['pwr']['db_token'].'/addnewmadeline?d=pls&from='.$id, 'POST');
$request = new Request('https://id.pwrtelegram.xyz/db' . $this->settings['pwr']['db_token'] . '/addnewmadeline?d=pls&from=' . $id, 'POST');
$request->setHeader('content-type', 'application/json');
$request->setBody($payload);
$result = yield (
yield $this->datacenter->getHTTPClient()->request($request)
)->getBody()->buffer();
$this->logger->logger("============ $result =============", \danog\MadelineProto\Logger::VERBOSE);
$result = yield (yield $this->datacenter->getHTTPClient()->request($request))->getBody()->buffer();
$this->logger->logger("============ {$result} =============", \danog\MadelineProto\Logger::VERBOSE);
$this->qres = [];
$this->last_stored = \time() + 10;
} catch (\danog\MadelineProto\Exception $e) {
$this->logger->logger('======= COULD NOT STORE IN DB DUE TO '.$e->getMessage().' =============', \danog\MadelineProto\Logger::VERBOSE);
$this->logger->logger('======= COULD NOT STORE IN DB DUE TO ' . $e->getMessage() . ' =============', \danog\MadelineProto\Logger::VERBOSE);
}
}
/**
* Resolve username (use getInfo instead).
*
@ -1069,11 +994,10 @@ trait PeerHandler
$this->caching_simple_username[$username] = true;
$res = yield $this->methodCallAsyncRead('contacts.resolveUsername', ['username' => \str_replace('@', '', $username)], ['datacenter' => $this->datacenter->curdc]);
} catch (\danog\MadelineProto\RPCErrorException $e) {
$this->logger->logger('Username resolution failed with error '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);
$this->logger->logger('Username resolution failed with error ' . $e->getMessage(), \danog\MadelineProto\Logger::ERROR);
if (\strpos($e->rpc, 'FLOOD_WAIT_') === 0 || $e->rpc === 'AUTH_KEY_UNREGISTERED' || $e->rpc === 'USERNAME_INVALID') {
throw $e;
}
return false;
} finally {
if (isset($this->caching_simple_username[$username])) {
@ -1083,7 +1007,6 @@ trait PeerHandler
if ($res['_'] === 'contacts.resolvedPeer') {
return $res;
}
return false;
}
}

View File

@ -38,7 +38,6 @@ class ReferenceDatabase implements TLCallback
const PHOTO_LOCATION_LOCATION = 2;
// DEPRECATED: 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)
@ -57,42 +56,15 @@ class ReferenceDatabase implements TLCallback
const STICKER_SET_EMOTICON_ORIGIN = 8;
//
const WALLPAPER_ORIGIN = 9;
const LOCATION_CONTEXT = [
//'inputFileLocation' => self::PHOTO_LOCATION_LOCATION, // DEPRECATED
'inputDocumentFileLocation' => self::DOCUMENT_LOCATION,
'inputPhotoFileLocation' => self::PHOTO_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,
'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,
'inputPhotoFileLocation' => self::PHOTO_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, '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.
*
@ -105,70 +77,56 @@ class ReferenceDatabase implements TLCallback
private $API;
private $refresh = false;
private $refreshCount = 0;
public function __construct(MTProto $API)
{
$this->API = $API;
$this->init();
}
public function __wakeup()
{
$this->init();
}
public function __sleep()
{
return ['db', 'API'];
}
public function init()
{
foreach ($this->db as $key => $value) {
if ($key[0] === "0") { // Unsetting deprecated DOCUMENT_LOCATION_LOCATION
if ($key[0] === "0") {
// Unsetting deprecated DOCUMENT_LOCATION_LOCATION
unset($this->db[$key]);
}
}
}
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']]]
);
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, 'populateReference']);
}
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->API->logger->logger('Found ' . \count($this->cacheContexts) . ' pending contexts', \danog\MadelineProto\Logger::ERROR);
$this->cacheContexts = [];
}
if ($this->cache) {
@ -176,7 +134,6 @@ class ReferenceDatabase implements TLCallback
$this->cache = [];
}
}
public function addReference(array $location)
{
if (!$this->cacheContexts) {
@ -189,18 +146,15 @@ class ReferenceDatabase implements TLCallback
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'];
}
@ -209,15 +163,13 @@ class ReferenceDatabase implements TLCallback
$frames = \array_reverse($frames);
$tl_trace = \array_shift($frames);
foreach ($frames as $frame) {
$tl_trace .= "['".$frame."']";
$tl_trace .= "['" . $frame . "']";
}
$this->API->logger->logger($tl_trace, \danog\MadelineProto\Logger::ERROR);
return false;
}
if (!isset($location['file_reference'])) {
$this->API->logger->logger("Object {$location['_']} does not have reference", \danog\MadelineProto\Logger::ERROR);
return false;
}
$key = \count($this->cacheContexts) - 1;
@ -232,27 +184,24 @@ class ReferenceDatabase implements TLCallback
$locationType = self::PHOTO_LOCATION_LOCATION;
break;
default:
throw new Exception('Unknown location type provided: '.$location['_']);
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);
$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");
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->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;
@ -261,8 +210,7 @@ class ReferenceDatabase implements TLCallback
}
$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);
$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];
@ -316,12 +264,10 @@ class ReferenceDatabase implements TLCallback
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);
$this->API->logger->logger("Skipped origin {$originType} ({$data['_']}) for " . \count($cache) . ' references', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
return;
}
break;
@ -334,19 +280,17 @@ class ReferenceDatabase implements TLCallback
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);
$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->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;
@ -355,8 +299,7 @@ class ReferenceDatabase implements TLCallback
}
$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);
$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];
@ -384,16 +327,13 @@ class ReferenceDatabase implements TLCallback
foreach ($res['photos'] as $photo) {
$origin['max_id'] = $photo['id'];
$dc_id = $photo['dc_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'])) {
@ -409,8 +349,7 @@ class ReferenceDatabase implements TLCallback
}
}
}
$this->API->logger->logger("Added origin $originType ({$data['_']}) to $count references", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->API->logger->logger("Added origin {$originType} ({$data['_']}) to {$count} references", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
return;
case 'messages.getStickers':
$origin['emoticon'] = $body['emoticon'];
@ -421,9 +360,8 @@ class ReferenceDatabase implements TLCallback
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);
$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])) {
@ -431,17 +369,14 @@ class ReferenceDatabase implements TLCallback
}
$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) {
@ -459,23 +394,18 @@ class ReferenceDatabase implements TLCallback
$this->refreshCount--;
}
}
public function refreshReference(int $locationType, array $location)
{
return $this->refreshReferenceInternal($this->serializeLocation($locationType, $location));
}
public function refreshReferenceInternal(string $location)
public function refreshReferenceInternal(string $location): \Generator
{
if (isset($this->refreshed[$location])) {
$this->API->logger->logger('Reference already refreshed!', \danog\MadelineProto\Logger::VERBOSE);
return $this->db[$location]['reference'];
}
\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);
@ -521,67 +451,54 @@ class ReferenceDatabase implements TLCallback
yield $this->API->methodCallAsyncRead('account.getWallPapers', $origin, ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
break;
default:
throw new \danog\MadelineProto\Exception("Unknown origin type $originType");
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 populateReference(array $object)
public function populateReference(array $object): \Generator
{
$object['file_reference'] = yield $this->getReference(self::LOCATION_CONTEXT[$object['_']], $object);
return $object;
}
public function getReference(int $locationType, array $location)
{
$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);
$this->API->logger->logger("Using outdated file reference for location of type {$locationType} object {$location['_']}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
return $location['file_reference'];
}
if (!$this->refresh) {
$this->API->logger->logger("Using null file reference for location of type $locationType object {$location['_']}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
$this->API->logger->logger("Using null file reference for location of type {$locationType} object {$location['_']}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
return '';
}
throw new \danog\MadelineProto\Exception("Could not find file reference for location of type $locationType object {$location['_']}");
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);
$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 $this->db[$locationString]['reference'];
}
private function serializeLocation(int $locationType, array $location)
{
switch ($locationType) {
case self::DOCUMENT_LOCATION:
case self::PHOTO_LOCATION:
return $locationType.(\is_int($location['id']) ? \danog\MadelineProto\Tools::packSignedLong($location['id']) : $location['id']);
return $locationType . (\is_int($location['id']) ? \danog\MadelineProto\Tools::packSignedLong($location['id']) : $location['id']);
case self::PHOTO_LOCATION_LOCATION:
$dc_id = \danog\MadelineProto\Tools::packSignedInt($location['dc_id']);
$volume_id = \is_int($location['volume_id']) ? \danog\MadelineProto\Tools::packSignedLong($location['volume_id']) : $location['volume_id'];
$local_id = \danog\MadelineProto\Tools::packSignedInt($location['local_id']);
return $locationType.$dc_id.$volume_id.$local_id;
return $locationType . $dc_id . $volume_id . $local_id;
}
}
public function __debugInfo()
{
return ['ReferenceDatabase instance '.\spl_object_hash($this)];
return ['ReferenceDatabase instance ' . \spl_object_hash($this)];
}
}

View File

@ -34,7 +34,6 @@ trait UpdateHandler
private $channels_state;
public $updates = [];
public $updates_key = 0;
/**
* PWR update handler.
*
@ -56,7 +55,6 @@ trait UpdateHandler
\in_array($this->settings['pwr']['updateHandler'], [['danog\\MadelineProto\\API', 'getUpdatesUpdateHandler'], 'getUpdatesUpdateHandler']) ? $this->getUpdatesUpdateHandler($update) : $this->settings['pwr']['updateHandler']($update);
}
}
/**
* Getupdates update handler.
*
@ -73,7 +71,6 @@ trait UpdateHandler
}
$this->updates[$this->updates_key++] = $update;
}
/**
* Get updates.
*
@ -92,9 +89,7 @@ trait UpdateHandler
if (!$this->settings['updates']['run_callback']) {
$this->settings['updates']['run_callback'] = true;
}
$params = \array_merge(self::DEFAULT_GETUPDATES_PARAMS, $params);
if (empty($this->updates)) {
$this->update_deferred = new Deferred();
if (!$params['timeout']) {
@ -102,11 +97,9 @@ trait UpdateHandler
}
yield \danog\MadelineProto\Tools::first([$this->waitUpdate(), \danog\MadelineProto\Tools::sleep($params['timeout'])]);
}
if (empty($this->updates)) {
return [];
}
if ($params['offset'] < 0) {
$params['offset'] = \array_reverse(\array_keys((array) $this->updates))[\abs($params['offset']) - 1];
}
@ -118,13 +111,10 @@ trait UpdateHandler
$updates[] = ['update_id' => $key, 'update' => $value];
}
}
return $updates;
}
public $update_resolved = false;
public $update_deferred;
/**
* Wait for update.
*
@ -141,7 +131,6 @@ trait UpdateHandler
$this->update_resolved = false;
$this->update_deferred = new Deferred();
}
/**
* Signal update.
*
@ -161,8 +150,6 @@ trait UpdateHandler
}
});
}
/**
* Check message ID.
*
@ -177,7 +164,6 @@ trait UpdateHandler
if (!isset($message['to_id'])) {
return true;
}
try {
$peer_id = $this->getId($message['to_id']);
} catch (\danog\MadelineProto\Exception $e) {
@ -188,13 +174,10 @@ trait UpdateHandler
$message_id = $message['id'];
if (!isset($this->msg_ids[$peer_id]) || $message_id > $this->msg_ids[$peer_id]) {
$this->msg_ids[$peer_id] = $message_id;
return true;
}
return false;
}
/**
* Get channel state.
*
@ -202,16 +185,14 @@ trait UpdateHandler
*
* @return UpdatesState|UpdatesState[]
*/
public function loadUpdateState()
public function loadUpdateState(): \Generator
{
if (!$this->got_state) {
$this->got_state = true;
$this->channels_state->get(false, yield $this->getUpdatesState());
$this->channels_state->get(false, yield from $this->getUpdatesState());
}
return $this->channels_state->get(false);
}
/**
* Load channel state.
*
@ -226,7 +207,6 @@ trait UpdateHandler
{
return $this->channels_state->get($channelId, $init);
}
/**
* Get channel states.
*
@ -238,7 +218,6 @@ trait UpdateHandler
{
return $this->channels_state;
}
/**
* Get update state.
*
@ -250,11 +229,8 @@ trait UpdateHandler
{
$data = yield $this->methodCallAsyncRead('updates.getState', [], ['datacenter' => $this->settings['connection_settings']['default_dc']]);
yield $this->getCdnConfig($this->settings['connection_settings']['default_dc']);
return $data;
}
/**
* Undocumented function.
*
@ -273,16 +249,13 @@ trait UpdateHandler
if ($actual_updates) {
$updates = $actual_updates;
}
$this->logger->logger('Parsing updates ('.$updates['_'].') received via the socket...', \danog\MadelineProto\Logger::VERBOSE);
$this->logger->logger('Parsing updates (' . $updates['_'] . ') received via the socket...', \danog\MadelineProto\Logger::VERBOSE);
switch ($updates['_']) {
case 'updates':
case 'updatesCombined':
$result = [];
foreach ($updates['updates'] as $key => $update) {
if ($update['_'] === 'updateNewMessage' || $update['_'] === 'updateReadMessagesContents' ||
$update['_'] === 'updateEditMessage' || $update['_'] === 'updateDeleteMessages' ||
$update['_'] === 'updateReadHistoryInbox' || $update['_'] === 'updateReadHistoryOutbox' ||
$update['_'] === 'updateWebPage' || $update['_'] === 'updateMessageID') {
if ($update['_'] === 'updateNewMessage' || $update['_'] === 'updateReadMessagesContents' || $update['_'] === 'updateEditMessage' || $update['_'] === 'updateDeleteMessages' || $update['_'] === 'updateReadHistoryInbox' || $update['_'] === 'updateReadHistoryOutbox' || $update['_'] === 'updateWebPage' || $update['_'] === 'updateMessageID') {
$result[yield $this->feeders[false]->feedSingle($update)] = true;
unset($updates['updates'][$key]);
}
@ -308,20 +281,18 @@ trait UpdateHandler
$updates['user_id'] = (yield $this->getInfo($updates['request']['body']['peer']))['bot_api_id'];
$updates['message'] = $updates['request']['body']['message'];
unset($updates['request']);
// no break
// no 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 (!yield $this->peerIsset($from_id) || !yield $this->peerIsset($to_id) || isset($updates['via_bot_id']) && !yield $this->peerIsset($updates['via_bot_id']) || isset($updates['entities']) && !yield $this->entitiesPeerIsset($updates['entities']) || isset($updates['fwd_from']) && !yield $this->fwdPeerIsset($updates['fwd_from'])) {
if (!(yield from $this->peerIsset($from_id) || !(yield from $this->peerIsset($to_id) || isset($updates['via_bot_id']) && !(yield from $this->peerIsset($updates['via_bot_id']) || isset($updates['entities']) && !(yield from $this->entitiesPeerIsset($updates['entities']) || isset($updates['fwd_from']) && !yield $this->fwdPeerIsset($updates['fwd_from'])))))) {
yield $this->updaters[false]->resume();
return;
}
$message = $updates;
$message['_'] = 'message';
$message['from_id'] = $from_id;
try {
$message['to_id'] = (yield $this->getInfo($to_id))['Peer'];
} catch (\danog\MadelineProto\Exception $e) {
@ -340,7 +311,7 @@ trait UpdateHandler
$this->updaters[false]->resume();
break;
default:
throw new \danog\MadelineProto\ResponseException('Unrecognized update received: '.\var_export($updates, true));
throw new \danog\MadelineProto\ResponseException('Unrecognized update received: ' . \var_export($updates, true));
break;
}
}
@ -368,13 +339,11 @@ trait UpdateHandler
$this->logger->logger('Got new dc options', \danog\MadelineProto\Logger::VERBOSE);
$this->config['dc_options'] = $update['dc_options'];
yield $this->parseConfig();
return;
}
if ($update['_'] === 'updatePhoneCall') {
if (!\class_exists('\\danog\\MadelineProto\\VoIP')) {
$this->logger->logger('The php-libtgvoip extension is required to accept and manage calls. See daniil.it/MadelineProto for more info.', \danog\MadelineProto\Logger::WARNING);
return;
}
switch ($update['phone_call']['_']) {
@ -403,54 +372,50 @@ trait UpdateHandler
if (!isset($this->calls[$update['phone_call']['id']])) {
return;
}
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'])) {
if (isset($update['qts'])) {
$cur_state = yield $this->loadUpdateState();
$cur_state = (yield from $this->loadUpdateState());
if ($cur_state->qts() === -1) {
$cur_state->qts($update['qts']);
}
if ($update['qts'] < $cur_state->qts()) {
$this->logger->logger('Duplicate update. update qts: '.$update['qts'].' <= current qts '.$cur_state->qts().', chat id: '.$update['message']['chat_id'], \danog\MadelineProto\Logger::ERROR);
$this->logger->logger('Duplicate update. update qts: ' . $update['qts'] . ' <= current qts ' . $cur_state->qts() . ', chat id: ' . $update['message']['chat_id'], \danog\MadelineProto\Logger::ERROR);
return false;
}
if ($update['qts'] > $cur_state->qts() + 1) {
$this->logger->logger('Qts hole. Fetching updates manually: update qts: '.$update['qts'].' > current qts '.$cur_state->qts().'+1, chat id: '.$update['message']['chat_id'], \danog\MadelineProto\Logger::ERROR);
$this->logger->logger('Qts hole. Fetching updates manually: update qts: ' . $update['qts'] . ' > current qts ' . $cur_state->qts() . '+1, chat id: ' . $update['message']['chat_id'], \danog\MadelineProto\Logger::ERROR);
$this->updaters[false]->resumeDefer();
return false;
}
$this->logger->logger('Applying qts: '.$update['qts'].' over current qts '.$cur_state->qts().', chat id: '.$update['message']['chat_id'], \danog\MadelineProto\Logger::VERBOSE);
$this->logger->logger('Applying qts: ' . $update['qts'] . ' over current qts ' . $cur_state->qts() . ', chat id: ' . $update['message']['chat_id'], \danog\MadelineProto\Logger::VERBOSE);
yield $this->methodCallAsyncRead('messages.receivedQueue', ['max_qts' => $cur_state->qts($update['qts'])], ['datacenter' => $this->settings['connection_settings']['default_dc']]);
}
yield $this->handleEncryptedUpdate($update);
return;
}
/*
if ($update['_'] === 'updateEncryptedChatTyping') {
$update = ['_' => 'updateUserTyping', 'user_id' => $this->encrypted_chats[$update['chat_id']]['user_id'], 'action' => ['_' => 'sendMessageTypingAction']];
}
*/
if ($update['_'] === 'updateEncryptedChatTyping') {
$update = ['_' => 'updateUserTyping', 'user_id' => $this->encrypted_chats[$update['chat_id']]['user_id'], 'action' => ['_' => 'sendMessageTypingAction']];
}
*/
if ($update['_'] === 'updateEncryption') {
switch ($update['chat']['_']) {
case 'encryptedChatRequested':
if ($this->settings['secret_chats']['accept_chats'] === false || \is_array($this->settings['secret_chats']['accept_chats']) && !\in_array($update['chat']['admin_id'], $this->settings['secret_chats']['accept_chats'])) {
return;
}
$this->logger->logger('Accepting secret chat '.$update['chat']['id'], \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger('Accepting secret chat ' . $update['chat']['id'], \danog\MadelineProto\Logger::NOTICE);
try {
yield $this->acceptSecretChat($update['chat']);
} catch (RPCErrorException $e) {
$this->logger->logger("Error while accepting secret chat: $e", Logger::FATAL_ERROR);
$this->logger->logger("Error while accepting secret chat: {$e}", Logger::FATAL_ERROR);
}
break;
case 'encryptedChatDiscarded':
$this->logger->logger('Deleting secret chat '.$update['chat']['id'].' because it was revoked by the other user', \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger('Deleting secret chat ' . $update['chat']['id'] . ' because it was revoked by the other user', \danog\MadelineProto\Logger::NOTICE);
if (isset($this->secret_chats[$update['chat']['id']])) {
unset($this->secret_chats[$update['chat']['id']]);
}
@ -460,17 +425,15 @@ trait UpdateHandler
if (isset($this->temp_rekeyed_secret_chats[$update['chat']['id']])) {
unset($this->temp_rekeyed_secret_chats[$update['chat']['id']]);
}
break;
case 'encryptedChat':
$this->logger->logger('Completing creation of secret chat '.$update['chat']['id'], \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger('Completing creation of secret chat ' . $update['chat']['id'], \danog\MadelineProto\Logger::NOTICE);
yield $this->completeSecretChat($update['chat']);
break;
}
//$this->logger->logger($update, \danog\MadelineProto\Logger::NOTICE);
}
//if ($update['_'] === 'updateServiceNotification' && strpos($update['type'], 'AUTH_KEY_DROP_') === 0) {
//}
if (!$this->settings['updates']['handle_updates']) {
return;
@ -488,7 +451,6 @@ trait UpdateHandler
$this->getUpdatesUpdateHandler($update);
}
}
/**
* Send update to webhook.
*
@ -502,23 +464,20 @@ trait UpdateHandler
//$this->logger->logger($update, $payload, json_last_error());
if ($payload === '') {
$this->logger->logger('EMPTY UPDATE');
return;
}
\danog\MadelineProto\Tools::callFork((function () use ($payload) {
\danog\MadelineProto\Tools::callFork((function () use ($payload): \Generator {
$request = new Request($this->hook_url, 'POST');
$request->setHeader('content-type', 'application/json');
$request->setBody($payload);
$result = yield (yield $this->datacenter->getHTTPClient()->request($request))->getBody()->buffer();
$this->logger->logger('Result of webhook query is '.$result, \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger('Result of webhook query is ' . $result, \danog\MadelineProto\Logger::NOTICE);
$result = \json_decode($result, true);
if (\is_array($result) && isset($result['method']) && $result['method'] != '' && \is_string($result['method'])) {
try {
$this->logger->logger('Reverse webhook command returned', yield $this->methodCallAsyncRead($result['method'], $result, ['datacenter' => $this->datacenter->curdc]));
} catch (\Throwable $e) {
$this->logger->logger("Reverse webhook command returned: $e");
$this->logger->logger("Reverse webhook command returned: {$e}");
}
}
})());

View File

@ -48,21 +48,18 @@ class UpdatesState
* @var int
*/
private $date = 1;
/**
* Channel ID.
*
* @var int|bool
*/
private $channelId;
/**
* Is busy?
*
* @var bool
*/
private $syncLoading = false;
/**
* Init function.
*
@ -74,7 +71,6 @@ class UpdatesState
$this->channelId = $channelId;
$this->update($init);
}
/**
* Sleep function.
*
@ -84,7 +80,6 @@ class UpdatesState
{
return $this->channelId ? ['pts', 'channelId'] : ['pts', 'qts', 'seq', 'date', 'channelId'];
}
/**
* Is this state relative to a channel?
*
@ -94,7 +89,6 @@ class UpdatesState
{
return (bool) $this->channelId;
}
/**
* Get the channel ID.
*
@ -104,7 +98,6 @@ class UpdatesState
{
return $this->channelId;
}
/**
* Are we currently busy?
*
@ -117,10 +110,8 @@ class UpdatesState
if ($set !== null) {
$this->syncLoading = $set;
}
return $this->syncLoading;
}
/**
* Update multiple parameters.
*
@ -135,10 +126,8 @@ class UpdatesState
$this->{$param}($init[$param]);
}
}
return $this;
}
/**
* Get/set PTS.
*
@ -151,10 +140,8 @@ class UpdatesState
if ($set !== 0 && $set > $this->pts) {
$this->pts = $set;
}
return $this->pts;
}
/**
* Get/set QTS.
*
@ -167,10 +154,8 @@ class UpdatesState
if ($set !== 0 && $set > $this->qts) {
$this->qts = $set;
}
return $this->qts;
}
/**
* Get/set seq.
*
@ -183,10 +168,8 @@ class UpdatesState
if ($set !== 0 && $set > $this->seq) {
$this->seq = $set;
}
return $this->seq;
}
/**
* Get/set date.
*
@ -199,10 +182,8 @@ class UpdatesState
if ($set !== 0 && $set > $this->date) {
$this->date = $set;
}
return $this->date;
}
/**
* Check validity of PTS contained in update.
*
@ -214,7 +195,6 @@ class UpdatesState
{
return $update['pts'] - ($this->pts + $update['pts_count']);
}
/**
* Check validity of seq contained in update.
*

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,5 @@
<?php
/**
* MyTelegramOrgWrapper module.
*
@ -27,7 +28,6 @@ use Amp\Http\Client\Request;
class MyTelegramOrgWrapper
{
use Tools;
private $logged = false;
private $hash = '';
private $number;
@ -36,75 +36,60 @@ class MyTelegramOrgWrapper
private $async = true;
private $jar;
const MY_TELEGRAM_URL = 'https://my.telegram.org';
public function __sleep()
{
return ['logged', 'hash', 'number', 'creation_hash', 'settings', 'async', 'jar'];
}
public function __construct($settings = [])
{
$this->settings = MTProto::getSettings($settings, $this->settings);
$this->__wakeup();
}
public function __wakeup()
{
if ($this->settings === null) {
$this->settings = [];
}
if (!$this->jar || !($this->jar instanceof InMemoryCookieJar)) {
$this->jar = new InMemoryCookieJar;
if (!$this->jar || !$this->jar instanceof InMemoryCookieJar) {
$this->jar = new InMemoryCookieJar();
}
$this->settings = MTProto::getSettings($this->settings);
$this->datacenter = new DataCenter(
new class($this->settings) {
public function __construct($settings)
{
$this->logger = Logger::getLoggerFromSettings($settings);
}
public function getLogger()
{
return $this->logger;
}
},
[],
$this->settings['connection_settings'],
true,
$this->jar
);
$this->datacenter = new DataCenter(new class($this->settings) {
public function __construct($settings)
{
$this->logger = Logger::getLoggerFromSettings($settings);
}
public function getLogger()
{
return $this->logger;
}
}, [], $this->settings['connection_settings'], true, $this->jar);
}
public function login($number)
public function login($number): \Generator
{
$this->number = $number;
$request = new Request(self::MY_TELEGRAM_URL.'/auth/send_password', 'POST');
$request = new Request(self::MY_TELEGRAM_URL . '/auth/send_password', 'POST');
$request->setBody(\http_build_query(['phone' => $number]));
$request->setHeaders($this->getHeaders('origin'));
$response = yield $this->datacenter->getHTTPClient()->request($request);
$result = yield $response->getBody()->buffer();
$resulta = \json_decode($result, true);
if (!isset($resulta['random_hash'])) {
throw new Exception($result);
}
$this->hash = $resulta['random_hash'];
}
public function completeLogin($password)
public function completeLogin($password): \Generator
{
if ($this->logged) {
throw new Exception('Already logged in!');
}
$request = new Request(self::MY_TELEGRAM_URL.'/auth/login', 'POST');
$request = new Request(self::MY_TELEGRAM_URL . '/auth/login', 'POST');
$request->setBody(\http_build_query(['phone' => $this->number, 'random_hash' => $this->hash, 'password' => $password]));
$request->setHeaders($this->getHeaders('origin'));
$request->setHeader('user-agent', 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13');
$response = yield $this->datacenter->getHTTPClient()->request($request);
$result = yield $response->getBody()->buffer();
switch ($result) {
case 'true':
//Logger::log(['Login OK'], Logger::VERBOSE);
@ -112,52 +97,41 @@ class MyTelegramOrgWrapper
default:
throw new Exception($result);
}
return $this->logged = true;
}
public function loggedIn()
{
return $this->logged;
}
public function hasApp()
public function hasApp(): \Generator
{
if (!$this->logged) {
throw new Exception('Not logged in!');
}
$request = new Request(self::MY_TELEGRAM_URL.'/apps');
$request = new Request(self::MY_TELEGRAM_URL . '/apps');
$request->setHeaders($this->getHeaders('refer'));
$response = yield $this->datacenter->getHTTPClient()->request($request);
$result = yield $response->getBody()->buffer();
$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];
return false;
}
$this->logged = false;
throw new Exception($title);
}
public function getApp()
public function getApp(): \Generator
{
if (!$this->logged) {
throw new Exception('Not logged in!');
}
$request = new Request(self::MY_TELEGRAM_URL.'/apps');
$request = new Request(self::MY_TELEGRAM_URL . '/apps');
$request->setHeaders($this->getHeaders('refer'));
$response = yield $this->datacenter->getHTTPClient()->request($request);
$result = yield $response->getBody()->buffer();
$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);
@ -168,41 +142,33 @@ class MyTelegramOrgWrapper
<span class="form-control input-xlarge uneditable-input" onclick="this.select();">', $result);
$asd = \explode('</span>', $cose[1]);
$api_hash = $asd[0];
return ['api_id' => (int) $api_id, 'api_hash' => $api_hash];
}
public function createApp($settings)
public function createApp($settings): \Generator
{
if (!$this->logged) {
throw new Exception('Not logged in!');
}
if (yield $this->hasApp()) {
if (yield from $this->hasApp()) {
throw new Exception('The app was already created!');
}
$request = new Request(self::MY_TELEGRAM_URL.'/apps/create', 'POST');
$request = new Request(self::MY_TELEGRAM_URL . '/apps/create', 'POST');
$request->setHeaders($this->getHeaders('app'));
$request->setBody(\http_build_query(['hash' => $this->creation_hash, 'app_title' => $settings['app_title'], 'app_shortname' => $settings['app_shortname'], 'app_url' => $settings['app_url'], 'app_platform' => $settings['app_platform'], 'app_desc' => $settings['app_desc']]));
$response = yield $this->datacenter->getHTTPClient()->request($request);
$result = yield $response->getBody()->buffer();
if ($result) {
throw new Exception(\html_entity_decode($result));
}
$request = new Request(self::MY_TELEGRAM_URL.'/apps');
$request = new Request(self::MY_TELEGRAM_URL . '/apps');
$request->setHeaders($this->getHeaders('refer'));
$response = yield $this->datacenter->getHTTPClient()->request($request);
$result = yield $response->getBody()->buffer();
$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);
@ -213,10 +179,8 @@ class MyTelegramOrgWrapper
<span class="form-control input-xlarge uneditable-input" onclick="this.select();">', $result);
$asd = \explode('</span>', $cose['1']);
$api_hash = $asd['0'];
return ['api_id' => (int) $api_id, 'api_hash' => $api_hash];
}
/**
* Function for generating curl request headers.
*/
@ -228,43 +192,39 @@ class MyTelegramOrgWrapper
$headers[] = 'Connection: keep-alive';
$headers[] = 'Accept-Language: it-IT,it;q=0.8,en-US;q=0.6,en;q=0.4';
$headers[] = 'User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36';
// Add additional headers based on the type of request.
switch ($httpType) {
case 'origin':
$headers[] = 'Origin: '.self::MY_TELEGRAM_URL;
$headers[] = 'Origin: ' . self::MY_TELEGRAM_URL;
//$headers[] = 'Accept-Encoding: gzip, deflate, br';
$headers[] = 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8';
$headers[] = 'Accept: application/json, text/javascript, */*; q=0.01';
$headers[] = 'Referer: '.self::MY_TELEGRAM_URL.'/auth';
$headers[] = 'Referer: ' . self::MY_TELEGRAM_URL . '/auth';
$headers[] = 'X-Requested-With: XMLHttpRequest';
break;
case 'refer':
//$headers[] = 'Accept-Encoding: gzip, deflate, sdch, br';
$headers[] = 'Upgrade-Insecure-Requests: 1';
$headers[] = 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8';
$headers[] = 'Referer: '.self::MY_TELEGRAM_URL;
$headers[] = 'Referer: ' . self::MY_TELEGRAM_URL;
$headers[] = 'Cache-Control: max-age=0';
break;
case 'app':
$headers[] = 'Origin: '.self::MY_TELEGRAM_URL;
$headers[] = 'Origin: ' . self::MY_TELEGRAM_URL;
//$headers[] = 'Accept-Encoding: gzip, deflate, br';
$headers[] = 'Content-Type: application/x-www-form-urlencoded; charset=UTF-8';
$headers[] = 'Accept: */*';
$headers[] = 'Referer: '.self::MY_TELEGRAM_URL.'/apps';
$headers[] = 'Referer: ' . self::MY_TELEGRAM_URL . '/apps';
$headers[] = 'X-Requested-With: XMLHttpRequest';
break;
}
$final_headers = [];
foreach ($headers as $header) {
list($key, $value) = \explode(':', $header, 2);
$final_headers[\trim($key)] = \trim($value);
}
return $final_headers;
}
public function async($async)
{
$this->async = $async;
@ -277,9 +237,8 @@ class MyTelegramOrgWrapper
{
$name .= '_async';
$async = \is_array(\end($arguments)) && isset(\end($arguments)['async']) ? \end($arguments)['async'] : $this->async;
if (!\method_exists($this, $name)) {
throw new Exception("$name does not exist!");
throw new Exception("{$name} does not exist!");
}
return $async ? $this->{$name}(...$arguments) : Tools::wait($this->{$name}(...$arguments));
}

View File

@ -22,12 +22,10 @@ namespace danog\MadelineProto;
class PTSException extends \Exception
{
use TL\PrettyException;
public function __toString()
{
return \get_class($this).($this->message !== '' ? ': ' : '').$this->message.PHP_EOL.'TL Trace:'.PHP_EOL.PHP_EOL.$this->getTLTrace().PHP_EOL;
return \get_class($this) . ($this->message !== '' ? ': ' : '') . $this->message . PHP_EOL . 'TL Trace:' . PHP_EOL . PHP_EOL . $this->getTLTrace() . PHP_EOL;
}
public function __construct($message, $file = '')
{
parent::__construct($message);

View File

@ -0,0 +1 @@
<?php

View File

@ -24,101 +24,43 @@ class RPCErrorException extends \Exception
use TL\PrettyException;
private $fetched = false;
public static $rollbar = true;
public static $descriptions = [
'RPC_MCGET_FAIL' => 'Telegram is having internal issues, please try again later.',
'RPC_CALL_FAIL' => 'Telegram is having internal issues, please try again later.',
'USER_PRIVACY_RESTRICTED' => "The user's privacy settings do not allow you to do this",
'CHANNEL_PRIVATE' => "You haven't joined this channel/supergroup",
'USER_IS_BOT' => "Bots can't send messages to other bots",
'BOT_METHOD_INVALID' => 'This method cannot be run by a bot',
'PHONE_CODE_EXPIRED' => 'The phone code you provided has expired, this may happen if it was sent to any chat on telegram (if the code is sent through a telegram chat (not the official account) to avoid it append or prepend to the code some chars)',
'USERNAME_INVALID' => 'The provided username is not valid',
'ACCESS_TOKEN_INVALID' => 'The provided token is not valid',
'ACTIVE_USER_REQUIRED' => 'The method is only available to already activated users',
'FIRSTNAME_INVALID' => 'The first name is invalid',
'LASTNAME_INVALID' => 'The last name is invalid',
'PHONE_NUMBER_INVALID' => 'The phone number is invalid',
'PHONE_CODE_HASH_EMPTY' => 'phone_code_hash is missing',
'PHONE_CODE_EMPTY' => 'phone_code is missing',
'PHONE_CODE_EXPIRED' => 'The confirmation code has expired',
'API_ID_INVALID' => 'The api_id/api_hash combination is invalid',
'PHONE_NUMBER_OCCUPIED' => 'The phone number is already in use',
'PHONE_NUMBER_UNOCCUPIED' => 'The phone number is not yet being used',
'USERS_TOO_FEW' => 'Not enough users (to create a chat, for example)',
'USERS_TOO_MUCH' => 'The maximum number of users has been exceeded (to create a chat, for example)',
'TYPE_CONSTRUCTOR_INVALID' => 'The type constructor is invalid',
'FILE_PART_INVALID' => 'The file part number is invalid',
'FILE_PARTS_INVALID' => 'The number of file parts is invalid',
'MD5_CHECKSUM_INVALID' => 'The MD5 checksums do not match',
'PHOTO_INVALID_DIMENSIONS' => 'The photo dimensions are invalid',
'FIELD_NAME_INVALID' => 'The field with the name FIELD_NAME is invalid',
'FIELD_NAME_EMPTY' => 'The field with the name FIELD_NAME is missing',
'MSG_WAIT_FAILED' => 'A waiting call returned an error',
'USERNAME_NOT_OCCUPIED' => 'The provided username is not occupied',
'PHONE_NUMBER_BANNED' => 'The provided phone number is banned from telegram',
'AUTH_KEY_UNREGISTERED' => 'The authorization key has expired',
'INVITE_HASH_EXPIRED' => 'The invite link has expired',
'USER_DEACTIVATED' => 'The user was deactivated',
'USER_ALREADY_PARTICIPANT' => 'The user is already in the group',
'MESSAGE_ID_INVALID' => 'The provided message id is invalid',
'PEER_ID_INVALID' => 'The provided peer id is invalid',
'CHAT_ID_INVALID' => 'The provided chat id is invalid',
'MESSAGE_DELETE_FORBIDDEN' => "You can't delete one of the messages you tried to delete, most likely because it is a service message.",
'CHAT_ADMIN_REQUIRED' => 'You must be an admin in this chat to do this',
-429 => 'Too many requests',
'PEER_FLOOD' => "You are spamreported, you can't do this",
];
public static $descriptions = ['RPC_MCGET_FAIL' => 'Telegram is having internal issues, please try again later.', 'RPC_CALL_FAIL' => 'Telegram is having internal issues, please try again later.', 'USER_PRIVACY_RESTRICTED' => "The user's privacy settings do not allow you to do this", 'CHANNEL_PRIVATE' => "You haven't joined this channel/supergroup", 'USER_IS_BOT' => "Bots can't send messages to other bots", 'BOT_METHOD_INVALID' => 'This method cannot be run by a bot', 'PHONE_CODE_EXPIRED' => 'The phone code you provided has expired, this may happen if it was sent to any chat on telegram (if the code is sent through a telegram chat (not the official account) to avoid it append or prepend to the code some chars)', 'USERNAME_INVALID' => 'The provided username is not valid', 'ACCESS_TOKEN_INVALID' => 'The provided token is not valid', 'ACTIVE_USER_REQUIRED' => 'The method is only available to already activated users', 'FIRSTNAME_INVALID' => 'The first name is invalid', 'LASTNAME_INVALID' => 'The last name is invalid', 'PHONE_NUMBER_INVALID' => 'The phone number is invalid', 'PHONE_CODE_HASH_EMPTY' => 'phone_code_hash is missing', 'PHONE_CODE_EMPTY' => 'phone_code is missing', 'PHONE_CODE_EXPIRED' => 'The confirmation code has expired', 'API_ID_INVALID' => 'The api_id/api_hash combination is invalid', 'PHONE_NUMBER_OCCUPIED' => 'The phone number is already in use', 'PHONE_NUMBER_UNOCCUPIED' => 'The phone number is not yet being used', 'USERS_TOO_FEW' => 'Not enough users (to create a chat, for example)', 'USERS_TOO_MUCH' => 'The maximum number of users has been exceeded (to create a chat, for example)', 'TYPE_CONSTRUCTOR_INVALID' => 'The type constructor is invalid', 'FILE_PART_INVALID' => 'The file part number is invalid', 'FILE_PARTS_INVALID' => 'The number of file parts is invalid', 'MD5_CHECKSUM_INVALID' => 'The MD5 checksums do not match', 'PHOTO_INVALID_DIMENSIONS' => 'The photo dimensions are invalid', 'FIELD_NAME_INVALID' => 'The field with the name FIELD_NAME is invalid', 'FIELD_NAME_EMPTY' => 'The field with the name FIELD_NAME is missing', 'MSG_WAIT_FAILED' => 'A waiting call returned an error', 'USERNAME_NOT_OCCUPIED' => 'The provided username is not occupied', 'PHONE_NUMBER_BANNED' => 'The provided phone number is banned from telegram', 'AUTH_KEY_UNREGISTERED' => 'The authorization key has expired', 'INVITE_HASH_EXPIRED' => 'The invite link has expired', 'USER_DEACTIVATED' => 'The user was deactivated', 'USER_ALREADY_PARTICIPANT' => 'The user is already in the group', 'MESSAGE_ID_INVALID' => 'The provided message id is invalid', 'PEER_ID_INVALID' => 'The provided peer id is invalid', 'CHAT_ID_INVALID' => 'The provided chat id is invalid', 'MESSAGE_DELETE_FORBIDDEN' => "You can't delete one of the messages you tried to delete, most likely because it is a service message.", 'CHAT_ADMIN_REQUIRED' => 'You must be an admin in this chat to do this', -429 => 'Too many requests', 'PEER_FLOOD' => "You are spamreported, you can't do this"];
public static $errorMethodMap = [];
private $caller = '';
public static function localizeMessage($method, $code, $error)
{
if (!$method || !$code || !$error) {
return $error;
}
$error = \preg_replace('/\d+$/', "X", $error);
$error = \preg_replace('/\\d+$/', "X", $error);
$description = self::$descriptions[$error] ?? '';
if (!isset(self::$errorMethodMap[$code][$method][$error])
|| !isset(self::$descriptions[$error])
|| $code === 500
) {
$res = \json_decode(@\file_get_contents('https://rpc.pwrtelegram.xyz/?method='.$method.'&code='.$code.'&error='.$error, false, \stream_context_create(['http'=>['timeout' => 3]])), true);
if (!isset(self::$errorMethodMap[$code][$method][$error]) || !isset(self::$descriptions[$error]) || $code === 500) {
$res = \json_decode(@\file_get_contents('https://rpc.pwrtelegram.xyz/?method=' . $method . '&code=' . $code . '&error=' . $error, false, \stream_context_create(['http' => ['timeout' => 3]])), true);
if (isset($res['ok']) && $res['ok'] && isset($res['result'])) {
$description = $res['result'];
self::$descriptions[$error] = $description;
self::$errorMethodMap[$code][$method][$error] = $error;
}
}
if (!$description) {
return $error;
}
return $description;
}
public function __toString()
{
$result = \sprintf(\danog\MadelineProto\Lang::$current_lang['rpc_tg_error'], self::localizeMessage($this->caller, $this->code, $this->message)." ({$this->code})", $this->rpc, $this->file, $this->line.PHP_EOL, \danog\MadelineProto\Magic::$revision.PHP_EOL.PHP_EOL).PHP_EOL.$this->getTLTrace().PHP_EOL;
$result = \sprintf(\danog\MadelineProto\Lang::$current_lang['rpc_tg_error'], self::localizeMessage($this->caller, $this->code, $this->message) . " ({$this->code})", $this->rpc, $this->file, $this->line . PHP_EOL, \danog\MadelineProto\Magic::$revision . PHP_EOL . PHP_EOL) . PHP_EOL . $this->getTLTrace() . PHP_EOL;
if (PHP_SAPI !== 'cli') {
$result = \str_replace(PHP_EOL, '<br>'.PHP_EOL, $result);
$result = \str_replace(PHP_EOL, '<br>' . PHP_EOL, $result);
}
return $result;
}
public function __construct($message = null, $code = 0, $caller = '', Exception $previous = null)
{
$this->rpc = $message;
parent::__construct($message, $code, $previous);
$this->prettifyTL($caller);
$this->caller = $caller;
$additional = [];
foreach ($this->getTrace() as $level) {
if (isset($level['function']) && $level['function'] === 'methodCall') {

View File

@ -1,4 +1,5 @@
<?php
/**
* RSA module.
*
@ -45,7 +46,6 @@ class RSA
* @var string
*/
public $fp;
/**
* Load RSA key.
*
@ -62,11 +62,9 @@ class RSA
$this->n = Tools::getVar($key, 'modulus');
$this->e = Tools::getVar($key, 'exponent');
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['computing_fingerprint'], Logger::ULTRA_VERBOSE);
$this->fp = \substr(\sha1((yield $TL->serializeObject(['type' => 'bytes'], $this->n->toBytes(), 'key')).(yield $TL->serializeObject(['type' => 'bytes'], $this->e->toBytes(), 'key')), true), -8);
$this->fp = \substr(\sha1(yield $TL->serializeObject(['type' => 'bytes'], $this->n->toBytes(), 'key') . yield $TL->serializeObject(['type' => 'bytes'], $this->e->toBytes(), 'key'), true), -8);
return $this;
}
/**
* Sleep function.
*
@ -76,7 +74,6 @@ class RSA
{
return ['e', 'n', 'fp'];
}
/**
* Encrypt data.
*
@ -87,7 +84,6 @@ class RSA
public function encrypt($data): string
{
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['rsa_encrypting'], Logger::VERBOSE);
return (new \tgseclib\Math\BigInteger((string) $data, 256))->powMod($this->e, $this->n)->toBytes();
}
}

View File

@ -40,7 +40,6 @@ trait AuthKeyHandler
* @var array
*/
protected $secret_chats = [];
/**
* Accept secret chat.
*
@ -53,8 +52,7 @@ trait AuthKeyHandler
//$this->logger->logger($params['id'],$this->secretChatStatus($params['id']));
if ($this->secretChatStatus($params['id']) !== 0) {
//$this->logger->logger($this->secretChatStatus($params['id']));
$this->logger->logger("I've already accepted secret chat ".$params['id']);
$this->logger->logger("I've already accepted secret chat " . $params['id']);
return false;
}
$dh_config = yield $this->getDhConfig();
@ -71,10 +69,9 @@ trait AuthKeyHandler
$g_b = $dh_config['g']->powMod($b, $dh_config['p']);
$this->checkG($g_b, $dh_config['p']);
yield $this->methodCallAsyncRead('messages.acceptEncryption', ['peer' => $params['id'], 'g_b' => $g_b->toBytes(), 'key_fingerprint' => $key['fingerprint']], ['datacenter' => $this->datacenter->curdc]);
yield $this->notifyLayer($params['id']);
$this->logger->logger('Secret chat '.$params['id'].' accepted successfully!', \danog\MadelineProto\Logger::NOTICE);
yield from $this->notifyLayer($params['id']);
$this->logger->logger('Secret chat ' . $params['id'] . ' accepted successfully!', \danog\MadelineProto\Logger::NOTICE);
}
/**
* Request secret chat.
*
@ -89,7 +86,7 @@ trait AuthKeyHandler
throw new \danog\MadelineProto\Exception('This peer is not present in the internal peer database');
}
$user = $user['InputUser'];
$this->logger->logger('Creating secret chat with '.$user['user_id'].'...', \danog\MadelineProto\Logger::VERBOSE);
$this->logger->logger('Creating secret chat with ' . $user['user_id'] . '...', \danog\MadelineProto\Logger::VERBOSE);
$dh_config = yield $this->getDhConfig();
$this->logger->logger('Generating a...', \danog\MadelineProto\Logger::VERBOSE);
$a = new \tgseclib\Math\BigInteger(\danog\MadelineProto\Tools::random(256), 256);
@ -99,11 +96,9 @@ trait AuthKeyHandler
$res = yield $this->methodCallAsyncRead('messages.requestEncryption', ['user_id' => $user, 'g_a' => $g_a->toBytes()], ['datacenter' => $this->datacenter->curdc]);
$this->temp_requested_secret_chats[$res['id']] = $a;
$this->updaters[false]->resume();
$this->logger->logger('Secret chat '.$res['id'].' requested successfully!', \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger('Secret chat ' . $res['id'] . ' requested successfully!', \danog\MadelineProto\Logger::NOTICE);
return $res['id'];
}
/**
* Complete secret chat.
*
@ -115,8 +110,7 @@ trait AuthKeyHandler
{
if ($this->secretChatStatus($params['id']) !== 1) {
//$this->logger->logger($this->secretChatStatus($params['id']));
$this->logger->logger('Could not find and complete secret chat '.$params['id']);
$this->logger->logger('Could not find and complete secret chat ' . $params['id']);
return false;
}
$dh_config = yield $this->getDhConfig();
@ -127,29 +121,25 @@ trait AuthKeyHandler
$key['fingerprint'] = \substr(\sha1($key['auth_key'], true), -8);
//$this->logger->logger($key);
if ($key['fingerprint'] !== $params['key_fingerprint']) {
yield $this->discardSecretChat($params['id']);
yield from $this->discardSecretChat($params['id']);
throw new \danog\MadelineProto\SecurityException('Invalid key fingerprint!');
}
$key['visualization_orig'] = \substr(\sha1($key['auth_key'], true), 16);
$key['visualization_46'] = \substr(\hash('sha256', $key['auth_key'], true), 20);
$this->secret_chats[$params['id']] = ['key' => $key, 'admin' => true, 'user_id' => $params['participant_id'], 'InputEncryptedChat' => ['chat_id' => $params['id'], 'access_hash' => $params['access_hash'], '_' => 'inputEncryptedChat'], 'in_seq_no_x' => 0, 'out_seq_no_x' => 1, 'in_seq_no' => 0, 'out_seq_no' => 0, 'layer' => 8, 'ttl' => 0, 'ttr' => 100, 'updated' => \time(), 'incoming' => [], 'outgoing' => [], 'created' => \time(), 'rekeying' => [0], 'key_x' => 'to server', 'mtproto' => 1];
yield $this->notifyLayer($params['id']);
$this->logger->logger('Secret chat '.$params['id'].' completed successfully!', \danog\MadelineProto\Logger::NOTICE);
yield from $this->notifyLayer($params['id']);
$this->logger->logger('Secret chat ' . $params['id'] . ' completed successfully!', \danog\MadelineProto\Logger::NOTICE);
}
private function notifyLayer($chat): \Generator
{
yield $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionNotifyLayer', 'layer' => $this->TL->getSecretLayer()]]], ['datacenter' => $this->datacenter->curdc]);
}
/**
* Temporary rekeyed secret chats.
*
* @var array
*/
protected $temp_rekeyed_secret_chats = [];
/**
* Rekey secret chat.
*
@ -162,7 +152,7 @@ trait AuthKeyHandler
if ($this->secret_chats[$chat]['rekeying'][0] !== 0) {
return;
}
$this->logger->logger('Rekeying secret chat '.$chat.'...', \danog\MadelineProto\Logger::VERBOSE);
$this->logger->logger('Rekeying secret chat ' . $chat . '...', \danog\MadelineProto\Logger::VERBOSE);
$dh_config = yield $this->getDhConfig();
$this->logger->logger('Generating a...', \danog\MadelineProto\Logger::VERBOSE);
$a = new \tgseclib\Math\BigInteger(\danog\MadelineProto\Tools::random(256), 256);
@ -174,10 +164,8 @@ trait AuthKeyHandler
$this->secret_chats[$chat]['rekeying'] = [1, $e];
yield $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionRequestKey', 'g_a' => $g_a->toBytes(), 'exchange_id' => $e]]], ['datacenter' => $this->datacenter->curdc]);
$this->updaters[false]->resume();
return $e;
}
/**
* Accept rekeying.
*
@ -197,11 +185,10 @@ trait AuthKeyHandler
}
if ($my_exchange_id->compare($other_exchange_id) === 0) {
$this->secret_chats[$chat]['rekeying'] = [0];
return;
}
}
$this->logger->logger('Accepting rekeying of secret chat '.$chat.'...', \danog\MadelineProto\Logger::VERBOSE);
$this->logger->logger('Accepting rekeying of secret chat ' . $chat . '...', \danog\MadelineProto\Logger::VERBOSE);
$dh_config = yield $this->getDhConfig();
$this->logger->logger('Generating b...', \danog\MadelineProto\Logger::VERBOSE);
$b = new \tgseclib\Math\BigInteger(\danog\MadelineProto\Tools::random(256), 256);
@ -218,7 +205,6 @@ trait AuthKeyHandler
yield $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAcceptKey', 'g_b' => $g_b->toBytes(), 'exchange_id' => $params['exchange_id'], 'key_fingerprint' => $key['fingerprint']]]], ['datacenter' => $this->datacenter->curdc]);
$this->updaters[false]->resume();
}
/**
* Commit rekeying of secret chat.
*
@ -231,10 +217,9 @@ trait AuthKeyHandler
{
if ($this->secret_chats[$chat]['rekeying'][0] !== 1 || !isset($this->temp_rekeyed_secret_chats[$params['exchange_id']])) {
$this->secret_chats[$chat]['rekeying'] = [0];
return;
}
$this->logger->logger('Committing rekeying of secret chat '.$chat.'...', \danog\MadelineProto\Logger::VERBOSE);
$this->logger->logger('Committing rekeying of secret chat ' . $chat . '...', \danog\MadelineProto\Logger::VERBOSE);
$dh_config = yield $this->getDhConfig();
$params['g_b'] = new \tgseclib\Math\BigInteger((string) $params['g_b'], 256);
$this->checkG($params['g_b'], $dh_config['p']);
@ -244,7 +229,6 @@ trait AuthKeyHandler
$key['visualization_46'] = \substr(\hash('sha256', $key['auth_key'], true), 20);
if ($key['fingerprint'] !== $params['key_fingerprint']) {
yield $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAbortKey', 'exchange_id' => $params['exchange_id']]]], ['datacenter' => $this->datacenter->curdc]);
throw new \danog\MadelineProto\SecurityException('Invalid key fingerprint!');
}
yield $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionCommitKey', 'exchange_id' => $params['exchange_id'], 'key_fingerprint' => $key['fingerprint']]]], ['datacenter' => $this->datacenter->curdc]);
@ -256,7 +240,6 @@ trait AuthKeyHandler
$this->secret_chats[$chat]['updated'] = \time();
$this->updaters[false]->resume();
}
/**
* Complete rekeying.
*
@ -272,10 +255,9 @@ trait AuthKeyHandler
}
if ($this->temp_rekeyed_secret_chats[$params['exchange_id']]['fingerprint'] !== $params['key_fingerprint']) {
yield $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionAbortKey', 'exchange_id' => $params['exchange_id']]]], ['datacenter' => $this->datacenter->curdc]);
throw new \danog\MadelineProto\SecurityException('Invalid key fingerprint!');
}
$this->logger->logger('Completing rekeying of secret chat '.$chat.'...', \danog\MadelineProto\Logger::VERBOSE);
$this->logger->logger('Completing rekeying of secret chat ' . $chat . '...', \danog\MadelineProto\Logger::VERBOSE);
$this->secret_chats[$chat]['rekeying'] = [0];
$this->secret_chats[$chat]['old_key'] = $this->secret_chats[$chat]['key'];
$this->secret_chats[$chat]['key'] = $this->temp_rekeyed_secret_chats[$params['exchange_id']];
@ -283,11 +265,9 @@ trait AuthKeyHandler
$this->secret_chats[$chat]['updated'] = \time();
unset($this->temp_rekeyed_secret_chats[$params['exchange_id']]);
yield $this->methodCallAsyncRead('messages.sendEncryptedService', ['peer' => $chat, 'message' => ['_' => 'decryptedMessageService', 'action' => ['_' => 'decryptedMessageActionNoop']]], ['datacenter' => $this->datacenter->curdc]);
$this->logger->logger('Secret chat '.$chat.' rekeyed successfully!', \danog\MadelineProto\Logger::VERBOSE);
$this->logger->logger('Secret chat ' . $chat . ' rekeyed successfully!', \danog\MadelineProto\Logger::VERBOSE);
return true;
}
/**
* Get secret chat status.
*
@ -303,10 +283,8 @@ trait AuthKeyHandler
if (isset($this->temp_requested_secret_chats[$chat])) {
return MTProto::SECRET_REQUESTED;
}
return MTProto::SECRET_EMPTY;
}
/**
* Get secret chat.
*
@ -318,7 +296,6 @@ trait AuthKeyHandler
{
return $this->secret_chats[\is_array($chat) ? $chat['chat_id'] : $chat];
}
/**
* Check whether secret chat exists.
*
@ -330,7 +307,6 @@ trait AuthKeyHandler
{
return isset($this->secret_chats[\is_array($chat) ? $chat['chat_id'] : $chat]);
}
/**
* Discard secret chat.
*
@ -340,15 +316,13 @@ trait AuthKeyHandler
*/
public function discardSecretChat(int $chat): \Generator
{
$this->logger->logger('Discarding secret chat '.$chat.'...', \danog\MadelineProto\Logger::VERBOSE);
$this->logger->logger('Discarding secret chat ' . $chat . '...', \danog\MadelineProto\Logger::VERBOSE);
if (isset($this->secret_chats[$chat])) {
unset($this->secret_chats[$chat]);
}
if (isset($this->temp_requested_secret_chats[$chat])) {
unset($this->temp_requested_secret_chats[$chat]);
}
try {
yield $this->methodCallAsyncRead('messages.discardEncryption', ['chat_id' => $chat], ['datacenter' => $this->datacenter->curdc]);
} catch (\danog\MadelineProto\RPCErrorException $e) {

View File

@ -38,7 +38,6 @@ trait MessageHandler
{
if (!isset($this->secret_chats[$chat_id])) {
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['secret_chat_skipping'], $chat_id));
return false;
}
$message['random_id'] = \danog\MadelineProto\Tools::random(8);
@ -52,30 +51,27 @@ trait MessageHandler
}
$this->secret_chats[$chat_id]['outgoing'][$this->secret_chats[$chat_id]['out_seq_no']] = $message;
$message = yield $this->TL->serializeObject(['type' => $constructor = $this->secret_chats[$chat_id]['layer'] === 8 ? 'DecryptedMessage' : 'DecryptedMessageLayer'], $message, $constructor, $this->secret_chats[$chat_id]['layer']);
$message = \danog\MadelineProto\Tools::packUnsignedInt(\strlen($message)).$message;
$message = \danog\MadelineProto\Tools::packUnsignedInt(\strlen($message)) . $message;
if ($this->secret_chats[$chat_id]['mtproto'] === 2) {
$padding = \danog\MadelineProto\Tools::posmod(-\strlen($message), 16);
if ($padding < 12) {
$padding += 16;
}
$message .= \danog\MadelineProto\Tools::random($padding);
$message_key = \substr(\hash('sha256', \substr($this->secret_chats[$chat_id]['key']['auth_key'], 88 + ($this->secret_chats[$chat_id]['admin'] ? 0 : 8), 32).$message, true), 8, 16);
$message_key = \substr(\hash('sha256', \substr($this->secret_chats[$chat_id]['key']['auth_key'], 88 + ($this->secret_chats[$chat_id]['admin'] ? 0 : 8), 32) . $message, true), 8, 16);
list($aes_key, $aes_iv) = $this->aesCalculate($message_key, $this->secret_chats[$chat_id]['key']['auth_key'], $this->secret_chats[$chat_id]['admin']);
} else {
$message_key = \substr(\sha1($message, true), -16);
list($aes_key, $aes_iv) = $this->oldAesCalculate($message_key, $this->secret_chats[$chat_id]['key']['auth_key'], true);
$message .= \danog\MadelineProto\Tools::random(\danog\MadelineProto\Tools::posmod(-\strlen($message), 16));
}
$message = $this->secret_chats[$chat_id]['key']['fingerprint'].$message_key.$this->igeEncrypt($message, $aes_key, $aes_iv);
$message = $this->secret_chats[$chat_id]['key']['fingerprint'] . $message_key . $this->igeEncrypt($message, $aes_key, $aes_iv);
return $message;
}
private function handleEncryptedUpdate(array $message): \Generator
{
if (!isset($this->secret_chats[$message['message']['chat_id']])) {
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['secret_chat_skipping'], $message['message']['chat_id']));
return false;
}
$auth_key_id = \substr($message['message']['bytes'], 0, 8);
@ -84,40 +80,36 @@ trait MessageHandler
if (isset($this->secret_chats[$message['message']['chat_id']]['old_key']['fingerprint'])) {
if ($auth_key_id !== $this->secret_chats[$message['message']['chat_id']]['old_key']['fingerprint']) {
yield $this->discardSecretChat($message['message']['chat_id']);
throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['fingerprint_mismatch']);
}
$old = true;
} else {
yield $this->discardSecretChat($message['message']['chat_id']);
throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['fingerprint_mismatch']);
}
}
$message_key = \substr($message['message']['bytes'], 8, 16);
$encrypted_data = \substr($message['message']['bytes'], 24);
if ($this->secret_chats[$message['message']['chat_id']]['mtproto'] === 2) {
$this->logger->logger('Trying MTProto v2 decryption for chat '.$message['message']['chat_id'].'...', \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger('Trying MTProto v2 decryption for chat ' . $message['message']['chat_id'] . '...', \danog\MadelineProto\Logger::NOTICE);
try {
$message_data = $this->tryMTProtoV2Decrypt($message_key, $message['message']['chat_id'], $old, $encrypted_data);
$this->logger->logger('MTProto v2 decryption OK for chat '.$message['message']['chat_id'].'...', \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger('MTProto v2 decryption OK for chat ' . $message['message']['chat_id'] . '...', \danog\MadelineProto\Logger::NOTICE);
} catch (\danog\MadelineProto\SecurityException $e) {
$this->logger->logger('MTProto v2 decryption failed with message '.$e->getMessage().', trying MTProto v1 decryption for chat '.$message['message']['chat_id'].'...', \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger('MTProto v2 decryption failed with message ' . $e->getMessage() . ', trying MTProto v1 decryption for chat ' . $message['message']['chat_id'] . '...', \danog\MadelineProto\Logger::NOTICE);
$message_data = $this->tryMTProtoV1Decrypt($message_key, $message['message']['chat_id'], $old, $encrypted_data);
$this->logger->logger('MTProto v1 decryption OK for chat '.$message['message']['chat_id'].'...', \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger('MTProto v1 decryption OK for chat ' . $message['message']['chat_id'] . '...', \danog\MadelineProto\Logger::NOTICE);
$this->secret_chats[$message['message']['chat_id']]['mtproto'] = 1;
}
} else {
$this->logger->logger('Trying MTProto v1 decryption for chat '.$message['message']['chat_id'].'...', \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger('Trying MTProto v1 decryption for chat ' . $message['message']['chat_id'] . '...', \danog\MadelineProto\Logger::NOTICE);
try {
$message_data = $this->tryMTProtoV1Decrypt($message_key, $message['message']['chat_id'], $old, $encrypted_data);
$this->logger->logger('MTProto v1 decryption OK for chat '.$message['message']['chat_id'].'...', \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger('MTProto v1 decryption OK for chat ' . $message['message']['chat_id'] . '...', \danog\MadelineProto\Logger::NOTICE);
} catch (\danog\MadelineProto\SecurityException $e) {
$this->logger->logger('MTProto v1 decryption failed with message '.$e->getMessage().', trying MTProto v2 decryption for chat '.$message['message']['chat_id'].'...', \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger('MTProto v1 decryption failed with message ' . $e->getMessage() . ', trying MTProto v2 decryption for chat ' . $message['message']['chat_id'] . '...', \danog\MadelineProto\Logger::NOTICE);
$message_data = $this->tryMTProtoV2Decrypt($message_key, $message['message']['chat_id'], $old, $encrypted_data);
$this->logger->logger('MTProto v2 decryption OK for chat '.$message['message']['chat_id'].'...', \danog\MadelineProto\Logger::NOTICE);
$this->logger->logger('MTProto v2 decryption OK for chat ' . $message['message']['chat_id'] . '...', \danog\MadelineProto\Logger::NOTICE);
$this->secret_chats[$message['message']['chat_id']]['mtproto'] = 2;
}
}
@ -131,7 +123,6 @@ trait MessageHandler
$this->secret_chats[$message['message']['chat_id']]['incoming'][$this->secret_chats[$message['message']['chat_id']]['in_seq_no']] = $message['message'];
yield $this->handleDecryptedUpdate($message);
}
private function tryMTProtoV1Decrypt($message_key, $chat_id, $old, $encrypted_data)
{
list($aes_key, $aes_iv) = $this->oldAesCalculate($message_key, $this->secret_chats[$chat_id][$old ? 'old_key' : 'key']['auth_key'], true);
@ -150,10 +141,8 @@ trait MessageHandler
if (\strlen($decrypted_data) % 16 != 0) {
throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['length_not_divisible_16']);
}
return $message_data;
}
private function tryMTProtoV2Decrypt($message_key, $chat_id, $old, $encrypted_data)
{
list($aes_key, $aes_iv) = $this->aesCalculate($message_key, $this->secret_chats[$chat_id][$old ? 'old_key' : 'key']['auth_key'], !$this->secret_chats[$chat_id]['admin']);
@ -163,7 +152,7 @@ trait MessageHandler
if ($message_data_length > \strlen($decrypted_data)) {
throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['msg_data_length_too_big']);
}
if ($message_key != \substr(\hash('sha256', \substr($this->secret_chats[$chat_id][$old ? 'old_key' : 'key']['auth_key'], 88 + ($this->secret_chats[$chat_id]['admin'] ? 8 : 0), 32).$decrypted_data, true), 8, 16)) {
if ($message_key != \substr(\hash('sha256', \substr($this->secret_chats[$chat_id][$old ? 'old_key' : 'key']['auth_key'], 88 + ($this->secret_chats[$chat_id]['admin'] ? 8 : 0), 32) . $decrypted_data, true), 8, 16)) {
throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['msg_key_mismatch']);
}
if (\strlen($decrypted_data) - 4 - $message_data_length < 12) {
@ -175,7 +164,6 @@ trait MessageHandler
if (\strlen($decrypted_data) % 16 != 0) {
throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['length_not_divisible_16']);
}
return $message_data;
}
}

View File

@ -24,7 +24,7 @@ namespace danog\MadelineProto\SecretChats;
*/
trait ResponseHandler
{
private function handleDecryptedUpdate($update)
private function handleDecryptedUpdate($update): \Generator
{
/*if (isset($update['message']['decrypted_message']['random_bytes']) && strlen($update['message']['decrypted_message']['random_bytes']) < 15) {
throw new \danog\MadelineProto\ResponseException(\danog\MadelineProto\Lang::$current_lang['rand_bytes_too_short']);
@ -35,15 +35,12 @@ trait ResponseHandler
switch ($update['message']['decrypted_message']['action']['_']) {
case 'decryptedMessageActionRequestKey':
yield $this->acceptRekey($update['message']['chat_id'], $update['message']['decrypted_message']['action']);
return;
case 'decryptedMessageActionAcceptKey':
yield $this->commitRekey($update['message']['chat_id'], $update['message']['decrypted_message']['action']);
return;
case 'decryptedMessageActionCommitKey':
yield $this->completeRekey($update['message']['chat_id'], $update['message']['decrypted_message']['action']);
return;
case 'decryptedMessageActionNotifyLayer':
$this->secret_chats[$update['message']['chat_id']]['layer'] = $update['message']['decrypted_message']['action']['layer'];
@ -53,13 +50,10 @@ trait ResponseHandler
if ($update['message']['decrypted_message']['action']['layer'] >= 73) {
$this->secret_chats[$update['message']['chat_id']]['mtproto'] = 2;
}
return;
case 'decryptedMessageActionSetMessageTTL':
$this->secret_chats[$update['message']['chat_id']]['ttl'] = $update['message']['decrypted_message']['action']['ttl_seconds'];
yield $this->saveUpdate($update);
return;
case 'decryptedMessageActionNoop':
return;
@ -68,14 +62,13 @@ trait ResponseHandler
$update['message']['decrypted_message']['action']['end_seq_no'] -= $this->secret_chats[$update['message']['chat_id']]['out_seq_no_x'];
$update['message']['decrypted_message']['action']['start_seq_no'] /= 2;
$update['message']['decrypted_message']['action']['end_seq_no'] /= 2;
$this->logger->logger('Resending messages for secret chat '.$update['message']['chat_id'], \danog\MadelineProto\Logger::WARNING);
$this->logger->logger('Resending messages for secret chat ' . $update['message']['chat_id'], \danog\MadelineProto\Logger::WARNING);
foreach ($this->secret_chats[$update['message']['chat_id']]['outgoing'] as $seq => $message) {
if ($seq >= $update['message']['decrypted_message']['action']['start_seq_no'] && $seq <= $update['message']['decrypted_message']['action']['end_seq_no']) {
//throw new \danog\MadelineProto\ResponseException(\danog\MadelineProto\Lang::$current_lang['resending_unsupported']);
yield $this->methodCallAsyncRead('messages.sendEncrypted', ['peer' => $update['message']['chat_id'], 'message' => $update['message']['decrypted_message']], ['datacenter' => $this->datacenter->curdc]);
}
}
return;
default:
// yield $this->saveUpdate(['_' => 'updateNewDecryptedMessage', 'peer' => $this->secret_chats[$update['message']['chat_id']]['InputEncryptedChat'], 'in_seq_no' => $this->get_in_seq_no($update['message']['chat_id']), 'out_seq_no' => $this->get_out_seq_no($update['message']['chat_id']), 'message' => $update['message']['decrypted_message']]);
@ -95,11 +88,11 @@ trait ResponseHandler
}
}
$update['message']['decrypted_message'] = $update['message']['decrypted_message']['message'];
yield $this->handleDecryptedUpdate($update);
yield from $this->handleDecryptedUpdate($update);
}
break;
default:
throw new \danog\MadelineProto\ResponseException(\danog\MadelineProto\Lang::$current_lang['unrecognized_dec_msg'].\var_export($update, true));
throw new \danog\MadelineProto\ResponseException(\danog\MadelineProto\Lang::$current_lang['unrecognized_dec_msg'] . \var_export($update, true));
break;
}
}

View File

@ -24,7 +24,7 @@ namespace danog\MadelineProto\SecretChats;
*/
trait SeqNoHandler
{
private function checkSecretInSeqNo($chat_id, $seqno)
private function checkSecretInSeqNo($chat_id, $seqno): \Generator
{
$seqno = ($seqno - $this->secret_chats[$chat_id]['out_seq_no_x']) / 2;
$last = 0;
@ -32,7 +32,6 @@ trait SeqNoHandler
if (isset($message['decrypted_message']['in_seq_no'])) {
if (($message['decrypted_message']['in_seq_no'] - $this->secret_chats[$chat_id]['out_seq_no_x']) / 2 < $last) {
yield $this->discardSecretChat($chat_id);
throw new \danog\MadelineProto\SecurityException('in_seq_no is not increasing');
}
$last = ($message['decrypted_message']['in_seq_no'] - $this->secret_chats[$chat_id]['out_seq_no_x']) / 2;
@ -40,14 +39,11 @@ trait SeqNoHandler
}
if ($seqno > $this->secret_chats[$chat_id]['out_seq_no'] + 1) {
yield $this->discardSecretChat($chat_id);
throw new \danog\MadelineProto\SecurityException('in_seq_no is too big');
}
return true;
}
private function checkSecretOutSeqNo($chat_id, $seqno)
private function checkSecretOutSeqNo($chat_id, $seqno): \Generator
{
$seqno = ($seqno - $this->secret_chats[$chat_id]['in_seq_no_x']) / 2;
$C = 0;
@ -55,8 +51,7 @@ trait SeqNoHandler
if (isset($message['decrypted_message']['out_seq_no']) && $C < $this->secret_chats[$chat_id]['in_seq_no']) {
if (($message['decrypted_message']['out_seq_no'] - $this->secret_chats[$chat_id]['in_seq_no_x']) / 2 !== $C) {
yield $this->discardSecretChat($chat_id);
throw new \danog\MadelineProto\SecurityException('out_seq_no hole: should be '.$C.', is '.($message['decrypted_message']['out_seq_no'] - $this->secret_chats[$chat_id]['in_seq_no_x']) / 2);
throw new \danog\MadelineProto\SecurityException('out_seq_no hole: should be ' . $C . ', is ' . ($message['decrypted_message']['out_seq_no'] - $this->secret_chats[$chat_id]['in_seq_no_x']) / 2);
}
$C++;
}
@ -64,25 +59,20 @@ trait SeqNoHandler
//$this->logger->logger($C, $seqno);
if ($seqno < $C) {
// <= C
$this->logger->logger('WARNING: dropping repeated message with seqno '.$seqno);
$this->logger->logger('WARNING: dropping repeated message with seqno ' . $seqno);
return false;
}
if ($seqno > $C) {
// > C+1
yield $this->discardSecretChat($chat_id);
throw new \danog\MadelineProto\SecurityException('WARNING: out_seq_no gap detected ('.$seqno.' > '.$C.')!');
throw new \danog\MadelineProto\SecurityException('WARNING: out_seq_no gap detected (' . $seqno . ' > ' . $C . ')!');
}
return true;
}
private function generateSecretInSeqNo($chat)
{
return $this->secret_chats[$chat]['layer'] > 8 ? $this->secret_chats[$chat]['in_seq_no'] * 2 + $this->secret_chats[$chat]['in_seq_no_x'] : -1;
}
private function generateSecretOutSeqNo($chat)
{
return $this->secret_chats[$chat]['layer'] > 8 ? $this->secret_chats[$chat]['out_seq_no'] * 2 + $this->secret_chats[$chat]['out_seq_no_x'] : -1;

View File

@ -27,7 +27,6 @@ class Serialization
public static function realpaths($file)
{
$file = Absolute::absolute($file);
return ['file' => $file, 'lockfile' => $file.'.lock', 'tempfile' => $file.'.temp.session'];
return ['file' => $file, 'lockfile' => $file . '.lock', 'tempfile' => $file . '.temp.session'];
}
}

View File

@ -27,41 +27,34 @@ class Server
private $settings;
private $pids = [];
private $mypid;
public function __construct($settings)
{
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
\danog\MadelineProto\Logger::constructor(3);
if (!\extension_loaded('sockets')) {
throw new Exception(['extension', 'sockets']);
}
if (!\extension_loaded('pcntl')) {
throw new Exception(['extension', 'pcntl']);
}
$this->settings = $settings;
$this->mypid = \getmypid();
}
public function start()
{
\pcntl_signal(SIGTERM, [$this, 'sigHandler']);
\pcntl_signal(SIGINT, [$this, 'sigHandler']);
\pcntl_signal(SIGCHLD, [$this, 'sigHandler']);
$this->sock = new \Socket($this->settings['type'], SOCK_STREAM, $this->settings['protocol']);
$this->sock->bind($this->settings['address'], $this->settings['port']);
$this->sock->listen();
$this->sock->setBlocking(true);
$timeout = 2;
$this->sock->setOption(\SOL_SOCKET, \SO_RCVTIMEO, $timeout);
$this->sock->setOption(\SOL_SOCKET, \SO_SNDTIMEO, $timeout);
\danog\MadelineProto\Logger::log('Server started! Listening on '.$this->settings['address'].':'.$this->settings['port']);
\danog\MadelineProto\Logger::log('Server started! Listening on ' . $this->settings['address'] . ':' . $this->settings['port']);
while (true) {
\pcntl_signal_dispatch();
try {
if ($sock = $this->sock->accept()) {
$this->handle($sock);
@ -70,7 +63,6 @@ class Server
}
}
}
private function handle($socket)
{
$pid = \pcntl_fork();
@ -83,29 +75,25 @@ class Server
$handler->loop();
die;
}
public function __destruct()
{
if ($this->mypid === \getmypid()) {
\danog\MadelineProto\Logger::log('Shutting main process '.$this->mypid.' down');
\danog\MadelineProto\Logger::log('Shutting main process ' . $this->mypid . ' down');
unset($this->sock);
foreach ($this->pids as $pid) {
\danog\MadelineProto\Logger::log("Waiting for $pid");
\danog\MadelineProto\Logger::log("Waiting for {$pid}");
\pcntl_wait($pid);
}
\danog\MadelineProto\Logger::log('Done, closing main process');
return;
}
}
public function sigHandler($sig)
{
switch ($sig) {
case SIGTERM:
case SIGINT:
exit();
exit;
case SIGCHLD:
\pcntl_waitpid(-1, $status);
break;

View File

@ -1,4 +1,5 @@
<?php
/**
* Shutdown module.
*

View File

@ -1,4 +1,5 @@
<?php
/**
* ADNL stream wrapper.
*
@ -24,7 +25,6 @@ use danog\MadelineProto\Stream\Async\BufferedStream;
use danog\MadelineProto\Stream\BufferedStreamInterface;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\MTProtoBufferInterface;
use danog\MadelineProto\Stream\RawStreamInterface;
use danog\MadelineProto\Tools;
@ -39,7 +39,6 @@ class ADNLStream implements BufferedStreamInterface, MTProtoBufferInterface
{
use BufferedStream;
private $stream;
/**
* Connect to stream.
*
@ -51,7 +50,6 @@ class ADNLStream implements BufferedStreamInterface, MTProtoBufferInterface
{
$this->stream = yield $ctx->getStream($header);
}
/**
* Async close.
*
@ -61,7 +59,6 @@ class ADNLStream implements BufferedStreamInterface, MTProtoBufferInterface
{
return $this->stream->disconnect();
}
/**
* Get write buffer asynchronously.
*
@ -77,10 +74,8 @@ class ADNLStream implements BufferedStreamInterface, MTProtoBufferInterface
$this->stream->startWriteHash();
$this->stream->checkWriteHash($length - 32);
yield $buffer->bufferWrite(Tools::random(32));
return $buffer;
}
/**
* Get read buffer asynchronously.
*
@ -96,10 +91,8 @@ class ADNLStream implements BufferedStreamInterface, MTProtoBufferInterface
$this->stream->checkReadHash($length);
yield $buffer->bufferRead(32);
$length -= 32;
return $buffer;
}
/**
* {@inheritdoc}
*
@ -118,8 +111,6 @@ class ADNLStream implements BufferedStreamInterface, MTProtoBufferInterface
{
return $this->stream;
}
public static function getName(): string
{
return __CLASS__;

View File

@ -1,4 +1,5 @@
<?php
/**
* Buffer helper trait.
*
@ -33,7 +34,6 @@ trait Buffer
{
return \danog\MadelineProto\Tools::call($this->bufferReadGenerator($length));
}
public function bufferWrite(string $data): Promise
{
return \danog\MadelineProto\Tools::call($this->bufferWriteGenerator($data));

View File

@ -1,4 +1,5 @@
<?php
/**
* Buffered stream helper trait.
*
@ -30,7 +31,6 @@ use Amp\Promise;
trait BufferedStream
{
use Stream;
/**
* Get read buffer asynchronously.
*
@ -42,7 +42,6 @@ trait BufferedStream
{
return \danog\MadelineProto\Tools::call($this->getReadBufferGenerator($length));
}
/**
* Get write buffer asynchronously.
*

View File

@ -1,4 +1,5 @@
<?php
/**
* Raw stream helper trait.
*
@ -30,17 +31,14 @@ use Amp\Promise;
trait RawStream
{
use Stream;
public function read(): Promise
{
return \danog\MadelineProto\Tools::call($this->readGenerator());
}
public function write(string $data): Promise
{
return \danog\MadelineProto\Tools::call($this->writeGenerator($data));
}
public function end(string $finalData = ''): Promise
{
return \danog\MadelineProto\Tools::call($this->endGenerator($finalData));

View File

@ -1,4 +1,5 @@
<?php
/**
* Generic stream helper trait.
*

View File

@ -1,4 +1,5 @@
<?php
/**
* Buffer interface.
*
@ -35,7 +36,6 @@ interface BufferInterface
* @return Promise
*/
public function bufferRead(int $length): Promise;
/**
* Write data asynchronously.
*

View File

@ -1,4 +1,5 @@
<?php
/**
* Buffered proxy stream interface.
*

View File

@ -1,4 +1,5 @@
<?php
/**
* Buffered stream interface.
*
@ -35,7 +36,6 @@ interface BufferedStreamInterface extends StreamInterface
* @return Promise
*/
public function getReadBuffer(&$length): Promise;
/**
* Get write buffer asynchronously.
*
@ -44,7 +44,6 @@ interface BufferedStreamInterface extends StreamInterface
* @return Promise
*/
public function getWriteBuffer(int $length, string $append = ''): Promise;
/**
* Get stream name.
*

View File

@ -1,4 +1,5 @@
<?php
/**
* Buffered raw stream.
*
@ -36,14 +37,11 @@ use danog\MadelineProto\Stream\RawStreamInterface;
class BufferedRawStream implements BufferedStreamInterface, BufferInterface, 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.
*
@ -55,10 +53,8 @@ class BufferedRawStream implements BufferedStreamInterface, BufferInterface, Raw
{
$this->stream = yield $ctx->getStream($header);
$this->memory_stream = \fopen('php://memory', 'r+');
return true;
}
/**
* Async chunked read.
*
@ -71,7 +67,6 @@ class BufferedRawStream implements BufferedStreamInterface, BufferInterface, Raw
}
return $this->stream->read();
}
/**
* Async write.
*
@ -86,7 +81,6 @@ class BufferedRawStream implements BufferedStreamInterface, BufferInterface, Raw
}
return $this->stream->write($data);
}
/**
* Async close.
*
@ -103,7 +97,6 @@ class BufferedRawStream implements BufferedStreamInterface, BufferInterface, Raw
$this->stream = null;
}
}
/**
* Get read buffer asynchronously.
*
@ -128,10 +121,8 @@ class BufferedRawStream implements BufferedStreamInterface, BufferInterface, Raw
\fclose($this->memory_stream);
$this->memory_stream = $new_memory_stream;
}
return new \Amp\Success($this);
}
/**
* Get write buffer asynchronously.
*
@ -145,10 +136,8 @@ class BufferedRawStream implements BufferedStreamInterface, BufferInterface, Raw
$this->append = $append;
$this->append_after = $length - \strlen($append);
}
return new \Amp\Success($this);
}
/**
* Read data asynchronously.
*
@ -169,7 +158,6 @@ class BufferedRawStream implements BufferedStreamInterface, BufferInterface, Raw
}
return \danog\MadelineProto\Tools::call($this->bufferReadGenerator($length));
}
/**
* Read data asynchronously.
*
@ -185,22 +173,18 @@ class BufferedRawStream implements BufferedStreamInterface, BufferInterface, Raw
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.
*
@ -218,14 +202,11 @@ class BufferedRawStream implements BufferedStreamInterface, BufferInterface, Raw
} 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);
}
/**
* {@inheritdoc}
*
@ -244,7 +225,6 @@ class BufferedRawStream implements BufferedStreamInterface, BufferInterface, Raw
{
return $this->stream;
}
/**
* Get class name.
*

View File

@ -1,4 +1,5 @@
<?php
/**
* AES CTR stream wrapper.
*
@ -26,7 +27,6 @@ use danog\MadelineProto\Stream\BufferedProxyStreamInterface;
use danog\MadelineProto\Stream\BufferInterface;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\RawStreamInterface;
use tgseclib\Crypt\AES;
/**
@ -48,7 +48,6 @@ class CtrStream implements BufferedProxyStreamInterface, BufferInterface
private $extra;
private $append = '';
private $append_after = 0;
/**
* Connect to stream.
*
@ -62,15 +61,12 @@ class CtrStream implements BufferedProxyStreamInterface, BufferInterface
$this->encrypt->enableContinuousBuffer();
$this->encrypt->setKey($this->extra['encrypt']['key']);
$this->encrypt->setIV($this->extra['encrypt']['iv']);
$this->decrypt = new \tgseclib\Crypt\AES('ctr');
$this->decrypt->enableContinuousBuffer();
$this->decrypt->setKey($this->extra['decrypt']['key']);
$this->decrypt->setIV($this->extra['decrypt']['iv']);
$this->stream = yield $ctx->getStream($header);
}
/**
* Async close.
*
@ -80,7 +76,6 @@ class CtrStream implements BufferedProxyStreamInterface, BufferInterface
{
return $this->stream->disconnect();
}
/**
* Get write buffer asynchronously.
*
@ -95,10 +90,8 @@ class CtrStream implements BufferedProxyStreamInterface, BufferInterface
$this->append = $append;
$this->append_after = $length - \strlen($append);
}
return $this;
}
/**
* Get read buffer asynchronously.
*
@ -109,10 +102,8 @@ class CtrStream implements BufferedProxyStreamInterface, BufferInterface
public function getReadBufferGenerator(&$length): \Generator
{
$this->read_buffer = yield $this->stream->getReadBuffer($length);
return $this;
}
/**
* Decrypts read data asynchronously.
*
@ -126,7 +117,6 @@ class CtrStream implements BufferedProxyStreamInterface, BufferInterface
{
return @$this->decrypt->encrypt(yield $this->read_buffer->bufferRead($length));
}
/**
* Writes data to the stream.
*
@ -146,14 +136,11 @@ class CtrStream implements BufferedProxyStreamInterface, BufferInterface
} elseif ($this->append_after < 0) {
$this->append_after = 0;
$this->append = '';
throw new \danog\MadelineProto\Exception('Tried to send too much out of frame data, cannot append');
}
}
return $this->write_buffer->bufferWrite(@$this->encrypt->encrypt($data));
}
/**
* Set obfuscation keys/IVs.
*
@ -165,7 +152,6 @@ class CtrStream implements BufferedProxyStreamInterface, BufferInterface
{
$this->extra = $data;
}
/**
* {@inheritdoc}
*
@ -175,7 +161,6 @@ class CtrStream implements BufferedProxyStreamInterface, BufferInterface
{
return $this->stream->getSocket();
}
/**
* {@inheritDoc}
*
@ -193,7 +178,6 @@ class CtrStream implements BufferedProxyStreamInterface, BufferInterface
{
return $this->decrypt;
}
public static function getName(): string
{
return __CLASS__;

View File

@ -1,4 +1,5 @@
<?php
/**
* Hash stream wrapper.
*
@ -23,7 +24,6 @@ use danog\MadelineProto\Stream\Async\BufferedStream;
use danog\MadelineProto\Stream\BufferedProxyStreamInterface;
use danog\MadelineProto\Stream\BufferInterface;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\RawStreamInterface;
/**
@ -45,7 +45,6 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
private $read_check_pos = 0;
private $stream;
private $rev = false;
/**
* Enable read hashing.
*
@ -55,7 +54,6 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
{
$this->read_hash = \hash_init($this->hash_name);
}
/**
* Check the read hash after N bytes are read.
*
@ -67,7 +65,6 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
{
$this->read_check_after = $after;
}
/**
* Stop read hashing and get final hash.
*
@ -82,10 +79,8 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
$this->read_hash = null;
$this->read_check_after = 0;
$this->read_check_pos = 0;
return $hash;
}
/**
* Check if we are read hashing.
*
@ -95,7 +90,6 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
{
return $this->read_hash !== null;
}
/**
* Enable write hashing.
*
@ -105,7 +99,6 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
{
$this->write_hash = \hash_init($this->hash_name);
}
/**
* Write the write hash after N bytes are read.
*
@ -117,7 +110,6 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
{
$this->write_check_after = $after;
}
/**
* Stop write hashing and get final hash.
*
@ -132,10 +124,8 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
$this->write_hash = null;
$this->write_check_after = 0;
$this->write_check_pos = 0;
return $hash;
}
/**
* Check if we are write hashing.
*
@ -145,7 +135,6 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
{
return $this->write_hash !== null;
}
/**
* Hashes read data asynchronously.
*
@ -167,7 +156,6 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
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);
@ -175,10 +163,8 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
if ($this->read_check_after) {
$this->read_check_pos += $length;
}
return $data;
}
/**
* Set the hash algorithm.
*
@ -196,7 +182,6 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
}
$this->hash_name = $hash;
}
/**
* Connect to stream.
*
@ -212,10 +197,8 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
$this->read_hash = null;
$this->read_check_after = 0;
$this->read_check_pos = 0;
$this->stream = yield $ctx->getStream($header);
}
/**
* Async close.
*
@ -225,7 +208,6 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
{
return $this->stream->disconnect();
}
/**
* Get read buffer asynchronously.
*
@ -237,13 +219,10 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
{
//if ($this->read_hash) {
$this->read_buffer = yield $this->stream->getReadBuffer($length);
return $this;
//}
//return yield $this->stream->getReadBuffer($length);
}
/**
* Get write buffer asynchronously.
*
@ -255,13 +234,10 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
{
//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.
*
@ -274,10 +250,8 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
if ($this->read_hash === null) {
return $this->read_buffer->bufferRead($length);
}
return \danog\MadelineProto\Tools::call($this->bufferReadGenerator($length));
}
/**
* Writes data to the stream.
*
@ -292,15 +266,13 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
if ($this->write_hash === null) {
return $this->write_buffer->bufferWrite($data);
}
$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());
return $this->write_buffer->bufferWrite($data . $this->getWriteHash());
}
if ($this->write_check_after) {
$this->write_check_pos += $length;
@ -308,10 +280,8 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
if ($this->write_hash) {
\hash_update($this->write_hash, $data);
}
return $this->write_buffer->bufferWrite($data);
}
/**
* {@inheritdoc}
*
@ -321,7 +291,6 @@ class HashedBufferedStream implements BufferedProxyStreamInterface, BufferInterf
{
return $this->stream->getSocket();
}
/**
* {@inheritDoc}
*

View File

@ -1,4 +1,5 @@
<?php
/**
* Buffered raw stream.
*
@ -44,7 +45,6 @@ class SimpleBufferedRawStream extends BufferedRawStream implements BufferedStrea
if ($buffer_length < $length && $buffer_length) {
\fseek($this->memory_stream, $offset + $buffer_length);
}
while ($buffer_length < $length) {
$chunk = yield $this->read();
if ($chunk === null) {
@ -55,7 +55,6 @@ class SimpleBufferedRawStream extends BufferedRawStream implements BufferedStrea
$buffer_length += \strlen($chunk);
}
\fseek($this->memory_stream, $offset);
return \fread($this->memory_stream, $length);
}
/**
@ -67,7 +66,6 @@ class SimpleBufferedRawStream extends BufferedRawStream implements BufferedStrea
{
return $this->stream;
}
/**
* Get class name.
*

View File

@ -1,4 +1,5 @@
<?php
/**
* Connection context.
*
@ -107,14 +108,12 @@ class ConnectionContext
* @var int
*/
private $key = 0;
/**
* Read callback.
*
* @var callable
*/
private $readCallback;
/**
* Set the socket context.
*
@ -125,10 +124,8 @@ class ConnectionContext
public function setSocketContext(ConnectContext $socketContext): self
{
$this->socketContext = $socketContext;
return $this;
}
/**
* Get the socket context.
*
@ -138,7 +135,6 @@ class ConnectionContext
{
return $this->socketContext;
}
/**
* Set the connection URI.
*
@ -149,10 +145,8 @@ class ConnectionContext
public function setUri($uri): self
{
$this->uri = $uri instanceof Uri ? $uri : new Uri($uri);
return $this;
}
/**
* Get the URI as a string.
*
@ -162,7 +156,6 @@ class ConnectionContext
{
return (string) $this->uri;
}
/**
* Get the URI.
*
@ -172,7 +165,6 @@ class ConnectionContext
{
return $this->uri;
}
/**
* Set the cancellation token.
*
@ -183,10 +175,8 @@ class ConnectionContext
public function setCancellationToken($cancellationToken): self
{
$this->cancellationToken = $cancellationToken;
return $this;
}
/**
* Get the cancellation token.
*
@ -215,10 +205,8 @@ class ConnectionContext
public function setTest(bool $test): self
{
$this->test = $test;
return $this;
}
/**
* Whether this is a test connection.
*
@ -237,7 +225,6 @@ class ConnectionContext
{
return $this->media;
}
/**
* Whether this is a CDN connection.
*
@ -247,7 +234,6 @@ class ConnectionContext
{
return $this->cdn;
}
/**
* Whether this connection context will only be used by the DNS client.
*
@ -257,7 +243,6 @@ class ConnectionContext
{
return $this->isDns;
}
/**
* Whether this connection context will only be used by the DNS client.
*
@ -279,10 +264,8 @@ class ConnectionContext
public function secure(bool $secure): self
{
$this->secure = $secure;
return $this;
}
/**
* Whether to use TLS with socket connections.
*
@ -292,7 +275,6 @@ class ConnectionContext
{
return $this->secure;
}
/**
* Set the DC ID.
*
@ -304,15 +286,13 @@ class ConnectionContext
{
$int = \intval($dc);
if (!(1 <= $int && $int <= 1000)) {
throw new Exception("Invalid DC id provided: $dc");
throw new Exception("Invalid DC id provided: {$dc}");
}
$this->dc = $dc;
$this->media = \strpos($dc, '_media') !== false;
$this->cdn = \strpos($dc, '_cdn') !== false;
return $this;
}
/**
* Get the DC ID.
*
@ -322,7 +302,6 @@ class ConnectionContext
{
return $this->dc;
}
/**
* Get the int DC ID.
*
@ -337,10 +316,8 @@ class ConnectionContext
if ($this->media) {
$dc = -$dc;
}
return $dc;
}
/**
* Whether to use ipv6.
*
@ -351,10 +328,8 @@ class ConnectionContext
public function setIpv6(bool $ipv6): self
{
$this->ipv6 = $ipv6;
return $this;
}
/**
* Whether to use ipv6.
*
@ -364,7 +339,6 @@ class ConnectionContext
{
return $this->ipv6;
}
/**
* Add a stream to the stream chain.
*
@ -377,10 +351,8 @@ class ConnectionContext
{
$this->nextStreams[] = [$streamName, $extra];
$this->key = \count($this->nextStreams) - 1;
return $this;
}
/**
* Set read callback, called every time the socket reads at least a byte.
*
@ -392,7 +364,6 @@ class ConnectionContext
{
$this->readCallback = $callable;
}
/**
* Check if a read callback is present.
*
@ -402,7 +373,6 @@ class ConnectionContext
{
return $this->readCallback !== null;
}
/**
* Get read callback.
*
@ -412,7 +382,6 @@ class ConnectionContext
{
return $this->readCallback;
}
/**
* Get the current stream name from the stream chain.
*
@ -422,7 +391,6 @@ class ConnectionContext
{
return $this->nextStreams[$this->key][0];
}
/**
* Check if has stream within stream chain.
*
@ -439,7 +407,6 @@ class ConnectionContext
}
return false;
}
/**
* Get a stream from the stream chain.
*
@ -455,11 +422,8 @@ class ConnectionContext
$obj->setExtra($extra);
}
yield $obj->connect($this, $buffer);
return $obj;
}
/**
* Get the inputClientProxy proxy MTProto object.
*
@ -499,13 +463,11 @@ class ConnectionContext
}
$string .= \preg_replace('/.*\\\\/', '', $stream[0]);
if ($stream[1] && $stream[0] !== DefaultStream::getName()) {
$string .= ' ('.\json_encode($stream[1]).')';
$string .= ' (' . \json_encode($stream[1]) . ')';
}
}
return $string;
}
/**
* Returns a representation of the context.
*

View File

@ -1,4 +1,5 @@
<?php
/**
* MTProto buffer interface.
*

View File

@ -1,4 +1,5 @@
<?php
/**
* Abridged stream wrapper.
*
@ -34,9 +35,7 @@ use danog\MadelineProto\Stream\RawStreamInterface;
class AbridgedStream implements BufferedStreamInterface, MTProtoBufferInterface
{
use BufferedStream;
private $stream;
/**
* Connect to stream.
*
@ -46,9 +45,8 @@ class AbridgedStream implements BufferedStreamInterface, MTProtoBufferInterface
*/
public function connectGenerator(ConnectionContext $ctx, string $header = ''): \Generator
{
$this->stream = yield $ctx->getStream(\chr(239).$header);
$this->stream = yield $ctx->getStream(\chr(239) . $header);
}
/**
* Async close.
*
@ -58,7 +56,6 @@ class AbridgedStream implements BufferedStreamInterface, MTProtoBufferInterface
{
return $this->stream->disconnect();
}
/**
* Get write buffer asynchronously.
*
@ -72,14 +69,12 @@ class AbridgedStream implements BufferedStreamInterface, MTProtoBufferInterface
if ($length < 127) {
$message = \chr($length);
} else {
$message = \chr(127).\substr(\pack('V', $length), 0, 3);
$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.
*
@ -92,13 +87,11 @@ class AbridgedStream implements BufferedStreamInterface, MTProtoBufferInterface
$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 = \unpack('V', yield $buffer->bufferRead(3) . "\0")[1];
}
$length <<= 2;
return $buffer;
}
/**
* {@inheritdoc}
*
@ -108,7 +101,6 @@ class AbridgedStream implements BufferedStreamInterface, MTProtoBufferInterface
{
return $this->stream->getSocket();
}
/**
* {@inheritDoc}
*

View File

@ -1,4 +1,5 @@
<?php
/**
* TCP full stream wrapper.
*
@ -40,7 +41,6 @@ class FullStream implements BufferedStreamInterface, MTProtoBufferInterface
private $stream;
private $in_seq_no = -1;
private $out_seq_no = -1;
/**
* Stream to use as data source.
*
@ -54,10 +54,8 @@ class FullStream implements BufferedStreamInterface, MTProtoBufferInterface
$this->out_seq_no = -1;
$this->stream = new HashedBufferedStream();
$this->stream->setExtra('crc32b_rev');
return $this->stream->connect($ctx, $header);
}
/**
* Async close.
*
@ -67,7 +65,6 @@ class FullStream implements BufferedStreamInterface, MTProtoBufferInterface
{
return $this->stream->disconnect();
}
/**
* Get write buffer asynchronously.
*
@ -82,10 +79,8 @@ class FullStream implements BufferedStreamInterface, MTProtoBufferInterface
$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.
*
@ -105,10 +100,8 @@ class FullStream implements BufferedStreamInterface, MTProtoBufferInterface
if ($in_seq_no != $this->in_seq_no) {
throw new \danog\MadelineProto\Exception('Incoming seq_no mismatch');
}
return $buffer;
}
/**
* {@inheritdoc}
*
@ -118,7 +111,6 @@ class FullStream implements BufferedStreamInterface, MTProtoBufferInterface
{
return $this->stream->getSocket();
}
/**
* {@inheritDoc}
*

View File

@ -1,4 +1,5 @@
<?php
/**
* HTTP stream wrapper.
*
@ -45,7 +46,6 @@ class HttpStream implements MTProtoBufferInterface, BufferedProxyStreamInterface
* @var \Amp\Uri\Uri
*/
private $uri;
/**
* Connect to stream.
*
@ -59,7 +59,6 @@ class HttpStream implements MTProtoBufferInterface, BufferedProxyStreamInterface
$this->stream = yield $ctx->getStream($header);
$this->uri = $ctx->getUri();
}
/**
* Set proxy data.
*
@ -70,10 +69,9 @@ class HttpStream implements MTProtoBufferInterface, BufferedProxyStreamInterface
public function setExtra($extra)
{
if (isset($extra['user']) && isset($extra['password'])) {
$this->header = \base64_encode($extra['user'].':'.$extra['password'])."\r\n";
$this->header = \base64_encode($extra['user'] . ':' . $extra['password']) . "\r\n";
}
}
/**
* Async close.
*
@ -83,7 +81,6 @@ class HttpStream implements MTProtoBufferInterface, BufferedProxyStreamInterface
{
return $this->stream->disconnect();
}
/**
* Get write buffer asynchronously.
*
@ -93,13 +90,11 @@ class HttpStream implements MTProtoBufferInterface, BufferedProxyStreamInterface
*/
public function getWriteBufferGenerator(int $length, string $append = ''): \Generator
{
$headers = 'POST '.$this->uri->getPath()." HTTP/1.1\r\nHost: ".$this->uri->getHost().':'.$this->uri->getPort()."\r\n"."Content-Type: application/x-www-form-urlencoded\r\nConnection: keep-alive\r\nKeep-Alive: timeout=100000, max=10000000\r\nContent-Length: ".$length.$this->header."\r\n\r\n";
$headers = 'POST ' . $this->uri->getPath() . " HTTP/1.1\r\nHost: " . $this->uri->getHost() . ':' . $this->uri->getPort() . "\r\n" . "Content-Type: application/x-www-form-urlencoded\r\nConnection: keep-alive\r\nKeep-Alive: timeout=100000, max=10000000\r\nContent-Length: " . $length . $this->header . "\r\n\r\n";
$buffer = yield $this->stream->getWriteBuffer(\strlen($headers) + $length, $append);
yield $buffer->bufferWrite($headers);
return $buffer;
}
/**
* Get read buffer asynchronously.
*
@ -115,7 +110,8 @@ class HttpStream implements MTProtoBufferInterface, BufferedProxyStreamInterface
while (true) {
$piece = yield $buffer->bufferRead(2);
$headers .= $piece;
if ($piece === "\n\r") { // Assume end of headers with \r\n\r\n
if ($piece === "\n\r") {
// Assume end of headers with \r\n\r\n
$headers .= yield $buffer->bufferRead(1);
break;
}
@ -125,7 +121,6 @@ class HttpStream implements MTProtoBufferInterface, BufferedProxyStreamInterface
$was_crlf = $piece === "\r\n";
}
$headers = \explode("\r\n", $headers);
list($protocol, $code, $description) = \explode(' ', $headers[0], 3);
list($protocol, $protocol_version) = \explode('/', $protocol);
if ($protocol !== 'HTTP') {
@ -133,7 +128,7 @@ class HttpStream implements MTProtoBufferInterface, BufferedProxyStreamInterface
}
$code = (int) $code;
unset($headers[0]);
if (\array_pop($headers).\array_pop($headers) !== '') {
if (\array_pop($headers) . \array_pop($headers) !== '') {
throw new \danog\MadelineProto\Exception('Wrong last header');
}
foreach ($headers as $key => $current_header) {
@ -141,31 +136,24 @@ class HttpStream implements MTProtoBufferInterface, BufferedProxyStreamInterface
$current_header = \explode(':', $current_header, 2);
$headers[\strtolower($current_header[0])] = \trim($current_header[1]);
}
$close = $protocol === 'HTTP/1.0';
if (isset($headers['connection'])) {
$close = \strtolower($headers['connection']) === 'close';
}
if ($code !== 200) {
$read = '';
if (isset($headers['content-length'])) {
$read = yield $buffer->bufferRead((int) $headers['content-length']);
}
if ($close) {
$this->disconnect();
yield $this->connect($this->ctx);
}
\danog\MadelineProto\Logger::log($read);
$this->code = \danog\MadelineProto\Tools::packSignedInt(-$code);
$length = 4;
return $this;
}
if ($close) {
$this->stream->disconnect();
yield $this->stream->connect($this->ctx);
@ -173,15 +161,12 @@ class HttpStream implements MTProtoBufferInterface, BufferedProxyStreamInterface
if (isset($headers['content-length'])) {
$length = (int) $headers['content-length'];
}
return $buffer;
}
public function bufferRead(int $length): Promise
{
return new Success($this->code);
}
/**
* {@inheritdoc}
*
@ -200,7 +185,6 @@ class HttpStream implements MTProtoBufferInterface, BufferedProxyStreamInterface
{
return $this->stream;
}
public static function getName(): string
{
return __CLASS__;

View File

@ -1,4 +1,5 @@
<?php
/**
* HTTPS stream wrapper.
*
@ -20,7 +21,6 @@ namespace danog\MadelineProto\Stream\MTProtoTransport;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\MTProtoBufferInterface;
use danog\MadelineProto\Stream\RawStreamInterface;
/**
@ -41,7 +41,6 @@ class HttpsStream extends HttpStream implements MTProtoBufferInterface
{
return parent::connectGenerator($ctx->getCtx()->secure(true), $header);
}
/**
* {@inheritDoc}
*

View File

@ -1,4 +1,5 @@
<?php
/**
* TCP Intermediate stream wrapper.
*
@ -24,7 +25,6 @@ use danog\MadelineProto\Stream\Async\BufferedStream;
use danog\MadelineProto\Stream\BufferedStreamInterface;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\MTProtoBufferInterface;
use danog\MadelineProto\Stream\RawStreamInterface;
/**
@ -38,7 +38,6 @@ class IntermediatePaddedStream implements BufferedStreamInterface, MTProtoBuffer
{
use BufferedStream;
private $stream;
/**
* Connect to stream.
*
@ -48,9 +47,8 @@ class IntermediatePaddedStream implements BufferedStreamInterface, MTProtoBuffer
*/
public function connectGenerator(ConnectionContext $ctx, string $header = ''): \Generator
{
$this->stream = yield $ctx->getStream(\str_repeat(\chr(221), 4).$header);
$this->stream = yield $ctx->getStream(\str_repeat(\chr(221), 4) . $header);
}
/**
* Async close.
*
@ -60,7 +58,6 @@ class IntermediatePaddedStream implements BufferedStreamInterface, MTProtoBuffer
{
return $this->stream->disconnect();
}
/**
* Get write buffer asynchronously.
*
@ -71,12 +68,10 @@ class IntermediatePaddedStream implements BufferedStreamInterface, MTProtoBuffer
public function getWriteBufferGenerator(int $length, string $append = ''): \Generator
{
$padding_length = \danog\MadelineProto\Tools::randomInt($modulus = 16);
$buffer = yield $this->stream->getWriteBuffer(4 + $length + $padding_length, $append.\danog\MadelineProto\Tools::random($padding_length));
$buffer = yield $this->stream->getWriteBuffer(4 + $length + $padding_length, $append . \danog\MadelineProto\Tools::random($padding_length));
yield $buffer->bufferWrite(\pack('V', $padding_length + $length));
return $buffer;
}
/**
* Get read buffer asynchronously.
*
@ -88,10 +83,8 @@ class IntermediatePaddedStream implements BufferedStreamInterface, MTProtoBuffer
{
$buffer = yield $this->stream->getReadBuffer($l);
$length = \unpack('V', yield $buffer->bufferRead(4))[1];
return $buffer;
}
/**
* {@inheritdoc}
*
@ -110,7 +103,6 @@ class IntermediatePaddedStream implements BufferedStreamInterface, MTProtoBuffer
{
return $this->stream;
}
public static function getName(): string
{
return __CLASS__;

View File

@ -1,4 +1,5 @@
<?php
/**
* TCP Intermediate stream wrapper.
*
@ -24,7 +25,6 @@ use danog\MadelineProto\Stream\Async\BufferedStream;
use danog\MadelineProto\Stream\BufferedStreamInterface;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\MTProtoBufferInterface;
use danog\MadelineProto\Stream\RawStreamInterface;
/**
@ -38,7 +38,6 @@ class IntermediateStream implements BufferedStreamInterface, MTProtoBufferInterf
{
use BufferedStream;
private $stream;
/**
* Connect to stream.
*
@ -48,9 +47,8 @@ class IntermediateStream implements BufferedStreamInterface, MTProtoBufferInterf
*/
public function connectGenerator(ConnectionContext $ctx, string $header = ''): \Generator
{
$this->stream = yield $ctx->getStream(\str_repeat(\chr(238), 4).$header);
$this->stream = yield $ctx->getStream(\str_repeat(\chr(238), 4) . $header);
}
/**
* Async close.
*
@ -60,7 +58,6 @@ class IntermediateStream implements BufferedStreamInterface, MTProtoBufferInterf
{
return $this->stream->disconnect();
}
/**
* Get write buffer asynchronously.
*
@ -72,10 +69,8 @@ class IntermediateStream implements BufferedStreamInterface, MTProtoBufferInterf
{
$buffer = yield $this->stream->getWriteBuffer($length + 4, $append);
yield $buffer->bufferWrite(\pack('V', $length));
return $buffer;
}
/**
* Get read buffer asynchronously.
*
@ -87,10 +82,8 @@ class IntermediateStream implements BufferedStreamInterface, MTProtoBufferInterf
{
$buffer = yield $this->stream->getReadBuffer($l);
$length = \unpack('V', yield $buffer->bufferRead(4))[1];
return $buffer;
}
/**
* {@inheritdoc}
*
@ -109,8 +102,6 @@ class IntermediateStream implements BufferedStreamInterface, MTProtoBufferInterf
{
return $this->stream;
}
public static function getName(): string
{
return __CLASS__;

View File

@ -1,4 +1,5 @@
<?php
/**
* Obfuscated2 stream wrapper.
*
@ -33,10 +34,8 @@ use danog\MadelineProto\Stream\ConnectionContext;
class ObfuscatedStream extends CtrStream implements BufferedProxyStreamInterface
{
use Stream;
private $stream;
private $extra;
/**
* Connect to stream.
*
@ -48,52 +47,30 @@ class ObfuscatedStream extends CtrStream implements BufferedProxyStreamInterface
{
if (isset($this->extra['address'])) {
$ctx = $ctx->getCtx();
$ctx->setUri('tcp://'.$this->extra['address'].':'.$this->extra['port']);
$ctx->setUri('tcp://' . $this->extra['address'] . ':' . $this->extra['port']);
}
do {
$random = \danog\MadelineProto\Tools::random(64);
} while (\in_array(\substr($random, 0, 4), ['PVrG', 'GET ', 'POST', 'HEAD', \str_repeat(\chr(238), 4), \str_repeat(\chr(221), 4)]) || $random[0] === \chr(0xef) || \substr($random, 4, 4) === "\0\0\0\0");
if (\strlen($header) === 1) {
$header = \str_repeat($header, 4);
}
$random = \substr_replace($random, $header.\substr($random, 56 + \strlen($header)), 56);
$random = \substr_replace($random, \pack('s', $ctx->getIntDc()).\substr($random, 60 + 2), 60);
$random = \substr_replace($random, $header . \substr($random, 56 + \strlen($header)), 56);
$random = \substr_replace($random, \pack('s', $ctx->getIntDc()) . \substr($random, 60 + 2), 60);
$reversed = \strrev($random);
$key = \substr($random, 8, 32);
$keyRev = \substr($reversed, 8, 32);
if (isset($this->extra['secret'])) {
$key = \hash('sha256', $key.$this->extra['secret'], true);
$keyRev = \hash('sha256', $keyRev.$this->extra['secret'], true);
$key = \hash('sha256', $key . $this->extra['secret'], true);
$keyRev = \hash('sha256', $keyRev . $this->extra['secret'], true);
}
$iv = \substr($random, 40, 16);
$ivRev = \substr($reversed, 40, 16);
parent::setExtra(
[
'encrypt' => [
'key' => $key,
'iv' => $iv
],
'decrypt' => [
'key' => $keyRev,
'iv' => $ivRev
]
]
);
parent::setExtra(['encrypt' => ['key' => $key, 'iv' => $iv], 'decrypt' => ['key' => $keyRev, 'iv' => $ivRev]]);
yield from parent::connectGenerator($ctx);
$random = \substr_replace($random, \substr(@$this->getEncryptor()->encrypt($random), 56, 8), 56, 8);
yield $this->getStream()->write($random);
}
/**
* Does nothing.
*
@ -111,7 +88,6 @@ class ObfuscatedStream extends CtrStream implements BufferedProxyStreamInterface
$extra['secret'] = \substr($extra['secret'], 1, 16);
}
}
$this->extra = $extra;
}
public static function getName(): string

View File

@ -1,4 +1,5 @@
<?php
/**
* HTTP proxy stream wrapper.
*
@ -25,7 +26,6 @@ use danog\MadelineProto\Stream\Async\RawStream;
use danog\MadelineProto\Stream\BufferedProxyStreamInterface;
use danog\MadelineProto\Stream\ConnectionContext;
use danog\MadelineProto\Stream\RawProxyStreamInterface;
use danog\MadelineProto\Stream\RawStreamInterface;
/**
@ -37,7 +37,6 @@ class HttpProxy implements RawProxyStreamInterface, BufferedProxyStreamInterface
{
use RawStream;
private $extra;
/**
* Connect to stream.
*
@ -50,33 +49,28 @@ class HttpProxy implements RawProxyStreamInterface, BufferedProxyStreamInterface
$ctx = $ctx->getCtx();
$uri = $ctx->getUri();
$secure = $ctx->isSecure();
if ($secure) {
$ctx->setSocketContext($ctx->getSocketContext()->withTlsContext(new ClientTlsContext($uri->getHost())));
}
$ctx->setUri('tcp://'.$this->extra['address'].':'.$this->extra['port'])->secure(false);
$ctx->setUri('tcp://' . $this->extra['address'] . ':' . $this->extra['port'])->secure(false);
$this->stream = yield $ctx->getStream();
$address = $uri->getHost();
$port = $uri->getPort();
try {
if (\strlen(\inet_pton($address) === 16)) {
$address = '['.$address.']';
$address = '[' . $address . ']';
}
} catch (\danog\MadelineProto\Exception $e) {
}
yield $this->stream->write("CONNECT $address:$port HTTP/1.1\r\nHost: $address:$port\r\nAccept: */*\r\n".$this->getProxyAuthHeader()."Connection: keep-Alive\r\n\r\n");
yield $this->stream->write("CONNECT {$address}:{$port} HTTP/1.1\r\nHost: {$address}:{$port}\r\nAccept: */*\r\n" . $this->getProxyAuthHeader() . "Connection: keep-Alive\r\n\r\n");
$buffer = yield $this->stream->getReadBuffer($l);
$headers = '';
$was_crlf = false;
while (true) {
$piece = yield $buffer->bufferRead(2);
$headers .= $piece;
if ($piece === "\n\r") { // Assume end of headers with \r\n\r\n
if ($piece === "\n\r") {
// Assume end of headers with \r\n\r\n
$headers .= yield $buffer->bufferRead(1);
break;
}
@ -86,7 +80,6 @@ class HttpProxy implements RawProxyStreamInterface, BufferedProxyStreamInterface
$was_crlf = $piece === "\r\n";
}
$headers = \explode("\r\n", $headers);
list($protocol, $code, $description) = \explode(' ', $headers[0], 3);
list($protocol, $protocol_version) = \explode('/', $protocol);
if ($protocol !== 'HTTP') {
@ -94,7 +87,7 @@ class HttpProxy implements RawProxyStreamInterface, BufferedProxyStreamInterface
}
$code = (int) $code;
unset($headers[0]);
if (\array_pop($headers).\array_pop($headers) !== '') {
if (\array_pop($headers) . \array_pop($headers) !== '') {
throw new \danog\MadelineProto\Exception('Wrong last header');
}
foreach ($headers as $key => $current_header) {
@ -102,28 +95,22 @@ class HttpProxy implements RawProxyStreamInterface, BufferedProxyStreamInterface
$current_header = \explode(':', $current_header, 2);
$headers[\strtolower($current_header[0])] = \trim($current_header[1]);
}
$close = $protocol === 'HTTP/1.0';
if (isset($headers['connection'])) {
$close = \strtolower($headers['connection']) === 'close';
}
if ($code !== 200) {
$read = '';
if (isset($headers['content-length'])) {
$read = yield $buffer->bufferRead((int) $headers['content-length']);
}
if ($close) {
$this->disconnect();
yield $this->connect($ctx);
}
\danog\MadelineProto\Logger::log(\trim($read));
throw new \danog\MadelineProto\Exception($description, $code);
}
if ($close) {
yield $this->stream->disconnect();
yield $this->stream->connect($ctx);
@ -132,17 +119,14 @@ class HttpProxy implements RawProxyStreamInterface, BufferedProxyStreamInterface
$length = (int) $headers['content-length'];
$read = yield $buffer->bufferRead($length);
}
if ($secure) {
yield $this->getSocket()->setupTls();
}
\danog\MadelineProto\Logger::log('Connected to '.$address.':'.$port.' via http');
\danog\MadelineProto\Logger::log('Connected to ' . $address . ':' . $port . ' via http');
if (\strlen($header)) {
yield (yield $this->stream->getWriteBuffer(\strlen($header)))->bufferWrite($header);
}
}
/**
* Async close.
*
@ -152,7 +136,6 @@ class HttpProxy implements RawProxyStreamInterface, BufferedProxyStreamInterface
{
return $this->stream->disconnect();
}
/**
* Get write buffer asynchronously.
*
@ -164,7 +147,6 @@ class HttpProxy implements RawProxyStreamInterface, BufferedProxyStreamInterface
{
return $this->stream->getWriteBuffer($length, $append);
}
/**
* Get read buffer asynchronously.
*
@ -176,26 +158,21 @@ class HttpProxy implements RawProxyStreamInterface, BufferedProxyStreamInterface
{
return $this->stream->getReadBuffer($length);
}
public function read(): Promise
{
return $this->stream->read();
}
public function write(string $data): Promise
{
return $this->stream->write($data);
}
private function getProxyAuthHeader()
{
if (!isset($this->extra['username']) || !isset($this->extra['password'])) {
return '';
}
return 'Proxy-Authorization: Basic '.\base64_encode($this->extra['username'].':'.$this->extra['password'])."\r\n";
return 'Proxy-Authorization: Basic ' . \base64_encode($this->extra['username'] . ':' . $this->extra['password']) . "\r\n";
}
/**
* Sets proxy data.
*
@ -207,7 +184,6 @@ class HttpProxy implements RawProxyStreamInterface, BufferedProxyStreamInterface
{
$this->extra = $extra;
}
/**
* {@inheritdoc}
*
@ -217,7 +193,6 @@ class HttpProxy implements RawProxyStreamInterface, BufferedProxyStreamInterface
{
return $this->stream->getSocket();
}
/**
* {@inheritDoc}
*

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