Refactor and clean up serialization and APIFactory APIs, automatically unreference instance on destruction to clean up memory
This commit is contained in:
parent
82599e686e
commit
b5267beaa2
@ -19,13 +19,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;
|
||||
use function Amp\File\rename as renameAsync;
|
||||
|
||||
if (!\defined('MADELINEPROTO_TEST')) {
|
||||
\define('MADELINEPROTO_TEST', 'NOT PONY');
|
||||
}
|
||||
@ -36,16 +29,31 @@ if (!\defined('MADELINEPROTO_TEST')) {
|
||||
class API extends InternalDoc
|
||||
{
|
||||
use \danog\Serializable;
|
||||
use \danog\MadelineProto\Wrappers\ApiStart;
|
||||
use \danog\MadelineProto\Wrappers\ApiTemplates;
|
||||
public $session;
|
||||
public $serialized = 0;
|
||||
use \danog\MadelineProto\ApiWrappers\Start;
|
||||
use \danog\MadelineProto\ApiWrappers\Templates;
|
||||
/**
|
||||
* Session path.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public string $session = '';
|
||||
|
||||
/**
|
||||
* Instance of MadelineProto.
|
||||
*
|
||||
* @var MTProto
|
||||
* @var ?MTProto
|
||||
*/
|
||||
public $API;
|
||||
|
||||
/**
|
||||
* Storage for externally set properties to be serialized.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected array $storage = [];
|
||||
|
||||
/**
|
||||
* Whether we're getting our API ID.
|
||||
*
|
||||
@ -53,172 +61,107 @@ class API extends InternalDoc
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $getting_api_id = false;
|
||||
private bool $gettingApiId = false;
|
||||
|
||||
/**
|
||||
* my.telegram.org API wrapper.
|
||||
*
|
||||
* @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 $oldInstance = false;
|
||||
private $destructing = false;
|
||||
private bool $oldInstance = false;
|
||||
/**
|
||||
* Whether we're destructing.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private bool $destructing = false;
|
||||
|
||||
|
||||
/**
|
||||
* API wrapper (to avoid circular references).
|
||||
*
|
||||
* @var APIWrapper
|
||||
*/
|
||||
private $wrapper;
|
||||
|
||||
|
||||
/**
|
||||
* Magic constructor function.
|
||||
*
|
||||
* @param array $params Params
|
||||
* @param array $settings Settings
|
||||
* @param string $session Session name
|
||||
* @param array $settings Settings
|
||||
*
|
||||
* @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();
|
||||
$deferred = new Deferred();
|
||||
$this->asyncAPIPromise = $deferred->promise();
|
||||
$this->asyncAPIPromise->onResolve(function () {
|
||||
$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'])) {
|
||||
$this->setInitPromise($this->__construct_async($session, $settings));
|
||||
foreach (\get_class_vars(APIFactory::class) as $key => $var) {
|
||||
if (\in_array($key, ['namespace', 'API', 'lua', 'async', 'asyncAPIPromise', 'methods'])) {
|
||||
continue;
|
||||
}
|
||||
if (\is_null($this->{$key})) {
|
||||
$this->{$key} = new APIFactory($key, $this->API, $this->async);
|
||||
if (!$this->{$key}) {
|
||||
$this->{$key} = $this->exportNamespace($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Async constructor function.
|
||||
*
|
||||
* @param mixed $params Params
|
||||
* @param mixed $settings Settings
|
||||
* @param mixed $deferred Deferred
|
||||
* @param string $session Session name
|
||||
* @param array $settings Settings
|
||||
*
|
||||
* @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);
|
||||
if (!isset($params['app_info']['api_id']) || !$params['app_info']['api_id']) {
|
||||
$app = (yield from $this->APIStart($params));
|
||||
$params['app_info']['api_id'] = $app['api_id'];
|
||||
$params['app_info']['api_hash'] = $app['api_hash'];
|
||||
$session = Absolute::absolute($session);
|
||||
if ($unserialized = yield from Serialization::legacyUnserialize($session)) {
|
||||
$unserialized->storage = $unserialized->storage ?? [];
|
||||
$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();
|
||||
$deferred->resolve();
|
||||
Logger::log(\danog\MadelineProto\Lang::$current_lang['apifactory_start'], Logger::VERBOSE);
|
||||
|
||||
if (!isset($settings['app_info']['api_id']) || !$settings['app_info']['api_id']) {
|
||||
$app = (yield from $this->APIStart($settings));
|
||||
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();
|
||||
$this->APIFactory();
|
||||
$this->asyncInitPromise = null;
|
||||
//\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);
|
||||
$this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable async.
|
||||
*
|
||||
@ -228,9 +171,9 @@ class API extends InternalDoc
|
||||
*/
|
||||
public function async(bool $async): void
|
||||
{
|
||||
$this->async = $async;
|
||||
parent::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, EventHandler::class)) {
|
||||
$this->API->setEventHandler($this->API->event_handler);
|
||||
}
|
||||
}
|
||||
@ -242,23 +185,27 @@ class API extends InternalDoc
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
if (\danog\MadelineProto\Magic::$has_thread && \is_object(\Thread::getCurrentThread()) || Magic::isFork()) {
|
||||
return;
|
||||
}
|
||||
if ($this->asyncInitPromise) {
|
||||
$this->init();
|
||||
}
|
||||
$this->init();
|
||||
if (!$this->oldInstance) {
|
||||
$this->logger('Shutting down MadelineProto (API)');
|
||||
if ($this->API) {
|
||||
$this->API->logger('Shutting down MadelineProto (normally or due to an exception, idk)');
|
||||
$this->API->destructing = true;
|
||||
} else {
|
||||
Logger::log('Shutting down MadelineProto (normally or due to an exception, idk)');
|
||||
}
|
||||
$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.
|
||||
@ -269,18 +216,7 @@ class API extends InternalDoc
|
||||
*/
|
||||
public function __sleep(): array
|
||||
{
|
||||
return ['API', 'web_api_template', 'getting_api_id', 'my_telegram_org_wrapper'];
|
||||
}
|
||||
/**
|
||||
* Custom fast getSelf.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
public function myGetSelf()
|
||||
{
|
||||
return isset($this->API) && isset($this->API->authorization['user']) ? $this->API->authorization['user'] : false;
|
||||
return APIWrapper::__sleep();
|
||||
}
|
||||
/**
|
||||
* Init API wrapper.
|
||||
@ -289,122 +225,20 @@ class API extends InternalDoc
|
||||
*/
|
||||
private function APIFactory(): void
|
||||
{
|
||||
if ($this->API && !$this->API->asyncInitPromise) {
|
||||
if ($this->API && $this->API->inited()) {
|
||||
foreach ($this->API->getMethodNamespaces() as $namespace) {
|
||||
$this->{$namespace} = new APIFactory($namespace, $this->API, $this->async);
|
||||
}
|
||||
$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)]);
|
||||
}
|
||||
if (!$this->{$namespace}) {
|
||||
$this->{$namespace} = $this->exportNamespace($namespace);
|
||||
}
|
||||
}
|
||||
$this->methods = [];
|
||||
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);
|
||||
}
|
||||
}
|
||||
$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->methods = self::getInternalMethodList($this->API);
|
||||
$this->API->wrapper = $this->wrapper;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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).
|
||||
@ -428,10 +262,10 @@ class API extends InternalDoc
|
||||
$this->loop();
|
||||
} catch (\Throwable $e) {
|
||||
$thrown = true;
|
||||
$this->logger((string) $e, Logger::FATAL_ERROR);
|
||||
try {
|
||||
$this->report("Surfaced: $e");
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger((string) $e, Logger::FATAL_ERROR);
|
||||
}
|
||||
}
|
||||
} while ($thrown);
|
||||
|
188
src/danog/MadelineProto/APIWrapper.php
Normal file
188
src/danog/MadelineProto/APIWrapper.php
Normal 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;
|
||||
})());
|
||||
}
|
||||
}
|
@ -30,7 +30,7 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $namespace = '';
|
||||
private string $namespace = '';
|
||||
/**
|
||||
* MTProto instance.
|
||||
*
|
||||
@ -46,7 +46,7 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $lua = false;
|
||||
public bool $lua = false;
|
||||
/**
|
||||
* Whether async is enabled.
|
||||
*
|
||||
@ -54,24 +54,43 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $async = false;
|
||||
/**
|
||||
* Async init promise.
|
||||
*
|
||||
* @var Promise
|
||||
*/
|
||||
public $asyncAPIPromise;
|
||||
private bool $async = false;
|
||||
/**
|
||||
* Method list.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $methods = [];
|
||||
public function __construct($namespace, &$API, &$async)
|
||||
protected array $methods = [];
|
||||
/**
|
||||
* Export APIFactory instance with the specified namespace.
|
||||
*
|
||||
* @param string $namespace Namespace
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
protected function exportNamespace(string $namespace = ''): self
|
||||
{
|
||||
$this->namespace = $namespace.'.';
|
||||
$this->API =& $API;
|
||||
$this->async =& $async;
|
||||
$class = array_reverse(array_values(class_parents(static::class)))[3];
|
||||
$instance = new $class;
|
||||
$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.
|
||||
@ -101,7 +120,9 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
||||
if ($async) {
|
||||
return $yielded;
|
||||
}
|
||||
|
||||
$yielded = Tools::wait($yielded);
|
||||
|
||||
if (!$this->lua) {
|
||||
return $yielded;
|
||||
}
|
||||
@ -124,30 +145,8 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
||||
*/
|
||||
public function __call_async(string $name, array $arguments): \Generator
|
||||
{
|
||||
if ($this->asyncInitPromise) {
|
||||
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');
|
||||
}
|
||||
yield from $this->initAsynchronously();
|
||||
|
||||
$lower_name = \strtolower($name);
|
||||
if ($this->namespace !== '' || !isset($this->methods[$lower_name])) {
|
||||
$name = $this->namespace.$name;
|
||||
@ -160,6 +159,61 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
||||
$res = $this->methods[$lower_name](...$arguments);
|
||||
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.
|
||||
*
|
||||
@ -171,17 +225,13 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
||||
*/
|
||||
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') {
|
||||
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.
|
||||
@ -195,16 +245,7 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
||||
*/
|
||||
public function __set(string $name, $value)
|
||||
{
|
||||
if ($this->asyncAPIPromise) {
|
||||
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;
|
||||
return $this->storage[$name] = $value;
|
||||
}
|
||||
/**
|
||||
* Whether an attribute exists.
|
||||
@ -215,10 +256,7 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
||||
*/
|
||||
public function __isset(string $name): bool
|
||||
{
|
||||
if ($this->asyncAPIPromise) {
|
||||
Tools::wait($this->asyncAPIPromise);
|
||||
}
|
||||
return isset($this->API->storage[$name]);
|
||||
return isset($this->storage[$name]);
|
||||
}
|
||||
/**
|
||||
* Unset attribute.
|
||||
@ -229,9 +267,6 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
||||
*/
|
||||
public function __unset(string $name): void
|
||||
{
|
||||
if ($this->asyncAPIPromise) {
|
||||
Tools::wait($this->asyncAPIPromise);
|
||||
}
|
||||
unset($this->API->storage[$name]);
|
||||
unset($this->storage[$name]);
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +184,7 @@ class AnnotationsBuilder
|
||||
$name = \str_ireplace('async', '', $name);
|
||||
}
|
||||
}
|
||||
$name = Tools::fromSnakeCase($name);
|
||||
$name = Tools::toCamelCase($name);
|
||||
$name = \str_ireplace(['mtproto', 'api'], ['MTProto', 'API'], $name);
|
||||
$doc = 'public function ';
|
||||
$doc .= $name;
|
||||
|
@ -17,17 +17,25 @@
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto\Wrappers;
|
||||
namespace danog\MadelineProto\ApiWrappers;
|
||||
|
||||
use danog\MadelineProto\MyTelegramOrgWrapper;
|
||||
use danog\MadelineProto\Tools;
|
||||
use function Amp\ByteStream\getStdout;
|
||||
|
||||
/**
|
||||
* 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') {
|
||||
$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: ');
|
||||
return $app;
|
||||
}
|
||||
$this->my_telegram_org_wrapper = 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->my_telegram_org_wrapper->completeLogin(yield Tools::readLine('Enter the verification code you received in telegram: '));
|
||||
if (!(yield from $this->my_telegram_org_wrapper->hasApp())) {
|
||||
$this->myTelegramOrgWrapper = new \danog\MadelineProto\MyTelegramOrgWrapper($settings);
|
||||
yield from $this->myTelegramOrgWrapper->login(yield Tools::readLine('Enter a phone number that is already registered on Telegram: '));
|
||||
yield from $this->myTelegramOrgWrapper->completeLogin(yield Tools::readLine('Enter the verification code you received in telegram: '));
|
||||
if (!(yield from $this->myTelegramOrgWrapper->hasApp())) {
|
||||
$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: ');
|
||||
$url = yield Tools::readLine('Enter the app/website\'s URL, or t.me/yourusername: ');
|
||||
$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 {
|
||||
$app = (yield from $this->my_telegram_org_wrapper->getApp());
|
||||
$app = (yield from $this->myTelegramOrgWrapper->getApp());
|
||||
}
|
||||
return $app;
|
||||
}
|
||||
$this->getting_api_id = true;
|
||||
if (!isset($this->my_telegram_org_wrapper)) {
|
||||
$this->gettingApiId = true;
|
||||
if (!isset($this->myTelegramOrgWrapper)) {
|
||||
if (isset($_POST['api_id']) && isset($_POST['api_hash'])) {
|
||||
$app['api_id'] = (int) $_POST['api_id'];
|
||||
$app['api_hash'] = $_POST['api_hash'];
|
||||
$this->getting_api_id = false;
|
||||
$this->gettingApiId = false;
|
||||
return $app;
|
||||
} elseif (isset($_POST['phone_number'])) {
|
||||
yield from $this->webAPIPhoneLogin($settings);
|
||||
} else {
|
||||
yield from $this->webAPIEcho();
|
||||
}
|
||||
} elseif (!$this->my_telegram_org_wrapper->loggedIn()) {
|
||||
} elseif (!$this->myTelegramOrgWrapper->loggedIn()) {
|
||||
if (isset($_POST['code'])) {
|
||||
yield from $this->webAPICompleteLogin();
|
||||
if (yield from $this->my_telegram_org_wrapper->hasApp()) {
|
||||
return yield from $this->my_telegram_org_wrapper->getApp();
|
||||
if (yield from $this->myTelegramOrgWrapper->hasApp()) {
|
||||
return yield from $this->myTelegramOrgWrapper->getApp();
|
||||
}
|
||||
yield from $this->webAPIEcho();
|
||||
} elseif (isset($_POST['api_id']) && isset($_POST['api_hash'])) {
|
||||
$app['api_id'] = (int) $_POST['api_id'];
|
||||
$app['api_hash'] = $_POST['api_hash'];
|
||||
$this->getting_api_id = false;
|
||||
$this->gettingApiId = false;
|
||||
return $app;
|
||||
} elseif (isset($_POST['phone_number'])) {
|
||||
yield from $this->webAPIPhoneLogin($settings);
|
||||
} else {
|
||||
$this->my_telegram_org_wrapper = null;
|
||||
$this->myTelegramOrgWrapper = null;
|
||||
yield from $this->webAPIEcho();
|
||||
}
|
||||
} else {
|
||||
if (isset($_POST['app_title'], $_POST['app_shortname'], $_POST['app_url'], $_POST['app_platform'], $_POST['app_desc'])) {
|
||||
$app = (yield from $this->webAPICreateApp());
|
||||
$this->getting_api_id = false;
|
||||
$this->gettingApiId = false;
|
||||
return $app;
|
||||
}
|
||||
yield from $this->webAPIEcho("You didn't provide all of the required parameters!");
|
||||
}
|
||||
$this->asyncInitPromise = null;
|
||||
exit;
|
||||
return null;
|
||||
}
|
||||
private function webAPIPhoneLogin($settings): \Generator
|
||||
private function webAPIPhoneLogin(array $settings): \Generator
|
||||
{
|
||||
try {
|
||||
$this->my_telegram_org_wrapper = new \danog\MadelineProto\MyTelegramOrgWrapper($settings);
|
||||
yield from $this->my_telegram_org_wrapper->login($_POST['phone_number']);
|
||||
$this->myTelegramOrgWrapper = new MyTelegramOrgWrapper($settings);
|
||||
yield from $this->myTelegramOrgWrapper->login($_POST['phone_number']);
|
||||
yield from $this->webAPIEcho();
|
||||
} catch (\Throwable $e) {
|
||||
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
|
||||
{
|
||||
try {
|
||||
yield from $this->my_telegram_org_wrapper->completeLogin($_POST['code']);
|
||||
yield from $this->myTelegramOrgWrapper->completeLogin($_POST['code']);
|
||||
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
||||
yield from $this->webAPIEcho('ERROR: ' . $e->getMessage() . '. Try again.');
|
||||
} 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 {
|
||||
$params = $_POST;
|
||||
unset($params['creating_app']);
|
||||
$app = (yield from $this->my_telegram_org_wrapper->createApp($params));
|
||||
$app = (yield from $this->myTelegramOrgWrapper->createApp($params));
|
||||
return $app;
|
||||
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
||||
yield from $this->webAPIEcho('ERROR: ' . $e->getMessage() . ' Try again.');
|
@ -17,29 +17,29 @@
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto\Wrappers;
|
||||
namespace danog\MadelineProto\ApiWrappers;
|
||||
|
||||
use function Amp\ByteStream\getOutputBufferStream;
|
||||
|
||||
trait ApiTemplates
|
||||
trait Templates
|
||||
{
|
||||
private $web_api_template = '<!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>';
|
||||
private function webAPIEchoTemplate($message, $form)
|
||||
/**
|
||||
* API template.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
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>';
|
||||
/**
|
||||
* Generate page from template.
|
||||
*
|
||||
* @param string $message Message
|
||||
* @param string $form Form
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
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.
|
||||
@ -48,24 +48,31 @@ trait ApiTemplates
|
||||
*/
|
||||
public function getWebAPITemplate(): string
|
||||
{
|
||||
return $this->web_template;
|
||||
return $this->webApiTemplate;
|
||||
}
|
||||
/**
|
||||
* Set web API login HTML template 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
|
||||
{
|
||||
$stdout = getOutputBufferStream();
|
||||
if (!isset($this->my_telegram_org_wrapper)) {
|
||||
if (!isset($this->myTelegramOrgWrapper)) {
|
||||
if (isset($_POST['type'])) {
|
||||
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>Go to API development tools</li>
|
||||
<li>
|
||||
@ -79,19 +86,19 @@ trait ApiTemplates
|
||||
<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/>'));
|
||||
} 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 {
|
||||
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 {
|
||||
if (!$this->my_telegram_org_wrapper->loggedIn()) {
|
||||
yield $stdout->write($this->webAPIEchoTemplate('Enter your code<br><b>' . $message . '</b>', '<input type="text" name="code" placeholder="Code" required/>'));
|
||||
if (!$this->myTelegramOrgWrapper->loggedIn()) {
|
||||
yield $stdout->write($this->webAPIEchoTemplate('Enter your code<br><b>'.$message.'</b>', '<input type="text" name="code" placeholder="Code" required/>'));
|
||||
} 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>
|
||||
<br>Enter the app'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>
|
@ -35,7 +35,7 @@ class AsyncConstruct
|
||||
*
|
||||
* @var Promise
|
||||
*/
|
||||
public $asyncInitPromise;
|
||||
private $asyncInitPromise;
|
||||
/**
|
||||
* Blockingly init.
|
||||
*
|
||||
@ -58,10 +58,30 @@ class AsyncConstruct
|
||||
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.
|
||||
*
|
||||
* @param Promise $promise Promise
|
||||
* @param Promise|\Generator $promise Promise
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
@ -69,12 +89,14 @@ class AsyncConstruct
|
||||
*/
|
||||
public function setInitPromise($promise): void
|
||||
{
|
||||
$this->asyncInitPromise = Tools::callFork($promise);
|
||||
$this->asyncInitPromise->onResolve(function ($error, $result) {
|
||||
if ($error) {
|
||||
throw $error;
|
||||
$this->asyncInitPromise = Tools::call($promise);
|
||||
$this->asyncInitPromise->onResolve(
|
||||
function (?\Throwable $error, $result): void {
|
||||
if ($error) {
|
||||
throw $error;
|
||||
}
|
||||
$this->asyncInitPromise = null;
|
||||
}
|
||||
$this->asyncInitPromise = null;
|
||||
});
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -27,18 +27,17 @@ class EventHandler extends InternalDoc
|
||||
/**
|
||||
* 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) {
|
||||
return;
|
||||
}
|
||||
$this->API = $MadelineProto->API;
|
||||
$this->async =& $MadelineProto->async;
|
||||
$this->methods =& $MadelineProto->methods;
|
||||
self::link($this, $MadelineProto->getFactory());
|
||||
$this->API =& $MadelineProto->getAPI();
|
||||
foreach ($this->API->getMethodNamespaces() as $namespace) {
|
||||
$this->{$namespace} = new APIFactory($namespace, $this->API, $this->async);
|
||||
$this->{$namespace} = $this->exportNamespace($namespace);
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
@ -4134,6 +4134,18 @@ class InternalDoc extends APIFactory
|
||||
{
|
||||
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.
|
||||
*
|
||||
@ -4207,14 +4219,23 @@ class InternalDoc extends APIFactory
|
||||
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 = [])
|
||||
{
|
||||
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.
|
||||
*
|
||||
@ -4246,6 +4267,15 @@ class InternalDoc extends APIFactory
|
||||
{
|
||||
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.
|
||||
*
|
||||
@ -5250,24 +5280,24 @@ class InternalDoc extends APIFactory
|
||||
/**
|
||||
* Convert to camelCase.
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $input 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.
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $input 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.
|
||||
|
@ -29,18 +29,33 @@ use danog\MadelineProto\MTProto;
|
||||
*/
|
||||
class PeriodicLoop extends ResumableSignalLoop
|
||||
{
|
||||
/**
|
||||
* Callback
|
||||
*
|
||||
* @var callable
|
||||
*/
|
||||
private $callback;
|
||||
private $name;
|
||||
/**
|
||||
* Loop name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private string $name;
|
||||
/**
|
||||
* Loop timeeout
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $timeout;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \danog\MadelineProto\MTProto $API Instance of MTProto class
|
||||
* @param callable $callback Callback to call
|
||||
* @param string $name Loop name
|
||||
* @param int $timeout Loop timeout
|
||||
* @param \danog\MadelineProto\API $API Instance of MTProto class
|
||||
* @param callable $callback Callback to call
|
||||
* @param string $name Loop name
|
||||
* @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->callback = $callback;
|
||||
|
@ -54,7 +54,6 @@ abstract class Loop implements LoopInterface
|
||||
}
|
||||
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);
|
||||
try {
|
||||
@ -62,7 +61,6 @@ abstract class Loop implements LoopInterface
|
||||
} finally {
|
||||
$this->exitedLoop();
|
||||
$this->API->logger->logger("Physically exited {$this}", Logger::ULTRA_VERBOSE);
|
||||
//return null;
|
||||
}
|
||||
}
|
||||
public function exitedLoop()
|
||||
|
@ -36,7 +36,7 @@ abstract class ResumableSignalLoop extends SignalLoop implements ResumableLoopIn
|
||||
use Tools;
|
||||
private $resume;
|
||||
private $pause;
|
||||
private $resumeWatcher;
|
||||
protected $resumeWatcher;
|
||||
public function pause($time = null): Promise
|
||||
{
|
||||
if (!\is_null($time)) {
|
||||
@ -80,4 +80,13 @@ abstract class ResumableSignalLoop extends SignalLoop implements ResumableLoopIn
|
||||
Loop::defer([$this, 'resume']);
|
||||
return $this->pause ? $this->pause->promise() : null;
|
||||
}
|
||||
|
||||
public function exitedLoop()
|
||||
{
|
||||
parent::exitedLoop();
|
||||
if ($this->resumeWatcher) {
|
||||
Loop::cancel($this->resumeWatcher);
|
||||
$this->resumeWatcher = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ namespace danog\MadelineProto;
|
||||
use Amp\Dns\Resolver;
|
||||
use Amp\Http\Client\DelegateHttpClient;
|
||||
use Amp\Loop;
|
||||
use Amp\Success;
|
||||
use danog\MadelineProto\Async\AsyncConstruct;
|
||||
use danog\MadelineProto\Loop\Generic\PeriodicLoop;
|
||||
use danog\MadelineProto\Loop\Update\FeedLoop;
|
||||
@ -204,7 +205,7 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
/**
|
||||
* Instance of wrapper API.
|
||||
*
|
||||
* @var API|null
|
||||
* @var ?APIWrapper
|
||||
*/
|
||||
public $wrapper;
|
||||
/**
|
||||
@ -321,18 +322,6 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
* @var array
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@ -439,7 +428,7 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
{
|
||||
Magic::classExists();
|
||||
// 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->cleanupProperties();
|
||||
// Load rsa keys
|
||||
@ -544,9 +533,6 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
'temp_requested_secret_chats',
|
||||
'temp_rekeyed_secret_chats',
|
||||
|
||||
// Object storage
|
||||
'storage',
|
||||
|
||||
// Report URI
|
||||
'reportDest'
|
||||
];
|
||||
@ -659,13 +645,14 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
/**
|
||||
* Prompt serialization of instance.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function serialize()
|
||||
{
|
||||
if ($this->wrapper instanceof API && isset($this->wrapper->session) && !\is_null($this->wrapper->session) && !$this->asyncInitPromise) {
|
||||
//$this->logger->logger("Didn't serialize in a while, doing that now...");
|
||||
$this->wrapper->serialize($this->wrapper->session);
|
||||
if ($this->wrapper && $this->inited()) {
|
||||
$this->wrapper->serialize();
|
||||
}
|
||||
}
|
||||
/**
|
||||
@ -852,8 +839,9 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
$backtrace = \debug_backtrace(0, 3);
|
||||
$this->asyncInitPromise = true;
|
||||
$backtrace = \debug_backtrace(0, 4);
|
||||
$backtrace = end($backtrace);
|
||||
$this->forceInit(false);
|
||||
$this->setInitPromise($this->__wakeup_async($backtrace));
|
||||
}
|
||||
/**
|
||||
@ -884,9 +872,9 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
}
|
||||
$force = false;
|
||||
$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 (\count($backtrace[2]['args']) >= 2) {
|
||||
$this->parseSettings(\array_replace_recursive($this->settings, $backtrace[2]['args'][1]));
|
||||
if (isset($backtrace['function'], $backtrace['class'], $backtrace['args']) && $backtrace['class'] === 'danog\\MadelineProto\\API' && $backtrace['function'] === '__construct_async') {
|
||||
if (\count($backtrace['args']) >= 2) {
|
||||
yield from $this->updateSettings($backtrace['args'][1], false);
|
||||
}
|
||||
}
|
||||
if (($this->settings['tl_schema']['src']['botAPI'] ?? '') !== __DIR__.'/TL_botAPI.tl') {
|
||||
@ -922,7 +910,7 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
}
|
||||
}
|
||||
$this->startLoops();
|
||||
if (yield from $this->getSelf()) {
|
||||
if (yield from $this->fullGetSelf()) {
|
||||
$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();
|
||||
}
|
||||
/**
|
||||
* Destructor.
|
||||
* Unreference instance, allowing destruction.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __destruct()
|
||||
public function unreference()
|
||||
{
|
||||
$this->stopLoops();
|
||||
if (isset($this->seqUpdater)) {
|
||||
@ -957,13 +949,21 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
if (isset($this->feeders[$channelId])) {
|
||||
$this->feeders[$channelId]->signal(true);
|
||||
}
|
||||
if (!isset($this->updaters[$channelId])) {
|
||||
if (isset($this->updaters[$channelId])) {
|
||||
$this->updaters[$channelId]->signal(true);
|
||||
}
|
||||
}
|
||||
foreach ($this->datacenter->getDataCenterConnections() as $datacenter) {
|
||||
$datacenter->disconnect();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Destructor.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->logger('Shutting down MadelineProto (MTProto)');
|
||||
$this->unreference();
|
||||
$this->logger("Successfully destroyed MadelineProto");
|
||||
}
|
||||
/**
|
||||
@ -1284,13 +1284,14 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
return $settings;
|
||||
}
|
||||
/**
|
||||
* Parse and store settings.
|
||||
* Parse, update and store settings.
|
||||
*
|
||||
* @param array $settings Settings
|
||||
* @param bool $reinit Whether to reinit the instance
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function parseSettings(array $settings): void
|
||||
public function updateSettings(array $settings, bool $reinit = true): \Generator
|
||||
{
|
||||
$settings = self::getSettings($settings, $this->settings);
|
||||
if ($settings['app_info'] === null) {
|
||||
@ -1302,6 +1303,11 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
}
|
||||
// Setup logger
|
||||
$this->setupLogger();
|
||||
|
||||
if ($reinit) {
|
||||
$this->__construct();
|
||||
yield from $this->initAsynchronously();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Setup logger.
|
||||
@ -1439,7 +1445,7 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
$this->feeders[$channelId]->signal(true);
|
||||
unset($this->feeders[$channelId]);
|
||||
}
|
||||
if (!isset($this->updaters[$channelId])) {
|
||||
if (isset($this->updaters[$channelId])) {
|
||||
$this->updaters[$channelId]->signal(true);
|
||||
unset($this->updaters[$channelId]);
|
||||
}
|
||||
@ -1487,7 +1493,7 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
if (isset($this->feeders[$channelId])) {
|
||||
$this->feeders[$channelId]->signal(true);
|
||||
}
|
||||
if (!isset($this->updaters[$channelId])) {
|
||||
if (isset($this->updaters[$channelId])) {
|
||||
$this->updaters[$channelId]->signal(true);
|
||||
}
|
||||
}
|
||||
@ -1505,7 +1511,7 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
*/
|
||||
public function startUpdateSystem($anyway = false): void
|
||||
{
|
||||
if ($this->asyncInitPromise && !$anyway) {
|
||||
if (!$this->inited() && !$anyway) {
|
||||
$this->logger("Not starting update system");
|
||||
return;
|
||||
}
|
||||
@ -1652,11 +1658,20 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
$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 {
|
||||
$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.
|
||||
*
|
||||
|
@ -22,8 +22,8 @@ namespace danog\MadelineProto\MTProtoTools;
|
||||
use Amp\ByteStream\InputStream;
|
||||
use Amp\ByteStream\IteratorStream;
|
||||
use Amp\ByteStream\OutputStream;
|
||||
use Amp\ByteStream\ResourceOutputStream;
|
||||
use Amp\ByteStream\ResourceInputStream;
|
||||
use Amp\ByteStream\ResourceOutputStream;
|
||||
use Amp\ByteStream\StreamException;
|
||||
use Amp\Deferred;
|
||||
use Amp\File\BlockingFile;
|
||||
|
@ -19,14 +19,109 @@
|
||||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use function Amp\File\exists;
|
||||
use function Amp\File\get;
|
||||
|
||||
/**
|
||||
* Manages serialization of the MadelineProto instance.
|
||||
*/
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -540,6 +540,9 @@ trait BotAPI
|
||||
if (($arguments['message'] ?? '') === '' || !isset($arguments['parse_mode'])) {
|
||||
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']['_'])) {
|
||||
$arguments['parse_mode'] = \str_replace('textParseMode', '', $arguments['parse_mode']['_']);
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ use function Amp\ByteStream\getOutputBufferStream;
|
||||
use function Amp\ByteStream\getStdin;
|
||||
use function Amp\ByteStream\getStdout;
|
||||
use function Amp\File\exists;
|
||||
use function Amp\File\get;
|
||||
use function Amp\Promise\all;
|
||||
use function Amp\Promise\any;
|
||||
use function Amp\Promise\first;
|
||||
@ -304,6 +305,7 @@ trait Tools
|
||||
});
|
||||
} catch (\Throwable $throwable) {
|
||||
Logger::log('Loop exceptionally stopped without resolving the promise', Logger::FATAL_ERROR);
|
||||
Logger::log((string) $throwable, Logger::FATAL_ERROR);
|
||||
throw $throwable;
|
||||
}
|
||||
} while (!$resolved && !(Magic::$signaled && !$ignoreSignal));
|
||||
@ -411,15 +413,6 @@ trait Tools
|
||||
{
|
||||
if ($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) {
|
||||
$promise = new Coroutine($promise);
|
||||
@ -632,22 +625,22 @@ trait Tools
|
||||
/**
|
||||
* Convert to camelCase.
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $input String
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function fromSnakeCase(string $input): string
|
||||
public static function toCamelCase(string $input): string
|
||||
{
|
||||
return \lcfirst(\str_replace('_', '', \ucwords($input, '_')));
|
||||
}
|
||||
/**
|
||||
* Convert to snake_case.
|
||||
*
|
||||
* @param string $input
|
||||
* @param string $input 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);
|
||||
$ret = $matches[0];
|
||||
@ -831,11 +824,34 @@ trait Tools
|
||||
* @return mixed
|
||||
* @access public
|
||||
*/
|
||||
public static function getVar($obj, string $var)
|
||||
public static function &getVar($obj, string $var)
|
||||
{
|
||||
$reflection = new \ReflectionClass(\get_class($obj));
|
||||
$prop = $reflection->getProperty($var);
|
||||
$prop->setAccessible(true);
|
||||
return $prop->getValue($obj);
|
||||
return \Closure::bind(
|
||||
function & () use ($var) {
|
||||
return $this->{$var};
|
||||
},
|
||||
$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();
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ trait Events
|
||||
$this->settings['updates']['callback'] = [$this, 'eventUpdateHandler'];
|
||||
$this->settings['updates']['handle_updates'] = true;
|
||||
$this->settings['updates']['run_callback'] = true;
|
||||
if (!$this->asyncInitPromise) {
|
||||
if ($this->inited()) {
|
||||
$this->startUpdateSystem();
|
||||
}
|
||||
}
|
||||
|
@ -170,7 +170,7 @@ trait Login
|
||||
$this->authorized = self::LOGGED_IN;
|
||||
yield from $this->initAuthorization();
|
||||
yield from $this->getPhoneConfig();
|
||||
$res = (yield from $this->getSelf());
|
||||
$res = (yield from $this->fullGetSelf());
|
||||
$callbacks = [$this, $this->referenceDatabase];
|
||||
if (!($this->authorization['user']['bot'] ?? false)) {
|
||||
$callbacks[] = $this->minDatabase;
|
||||
@ -189,7 +189,7 @@ trait Login
|
||||
if ($this->authorized !== self::LOGGED_IN) {
|
||||
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;
|
||||
return [$this->datacenter->curdc, $this->datacenter->getDataCenterConnection($this->datacenter->curdc)->getPermAuthKey()->getAuthKey()];
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ trait Start
|
||||
public function start(): \Generator
|
||||
{
|
||||
if ($this->authorized === self::LOGGED_IN) {
|
||||
return yield from $this->getSelf();
|
||||
return yield from $this->fullGetSelf();
|
||||
}
|
||||
if (PHP_SAPI === 'cli') {
|
||||
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();
|
||||
return yield from $this->getSelf();
|
||||
return yield from $this->fullGetSelf();
|
||||
}
|
||||
if ($this->authorized === self::NOT_LOGGED_IN) {
|
||||
if (isset($_POST['phone_number'])) {
|
||||
@ -81,7 +81,7 @@ trait Start
|
||||
}
|
||||
if ($this->authorized === self::LOGGED_IN) {
|
||||
$this->serialize();
|
||||
return yield from $this->getSelf();
|
||||
return yield from $this->fullGetSelf();
|
||||
}
|
||||
exit;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user