Refactor and clean up serialization and APIFactory APIs, automatically unreference instance on destruction to clean up memory

This commit is contained in:
Daniil Gentili 2020-02-25 18:02:32 +01:00
parent 82599e686e
commit b5267beaa2
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
20 changed files with 779 additions and 493 deletions

View File

@ -19,13 +19,6 @@
namespace danog\MadelineProto; namespace danog\MadelineProto;
use Amp\Deferred;
use Amp\Promise;
use function Amp\File\exists;
use function Amp\File\get;
use function Amp\File\put;
use function Amp\File\rename as renameAsync;
if (!\defined('MADELINEPROTO_TEST')) { if (!\defined('MADELINEPROTO_TEST')) {
\define('MADELINEPROTO_TEST', 'NOT PONY'); \define('MADELINEPROTO_TEST', 'NOT PONY');
} }
@ -36,16 +29,31 @@ if (!\defined('MADELINEPROTO_TEST')) {
class API extends InternalDoc class API extends InternalDoc
{ {
use \danog\Serializable; use \danog\Serializable;
use \danog\MadelineProto\Wrappers\ApiStart; use \danog\MadelineProto\ApiWrappers\Start;
use \danog\MadelineProto\Wrappers\ApiTemplates; use \danog\MadelineProto\ApiWrappers\Templates;
public $session; /**
public $serialized = 0; * Session path.
*
* @internal
*
* @var string
*/
public string $session = '';
/** /**
* Instance of MadelineProto. * Instance of MadelineProto.
* *
* @var MTProto * @var ?MTProto
*/ */
public $API; public $API;
/**
* Storage for externally set properties to be serialized.
*
* @var array
*/
protected array $storage = [];
/** /**
* Whether we're getting our API ID. * Whether we're getting our API ID.
* *
@ -53,172 +61,107 @@ class API extends InternalDoc
* *
* @var boolean * @var boolean
*/ */
public $getting_api_id = false; private bool $gettingApiId = false;
/** /**
* my.telegram.org API wrapper. * my.telegram.org API wrapper.
* *
* @internal * @internal
* *
* @var MyTelegramOrgWrapper * @var ?MyTelegramOrgWrapper
*/ */
public $my_telegram_org_wrapper; private $myTelegramOrgWrapper;
/** /**
* Async ini tpromise. * Whether this is an old instance.
* *
* @var Promise * @var boolean
*/ */
public $asyncAPIPromise; private bool $oldInstance = false;
private $oldInstance = false; /**
private $destructing = false; * Whether we're destructing.
*
* @var boolean
*/
private bool $destructing = false;
/**
* API wrapper (to avoid circular references).
*
* @var APIWrapper
*/
private $wrapper;
/** /**
* Magic constructor function. * Magic constructor function.
* *
* @param array $params Params * @param string $session Session name
* @param array $settings Settings * @param array $settings Settings
* *
* @return void * @return void
*/ */
public function __magic_construct($params = [], $settings = []): void public function __magic_construct(string $session, array $settings = []): void
{ {
$this->wrapper = new APIWrapper($this, $this->exportNamespace());
Magic::classExists(); Magic::classExists();
$deferred = new Deferred(); $this->setInitPromise($this->__construct_async($session, $settings));
$this->asyncAPIPromise = $deferred->promise(); foreach (\get_class_vars(APIFactory::class) as $key => $var) {
$this->asyncAPIPromise->onResolve(function () { if (\in_array($key, ['namespace', 'API', 'lua', 'async', 'asyncAPIPromise', 'methods'])) {
$this->asyncAPIPromise = null;
});
$this->setInitPromise($this->__construct_async($params, $settings, $deferred));
foreach (\get_object_vars(new APIFactory('', $this, $this->async)) as $key => $var) {
if (\in_array($key, ['namespace', 'API', 'lua', 'async', 'asyncAPIPromise', 'methods', 'asyncInitPromise'])) {
continue; continue;
} }
if (\is_null($this->{$key})) { if (!$this->{$key}) {
$this->{$key} = new APIFactory($key, $this->API, $this->async); $this->{$key} = $this->exportNamespace($key);
} }
} }
} }
/** /**
* Async constructor function. * Async constructor function.
* *
* @param mixed $params Params * @param string $session Session name
* @param mixed $settings Settings * @param array $settings Settings
* @param mixed $deferred Deferred
* *
* @return \Generator * @return \Generator
*/ */
public function __construct_async($params, $settings, $deferred): \Generator public function __construct_async(string $session, array $settings = []): \Generator
{ {
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);
}
$unserialized = \danog\Serialization::unserialize($tounserialize);
} catch (\danog\MadelineProto\Exception $e) {
if ($e->getFile() === 'MadelineProto' && $e->getLine() === 1) {
throw $e;
}
if (\MADELINEPROTO_TEST === 'pony') {
throw $e;
}
\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);
}
$changed = false;
if (\strpos($tounserialize, 'O:26:"danog\\MadelineProto\\Button":') !== false) {
Logger::log("SUBBING BUTTONS!");
$tounserialize = \str_replace('O:26:"danog\\MadelineProto\\Button":', 'O:35:"danog\\MadelineProto\\TL\\Types\\Button":', $tounserialize);
$changed = true;
}
if (\strpos($e->getMessage(), "Erroneous data format for unserializing 'phpseclib\\Math\\BigInteger'") === 0) {
Logger::log("SUBBING BIGINTEGOR!");
$tounserialize = \str_replace('phpseclib\\Math\\BigInteger', 'phpseclib\\Math\\BigIntegor', $tounserialize);
$changed = true;
}
if (\strpos($tounserialize, 'C:25:"phpseclib\\Math\\BigInteger"') !== false) {
Logger::log("SUBBING TGSECLIB old!");
$tounserialize = \str_replace('C:25:"phpseclib\\Math\\BigInteger"', 'C:24:"tgseclib\\Math\\BigInteger"', $tounserialize);
$changed = true;
}
if (\strpos($tounserialize, 'C:26:"phpseclib3\\Math\\BigInteger"') !== false) {
Logger::log("SUBBING TGSECLIB!");
$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) {
$unserialized = \unserialize($tounserialize);
}
} catch (\Throwable $e) {
Logger::log((string) $e, Logger::ERROR);
throw $e;
}
if ($unserialized instanceof \danog\PlaceHolder) {
$unserialized = \danog\Serialization::unserialize($tounserialize);
}
if ($unserialized === false) {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['deserialization_error']);
}
$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();
$unserialized->oldInstance = true;
$deferred->resolve();
yield from $this->API->initAsynchronously();
$this->APIFactory();
//\danog\MadelineProto\Logger::log('Ping...', Logger::ULTRA_VERBOSE);
$this->asyncInitPromise = null;
//$pong = yield $this->ping(['ping_id' => 3], ['async' => true]);
//\danog\MadelineProto\Logger::log('Pong: '.$pong['ping_id'], Logger::ULTRA_VERBOSE);
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
return;
}
}
$params = $settings;
}
Logger::constructorFromSettings($settings); Logger::constructorFromSettings($settings);
if (!isset($params['app_info']['api_id']) || !$params['app_info']['api_id']) { $session = Absolute::absolute($session);
$app = (yield from $this->APIStart($params)); if ($unserialized = yield from Serialization::legacyUnserialize($session)) {
$params['app_info']['api_id'] = $app['api_id']; $unserialized->storage = $unserialized->storage ?? [];
$params['app_info']['api_hash'] = $app['api_hash']; $unserialized->session = $session;
APIWrapper::link($this, $unserialized);
APIWrapper::link($this->wrapper, $this);
var_dump($this, $unserialized);
if (isset($this->API)) {
$this->storage = $this->API->storage ?? $this->storage;
unset($unserialized);
yield from $this->API->initAsynchronously();
$this->APIFactory();
$this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
return;
}
} }
$this->API = new MTProto($params);
$this->APIFactory(); if (!isset($settings['app_info']['api_id']) || !$settings['app_info']['api_id']) {
$deferred->resolve(); $app = (yield from $this->APIStart($settings));
Logger::log(\danog\MadelineProto\Lang::$current_lang['apifactory_start'], Logger::VERBOSE); if (!$app) {
$this->forceInit(true);
die();
}
$settings['app_info']['api_id'] = $app['api_id'];
$settings['app_info']['api_hash'] = $app['api_hash'];
}
$this->API = new MTProto($settings);
yield from $this->API->initAsynchronously(); yield from $this->API->initAsynchronously();
$this->APIFactory(); $this->APIFactory();
$this->asyncInitPromise = null; $this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
//\danog\MadelineProto\Logger::log('Ping...', Logger::ULTRA_VERBOSE);
//$pong = yield $this->ping(['ping_id' => 3], ['async' => true]);
//\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. * Enable or disable async.
* *
@ -228,9 +171,9 @@ class API extends InternalDoc
*/ */
public function async(bool $async): void public function async(bool $async): void
{ {
$this->async = $async; parent::async($async);
if ($this->API) { 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, EventHandler::class)) {
$this->API->setEventHandler($this->API->event_handler); $this->API->setEventHandler($this->API->event_handler);
} }
} }
@ -242,23 +185,27 @@ class API extends InternalDoc
*/ */
public function __destruct() public function __destruct()
{ {
if (\danog\MadelineProto\Magic::$has_thread && \is_object(\Thread::getCurrentThread()) || Magic::isFork()) { $this->init();
return;
}
if ($this->asyncInitPromise) {
$this->init();
}
if (!$this->oldInstance) { if (!$this->oldInstance) {
$this->logger('Shutting down MadelineProto (API)');
if ($this->API) { if ($this->API) {
$this->API->logger('Shutting down MadelineProto (normally or due to an exception, idk)');
$this->API->destructing = true; $this->API->destructing = true;
} else {
Logger::log('Shutting down MadelineProto (normally or due to an exception, idk)');
} }
$this->destructing = true; $this->destructing = true;
Tools::wait($this->serialize(), true); Tools::wait($this->wrapper->serialize(), true);
$this->API->unreference();
} else {
$this->logger('Shutting down MadelineProto (old deserialized instance of API)');
} }
//restore_error_handler(); }
/**
* Wakeup function
*
* @return void
*/
public function __wakeup(): void
{
$this->oldInstance = true;
} }
/** /**
* Sleep function. * Sleep function.
@ -269,18 +216,7 @@ class API extends InternalDoc
*/ */
public function __sleep(): array public function __sleep(): array
{ {
return ['API', 'web_api_template', 'getting_api_id', 'my_telegram_org_wrapper']; return APIWrapper::__sleep();
}
/**
* Custom fast getSelf.
*
* @internal
*
* @return array|false
*/
public function myGetSelf()
{
return isset($this->API) && isset($this->API->authorization['user']) ? $this->API->authorization['user'] : false;
} }
/** /**
* Init API wrapper. * Init API wrapper.
@ -289,122 +225,20 @@ class API extends InternalDoc
*/ */
private function APIFactory(): void private function APIFactory(): void
{ {
if ($this->API && !$this->API->asyncInitPromise) { if ($this->API && $this->API->inited()) {
foreach ($this->API->getMethodNamespaces() as $namespace) { foreach ($this->API->getMethodNamespaces() as $namespace) {
$this->{$namespace} = new APIFactory($namespace, $this->API, $this->async); if (!$this->{$namespace}) {
} $this->{$namespace} = $this->exportNamespace($namespace);
$methods = \get_class_methods($this->API);
foreach ($methods as $method) {
if ($method == 'methodCallAsyncRead') {
unset($methods[\array_search('methodCall', $methods)]);
} elseif (\stripos($method, 'async') !== false) {
if (\strpos($method, '_async') !== false) {
unset($methods[\array_search(\str_ireplace('_async', '', $method), $methods)]);
} else {
unset($methods[\array_search(\str_ireplace('async', '', $method), $methods)]);
}
} }
} }
$this->methods = []; $this->methods = self::getInternalMethodList($this->API);
foreach ($methods as $method) { $this->API->wrapper = $this->wrapper;
$actual_method = $method; if ($this->API->event_handler && \class_exists($this->API->event_handler) && \is_subclass_of($this->API->event_handler, EventHandler::class)) {
if ($method == 'methodCallAsyncRead') {
$method = 'methodCall';
} elseif (\stripos($method, 'async') !== false) {
if (\strpos($method, '_async') !== false) {
$method = \str_ireplace('_async', '', $method);
} else {
$method = \str_ireplace('async', '', $method);
}
}
$actual_method = $actual_method === 'getSelf' ? [$this, 'myGetSelf'] : [$this->API, $actual_method];
$this->methods[\strtolower($method)] = $actual_method;
if (\strpos($method, '_') !== false) {
$this->methods[\strtolower(\str_replace('_', '', $method))] = $actual_method;
} else {
$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')) {
$this->API->setEventHandler($this->API->event_handler); $this->API->setEventHandler($this->API->event_handler);
} }
} }
} }
/**
* Get full list of MTProto and API methods.
*
* @return array
*/
public function getAllMethods(): array
{
if ($this->asyncInitPromise) {
$this->init();
}
$methods = [];
foreach ($this->API->methods->by_id as $method) {
$methods[] = $method['method'];
}
return \array_merge($methods, \get_class_methods($this->API));
}
/**
* Serialize session.
*
* @param string $filename File name
*
* @internal Do not use this manually, the session is already serialized automatically
*
* @return Promise
*/
public function serialize(string $filename = ''): Promise
{
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;
}
if (isset($this->API->flushSettings) && $this->API->flushSettings) {
$this->API->flushSettings = false;
$this->API->__construct($this->API->settings);
}
if ($this->API === null && !$this->getting_api_id) {
return false;
}
if ($this->API && $this->API->asyncInitPromise) {
yield from $this->API->initAsynchronously();
}
$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'];
if ($this->API->settings['updates']['callback'] instanceof \Closure) {
$this->API->settings['updates']['callback'] = [$this->API, 'noop'];
}
$logger_closure = $this->API->settings['logger']['logger_param'];
if ($this->API->settings['logger']['logger_param'] instanceof \Closure) {
$this->API->settings['logger']['logger_param'] = [$this->API, 'noop'];
}
}
$wrote = yield put($realpaths['tempfile'], \serialize($this));
yield renameAsync($realpaths['tempfile'], $realpaths['file']);
} finally {
if (!$this->getting_api_id) {
$this->API->settings['updates']['callback'] = $update_closure;
$this->API->settings['logger']['logger_param'] = $logger_closure;
}
$unlock();
}
//Logger::log('Done serializing');
return $wrote;
})());
}
/** /**
* Start MadelineProto and the event handler (enables async). * Start MadelineProto and the event handler (enables async).
@ -428,10 +262,10 @@ class API extends InternalDoc
$this->loop(); $this->loop();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$thrown = true; $thrown = true;
$this->logger((string) $e, Logger::FATAL_ERROR);
try { try {
$this->report("Surfaced: $e"); $this->report("Surfaced: $e");
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->logger((string) $e, Logger::FATAL_ERROR);
} }
} }
} while ($thrown); } while ($thrown);

View File

@ -0,0 +1,188 @@
<?php
/**
* API wrapper module.
*
* This file is part of MadelineProto.
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Affero General Public License for more details.
* You should have received a copy of the GNU General Public License along with MadelineProto.
* If not, see <http://www.gnu.org/licenses/>.
*
* @author Daniil Gentili <daniil@daniil.it>
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
*
* @link https://docs.madelineproto.xyz MadelineProto documentation
*/
namespace danog\MadelineProto;
use Amp\Promise;
use Amp\Success;
use function Amp\File\put;
use function Amp\File\rename as renameAsync;
final class APIWrapper
{
/**
* MTProto instance.
*
* @var ?MTProto
*/
private ?MTProto $API = null;
/**
* Session path.
*
* @var string
*/
public string $session = '';
/**
* Getting API ID flag.
*
* @var bool
*/
private bool $gettingApiId = false;
/**
* Web API template
*
* @var string
*/
private string $webApiTemplate = '';
/**
* My.telegram.org wrapper
*
* @var ?MyTelegramOrgWrapper
*/
private $myTelegramOrgWrapper;
/**
* Serialization date.
*
* @var integer
*/
private int $serialized = 0;
/**
* AbstractAPIFactory instance
*
* @var AbstractAPIFactory
*/
private AbstractAPIFactory $factory;
/**
* API wrapper
*
* @param API $API API instance to wrap
* @param AbstractAPIFactory $factory Factory
*/
public function __construct(API $API, AbstractAPIFactory $factory)
{
self::link($this, $API);
$this->factory = $factory;
}
/**
* Link two APIWrapper and API instances
*
* @param API|APIWrapper $a Instance to which link
* @param API|APIWrapper $b Instance from which link
*
* @return void
*/
public static function link($a, $b): void
{
foreach (self::__sleep() as $var) {
Tools::setVar($a, $var, Tools::getVar($b, $var));
}
Tools::setVar($a, 'session', Tools::getVar($b, 'session'));
}
/**
* Sleep function.
*
* @internal
*
* @return array
*/
public static function __sleep(): array
{
return ['API', 'webApiTemplate', 'gettingApiId', 'myTelegramOrgWrapper', 'storage'];
}
/**
* Get MTProto instance
*
* @return MTProto|null
*/
public function &getAPI(): ?MTProto
{
return $this->API;
}
/**
* Get API factory
*
* @return AbstractAPIFactory
*/
public function getFactory(): AbstractAPIFactory
{
return $this->factory;
}
/**
* Serialie session.
*
* @return Promise
*/
public function serialize(): Promise
{
if (!$this->session) {
Logger::log("Not serializing, no session");
return new Success();
}
return Tools::callFork((function (): \Generator {
if (isset($this->API->flushSettings) && $this->API->flushSettings) {
$this->API->flushSettings = false;
$this->API->__construct($this->API->settings);
}
if ($this->API === null && !$this->gettingApiId) {
return false;
}
if ($this->API) {
yield from $this->API->initAsynchronously();
}
$this->serialized = \time();
$realpaths = Serialization::realpaths($this->session);
//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->gettingApiId) {
$update_closure = $this->API->settings['updates']['callback'];
if ($this->API->settings['updates']['callback'] instanceof \Closure) {
$this->API->settings['updates']['callback'] = [$this->API, 'noop'];
}
$logger_closure = $this->API->settings['logger']['logger_param'];
if ($this->API->settings['logger']['logger_param'] instanceof \Closure) {
$this->API->settings['logger']['logger_param'] = [$this->API, 'noop'];
}
}
$wrote = yield put($realpaths['tempfile'], \serialize($this));
yield renameAsync($realpaths['tempfile'], $realpaths['file']);
} finally {
if (!$this->gettingApiId) {
$this->API->settings['updates']['callback'] = $update_closure;
$this->API->settings['logger']['logger_param'] = $logger_closure;
}
$unlock();
}
//Logger::log('Done serializing');
return $wrote;
})());
}
}

View File

@ -30,7 +30,7 @@ abstract class AbstractAPIFactory extends AsyncConstruct
* *
* @var string * @var string
*/ */
public $namespace = ''; private string $namespace = '';
/** /**
* MTProto instance. * MTProto instance.
* *
@ -46,7 +46,7 @@ abstract class AbstractAPIFactory extends AsyncConstruct
* *
* @var boolean * @var boolean
*/ */
public $lua = false; public bool $lua = false;
/** /**
* Whether async is enabled. * Whether async is enabled.
* *
@ -54,24 +54,43 @@ abstract class AbstractAPIFactory extends AsyncConstruct
* *
* @var boolean * @var boolean
*/ */
public $async = false; private bool $async = false;
/**
* Async init promise.
*
* @var Promise
*/
public $asyncAPIPromise;
/** /**
* Method list. * Method list.
* *
* @var string[] * @var string[]
*/ */
protected $methods = []; protected array $methods = [];
public function __construct($namespace, &$API, &$async) /**
* Export APIFactory instance with the specified namespace.
*
* @param string $namespace Namespace
*
* @return self
*/
protected function exportNamespace(string $namespace = ''): self
{ {
$this->namespace = $namespace.'.'; $class = array_reverse(array_values(class_parents(static::class)))[3];
$this->API =& $API; $instance = new $class;
$this->async =& $async; $instance->namespace = $namespace.'.';
self::link($instance, $this);
return $instance;
}
/**
* Link two APIFactory instances
*
* @param self $a First instance
* @param self $b Second instance
*
* @return void
*/
protected static function link(self $a, self $b): void
{
$a->API =& $b->API;
$a->lua =& $b->lua;
$a->async =& $b->async;
$a->methods =& $b->methods;
} }
/** /**
* Enable or disable async. * Enable or disable async.
@ -101,7 +120,9 @@ abstract class AbstractAPIFactory extends AsyncConstruct
if ($async) { if ($async) {
return $yielded; return $yielded;
} }
$yielded = Tools::wait($yielded); $yielded = Tools::wait($yielded);
if (!$this->lua) { if (!$this->lua) {
return $yielded; return $yielded;
} }
@ -124,30 +145,8 @@ abstract class AbstractAPIFactory extends AsyncConstruct
*/ */
public function __call_async(string $name, array $arguments): \Generator public function __call_async(string $name, array $arguments): \Generator
{ {
if ($this->asyncInitPromise) { yield from $this->initAsynchronously();
yield from $this->initAsynchronously();
$this->API->logger->logger('Finished init asynchronously');
}
if (!$this->API) {
throw new Exception('API did not init!');
}
if ($this->API->asyncInitPromise) {
yield from $this->API->initAsynchronously();
$this->API->logger->logger('Finished init asynchronously');
}
if (isset($this->session) && !\is_null($this->session) && \time() - $this->serialized > $this->API->settings['serialization']['serialization_interval']) {
Logger::log("Didn't serialize in a while, doing that now...");
$this->serialize($this->session);
}
if ($this->API->flushSettings) {
$this->API->flushSettings = false;
$this->API->__construct($this->API->settings);
yield from $this->API->initAsynchronously();
}
if ($this->API->asyncInitPromise) {
yield from $this->API->initAsynchronously();
$this->API->logger->logger('Finished init asynchronously');
}
$lower_name = \strtolower($name); $lower_name = \strtolower($name);
if ($this->namespace !== '' || !isset($this->methods[$lower_name])) { if ($this->namespace !== '' || !isset($this->methods[$lower_name])) {
$name = $this->namespace.$name; $name = $this->namespace.$name;
@ -160,6 +159,61 @@ abstract class AbstractAPIFactory extends AsyncConstruct
$res = $this->methods[$lower_name](...$arguments); $res = $this->methods[$lower_name](...$arguments);
return $res instanceof \Generator ? yield from $res : yield $res; return $res instanceof \Generator ? yield from $res : yield $res;
} }
/**
* Get fully resolved method list for object, including snake_case and camelCase variants.
*
* @param API $value Value
*
* @return array
*/
protected static function getInternalMethodList($value): array
{
static $cache = [];
$class = \get_class($value);
if (isset($cache[$class])) {
return \array_map(
static function ($v) use ($value) {
return [$value, $v];
},
$cache[$class]
);
}
$methods = \get_class_methods($value);
foreach ($methods as $method) {
if ($method == 'methodCallAsyncRead') {
unset($methods[\array_search('methodCall', $methods)]);
} elseif (\stripos($method, 'async') !== false) {
if (\strpos($method, '_async') !== false) {
unset($methods[\array_search(\str_ireplace('_async', '', $method), $methods)]);
} else {
unset($methods[\array_search(\str_ireplace('async', '', $method), $methods)]);
}
}
}
$finalMethods = [];
foreach ($methods as $method) {
$actual_method = $method;
if ($method == 'methodCallAsyncRead') {
$method = 'methodCall';
} elseif (\stripos($method, 'async') !== false) {
if (\strpos($method, '_async') !== false) {
$method = \str_ireplace('_async', '', $method);
} else {
$method = \str_ireplace('async', '', $method);
}
}
$finalMethods[\strtolower($method)] = $actual_method;
if (\strpos($method, '_') !== false) {
$finalMethods[\strtolower(\str_replace('_', '', $method))] = $actual_method;
} else {
$finalMethods[\strtolower(Tools::toSnakeCase($method))] = $actual_method;
}
}
$cache[$class] = $finalMethods;
return self::getInternalMethodList($value);
}
/** /**
* Get attribute. * Get attribute.
* *
@ -171,17 +225,13 @@ abstract class AbstractAPIFactory extends AsyncConstruct
*/ */
public function &__get(string $name) public function &__get(string $name)
{ {
if ($this->asyncAPIPromise) {
Tools::wait($this->asyncAPIPromise);
}
if ($name === 'settings') {
$this->API->flushSettings = true;
return $this->API->settings;
}
if ($name === 'logger') { if ($name === 'logger') {
return $this->API->logger; if (isset($this->API)) {
return $this->API->logger;
}
return Logger::$default;
} }
return $this->API->storage[$name]; return $this->storage[$name];
} }
/** /**
* Set an attribute. * Set an attribute.
@ -195,16 +245,7 @@ abstract class AbstractAPIFactory extends AsyncConstruct
*/ */
public function __set(string $name, $value) public function __set(string $name, $value)
{ {
if ($this->asyncAPIPromise) { return $this->storage[$name] = $value;
Tools::wait($this->asyncAPIPromise);
}
if ($name === 'settings') {
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. * Whether an attribute exists.
@ -215,10 +256,7 @@ abstract class AbstractAPIFactory extends AsyncConstruct
*/ */
public function __isset(string $name): bool public function __isset(string $name): bool
{ {
if ($this->asyncAPIPromise) { return isset($this->storage[$name]);
Tools::wait($this->asyncAPIPromise);
}
return isset($this->API->storage[$name]);
} }
/** /**
* Unset attribute. * Unset attribute.
@ -229,9 +267,6 @@ abstract class AbstractAPIFactory extends AsyncConstruct
*/ */
public function __unset(string $name): void public function __unset(string $name): void
{ {
if ($this->asyncAPIPromise) { unset($this->storage[$name]);
Tools::wait($this->asyncAPIPromise);
}
unset($this->API->storage[$name]);
} }
} }

View File

@ -184,7 +184,7 @@ class AnnotationsBuilder
$name = \str_ireplace('async', '', $name); $name = \str_ireplace('async', '', $name);
} }
} }
$name = Tools::fromSnakeCase($name); $name = Tools::toCamelCase($name);
$name = \str_ireplace(['mtproto', 'api'], ['MTProto', 'API'], $name); $name = \str_ireplace(['mtproto', 'api'], ['MTProto', 'API'], $name);
$doc = 'public function '; $doc = 'public function ';
$doc .= $name; $doc .= $name;

View File

@ -17,17 +17,25 @@
* @link https://docs.madelineproto.xyz MadelineProto documentation * @link https://docs.madelineproto.xyz MadelineProto documentation
*/ */
namespace danog\MadelineProto\Wrappers; namespace danog\MadelineProto\ApiWrappers;
use danog\MadelineProto\MyTelegramOrgWrapper;
use danog\MadelineProto\Tools; use danog\MadelineProto\Tools;
use function Amp\ByteStream\getStdout; use function Amp\ByteStream\getStdout;
/** /**
* Manages simple logging in and out. * Manages simple logging in and out.
*/ */
trait ApiStart trait Start
{ {
public function APIStart($settings): \Generator /**
* Start API ID generation process.
*
* @param array $settings Settings
*
* @return \Generator
*/
private function APIStart(array $settings): \Generator
{ {
if (PHP_SAPI === 'cli') { if (PHP_SAPI === 'cli') {
$stdout = getStdout(); $stdout = getStdout();
@ -46,66 +54,65 @@ Note that you can also provide the API parameters directly in the code using the
$app['api_hash'] = yield Tools::readLine('6) Enter your API hash: '); $app['api_hash'] = yield Tools::readLine('6) Enter your API hash: ');
return $app; return $app;
} }
$this->my_telegram_org_wrapper = new \danog\MadelineProto\MyTelegramOrgWrapper($settings); $this->myTelegramOrgWrapper = new \danog\MadelineProto\MyTelegramOrgWrapper($settings);
yield from $this->my_telegram_org_wrapper->login(yield Tools::readLine('Enter a phone number that is already registered on Telegram: ')); yield from $this->myTelegramOrgWrapper->login(yield Tools::readLine('Enter a phone number that is already registered on Telegram: '));
yield from $this->my_telegram_org_wrapper->completeLogin(yield Tools::readLine('Enter the verification code you received in telegram: ')); yield from $this->myTelegramOrgWrapper->completeLogin(yield Tools::readLine('Enter the verification code you received in telegram: '));
if (!(yield from $this->my_telegram_org_wrapper->hasApp())) { if (!(yield from $this->myTelegramOrgWrapper->hasApp())) {
$app_title = yield Tools::readLine('Enter the app\'s name, can be anything: '); $app_title = yield Tools::readLine('Enter the app\'s name, can be anything: ');
$short_name = yield Tools::readLine('Enter the app\'s short name, can be anything: '); $short_name = yield Tools::readLine('Enter the app\'s short name, can be anything: ');
$url = yield Tools::readLine('Enter the app/website\'s URL, or t.me/yourusername: '); $url = yield Tools::readLine('Enter the app/website\'s URL, or t.me/yourusername: ');
$description = yield Tools::readLine('Describe your app: '); $description = yield Tools::readLine('Describe your app: ');
$app = (yield from $this->my_telegram_org_wrapper->createApp(['app_title' => $app_title, 'app_shortname' => $short_name, 'app_url' => $url, 'app_platform' => 'web', 'app_desc' => $description])); $app = (yield from $this->myTelegramOrgWrapper->createApp(['app_title' => $app_title, 'app_shortname' => $short_name, 'app_url' => $url, 'app_platform' => 'web', 'app_desc' => $description]));
} else { } else {
$app = (yield from $this->my_telegram_org_wrapper->getApp()); $app = (yield from $this->myTelegramOrgWrapper->getApp());
} }
return $app; return $app;
} }
$this->getting_api_id = true; $this->gettingApiId = true;
if (!isset($this->my_telegram_org_wrapper)) { if (!isset($this->myTelegramOrgWrapper)) {
if (isset($_POST['api_id']) && isset($_POST['api_hash'])) { if (isset($_POST['api_id']) && isset($_POST['api_hash'])) {
$app['api_id'] = (int) $_POST['api_id']; $app['api_id'] = (int) $_POST['api_id'];
$app['api_hash'] = $_POST['api_hash']; $app['api_hash'] = $_POST['api_hash'];
$this->getting_api_id = false; $this->gettingApiId = false;
return $app; return $app;
} elseif (isset($_POST['phone_number'])) { } elseif (isset($_POST['phone_number'])) {
yield from $this->webAPIPhoneLogin($settings); yield from $this->webAPIPhoneLogin($settings);
} else { } else {
yield from $this->webAPIEcho(); yield from $this->webAPIEcho();
} }
} elseif (!$this->my_telegram_org_wrapper->loggedIn()) { } elseif (!$this->myTelegramOrgWrapper->loggedIn()) {
if (isset($_POST['code'])) { if (isset($_POST['code'])) {
yield from $this->webAPICompleteLogin(); yield from $this->webAPICompleteLogin();
if (yield from $this->my_telegram_org_wrapper->hasApp()) { if (yield from $this->myTelegramOrgWrapper->hasApp()) {
return yield from $this->my_telegram_org_wrapper->getApp(); return yield from $this->myTelegramOrgWrapper->getApp();
} }
yield from $this->webAPIEcho(); yield from $this->webAPIEcho();
} elseif (isset($_POST['api_id']) && isset($_POST['api_hash'])) { } elseif (isset($_POST['api_id']) && isset($_POST['api_hash'])) {
$app['api_id'] = (int) $_POST['api_id']; $app['api_id'] = (int) $_POST['api_id'];
$app['api_hash'] = $_POST['api_hash']; $app['api_hash'] = $_POST['api_hash'];
$this->getting_api_id = false; $this->gettingApiId = false;
return $app; return $app;
} elseif (isset($_POST['phone_number'])) { } elseif (isset($_POST['phone_number'])) {
yield from $this->webAPIPhoneLogin($settings); yield from $this->webAPIPhoneLogin($settings);
} else { } else {
$this->my_telegram_org_wrapper = null; $this->myTelegramOrgWrapper = null;
yield from $this->webAPIEcho(); yield from $this->webAPIEcho();
} }
} else { } else {
if (isset($_POST['app_title'], $_POST['app_shortname'], $_POST['app_url'], $_POST['app_platform'], $_POST['app_desc'])) { if (isset($_POST['app_title'], $_POST['app_shortname'], $_POST['app_url'], $_POST['app_platform'], $_POST['app_desc'])) {
$app = (yield from $this->webAPICreateApp()); $app = (yield from $this->webAPICreateApp());
$this->getting_api_id = false; $this->gettingApiId = false;
return $app; return $app;
} }
yield from $this->webAPIEcho("You didn't provide all of the required parameters!"); yield from $this->webAPIEcho("You didn't provide all of the required parameters!");
} }
$this->asyncInitPromise = null; return null;
exit;
} }
private function webAPIPhoneLogin($settings): \Generator private function webAPIPhoneLogin(array $settings): \Generator
{ {
try { try {
$this->my_telegram_org_wrapper = new \danog\MadelineProto\MyTelegramOrgWrapper($settings); $this->myTelegramOrgWrapper = new MyTelegramOrgWrapper($settings);
yield from $this->my_telegram_org_wrapper->login($_POST['phone_number']); yield from $this->myTelegramOrgWrapper->login($_POST['phone_number']);
yield from $this->webAPIEcho(); yield from $this->webAPIEcho();
} catch (\Throwable $e) { } catch (\Throwable $e) {
yield from $this->webAPIEcho('ERROR: ' . $e->getMessage() . '. Try again.'); yield from $this->webAPIEcho('ERROR: ' . $e->getMessage() . '. Try again.');
@ -114,7 +121,7 @@ Note that you can also provide the API parameters directly in the code using the
private function webAPICompleteLogin(): \Generator private function webAPICompleteLogin(): \Generator
{ {
try { try {
yield from $this->my_telegram_org_wrapper->completeLogin($_POST['code']); yield from $this->myTelegramOrgWrapper->completeLogin($_POST['code']);
} catch (\danog\MadelineProto\RPCErrorException $e) { } catch (\danog\MadelineProto\RPCErrorException $e) {
yield from $this->webAPIEcho('ERROR: ' . $e->getMessage() . '. Try again.'); yield from $this->webAPIEcho('ERROR: ' . $e->getMessage() . '. Try again.');
} catch (\danog\MadelineProto\Exception $e) { } catch (\danog\MadelineProto\Exception $e) {
@ -126,7 +133,7 @@ Note that you can also provide the API parameters directly in the code using the
try { try {
$params = $_POST; $params = $_POST;
unset($params['creating_app']); unset($params['creating_app']);
$app = (yield from $this->my_telegram_org_wrapper->createApp($params)); $app = (yield from $this->myTelegramOrgWrapper->createApp($params));
return $app; return $app;
} catch (\danog\MadelineProto\RPCErrorException $e) { } catch (\danog\MadelineProto\RPCErrorException $e) {
yield from $this->webAPIEcho('ERROR: ' . $e->getMessage() . ' Try again.'); yield from $this->webAPIEcho('ERROR: ' . $e->getMessage() . ' Try again.');

View File

@ -17,29 +17,29 @@
* @link https://docs.madelineproto.xyz MadelineProto documentation * @link https://docs.madelineproto.xyz MadelineProto documentation
*/ */
namespace danog\MadelineProto\Wrappers; namespace danog\MadelineProto\ApiWrappers;
use function Amp\ByteStream\getOutputBufferStream; use function Amp\ByteStream\getOutputBufferStream;
trait ApiTemplates trait Templates
{ {
private $web_api_template = '<!DOCTYPE html> /**
<html> * API template.
<head> *
<title>MadelineProto</title> * @var string
</head> */
<body> private $webApiTemplate = '<!DOCTYPE html><html><head><title>MadelineProto</title></head><body><h1>MadelineProto</h1><p>%s</p><form method="POST">%s<button type="submit"/>Go</button></form></body></html>';
<h1>MadelineProto</h1> /**
<p>%s</p> * Generate page from template.
<form method="POST"> *
%s * @param string $message Message
<button type="submit"/>Go</button> * @param string $form Form
</form> *
</body> * @return string
</html>'; */
private function webAPIEchoTemplate($message, $form) private function webAPIEchoTemplate(string $message, string $form): string
{ {
return \sprintf($this->web_api_template, $message, $form); return \sprintf($this->webApiTemplate, $message, $form);
} }
/** /**
* Get web API login HTML template string. * Get web API login HTML template string.
@ -48,24 +48,31 @@ trait ApiTemplates
*/ */
public function getWebAPITemplate(): string public function getWebAPITemplate(): string
{ {
return $this->web_template; return $this->webApiTemplate;
} }
/** /**
* Set web API login HTML template string. * Set web API login HTML template string.
* *
* @return string * @return string
*/ */
public function setWebAPITemplate(string $template) public function setWebAPITemplate(string $template): void
{ {
$this->web_template = $template; $this->webApiTemplate = $template;
} }
/**
* Echo to browser.
*
* @param string $message Message to echo
*
* @return \Generator
*/
private function webAPIEcho(string $message = ''): \Generator private function webAPIEcho(string $message = ''): \Generator
{ {
$stdout = getOutputBufferStream(); $stdout = getOutputBufferStream();
if (!isset($this->my_telegram_org_wrapper)) { if (!isset($this->myTelegramOrgWrapper)) {
if (isset($_POST['type'])) { if (isset($_POST['type'])) {
if ($_POST['type'] === 'manual') { if ($_POST['type'] === 'manual') {
yield $stdout->write($this->webAPIEchoTemplate('Enter your API ID and API hash<br><b>' . $message . '</b><ol> yield $stdout->write($this->webAPIEchoTemplate('Enter your API ID and API hash<br><b>'.$message.'</b><ol>
<li>Login to my.telegram.org</li> <li>Login to my.telegram.org</li>
<li>Go to API development tools</li> <li>Go to API development tools</li>
<li> <li>
@ -79,19 +86,19 @@ trait ApiTemplates
<li>Click on create application</li> <li>Click on create application</li>
</ol>', '<input type="string" name="api_id" placeholder="API ID" required/><input type="string" name="api_hash" placeholder="API hash" required/>')); </ol>', '<input type="string" name="api_id" placeholder="API ID" required/><input type="string" name="api_hash" placeholder="API hash" required/>'));
} else { } else {
yield $stdout->write($this->webAPIEchoTemplate('Enter a phone number that is <b>already registered</b> on telegram to get the API ID<br><b>' . $message . '</b>', '<input type="text" name="phone_number" placeholder="Phone number" required/>')); yield $stdout->write($this->webAPIEchoTemplate('Enter a phone number that is <b>already registered</b> on telegram to get the API ID<br><b>'.$message.'</b>', '<input type="text" name="phone_number" placeholder="Phone number" required/>'));
} }
} else { } else {
if ($message) { if ($message) {
$message = '<br><br>' . $message; $message = '<br><br>'.$message;
} }
yield $stdout->write($this->webAPIEchoTemplate('Do you want to enter the API id and the API hash manually or automatically?<br>Note that you can also provide it directly in the code using the <a href="https://docs.madelineproto.xyz/docs/SETTINGS.html#settingsapp_infoapi_id">settings</a>.<b>' . $message . '</b>', '<select name="type"><option value="automatic">Automatically</option><option value="manual">Manually</option></select>')); yield $stdout->write($this->webAPIEchoTemplate('Do you want to enter the API id and the API hash manually or automatically?<br>Note that you can also provide it directly in the code using the <a href="https://docs.madelineproto.xyz/docs/SETTINGS.html#settingsapp_infoapi_id">settings</a>.<b>'.$message.'</b>', '<select name="type"><option value="automatic">Automatically</option><option value="manual">Manually</option></select>'));
} }
} else { } else {
if (!$this->my_telegram_org_wrapper->loggedIn()) { if (!$this->myTelegramOrgWrapper->loggedIn()) {
yield $stdout->write($this->webAPIEchoTemplate('Enter your code<br><b>' . $message . '</b>', '<input type="text" name="code" placeholder="Code" required/>')); yield $stdout->write($this->webAPIEchoTemplate('Enter your code<br><b>'.$message.'</b>', '<input type="text" name="code" placeholder="Code" required/>'));
} else { } else {
yield $stdout->write($this->webAPIEchoTemplate('Enter the API info<br><b>' . $message . '</b>', '<input type="hidden" name="creating_app" value="yes" required/> yield $stdout->write($this->webAPIEchoTemplate('Enter the API info<br><b>'.$message.'</b>', '<input type="hidden" name="creating_app" value="yes" required/>
Enter the app name, can be anything: <br><input type="text" name="app_title" required/><br> Enter the app name, can be anything: <br><input type="text" name="app_title" required/><br>
<br>Enter the app&apos;s short name, alphanumeric, 5-32 chars: <br><input type="text" name="app_shortname" required/><br> <br>Enter the app&apos;s short name, alphanumeric, 5-32 chars: <br><input type="text" name="app_shortname" required/><br>
<br>Enter the app/website URL, or https://t.me/yourusername: <br><input type="text" name="app_url" required/><br> <br>Enter the app/website URL, or https://t.me/yourusername: <br><input type="text" name="app_url" required/><br>

View File

@ -35,7 +35,7 @@ class AsyncConstruct
* *
* @var Promise * @var Promise
*/ */
public $asyncInitPromise; private $asyncInitPromise;
/** /**
* Blockingly init. * Blockingly init.
* *
@ -58,10 +58,30 @@ class AsyncConstruct
yield $this->asyncInitPromise; yield $this->asyncInitPromise;
} }
} }
/**
* Check if we've already inited.
*
* @return boolean
*/
public function inited(): bool
{
return !$this->asyncInitPromise;
}
/**
* Mark instance as (de)inited forcefully
*
* @param boolean $inited Whether to mark the instance as inited or deinited
*
* @return void
*/
public function forceInit(bool $inited): void
{
$this->asyncInitPromise = $inited ? null : true;
}
/** /**
* Set init promise. * Set init promise.
* *
* @param Promise $promise Promise * @param Promise|\Generator $promise Promise
* *
* @internal * @internal
* *
@ -69,12 +89,14 @@ class AsyncConstruct
*/ */
public function setInitPromise($promise): void public function setInitPromise($promise): void
{ {
$this->asyncInitPromise = Tools::callFork($promise); $this->asyncInitPromise = Tools::call($promise);
$this->asyncInitPromise->onResolve(function ($error, $result) { $this->asyncInitPromise->onResolve(
if ($error) { function (?\Throwable $error, $result): void {
throw $error; if ($error) {
throw $error;
}
$this->asyncInitPromise = null;
} }
$this->asyncInitPromise = null; );
});
} }
} }

View File

@ -27,18 +27,17 @@ class EventHandler extends InternalDoc
/** /**
* Constructor. * Constructor.
* *
* @param API|null $MadelineProto MadelineProto instance * @param APIWrapper|null $MadelineProto MadelineProto instance
*/ */
public function __construct(?API $MadelineProto) public function __construct(?APIWrapper $MadelineProto)
{ {
if (!$MadelineProto) { if (!$MadelineProto) {
return; return;
} }
$this->API = $MadelineProto->API; self::link($this, $MadelineProto->getFactory());
$this->async =& $MadelineProto->async; $this->API =& $MadelineProto->getAPI();
$this->methods =& $MadelineProto->methods;
foreach ($this->API->getMethodNamespaces() as $namespace) { foreach ($this->API->getMethodNamespaces() as $namespace) {
$this->{$namespace} = new APIFactory($namespace, $this->API, $this->async); $this->{$namespace} = $this->exportNamespace($namespace);
} }
} }
/** /**

View File

@ -4134,6 +4134,18 @@ class InternalDoc extends APIFactory
{ {
return \danog\MadelineProto\MTProto::getSettings($settings, $previousSettings); return \danog\MadelineProto\MTProto::getSettings($settings, $previousSettings);
} }
/**
* Parse, update and store settings.
*
* @param array $settings Settings
* @param bool $reinit Whether to reinit the instance
*
* @return void
*/
public function updateSettings(array $settings, bool $reinit = true, array $extra = [])
{
return $this->__call(__FUNCTION__, [$settings, $reinit, $extra]);
}
/** /**
* Setup logger. * Setup logger.
* *
@ -4207,14 +4219,23 @@ class InternalDoc extends APIFactory
return $this->__call(__FUNCTION__, [$config, $options, $extra]); return $this->__call(__FUNCTION__, [$config, $options, $extra]);
} }
/** /**
* Get info about the logged-in user. * Get info about the logged-in user, cached.
* *
* @return \Generator<array> * @return array|bool
*/ */
public function getSelf(array $extra = []) public function getSelf(array $extra = [])
{ {
return $this->__call(__FUNCTION__, [$extra]); return $this->__call(__FUNCTION__, [$extra]);
} }
/**
* Get info about the logged-in user, not cached.
*
* @return \Generator<array|bool>
*/
public function fullGetSelf(array $extra = [])
{
return $this->__call(__FUNCTION__, [$extra]);
}
/** /**
* Check if has report peers. * Check if has report peers.
* *
@ -4246,6 +4267,15 @@ class InternalDoc extends APIFactory
{ {
return $this->__call(__FUNCTION__, [$message, $extra]); return $this->__call(__FUNCTION__, [$message, $extra]);
} }
/**
* Get full list of MTProto and API methods.
*
* @return array
*/
public function getAllMethods(): array
{
return $this->API->getAllMethods();
}
/** /**
* Call method and wait asynchronously for response. * Call method and wait asynchronously for response.
* *
@ -5250,24 +5280,24 @@ class InternalDoc extends APIFactory
/** /**
* Convert to camelCase. * Convert to camelCase.
* *
* @param string $input * @param string $input String
* *
* @return string * @return string
*/ */
public function fromSnakeCase(string $input): string public function toCamelCase(string $input): string
{ {
return \danog\MadelineProto\MTProto::fromSnakeCase($input); return \danog\MadelineProto\MTProto::toCamelCase($input);
} }
/** /**
* Convert to snake_case. * Convert to snake_case.
* *
* @param string $input * @param string $input String
* *
* @return string * @return string
*/ */
public function fromCamelCase(string $input): string public function toSnakeCase(string $input): string
{ {
return \danog\MadelineProto\MTProto::fromCamelCase($input); return \danog\MadelineProto\MTProto::toSnakeCase($input);
} }
/** /**
* Create array. * Create array.

View File

@ -29,18 +29,33 @@ use danog\MadelineProto\MTProto;
*/ */
class PeriodicLoop extends ResumableSignalLoop class PeriodicLoop extends ResumableSignalLoop
{ {
/**
* Callback
*
* @var callable
*/
private $callback; private $callback;
private $name; /**
* Loop name
*
* @var string
*/
private string $name;
/**
* Loop timeeout
*
* @var int
*/
private $timeout; private $timeout;
/** /**
* Constructor. * Constructor.
* *
* @param \danog\MadelineProto\MTProto $API Instance of MTProto class * @param \danog\MadelineProto\API $API Instance of MTProto class
* @param callable $callback Callback to call * @param callable $callback Callback to call
* @param string $name Loop name * @param string $name Loop name
* @param int $timeout Loop timeout * @param int|float $timeout Loop timeout
*/ */
public function __construct(MTProto $API, $callback, string $name, $timeout) public function __construct($API, callable $callback, string $name, $timeout)
{ {
$this->API = $API; $this->API = $API;
$this->callback = $callback; $this->callback = $callback;

View File

@ -54,7 +54,6 @@ abstract class Loop implements LoopInterface
} }
private function loopImpl(): \Generator private function loopImpl(): \Generator
{ {
//yield ['my_trace' => debug_backtrace(0, 1)[0], (string) $this];
$this->startedLoop(); $this->startedLoop();
$this->API->logger->logger("Entered {$this}", Logger::ULTRA_VERBOSE); $this->API->logger->logger("Entered {$this}", Logger::ULTRA_VERBOSE);
try { try {
@ -62,7 +61,6 @@ abstract class Loop implements LoopInterface
} finally { } finally {
$this->exitedLoop(); $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() public function exitedLoop()

View File

@ -36,7 +36,7 @@ abstract class ResumableSignalLoop extends SignalLoop implements ResumableLoopIn
use Tools; use Tools;
private $resume; private $resume;
private $pause; private $pause;
private $resumeWatcher; protected $resumeWatcher;
public function pause($time = null): Promise public function pause($time = null): Promise
{ {
if (!\is_null($time)) { if (!\is_null($time)) {
@ -80,4 +80,13 @@ abstract class ResumableSignalLoop extends SignalLoop implements ResumableLoopIn
Loop::defer([$this, 'resume']); Loop::defer([$this, 'resume']);
return $this->pause ? $this->pause->promise() : null; return $this->pause ? $this->pause->promise() : null;
} }
public function exitedLoop()
{
parent::exitedLoop();
if ($this->resumeWatcher) {
Loop::cancel($this->resumeWatcher);
$this->resumeWatcher = null;
}
}
} }

View File

@ -22,6 +22,7 @@ namespace danog\MadelineProto;
use Amp\Dns\Resolver; use Amp\Dns\Resolver;
use Amp\Http\Client\DelegateHttpClient; use Amp\Http\Client\DelegateHttpClient;
use Amp\Loop; use Amp\Loop;
use Amp\Success;
use danog\MadelineProto\Async\AsyncConstruct; use danog\MadelineProto\Async\AsyncConstruct;
use danog\MadelineProto\Loop\Generic\PeriodicLoop; use danog\MadelineProto\Loop\Generic\PeriodicLoop;
use danog\MadelineProto\Loop\Update\FeedLoop; use danog\MadelineProto\Loop\Update\FeedLoop;
@ -204,7 +205,7 @@ class MTProto extends AsyncConstruct implements TLCallback
/** /**
* Instance of wrapper API. * Instance of wrapper API.
* *
* @var API|null * @var ?APIWrapper
*/ */
public $wrapper; public $wrapper;
/** /**
@ -321,18 +322,6 @@ class MTProto extends AsyncConstruct implements TLCallback
* @var array * @var array
*/ */
private $dialog_params = ['limit' => 0, 'offset_date' => 0, 'offset_id' => 0, 'offset_peer' => ['_' => 'inputPeerEmpty'], 'count' => 0]; private $dialog_params = ['limit' => 0, 'offset_date' => 0, 'offset_id' => 0, 'offset_peer' => ['_' => 'inputPeerEmpty'], 'count' => 0];
/**
* Whether new settings were set and should be applied.
*
* @var boolean
*/
public $flushSettings = false;
/**
* Storage for arbitrary data.
*
* @var array
*/
public $storage = [];
/** /**
* Support user ID. * Support user ID.
* *
@ -439,7 +428,7 @@ class MTProto extends AsyncConstruct implements TLCallback
{ {
Magic::classExists(); Magic::classExists();
// Parse and store settings // Parse and store settings
$this->parseSettings($settings); yield from $this->updateSettings($settings, false);
$this->logger->logger(Lang::$current_lang['inst_dc'], Logger::ULTRA_VERBOSE); $this->logger->logger(Lang::$current_lang['inst_dc'], Logger::ULTRA_VERBOSE);
$this->cleanupProperties(); $this->cleanupProperties();
// Load rsa keys // Load rsa keys
@ -544,9 +533,6 @@ class MTProto extends AsyncConstruct implements TLCallback
'temp_requested_secret_chats', 'temp_requested_secret_chats',
'temp_rekeyed_secret_chats', 'temp_rekeyed_secret_chats',
// Object storage
'storage',
// Report URI // Report URI
'reportDest' 'reportDest'
]; ];
@ -659,13 +645,14 @@ class MTProto extends AsyncConstruct implements TLCallback
/** /**
* Prompt serialization of instance. * Prompt serialization of instance.
* *
* @internal
*
* @return void * @return void
*/ */
public function serialize() public function serialize()
{ {
if ($this->wrapper instanceof API && isset($this->wrapper->session) && !\is_null($this->wrapper->session) && !$this->asyncInitPromise) { if ($this->wrapper && $this->inited()) {
//$this->logger->logger("Didn't serialize in a while, doing that now..."); $this->wrapper->serialize();
$this->wrapper->serialize($this->wrapper->session);
} }
} }
/** /**
@ -852,8 +839,9 @@ class MTProto extends AsyncConstruct implements TLCallback
*/ */
public function __wakeup() public function __wakeup()
{ {
$backtrace = \debug_backtrace(0, 3); $backtrace = \debug_backtrace(0, 4);
$this->asyncInitPromise = true; $backtrace = end($backtrace);
$this->forceInit(false);
$this->setInitPromise($this->__wakeup_async($backtrace)); $this->setInitPromise($this->__wakeup_async($backtrace));
} }
/** /**
@ -884,9 +872,9 @@ class MTProto extends AsyncConstruct implements TLCallback
} }
$force = false; $force = false;
$this->resetMTProtoSession(); $this->resetMTProtoSession();
if (isset($backtrace[2]['function'], $backtrace[2]['class'], $backtrace[2]['args']) && $backtrace[2]['class'] === 'danog\\MadelineProto\\API' && $backtrace[2]['function'] === '__construct_async') { if (isset($backtrace['function'], $backtrace['class'], $backtrace['args']) && $backtrace['class'] === 'danog\\MadelineProto\\API' && $backtrace['function'] === '__construct_async') {
if (\count($backtrace[2]['args']) >= 2) { if (\count($backtrace['args']) >= 2) {
$this->parseSettings(\array_replace_recursive($this->settings, $backtrace[2]['args'][1])); yield from $this->updateSettings($backtrace['args'][1], false);
} }
} }
if (($this->settings['tl_schema']['src']['botAPI'] ?? '') !== __DIR__.'/TL_botAPI.tl') { if (($this->settings['tl_schema']['src']['botAPI'] ?? '') !== __DIR__.'/TL_botAPI.tl') {
@ -922,7 +910,7 @@ class MTProto extends AsyncConstruct implements TLCallback
} }
} }
$this->startLoops(); $this->startLoops();
if (yield from $this->getSelf()) { if (yield from $this->fullGetSelf()) {
$this->authorized = self::LOGGED_IN; $this->authorized = self::LOGGED_IN;
} }
if ($this->authorized === self::LOGGED_IN) { if ($this->authorized === self::LOGGED_IN) {
@ -940,9 +928,13 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->updaters[false]->start(); $this->updaters[false]->start();
} }
/** /**
* Destructor. * Unreference instance, allowing destruction.
*
* @internal
*
* @return void
*/ */
public function __destruct() public function unreference()
{ {
$this->stopLoops(); $this->stopLoops();
if (isset($this->seqUpdater)) { if (isset($this->seqUpdater)) {
@ -957,13 +949,21 @@ class MTProto extends AsyncConstruct implements TLCallback
if (isset($this->feeders[$channelId])) { if (isset($this->feeders[$channelId])) {
$this->feeders[$channelId]->signal(true); $this->feeders[$channelId]->signal(true);
} }
if (!isset($this->updaters[$channelId])) { if (isset($this->updaters[$channelId])) {
$this->updaters[$channelId]->signal(true); $this->updaters[$channelId]->signal(true);
} }
} }
foreach ($this->datacenter->getDataCenterConnections() as $datacenter) { foreach ($this->datacenter->getDataCenterConnections() as $datacenter) {
$datacenter->disconnect(); $datacenter->disconnect();
} }
}
/**
* Destructor.
*/
public function __destruct()
{
$this->logger('Shutting down MadelineProto (MTProto)');
$this->unreference();
$this->logger("Successfully destroyed MadelineProto"); $this->logger("Successfully destroyed MadelineProto");
} }
/** /**
@ -1284,13 +1284,14 @@ class MTProto extends AsyncConstruct implements TLCallback
return $settings; return $settings;
} }
/** /**
* Parse and store settings. * Parse, update and store settings.
* *
* @param array $settings Settings * @param array $settings Settings
* @param bool $reinit Whether to reinit the instance
* *
* @return void * @return void
*/ */
private function parseSettings(array $settings): void public function updateSettings(array $settings, bool $reinit = true): \Generator
{ {
$settings = self::getSettings($settings, $this->settings); $settings = self::getSettings($settings, $this->settings);
if ($settings['app_info'] === null) { if ($settings['app_info'] === null) {
@ -1302,6 +1303,11 @@ class MTProto extends AsyncConstruct implements TLCallback
} }
// Setup logger // Setup logger
$this->setupLogger(); $this->setupLogger();
if ($reinit) {
$this->__construct();
yield from $this->initAsynchronously();
}
} }
/** /**
* Setup logger. * Setup logger.
@ -1439,7 +1445,7 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->feeders[$channelId]->signal(true); $this->feeders[$channelId]->signal(true);
unset($this->feeders[$channelId]); unset($this->feeders[$channelId]);
} }
if (!isset($this->updaters[$channelId])) { if (isset($this->updaters[$channelId])) {
$this->updaters[$channelId]->signal(true); $this->updaters[$channelId]->signal(true);
unset($this->updaters[$channelId]); unset($this->updaters[$channelId]);
} }
@ -1487,7 +1493,7 @@ class MTProto extends AsyncConstruct implements TLCallback
if (isset($this->feeders[$channelId])) { if (isset($this->feeders[$channelId])) {
$this->feeders[$channelId]->signal(true); $this->feeders[$channelId]->signal(true);
} }
if (!isset($this->updaters[$channelId])) { if (isset($this->updaters[$channelId])) {
$this->updaters[$channelId]->signal(true); $this->updaters[$channelId]->signal(true);
} }
} }
@ -1505,7 +1511,7 @@ class MTProto extends AsyncConstruct implements TLCallback
*/ */
public function startUpdateSystem($anyway = false): void public function startUpdateSystem($anyway = false): void
{ {
if ($this->asyncInitPromise && !$anyway) { if (!$this->inited() && !$anyway) {
$this->logger("Not starting update system"); $this->logger("Not starting update system");
return; return;
} }
@ -1652,11 +1658,20 @@ class MTProto extends AsyncConstruct implements TLCallback
$this->datacenter->curdc = $curdc; $this->datacenter->curdc = $curdc;
} }
/** /**
* Get info about the logged-in user. * Get info about the logged-in user, cached.
* *
* @return \Generator<array> * @return array|bool
*/ */
public function getSelf(): \Generator public function getSelf()
{
return $this->authorization['user'] ?? false;
}
/**
* Get info about the logged-in user, not cached.
*
* @return \Generator<array|bool>
*/
public function fullGetSelf(): \Generator
{ {
try { try {
$this->authorization = ['user' => (yield from $this->methodCallAsyncRead('users.getUsers', ['id' => [['_' => 'inputUserSelf']]], ['datacenter' => $this->datacenter->curdc]))[0]]; $this->authorization = ['user' => (yield from $this->methodCallAsyncRead('users.getUsers', ['id' => [['_' => 'inputUserSelf']]], ['datacenter' => $this->datacenter->curdc]))[0]];
@ -1718,6 +1733,19 @@ class MTProto extends AsyncConstruct implements TLCallback
} }
} }
} }
/**
* Get full list of MTProto and API methods.
*
* @return array
*/
public function getAllMethods(): array
{
$methods = [];
foreach ($this->getTL()->getMethods()->by_id as $method) {
$methods[] = $method['method'];
}
return \array_merge($methods, \get_class_methods(InternalDoc::class));
}
/** /**
* Called right before serialization of method starts. * Called right before serialization of method starts.
* *

View File

@ -22,8 +22,8 @@ namespace danog\MadelineProto\MTProtoTools;
use Amp\ByteStream\InputStream; use Amp\ByteStream\InputStream;
use Amp\ByteStream\IteratorStream; use Amp\ByteStream\IteratorStream;
use Amp\ByteStream\OutputStream; use Amp\ByteStream\OutputStream;
use Amp\ByteStream\ResourceOutputStream;
use Amp\ByteStream\ResourceInputStream; use Amp\ByteStream\ResourceInputStream;
use Amp\ByteStream\ResourceOutputStream;
use Amp\ByteStream\StreamException; use Amp\ByteStream\StreamException;
use Amp\Deferred; use Amp\Deferred;
use Amp\File\BlockingFile; use Amp\File\BlockingFile;

View File

@ -19,14 +19,109 @@
namespace danog\MadelineProto; namespace danog\MadelineProto;
use function Amp\File\exists;
use function Amp\File\get;
/** /**
* Manages serialization of the MadelineProto instance. * Manages serialization of the MadelineProto instance.
*/ */
class Serialization class Serialization
{ {
public static function realpaths($file) /**
* Extract path components for serialization.
*
* @param string $file Session path
*
* @return array
*/
public static function realpaths(string $file): array
{ {
$file = Absolute::absolute($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'];
} }
/**
* Unserialize legacy session.
*
* @param string $session Session name
*
* @internal
*
* @return \Generator
*/
public static function legacyUnserialize(string $session): \Generator
{
$realpaths = self::realpaths($session);
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);
}
$unserialized = \danog\Serialization::unserialize($tounserialize);
} catch (\danog\MadelineProto\Exception $e) {
if ($e->getFile() === 'MadelineProto' && $e->getLine() === 1) {
throw $e;
}
if (\MADELINEPROTO_TEST === 'pony') {
throw $e;
}
\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);
}
$changed = false;
if (\strpos($tounserialize, 'O:26:"danog\\MadelineProto\\Button":') !== false) {
Logger::log("SUBBING BUTTONS!");
$tounserialize = \str_replace('O:26:"danog\\MadelineProto\\Button":', 'O:35:"danog\\MadelineProto\\TL\\Types\\Button":', $tounserialize);
$changed = true;
}
if (\strpos($e->getMessage(), "Erroneous data format for unserializing 'phpseclib\\Math\\BigInteger'") === 0) {
Logger::log("SUBBING BIGINTEGOR!");
$tounserialize = \str_replace('phpseclib\\Math\\BigInteger', 'phpseclib\\Math\\BigIntegor', $tounserialize);
$changed = true;
}
if (\strpos($tounserialize, 'C:25:"phpseclib\\Math\\BigInteger"') !== false) {
Logger::log("SUBBING TGSECLIB old!");
$tounserialize = \str_replace('C:25:"phpseclib\\Math\\BigInteger"', 'C:24:"tgseclib\\Math\\BigInteger"', $tounserialize);
$changed = true;
}
if (\strpos($tounserialize, 'C:26:"phpseclib3\\Math\\BigInteger"') !== false) {
Logger::log("SUBBING TGSECLIB!");
$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) {
$unserialized = \unserialize($tounserialize);
}
} catch (\Throwable $e) {
Logger::log((string) $e, Logger::ERROR);
throw $e;
}
if ($unserialized instanceof \danog\PlaceHolder) {
$unserialized = \danog\Serialization::unserialize($tounserialize);
}
if ($unserialized === false) {
throw new Exception(\danog\MadelineProto\Lang::$current_lang['deserialization_error']);
}
return $unserialized;
}
}
} }

View File

@ -540,6 +540,9 @@ trait BotAPI
if (($arguments['message'] ?? '') === '' || !isset($arguments['parse_mode'])) { if (($arguments['message'] ?? '') === '' || !isset($arguments['parse_mode'])) {
return $arguments; return $arguments;
} }
if (!(\is_string($arguments['message']) || \is_object($arguments['message']) && \method_exists($arguments['message'], '__toString'))) {
throw new Exception('Messages can only be strings');
}
if (isset($arguments['parse_mode']['_'])) { if (isset($arguments['parse_mode']['_'])) {
$arguments['parse_mode'] = \str_replace('textParseMode', '', $arguments['parse_mode']['_']); $arguments['parse_mode'] = \str_replace('textParseMode', '', $arguments['parse_mode']['_']);
} }

View File

@ -30,6 +30,7 @@ use function Amp\ByteStream\getOutputBufferStream;
use function Amp\ByteStream\getStdin; use function Amp\ByteStream\getStdin;
use function Amp\ByteStream\getStdout; use function Amp\ByteStream\getStdout;
use function Amp\File\exists; use function Amp\File\exists;
use function Amp\File\get;
use function Amp\Promise\all; use function Amp\Promise\all;
use function Amp\Promise\any; use function Amp\Promise\any;
use function Amp\Promise\first; use function Amp\Promise\first;
@ -304,6 +305,7 @@ trait Tools
}); });
} catch (\Throwable $throwable) { } catch (\Throwable $throwable) {
Logger::log('Loop exceptionally stopped without resolving the promise', Logger::FATAL_ERROR); Logger::log('Loop exceptionally stopped without resolving the promise', Logger::FATAL_ERROR);
Logger::log((string) $throwable, Logger::FATAL_ERROR);
throw $throwable; throw $throwable;
} }
} while (!$resolved && !(Magic::$signaled && !$ignoreSignal)); } while (!$resolved && !(Magic::$signaled && !$ignoreSignal));
@ -411,15 +413,6 @@ trait Tools
{ {
if ($actual) { if ($actual) {
$promise = $actual; $promise = $actual;
} else {
$trace = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
$file = '';
if (isset($trace['file'])) {
$file .= \basename($trace['file'], '.php');
}
if (isset($trace['line'])) {
$file .= ":{$trace['line']}";
}
} }
if ($promise instanceof \Generator) { if ($promise instanceof \Generator) {
$promise = new Coroutine($promise); $promise = new Coroutine($promise);
@ -632,22 +625,22 @@ trait Tools
/** /**
* Convert to camelCase. * Convert to camelCase.
* *
* @param string $input * @param string $input String
* *
* @return string * @return string
*/ */
public static function fromSnakeCase(string $input): string public static function toCamelCase(string $input): string
{ {
return \lcfirst(\str_replace('_', '', \ucwords($input, '_'))); return \lcfirst(\str_replace('_', '', \ucwords($input, '_')));
} }
/** /**
* Convert to snake_case. * Convert to snake_case.
* *
* @param string $input * @param string $input String
* *
* @return string * @return string
*/ */
public static function fromCamelCase(string $input): string public static function toSnakeCase(string $input): string
{ {
\preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches); \preg_match_all('!([A-Z][A-Z0-9]*(?=$|[A-Z][a-z0-9])|[A-Za-z][a-z0-9]+)!', $input, $matches);
$ret = $matches[0]; $ret = $matches[0];
@ -831,11 +824,34 @@ trait Tools
* @return mixed * @return mixed
* @access public * @access public
*/ */
public static function getVar($obj, string $var) public static function &getVar($obj, string $var)
{ {
$reflection = new \ReflectionClass(\get_class($obj)); return \Closure::bind(
$prop = $reflection->getProperty($var); function & () use ($var) {
$prop->setAccessible(true); return $this->{$var};
return $prop->getValue($obj); },
$obj,
\get_class($obj)
)->__invoke();
}
/**
* Sets a private variable in an object.
*
* @param object $obj Object
* @param string $var Attribute name
* @param mixed $val Attribute value
*
* @return mixed
* @access public
*/
public static function setVar($obj, string $var, &$val): void
{
\Closure::bind(
function () use ($var, &$val) {
$this->{$var} =& $val;
},
$obj,
\get_class($obj)
)->__invoke();
} }
} }

View File

@ -82,7 +82,7 @@ trait Events
$this->settings['updates']['callback'] = [$this, 'eventUpdateHandler']; $this->settings['updates']['callback'] = [$this, 'eventUpdateHandler'];
$this->settings['updates']['handle_updates'] = true; $this->settings['updates']['handle_updates'] = true;
$this->settings['updates']['run_callback'] = true; $this->settings['updates']['run_callback'] = true;
if (!$this->asyncInitPromise) { if ($this->inited()) {
$this->startUpdateSystem(); $this->startUpdateSystem();
} }
} }

View File

@ -170,7 +170,7 @@ trait Login
$this->authorized = self::LOGGED_IN; $this->authorized = self::LOGGED_IN;
yield from $this->initAuthorization(); yield from $this->initAuthorization();
yield from $this->getPhoneConfig(); yield from $this->getPhoneConfig();
$res = (yield from $this->getSelf()); $res = (yield from $this->fullGetSelf());
$callbacks = [$this, $this->referenceDatabase]; $callbacks = [$this, $this->referenceDatabase];
if (!($this->authorization['user']['bot'] ?? false)) { if (!($this->authorization['user']['bot'] ?? false)) {
$callbacks[] = $this->minDatabase; $callbacks[] = $this->minDatabase;
@ -189,7 +189,7 @@ trait Login
if ($this->authorized !== self::LOGGED_IN) { if ($this->authorized !== self::LOGGED_IN) {
throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['not_loggedIn']); throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['not_loggedIn']);
} }
yield from $this->getSelf(); yield from $this->fullGetSelf();
$this->authorized_dc = $this->datacenter->curdc; $this->authorized_dc = $this->datacenter->curdc;
return [$this->datacenter->curdc, $this->datacenter->getDataCenterConnection($this->datacenter->curdc)->getPermAuthKey()->getAuthKey()]; return [$this->datacenter->curdc, $this->datacenter->getDataCenterConnection($this->datacenter->curdc)->getPermAuthKey()->getAuthKey()];
} }

View File

@ -34,7 +34,7 @@ trait Start
public function start(): \Generator public function start(): \Generator
{ {
if ($this->authorized === self::LOGGED_IN) { if ($this->authorized === self::LOGGED_IN) {
return yield from $this->getSelf(); return yield from $this->fullGetSelf();
} }
if (PHP_SAPI === 'cli') { if (PHP_SAPI === 'cli') {
if (\strpos(yield Tools::readLine('Do you want to login as user or bot (u/b)? '), 'b') !== false) { if (\strpos(yield Tools::readLine('Do you want to login as user or bot (u/b)? '), 'b') !== false) {
@ -50,7 +50,7 @@ trait Start
} }
} }
$this->serialize(); $this->serialize();
return yield from $this->getSelf(); return yield from $this->fullGetSelf();
} }
if ($this->authorized === self::NOT_LOGGED_IN) { if ($this->authorized === self::NOT_LOGGED_IN) {
if (isset($_POST['phone_number'])) { if (isset($_POST['phone_number'])) {
@ -81,7 +81,7 @@ trait Start
} }
if ($this->authorized === self::LOGGED_IN) { if ($this->authorized === self::LOGGED_IN) {
$this->serialize(); $this->serialize();
return yield from $this->getSelf(); return yield from $this->fullGetSelf();
} }
exit; exit;
} }