Implement IPC
This commit is contained in:
parent
7d29bd84bc
commit
c8cdf328c0
@ -92,7 +92,7 @@ class API extends InternalDoc
|
||||
private $wrapper;
|
||||
|
||||
/**
|
||||
* Global session unlock callback
|
||||
* Global session unlock callback.
|
||||
*
|
||||
* @var callback
|
||||
*/
|
||||
@ -146,6 +146,7 @@ class API extends InternalDoc
|
||||
|
||||
unset($unserialized);
|
||||
|
||||
$this->API->wrapper = $this->wrapper;
|
||||
yield from $this->API->initAsynchronously();
|
||||
$this->APIFactory();
|
||||
$this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
|
||||
@ -163,6 +164,7 @@ class API extends InternalDoc
|
||||
$settings['app_info']['api_hash'] = $app['api_hash'];
|
||||
}
|
||||
$this->API = new MTProto($settings);
|
||||
$this->API->wrapper = $this->wrapper;
|
||||
yield from $this->API->initAsynchronously();
|
||||
$this->APIFactory();
|
||||
$this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
|
||||
@ -186,14 +188,18 @@ class API extends InternalDoc
|
||||
{
|
||||
$this->init();
|
||||
if (!$this->oldInstance) {
|
||||
$this->logger->logger('Shutting down MadelineProto (API)');
|
||||
$this->logger->logger('Shutting down MadelineProto ('.\explode('\\', \get_class($this))[2].')');
|
||||
if ($this->API) {
|
||||
$this->API->destructing = true;
|
||||
$this->API->unreference();
|
||||
}
|
||||
$this->destructing = true;
|
||||
Tools::wait($this->wrapper->serialize());
|
||||
if ($this->unlock) ($this->unlock)();
|
||||
if (isset($this->wrapper)) {
|
||||
Tools::wait($this->wrapper->serialize());
|
||||
}
|
||||
if ($this->unlock) {
|
||||
($this->unlock)();
|
||||
}
|
||||
} else {
|
||||
$this->logger->logger('Shutting down MadelineProto (old deserialized instance of API)');
|
||||
}
|
||||
|
@ -160,6 +160,18 @@ final class APIWrapper
|
||||
return $this->factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get IPC path.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIpcPath(): string
|
||||
{
|
||||
return (new SessionPaths($this->session))->getIpcPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize session.
|
||||
*
|
||||
@ -171,6 +183,10 @@ final class APIWrapper
|
||||
Logger::log("Not serializing, no session");
|
||||
return new Success();
|
||||
}
|
||||
if ($this->API instanceof FastAPI) {
|
||||
Logger::log("Not serializing, IPC client");
|
||||
return new Success();
|
||||
}
|
||||
return Tools::callFork((function (): \Generator {
|
||||
if (isset($this->API->flushSettings) && $this->API->flushSettings) {
|
||||
$this->API->flushSettings = false;
|
||||
@ -183,9 +199,9 @@ final class APIWrapper
|
||||
yield from $this->API->initAsynchronously();
|
||||
}
|
||||
$this->serialized = \time();
|
||||
$realpaths = Serialization::realpaths($this->session);
|
||||
$realpaths = new SessionPaths($this->session);
|
||||
Logger::log('Waiting for exclusive lock of serialization lockfile...');
|
||||
$unlock = yield Tools::flock($realpaths['lockfile'], LOCK_EX);
|
||||
$unlock = yield Tools::flock($realpaths->getLockPath(), LOCK_EX);
|
||||
Logger::log('Lock acquired, serializing');
|
||||
try {
|
||||
if (!$this->gettingApiId) {
|
||||
@ -198,8 +214,8 @@ final class APIWrapper
|
||||
$this->API->settings['logger']['logger_param'] = [$this->API, 'noop'];
|
||||
}
|
||||
}
|
||||
$wrote = yield put($realpaths['tempfile'], \serialize($this));
|
||||
yield renameAsync($realpaths['tempfile'], $realpaths['file']);
|
||||
$wrote = yield put($realpaths->getTempPath(), \serialize($this));
|
||||
yield renameAsync($realpaths->getTempPath(), $realpaths->getSessionPath());
|
||||
} finally {
|
||||
if (!$this->gettingApiId) {
|
||||
$this->API->settings['updates']['callback'] = $update_closure;
|
||||
|
@ -168,7 +168,6 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
||||
$name = $this->namespace.$name;
|
||||
$aargs = isset($arguments[1]) && \is_array($arguments[1]) ? $arguments[1] : [];
|
||||
$aargs['apifactory'] = true;
|
||||
$aargs['datacenter'] = $this->API->datacenter->curdc;
|
||||
$args = isset($arguments[0]) && \is_array($arguments[0]) ? $arguments[0] : [];
|
||||
return yield from $this->API->methodCallAsyncRead($name, $args, $aargs);
|
||||
}
|
||||
|
@ -37,6 +37,9 @@ trait Start
|
||||
*/
|
||||
private function APIStart(array $settings): \Generator
|
||||
{
|
||||
if (\defined(\MADELINE_WORKER::class)) {
|
||||
throw new \danog\MadelineProto\Exception('Not inited!');
|
||||
}
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
$stdout = getStdout();
|
||||
yield $stdout->write('You did not define a valid API ID/API hash. Do you want to define it now manually, or automatically? (m/a)
|
||||
|
@ -36,25 +36,25 @@ class CombinedAPI
|
||||
{
|
||||
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
|
||||
\danog\MadelineProto\Magic::classExists();
|
||||
$realpaths = Serialization::realpaths($session);
|
||||
$this->session = $realpaths['file'];
|
||||
$realpaths = new SessionPaths($session);
|
||||
$this->session = $realpaths->getSessionPath();
|
||||
foreach ($paths as $path => $settings) {
|
||||
$this->addInstance($path, $settings);
|
||||
}
|
||||
if (\file_exists($realpaths['file'])) {
|
||||
if (!\file_exists($realpaths['lockfile'])) {
|
||||
\touch($realpaths['lockfile']);
|
||||
if (\file_exists($realpaths->getSessionPath())) {
|
||||
if (!\file_exists($realpaths->getLockPath())) {
|
||||
\touch($realpaths->getLockPath());
|
||||
\clearstatcache();
|
||||
}
|
||||
$realpaths['lockfile'] = \fopen($realpaths['lockfile'], 'r');
|
||||
$lock = \fopen($realpaths->getLockPath(), 'r');
|
||||
\danog\MadelineProto\Logger::log('Waiting for shared lock of serialization lockfile...');
|
||||
\flock($realpaths['lockfile'], LOCK_SH);
|
||||
\flock($lock, LOCK_SH);
|
||||
\danog\MadelineProto\Logger::log('Shared lock acquired, deserializing...');
|
||||
try {
|
||||
$tounserialize = \file_get_contents($realpaths['file']);
|
||||
$tounserialize = \file_get_contents($realpaths->getSessionPath());
|
||||
} finally {
|
||||
\flock($realpaths['lockfile'], LOCK_UN);
|
||||
\fclose($realpaths['lockfile']);
|
||||
\flock($lock, LOCK_UN);
|
||||
\fclose($lock);
|
||||
}
|
||||
$deserialized = \unserialize($tounserialize);
|
||||
/*foreach ($deserialized['instance_paths'] as $path) {
|
||||
@ -115,21 +115,21 @@ class CombinedAPI
|
||||
$filename = $this->session;
|
||||
}
|
||||
Logger::log(\danog\MadelineProto\Lang::$current_lang['serializing_madelineproto']);
|
||||
$realpaths = Serialization::realpaths($filename);
|
||||
if (!\file_exists($realpaths['lockfile'])) {
|
||||
\touch($realpaths['lockfile']);
|
||||
$realpaths = new SessionPaths($filename);
|
||||
if (!\file_exists($realpaths->getLockPath())) {
|
||||
\touch($realpaths->getLockPath());
|
||||
\clearstatcache();
|
||||
}
|
||||
$realpaths['lockfile'] = \fopen($realpaths['lockfile'], 'w');
|
||||
$lock = \fopen($realpaths->getLockPath(), 'w');
|
||||
\danog\MadelineProto\Logger::log('Waiting for exclusive lock of serialization lockfile...');
|
||||
\flock($realpaths['lockfile'], LOCK_EX);
|
||||
\flock($lock, LOCK_EX);
|
||||
\danog\MadelineProto\Logger::log('Lock acquired, serializing');
|
||||
try {
|
||||
$wrote = \file_put_contents($realpaths['tempfile'], \serialize(['event_handler' => $this->event_handler, 'event_handler_instance' => $this->event_handler_instance, 'instance_paths' => $this->instance_paths]));
|
||||
\rename($realpaths['tempfile'], $realpaths['file']);
|
||||
$wrote = \file_put_contents($realpaths->getTempPath(), \serialize(['event_handler' => $this->event_handler, 'event_handler_instance' => $this->event_handler_instance, 'instance_paths' => $this->instance_paths]));
|
||||
\rename($realpaths->getTempPath(), $realpaths->getSessionPath());
|
||||
} finally {
|
||||
\flock($realpaths['lockfile'], LOCK_UN);
|
||||
\fclose($realpaths['lockfile']);
|
||||
\flock($lock, LOCK_UN);
|
||||
\fclose($lock);
|
||||
}
|
||||
$this->serialized = \time();
|
||||
return $wrote;
|
||||
|
139
src/danog/MadelineProto/FastAPI.php
Normal file
139
src/danog/MadelineProto/FastAPI.php
Normal file
@ -0,0 +1,139 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* API module.
|
||||
*
|
||||
* This file is part of MadelineProto.
|
||||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU Affero General Public License for more details.
|
||||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @author Daniil Gentili <daniil@daniil.it>
|
||||
* @copyright 2016-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\File\StatCache;
|
||||
use danog\MadelineProto\Ipc\Client;
|
||||
use danog\MadelineProto\Ipc\Server;
|
||||
|
||||
use function Amp\File\exists;
|
||||
use function Amp\File\get;
|
||||
use function Amp\File\isfile;
|
||||
|
||||
/**
|
||||
* IPC API wrapper for MadelineProto.
|
||||
*/
|
||||
class FastAPI extends API
|
||||
{
|
||||
/**
|
||||
* Constructor function.
|
||||
*
|
||||
* @param string $session Session name
|
||||
* @param array $settings Settings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __magic_construct(string $session, array $settings = []): void
|
||||
{
|
||||
Magic::classExists(true);
|
||||
$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 (!$this->{$key}) {
|
||||
$this->{$key} = $this->exportNamespace($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Async constructor function.
|
||||
*
|
||||
* @param string $session Session name
|
||||
* @param array $settings Settings
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function __construct_async(string $session, array $settings = []): \Generator
|
||||
{
|
||||
$this->logger = Logger::constructorFromSettings($settings);
|
||||
$session = new SessionPaths($session);
|
||||
yield from $this->checkInit($session, $settings);
|
||||
if (!(yield exists($session->getIpcPath()))) {
|
||||
yield from Server::startMe($session);
|
||||
$inited = false;
|
||||
for ($x = 0; $x < 3; $x++) {
|
||||
$this->logger->logger("Waiting for IPC server to start...");
|
||||
yield Tools::sleep(0.1);
|
||||
if (yield from $this->checkInit($session, $settings)) {
|
||||
$inited = true;
|
||||
break;
|
||||
}
|
||||
yield from Server::startMe($session);
|
||||
}
|
||||
if (!$inited) {
|
||||
throw new Exception("The IPC server isn't running, please check logs!");
|
||||
}
|
||||
}
|
||||
$this->API = new Client($session->getIpcPath(), $this->logger);
|
||||
yield from $this->API->initAsynchronously();
|
||||
$this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
|
||||
}
|
||||
/**
|
||||
* Try initializing session.
|
||||
*
|
||||
* @param SessionPaths $session Session paths
|
||||
* @param array $settings Settings
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function checkInit(SessionPaths $session, array $settings): \Generator
|
||||
{
|
||||
StatCache::clear($session->getIpcPath());
|
||||
StatCache::clear($session->getSessionPath());
|
||||
if (!(yield exists($session->getSessionPath()))
|
||||
|| (yield exists($session->getIpcPath())
|
||||
&& yield isfile($session->getIpcPath())
|
||||
&& yield get($session->getIpcPath()) === 'not inited')
|
||||
) { // Should init API ID|session
|
||||
Logger::log("Session not initialized, initializing it now...");
|
||||
$API = new API($session->getSessionPath(), $settings);
|
||||
yield from $API->initAsynchronously();
|
||||
unset($API);
|
||||
return false; // Should start IPC server
|
||||
}
|
||||
return true; // All good, IPC server is running
|
||||
}
|
||||
/**
|
||||
* Start MadelineProto and the event handler (enables async).
|
||||
*
|
||||
* Also initializes error reporting, catching and reporting all errors surfacing from the event loop.
|
||||
*
|
||||
* @param string $eventHandler Event handler class name
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function startAndLoop(string $eventHandler): void
|
||||
{
|
||||
throw new Exception("Can't use ".__FUNCTION__." in an IPC client instance, please use a full ".API::class." instance, instead!");
|
||||
}
|
||||
/**
|
||||
* Start multiple instances of MadelineProto and the event handlers (enables async).
|
||||
*
|
||||
* @param API[] $instances Instances of madeline
|
||||
* @param string[]|string $eventHandler Event handler(s)
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public static function startAndLoopMulti(array $instances, $eventHandler): void
|
||||
{
|
||||
throw new Exception("Can't use ".__FUNCTION__." in an IPC client instance, please use a full ".API::class." instance, instead!");
|
||||
}
|
||||
}
|
163
src/danog/MadelineProto/Ipc/Client.php
Normal file
163
src/danog/MadelineProto/Ipc/Client.php
Normal file
@ -0,0 +1,163 @@
|
||||
<?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\Ipc;
|
||||
|
||||
use Amp\Deferred;
|
||||
use Amp\Ipc\Sync\ChannelledSocket;
|
||||
use danog\MadelineProto\API;
|
||||
use danog\MadelineProto\Async\AsyncConstruct;
|
||||
use danog\MadelineProto\Exception;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
use function Amp\Ipc\connect;
|
||||
|
||||
/**
|
||||
* IPC client.
|
||||
*/
|
||||
class Client extends AsyncConstruct
|
||||
{
|
||||
use \danog\MadelineProto\Wrappers\Start;
|
||||
use \danog\MadelineProto\Wrappers\Templates;
|
||||
|
||||
/**
|
||||
* IPC server socket.
|
||||
*/
|
||||
private ChannelledSocket $server;
|
||||
/**
|
||||
* Requests promise array.
|
||||
*/
|
||||
private array $requests = [];
|
||||
/**
|
||||
* Logger instance.
|
||||
*/
|
||||
public Logger $logger;
|
||||
/**
|
||||
* Constructor function.
|
||||
*
|
||||
* @param string $ipcPath IPC socket path
|
||||
* @param Logger $logger Logger
|
||||
*/
|
||||
public function __construct(string $ipcPath, Logger $logger)
|
||||
{
|
||||
$this->setInitPromise($this->__construct_async($ipcPath, $logger));
|
||||
}
|
||||
/**
|
||||
* Constructor function.
|
||||
*
|
||||
* @param string $ipcPath IPC socket path
|
||||
* @param Logger $logger Logger
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function __construct_async(string $ipcPath, Logger $logger): \Generator
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->logger("Connecting to IPC server...");
|
||||
$this->server = yield connect($ipcPath);
|
||||
$this->logger("Connected to IPC server!");
|
||||
Tools::callFork($this->loop());
|
||||
}
|
||||
/**
|
||||
* Logger.
|
||||
*
|
||||
* @param string $param Parameter
|
||||
* @param int $level Logging level
|
||||
* @param string $file File where the message originated
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function logger($param, int $level = Logger::NOTICE, string $file = ''): void
|
||||
{
|
||||
if ($file === null) {
|
||||
$file = \basename(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php');
|
||||
}
|
||||
isset($this->logger) ? $this->logger->logger($param, $level, $file) : Logger::$default->logger($param, $level, $file);
|
||||
}
|
||||
/**
|
||||
* Main loop.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function loop(): \Generator
|
||||
{
|
||||
while ($payload = yield $this->server->receive()) {
|
||||
[$id, $payload] = $payload;
|
||||
if (!isset($this->requests[$id])) {
|
||||
Logger::log("Got response for non-existing ID $id!");
|
||||
} else {
|
||||
$promise = $this->requests[$id];
|
||||
unset($this->requests[$id]);
|
||||
if ($payload instanceof ExitFailure) {
|
||||
$promise->fail($payload->getException());
|
||||
} else {
|
||||
$promise->resolve($payload);
|
||||
}
|
||||
unset($promise);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Unreference.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function unreference(): void
|
||||
{
|
||||
if (isset($this->server)) {
|
||||
Tools::wait($this->server->disconnect());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Call function.
|
||||
*
|
||||
* @param string $function Function name
|
||||
* @param array $arguments Arguments
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function __call(string $function, array $arguments): \Generator
|
||||
{
|
||||
$this->requests []= $deferred = new Deferred;
|
||||
yield $this->server->send([$function, $arguments]);
|
||||
return $deferred->promise();
|
||||
}
|
||||
/**
|
||||
* Placeholder.
|
||||
*
|
||||
* @param mixed ...$params Params
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setEventHandler(...$params): void
|
||||
{
|
||||
throw new Exception("Can't use ".__FUNCTION__." in an IPC client instance, please use a full ".API::class." instance, instead!");
|
||||
}
|
||||
/**
|
||||
* Placeholder.
|
||||
*
|
||||
* @param mixed ...$params Params
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getEventHandler(...$params): void
|
||||
{
|
||||
throw new Exception("Can't use ".__FUNCTION__." in an IPC client instance, please use a full ".API::class." instance, instead!");
|
||||
}
|
||||
}
|
64
src/danog/MadelineProto/Ipc/ExitFailure.php
Normal file
64
src/danog/MadelineProto/Ipc/ExitFailure.php
Normal file
@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc;
|
||||
|
||||
use danog\MadelineProto\RPCErrorException;
|
||||
|
||||
use function Amp\Parallel\Sync\flattenThrowableBacktrace;
|
||||
|
||||
final class ExitFailure
|
||||
{
|
||||
/** @var string */
|
||||
private $type;
|
||||
|
||||
/** @var string */
|
||||
private $message;
|
||||
|
||||
/** @var int|string */
|
||||
private $code;
|
||||
|
||||
/** @var string[] */
|
||||
private $trace;
|
||||
|
||||
/** @var string */
|
||||
private $tlTrace;
|
||||
|
||||
/** @var self|null */
|
||||
private $previous;
|
||||
|
||||
/** @var self|null */
|
||||
private $localized;
|
||||
|
||||
public function __construct(\Throwable $exception)
|
||||
{
|
||||
$this->type = \get_class($exception);
|
||||
$this->message = $exception->getMessage();
|
||||
$this->code = $exception->getCode();
|
||||
$this->trace = flattenThrowableBacktrace($exception);
|
||||
if (method_exists($exception, 'getTLTrace')) {
|
||||
$this->tlTrace = $exception->getTLTrace();
|
||||
}
|
||||
|
||||
if ($exception instanceof RPCErrorException) {
|
||||
$this->localized = $exception->getLocalization();
|
||||
}
|
||||
|
||||
if ($previous = $exception->getPrevious()) {
|
||||
$this->previous = new self($previous);
|
||||
}
|
||||
}
|
||||
|
||||
public function getException()
|
||||
{
|
||||
$previous = $this->previous ? $this->previous->getException() : null;
|
||||
|
||||
$exception = new $this->type($this->message, $this->code, $previous);
|
||||
if ($this->tlTrace) {
|
||||
$exception->setTLTrace($this->tlTrace);
|
||||
}
|
||||
if ($this->localized) {
|
||||
$exception->setLocalization($this->localized);
|
||||
}
|
||||
return $exception;
|
||||
}
|
||||
}
|
87
src/danog/MadelineProto/Ipc/Runner/ProcessRunner.php
Normal file
87
src/danog/MadelineProto/Ipc/Runner/ProcessRunner.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Runner;
|
||||
|
||||
use Amp\Process\Process as BaseProcess;
|
||||
use Amp\Promise;
|
||||
|
||||
final class ProcessRunner extends RunnerAbstract
|
||||
{
|
||||
/** @var string|null Cached path to located PHP binary. */
|
||||
private static $binaryPath;
|
||||
|
||||
/** @var \Amp\Process\Process */
|
||||
private $process;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $session Session path
|
||||
*/
|
||||
public function __construct(string $session)
|
||||
{
|
||||
if (\PHP_SAPI === "cli") {
|
||||
$binary = \PHP_BINARY;
|
||||
} else {
|
||||
$binary = self::$binaryPath ?? self::locateBinary();
|
||||
}
|
||||
|
||||
$options = [
|
||||
"html_errors" => "0",
|
||||
"display_errors" => "0",
|
||||
"log_errors" => "1",
|
||||
];
|
||||
|
||||
$runner = self::getScriptPath();
|
||||
|
||||
$command = \implode(" ", [
|
||||
'nohup',
|
||||
\escapeshellarg($binary),
|
||||
self::formatOptions($options),
|
||||
$runner,
|
||||
'madeline-ipc',
|
||||
\escapeshellarg($session),
|
||||
'&'
|
||||
]);
|
||||
\var_dumP($command);
|
||||
|
||||
$this->process = new BaseProcess($command);
|
||||
}
|
||||
private static function locateBinary(): string
|
||||
{
|
||||
$executable = \strncasecmp(\PHP_OS, "WIN", 3) === 0 ? "php.exe" : "php";
|
||||
|
||||
$paths = \array_filter(\explode(\PATH_SEPARATOR, \getenv("PATH")));
|
||||
$paths[] = \PHP_BINDIR;
|
||||
$paths = \array_unique($paths);
|
||||
|
||||
foreach ($paths as $path) {
|
||||
$path .= \DIRECTORY_SEPARATOR.$executable;
|
||||
if (\is_executable($path)) {
|
||||
return self::$binaryPath = $path;
|
||||
}
|
||||
}
|
||||
|
||||
throw new \Error("Could not locate PHP executable binary");
|
||||
}
|
||||
|
||||
private static function formatOptions(array $options): string
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($options as $option => $value) {
|
||||
$result[] = \sprintf("-d%s=%s", $option, $value);
|
||||
}
|
||||
|
||||
return \implode(" ", $result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the process.
|
||||
*
|
||||
* @return Promise<int> Resolved with the PID
|
||||
*/
|
||||
public function start(): Promise
|
||||
{
|
||||
return $this->process->start();
|
||||
}
|
||||
}
|
74
src/danog/MadelineProto/Ipc/Runner/RunnerAbstract.php
Normal file
74
src/danog/MadelineProto/Ipc/Runner/RunnerAbstract.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Runner;
|
||||
|
||||
use Amp\Promise;
|
||||
|
||||
abstract class RunnerAbstract
|
||||
{
|
||||
const SCRIPT_PATH = __DIR__."/entry.php";
|
||||
|
||||
/** @var string|null External version of SCRIPT_PATH if inside a PHAR. */
|
||||
protected static $pharScriptPath;
|
||||
|
||||
/** @var string|null PHAR path with a '.phar' extension. */
|
||||
protected static $pharCopy;
|
||||
|
||||
protected static function getScriptPath(string $alternateTmpDir = '')
|
||||
{
|
||||
/**
|
||||
* If using madeline.php, simply return madeline.php path.
|
||||
*/
|
||||
if (\defined(\MADELINE_PHP::class)) {
|
||||
return \MADELINE_PHP;
|
||||
}
|
||||
// Write process runner to external file if inside a PHAR different from madeline.phar,
|
||||
// because PHP can't open files inside a PHAR directly except for the stub.
|
||||
if (\strpos(self::SCRIPT_PATH, "phar://") === 0) {
|
||||
$alternateTmpDir = $alternateTmpDir ?: \sys_get_temp_dir();
|
||||
|
||||
if (self::$pharScriptPath) {
|
||||
$scriptPath = self::$pharScriptPath;
|
||||
} else {
|
||||
$path = \dirname(self::SCRIPT_PATH);
|
||||
|
||||
if (\substr(\Phar::running(false), -5) !== ".phar") {
|
||||
self::$pharCopy = $alternateTmpDir."/phar-".\bin2hex(\random_bytes(10)).".phar";
|
||||
\copy(\Phar::running(false), self::$pharCopy);
|
||||
|
||||
\register_shutdown_function(static function (): void {
|
||||
@\unlink(self::$pharCopy);
|
||||
});
|
||||
|
||||
$path = "phar://".self::$pharCopy."/".\substr($path, \strlen(\Phar::running(true)));
|
||||
}
|
||||
|
||||
$contents = \file_get_contents(self::SCRIPT_PATH);
|
||||
$contents = \str_replace("__DIR__", \var_export($path, true), $contents);
|
||||
$suffix = \bin2hex(\random_bytes(10));
|
||||
self::$pharScriptPath = $scriptPath = $alternateTmpDir."/madeline-ipc-".$suffix.".php";
|
||||
\file_put_contents($scriptPath, $contents);
|
||||
|
||||
\register_shutdown_function(static function (): void {
|
||||
@\unlink(self::$pharScriptPath);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$scriptPath = self::SCRIPT_PATH;
|
||||
}
|
||||
return $scriptPath;
|
||||
}
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $session Sessio path
|
||||
*/
|
||||
abstract public function __construct(string $session);
|
||||
|
||||
/**
|
||||
* Starts the execution process.
|
||||
*
|
||||
* @return Promise<int> Resolves with the PID
|
||||
*/
|
||||
abstract public function start(): Promise;
|
||||
}
|
107
src/danog/MadelineProto/Ipc/Runner/WebRunner.php
Normal file
107
src/danog/MadelineProto/Ipc/Runner/WebRunner.php
Normal file
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Runner;
|
||||
|
||||
use Amp\ByteStream\ResourceOutputStream;
|
||||
use Amp\Parallel\Context\ContextException;
|
||||
use Amp\Promise;
|
||||
|
||||
final class WebRunner extends RunnerAbstract
|
||||
{
|
||||
/** @var string|null Cached path to the runner script. */
|
||||
private static $runPath;
|
||||
/**
|
||||
* Initialization payload.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $params;
|
||||
|
||||
/**
|
||||
* Socket.
|
||||
*
|
||||
* @var ResourceOutputStream
|
||||
*/
|
||||
private $res;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param string $session Session path
|
||||
*/
|
||||
public function __construct(string $session)
|
||||
{
|
||||
if (!isset($_SERVER['SERVER_NAME'])) {
|
||||
throw new ContextException("Could not initialize web runner!");
|
||||
}
|
||||
|
||||
if (!self::$runPath) {
|
||||
$uri = \parse_url('tcp://'.$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
||||
if (\substr($uri, -1) === '/') { // http://example.com/path/ (assumed index.php)
|
||||
$uri .= 'index'; // Add fake file name
|
||||
}
|
||||
$uri = \str_replace('//', '/', $uri);
|
||||
|
||||
$rootDir = \debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
$rootDir = \end($rootDir)['file'] ?? '';
|
||||
if (!$rootDir) {
|
||||
throw new ContextException('Could not get entry file!');
|
||||
}
|
||||
$rootDir = \dirname($rootDir);
|
||||
$uriDir = \dirname($uri);
|
||||
|
||||
if (\substr($rootDir, -\strlen($uriDir)) !== $uriDir) {
|
||||
throw new ContextException("Mismatch between absolute root dir ($rootDir) and URI dir ($uriDir)");
|
||||
}
|
||||
|
||||
// Absolute root of (presumably) readable document root
|
||||
$localRootDir = \substr($rootDir, 0, \strlen($rootDir)-\strlen($uriDir)).DIRECTORY_SEPARATOR;
|
||||
|
||||
$runPath = self::getScriptPath($localRootDir);
|
||||
|
||||
if (\substr($runPath, 0, \strlen($localRootDir)) === $localRootDir) { // Process runner is within readable document root
|
||||
self::$runPath = \substr($runPath, \strlen($localRootDir)-1);
|
||||
} else {
|
||||
$contents = \file_get_contents(self::SCRIPT_PATH);
|
||||
$contents = \str_replace("__DIR__", \var_export($localRootDir, true), $contents);
|
||||
$suffix = \bin2hex(\random_bytes(10));
|
||||
$runPath = $localRootDir."/madeline-ipc-".$suffix.".php";
|
||||
\file_put_contents($runPath, $contents);
|
||||
|
||||
self::$runPath = \substr($runPath, \strlen($localRootDir)-1);
|
||||
|
||||
\register_shutdown_function(static function () use ($runPath): void {
|
||||
@\unlink($runPath);
|
||||
});
|
||||
}
|
||||
|
||||
self::$runPath = \str_replace(DIRECTORY_SEPARATOR, '/', self::$runPath);
|
||||
self::$runPath = \str_replace('//', '/', self::$runPath);
|
||||
}
|
||||
|
||||
$this->params = [
|
||||
'argv' => ['pony', 'madeline-ipc', $session]
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Start process.
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function start(): Promise
|
||||
{
|
||||
$params = \http_build_query($this->params);
|
||||
|
||||
$address = ($_SERVER['HTTPS'] ?? false ? 'tls' : 'tcp').'://'.$_SERVER['SERVER_NAME'];
|
||||
$port = $_SERVER['SERVER_PORT'];
|
||||
|
||||
$uri = self::$runPath.'?'.$params;
|
||||
|
||||
$payload = "GET $uri HTTP/1.1\r\nHost: ${_SERVER['SERVER_NAME']}\r\n\r\n";
|
||||
|
||||
// We don't care for results or timeouts here, PHP doesn't count IOwait time as execution time anyway
|
||||
// Technically should use amphp/socket, but I guess it's OK to not introduce another dependency just for a socket that will be used once.
|
||||
$this->res = new ResourceOutputStream(\fsockopen($address, $port));
|
||||
return $this->res->write($payload);
|
||||
}
|
||||
}
|
87
src/danog/MadelineProto/Ipc/Runner/entry.php
Normal file
87
src/danog/MadelineProto/Ipc/Runner/entry.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* IPC server entry 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
|
||||
*/
|
||||
|
||||
use Amp\Deferred;
|
||||
use danog\MadelineProto\API;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\SessionPaths;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
(static function () use (&$argv): void {
|
||||
$ipcPath = null;
|
||||
if (\defined(\MADELINE_WORKER_START::class)) {
|
||||
$ipcPath = \MADELINE_WORKER_START;
|
||||
} elseif (\count(\debug_backtrace(0)) === 1) {
|
||||
if (isset($GLOBALS['argv']) && !empty($GLOBALS['argv'])) {
|
||||
$arguments = $GLOBALS['argv'];
|
||||
} elseif (isset($_GET['argv']) && !empty($_GET['argv'])) {
|
||||
$arguments = $_GET['argv'];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
if (isset($arguments[1]) && $arguments[1] === 'madeline-ipc') {
|
||||
$ipcPath = $arguments[2];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
$paths = [
|
||||
\dirname(__DIR__, 7)."/autoload.php",
|
||||
\dirname(__DIR__, 5)."/vendor/autoload.php",
|
||||
];
|
||||
|
||||
foreach ($paths as $path) {
|
||||
if (\file_exists($path)) {
|
||||
$autoloadPath = $path;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($autoloadPath)) {
|
||||
\trigger_error("Could not locate autoload.php in any of the following files: ".\implode(", ", $paths), E_USER_ERROR);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
include $autoloadPath;
|
||||
}
|
||||
|
||||
if ($ipcPath) {
|
||||
if (!\file_exists($ipcPath)) {
|
||||
\trigger_error("IPC session $ipcPath does not exist!", E_USER_ERROR);
|
||||
exit(1);
|
||||
}
|
||||
\define(\MADELINE_WORKER::class, 1);
|
||||
|
||||
try {
|
||||
$API = new API($ipcPath);
|
||||
if ($API->hasEventHandler()) {
|
||||
$API->startAndLoop(\get_class($API->getEventHandler()));
|
||||
} else {
|
||||
$API->initSelfRestart();
|
||||
Tools::wait((new Deferred)->promise());
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Logger::log("Got exception $e in IPC server, exiting...", Logger::FATAL_ERROR);
|
||||
\trigger_error("Got exception $e in IPC server, exiting...", E_USER_ERROR);
|
||||
if ($e->getMessage() === 'Not inited!') {
|
||||
$ipc = (new SessionPaths($ipcPath))->getIpcPath();
|
||||
@\unlink($ipc);
|
||||
\file_put_contents($ipc, 'not inited');
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
133
src/danog/MadelineProto/Ipc/Server.php
Normal file
133
src/danog/MadelineProto/Ipc/Server.php
Normal file
@ -0,0 +1,133 @@
|
||||
<?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\Ipc;
|
||||
|
||||
use Amp\Ipc\IpcServer;
|
||||
use Amp\Ipc\Sync\ChannelledSocket;
|
||||
use danog\MadelineProto\Ipc\Runner\ProcessRunner;
|
||||
use danog\MadelineProto\Ipc\Runner\WebRunner;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Loop\Impl\SignalLoop;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
/**
|
||||
* IPC server.
|
||||
*/
|
||||
class Server extends SignalLoop
|
||||
{
|
||||
/**
|
||||
* IPC server.
|
||||
*/
|
||||
private IpcServer $server;
|
||||
/**
|
||||
* Set IPC path.
|
||||
*
|
||||
* @param string $path IPC path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setIpcPath(string $path): void
|
||||
{
|
||||
$this->server = new IpcServer($path);
|
||||
}
|
||||
/**
|
||||
* Start IPC server in background.
|
||||
*
|
||||
* @param string $session Session path
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public static function startMe(string $session): \Generator
|
||||
{
|
||||
try {
|
||||
Logger::log("Starting IPC server $session (process)");
|
||||
yield (new ProcessRunner($session))->start();
|
||||
return;
|
||||
} catch (\Throwable $e) {
|
||||
Logger::log($e);
|
||||
}
|
||||
Logger::log("Starting IPC server $session (web)");
|
||||
yield (new WebRunner($session))->start();
|
||||
}
|
||||
/**
|
||||
* Main loop.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function loop(): \Generator
|
||||
{
|
||||
while ($socket = yield $this->waitSignal($this->server->accept())) {
|
||||
Tools::callFork($this->clientLoop($socket));
|
||||
}
|
||||
$this->server->close();
|
||||
}
|
||||
/**
|
||||
* Client handler loop.
|
||||
*
|
||||
* @param ChannelledSocket $socket Client
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function clientLoop(ChannelledSocket $socket): \Generator
|
||||
{
|
||||
$this->API->logger("Accepted IPC client connection!");
|
||||
|
||||
$id = 0;
|
||||
while ($payload = yield $socket->receive()) {
|
||||
Tools::callFork($this->clientRequest($socket, $id++, $payload));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Handle client request.
|
||||
*
|
||||
* @param ChannelledSocket $socket Socket
|
||||
* @param integer $id Request ID
|
||||
* @param array $payload Payload
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function clientRequest(ChannelledSocket $socket, int $id, $payload): \Generator
|
||||
{
|
||||
try {
|
||||
$result = $this->API->{$payload[0]}(...$payload[1]);
|
||||
$result = $result instanceof \Generator ? yield from $result : yield $result;
|
||||
} catch (\Throwable $e) {
|
||||
$result = new ExitFailure($e);
|
||||
}
|
||||
try {
|
||||
yield $socket->send([$id, $result]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->API->logger("Got error while trying to send result of ${payload[0]}: $e", Logger::ERROR);
|
||||
try {
|
||||
yield $socket->send([$id, new ExitFailure($e)]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->API->logger("Got error while trying to send error of error of ${payload[0]}: $e", Logger::ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get the name of the loop.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return "IPC server";
|
||||
}
|
||||
}
|
@ -185,6 +185,9 @@ class Logger
|
||||
if ($mode === self::NO_LOGGER) {
|
||||
$mode = (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') ? Logger::ECHO_LOGGER : Logger::FILE_LOGGER;
|
||||
}
|
||||
if (\defined(\MADELINE_WORKER::class)) {
|
||||
$mode = Logger::FILE_LOGGER;
|
||||
}
|
||||
$level = \max($level, self::NOTICE);
|
||||
$max_size = \max($max_size, 100 * 1024);
|
||||
|
||||
|
@ -28,6 +28,7 @@ use danog\MadelineProto\Db\DbArray;
|
||||
use danog\MadelineProto\Db\DbPropertiesFabric;
|
||||
use danog\MadelineProto\Db\DbPropertiesTrait;
|
||||
use danog\MadelineProto\Db\Mysql;
|
||||
use danog\MadelineProto\Ipc\Server;
|
||||
use danog\MadelineProto\Loop\Generic\PeriodicLoop;
|
||||
use danog\MadelineProto\Loop\Update\FeedLoop;
|
||||
use danog\MadelineProto\Loop\Update\SeqLoop;
|
||||
@ -384,6 +385,12 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
* @var PeriodicLoop
|
||||
*/
|
||||
private $rpcLoop;
|
||||
/**
|
||||
* IPC server.
|
||||
*
|
||||
* @var Server
|
||||
*/
|
||||
private $ipcServer;
|
||||
/**
|
||||
* Feeder loops.
|
||||
*
|
||||
@ -678,6 +685,15 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
{
|
||||
return $this->datacenter->getDataCenterConnections();
|
||||
}
|
||||
/**
|
||||
* Get main DC ID.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDataCenterId(): int
|
||||
{
|
||||
return $this->datacenter->curdc;
|
||||
}
|
||||
/**
|
||||
* Prompt serialization of instance.
|
||||
*
|
||||
@ -716,12 +732,17 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
if (!$this->rpcLoop) {
|
||||
$this->rpcLoop = new PeriodicLoop($this, [$this, 'rpcReport'], 'config', 60);
|
||||
}
|
||||
if (!$this->ipcServer) {
|
||||
$this->ipcServer = new Server($this);
|
||||
$this->ipcServer->setIpcPath($this->wrapper->getIpcPath());
|
||||
}
|
||||
$this->callCheckerLoop->start();
|
||||
$this->serializeLoop->start();
|
||||
$this->phoneConfigLoop->start();
|
||||
$this->configLoop->start();
|
||||
$this->checkTosLoop->start();
|
||||
$this->rpcLoop->start();
|
||||
$this->ipcServer->start();
|
||||
}
|
||||
/**
|
||||
* Stop all internal loops.
|
||||
@ -754,6 +775,10 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
$this->rpcLoop->signal(true);
|
||||
$this->rpcLoop = null;
|
||||
}
|
||||
if ($this->ipcServer) {
|
||||
$this->ipcServer->signal(null);
|
||||
$this->ipcServer = null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Report RPC errors.
|
||||
@ -1056,7 +1081,7 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
*/
|
||||
public static function parseSettings(array $settings, array $previousSettings = []): array
|
||||
{
|
||||
Magic::classExists();
|
||||
//Magic::classExists();
|
||||
$settings = \array_replace_recursive($previousSettings, $settings);
|
||||
if (isset($previousSettings['connection_settings']['default_dc'])) {
|
||||
$settings['connection_settings']['default_dc'] = $previousSettings['connection_settings']['default_dc'];
|
||||
@ -1787,6 +1812,15 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||
}
|
||||
return $this->authorization['user'];
|
||||
}
|
||||
/**
|
||||
* Get authorization info.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getAuthorization(): int
|
||||
{
|
||||
return $this->authorized;
|
||||
}
|
||||
/**
|
||||
* IDs of peers where to report errors.
|
||||
*
|
||||
|
@ -19,20 +19,16 @@
|
||||
|
||||
namespace danog\MadelineProto\MTProtoSession;
|
||||
|
||||
use Amp\Deferred;
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Loop\Connection\WriteLoop;
|
||||
|
||||
/**
|
||||
* Manages acknowledgement of messages.
|
||||
*/
|
||||
trait AckHandler
|
||||
{
|
||||
/**
|
||||
* Acknowledge outgoing message ID
|
||||
* Acknowledge outgoing message ID.
|
||||
*
|
||||
* @param string|int $message_id Message Id
|
||||
*
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function ackOutgoingMessageId($message_id): bool
|
||||
@ -45,10 +41,10 @@ trait AckHandler
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* We have gotten response for outgoing message ID
|
||||
* We have gotten response for outgoing message ID.
|
||||
*
|
||||
* @param string|int $message_id Message ID
|
||||
*
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function gotResponseForOutgoingMessageId($message_id): bool
|
||||
@ -70,10 +66,10 @@ trait AckHandler
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
* Acknowledge incoming message ID
|
||||
* Acknowledge incoming message ID.
|
||||
*
|
||||
* @param string|int $message_id Message ID
|
||||
*
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function ackIncomingMessageId($message_id): bool
|
||||
|
@ -19,21 +19,17 @@
|
||||
|
||||
namespace danog\MadelineProto\MTProtoSession;
|
||||
|
||||
use Amp\Loop;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\MTProto;
|
||||
|
||||
/**
|
||||
* Manages responses.
|
||||
*/
|
||||
trait Reliable
|
||||
{
|
||||
/**
|
||||
* Send state info for message IDs
|
||||
* Send state info for message IDs.
|
||||
*
|
||||
* @param string|int $req_msg_id Message ID of msgs_state_req that initiated this
|
||||
* @param array $msg_ids Message IDs to send info about
|
||||
*
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function sendMsgsStateInfo($req_msg_id, array $msg_ids): \Generator
|
||||
@ -62,4 +58,4 @@ trait Reliable
|
||||
}
|
||||
$this->outgoing_messages[yield from $this->objectCall('msgs_state_info', ['req_msg_id' => $req_msg_id, 'info' => $info], ['postpone' => true])]['response'] = $req_msg_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -475,7 +475,7 @@ trait ResponseHandler
|
||||
}
|
||||
$botAPI = isset($request['botAPI']) && $request['botAPI'];
|
||||
if (isset($response['_']) && !$this->isCdn() && $this->API->getTL()->getConstructors()->findByPredicate($response['_'])['type'] === 'Updates') {
|
||||
$response['request'] = $request;
|
||||
$response['request'] = ['_' => $request['_'], 'body' => $request['body'] ?? []];
|
||||
\danog\MadelineProto\Tools::callForkDefer($this->API->handleUpdates($response));
|
||||
}
|
||||
unset($request);
|
||||
|
@ -128,11 +128,11 @@ class MinDatabase implements TLCallback
|
||||
}
|
||||
}
|
||||
$frames = \array_reverse($frames);
|
||||
$tl_trace = \array_shift($frames);
|
||||
$tlTrace = \array_shift($frames);
|
||||
foreach ($frames as $frame) {
|
||||
$tl_trace .= "['".$frame."']";
|
||||
$tlTrace .= "['".$frame."']";
|
||||
}
|
||||
$this->API->logger->logger($tl_trace, \danog\MadelineProto\Logger::ERROR);
|
||||
$this->API->logger->logger($tlTrace, \danog\MadelineProto\Logger::ERROR);
|
||||
return false;
|
||||
}
|
||||
$peers = [];
|
||||
|
@ -160,11 +160,11 @@ class ReferenceDatabase implements TLCallback
|
||||
}
|
||||
}
|
||||
$frames = \array_reverse($frames);
|
||||
$tl_trace = \array_shift($frames);
|
||||
$tlTrace = \array_shift($frames);
|
||||
foreach ($frames as $frame) {
|
||||
$tl_trace .= "['".$frame."']";
|
||||
$tlTrace .= "['".$frame."']";
|
||||
}
|
||||
$this->API->logger->logger($tl_trace, \danog\MadelineProto\Logger::ERROR);
|
||||
$this->API->logger->logger($tlTrace, \danog\MadelineProto\Logger::ERROR);
|
||||
return false;
|
||||
}
|
||||
if (!isset($location['file_reference'])) {
|
||||
|
@ -95,7 +95,7 @@ trait UpdateHandler
|
||||
if (!$params['timeout']) {
|
||||
$params['timeout'] = 0.001;
|
||||
}
|
||||
yield \danog\MadelineProto\Tools::first([$this->waitUpdate(), \danog\MadelineProto\Tools::sleep($params['timeout'])]);
|
||||
yield $this->waitUpdate();
|
||||
}
|
||||
if (empty($this->updates)) {
|
||||
return [];
|
||||
|
@ -19,14 +19,10 @@
|
||||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use Amp\DoH\DoHConfig;
|
||||
use Amp\DoH\Nameserver;
|
||||
use Amp\DoH\Rfc8484StubResolver;
|
||||
use Amp\Loop;
|
||||
use Amp\Loop\Driver;
|
||||
use ReflectionClass;
|
||||
use function Amp\ByteStream\getStdin;
|
||||
use function Amp\Dns\resolver;
|
||||
use function Amp\Promise\wait;
|
||||
|
||||
class Magic
|
||||
@ -103,12 +99,18 @@ class Magic
|
||||
* @var int
|
||||
*/
|
||||
public static $pid;
|
||||
/**
|
||||
* Whether we've inited all light constants.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
private static $initedLight = false;
|
||||
/**
|
||||
* Whether we've inited all static constants.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public static $inited = false;
|
||||
private static $inited = false;
|
||||
/**
|
||||
* Bigint zero.
|
||||
*
|
||||
@ -220,85 +222,35 @@ class Magic
|
||||
/**
|
||||
* Initialize magic constants.
|
||||
*
|
||||
* @param bool $light Use lightweight initialization routine
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function classExists()
|
||||
public static function classExists(bool $light = false): void
|
||||
{
|
||||
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
|
||||
\set_exception_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionHandler']);
|
||||
if (!self::$inited) {
|
||||
if (!\defined('\\tgseclib\\Crypt\\Common\\SymmetricKey::MODE_IGE') || \tgseclib\Crypt\Common\SymmetricKey::MODE_IGE !== 7) {
|
||||
throw new Exception(\danog\MadelineProto\Lang::$current_lang['phpseclib_fork']);
|
||||
}
|
||||
foreach (['xml', 'fileinfo', 'json', 'mbstring'] as $extension) {
|
||||
if (!\extension_loaded($extension)) {
|
||||
throw Exception::extension($extension);
|
||||
if (self::$inited || (self::$initedLight && $light)) {
|
||||
return;
|
||||
}
|
||||
if (!self::$initedLight) {
|
||||
// Setup error reporting
|
||||
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
|
||||
\set_exception_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionHandler']);
|
||||
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
|
||||
try {
|
||||
\error_reporting(E_ALL);
|
||||
\ini_set('log_errors', 1);
|
||||
\ini_set('error_log', Magic::$script_cwd.'/MadelineProto.log');
|
||||
\error_log('Enabled PHP logging');
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
//$this->logger->logger('Could not enable PHP logging');
|
||||
}
|
||||
}
|
||||
self::$has_thread = \class_exists(\Thread::class) && \method_exists(\Thread::class, 'getCurrentThread');
|
||||
self::$BIG_ENDIAN = \pack('L', 1) === \pack('N', 1);
|
||||
self::$bigint = PHP_INT_SIZE < 8;
|
||||
self::$ipv6 = (bool) \strlen(@\file_get_contents('http://ipv6.google.com', false, \stream_context_create(['http' => ['timeout' => 1]]))) > 0;
|
||||
\preg_match('/const V = (\\d+);/', @\file_get_contents('https://raw.githubusercontent.com/danog/MadelineProto/master/src/danog/MadelineProto/MTProto.php'), $matches);
|
||||
if (isset($matches[1]) && \danog\MadelineProto\MTProto::V < (int) $matches[1]) {
|
||||
throw new \danog\MadelineProto\Exception(\hex2bin(\danog\MadelineProto\Lang::$current_lang['v_error']), 0, null, 'MadelineProto', 1);
|
||||
}
|
||||
if (\class_exists('\\danog\\MadelineProto\\VoIP')) {
|
||||
if (!\defined('\\danog\\MadelineProto\\VoIP::PHP_LIBTGVOIP_VERSION') || !\in_array(\danog\MadelineProto\VoIP::PHP_LIBTGVOIP_VERSION, ['1.5.0'])) {
|
||||
throw new \danog\MadelineProto\Exception(\hex2bin(\danog\MadelineProto\Lang::$current_lang['v_tgerror']), 0, null, 'MadelineProto', 1);
|
||||
}
|
||||
}
|
||||
self::$emojis = \json_decode(self::JSON_EMOJIS);
|
||||
self::$zero = new \tgseclib\Math\BigInteger(0);
|
||||
self::$one = new \tgseclib\Math\BigInteger(1);
|
||||
self::$two = new \tgseclib\Math\BigInteger(2);
|
||||
self::$three = new \tgseclib\Math\BigInteger(3);
|
||||
self::$four = new \tgseclib\Math\BigInteger(4);
|
||||
self::$twoe1984 = new \tgseclib\Math\BigInteger('1751908409537131537220509645351687597690304110853111572994449976845956819751541616602568796259317428464425605223064365804210081422215355425149431390635151955247955156636234741221447435733643262808668929902091770092492911737768377135426590363166295684370498604708288556044687341394398676292971255828404734517580702346564613427770683056761383955397564338690628093211465848244049196353703022640400205739093118270803778352768276670202698397214556629204420309965547056893233608758387329699097930255380715679250799950923553703740673620901978370802540218870279314810722790539899334271514365444369275682816');
|
||||
self::$twoe2047 = new \tgseclib\Math\BigInteger('16158503035655503650357438344334975980222051334857742016065172713762327569433945446598600705761456731844358980460949009747059779575245460547544076193224141560315438683650498045875098875194826053398028819192033784138396109321309878080919047169238085235290822926018152521443787945770532904303776199561965192760957166694834171210342487393282284747428088017663161029038902829665513096354230157075129296432088558362971801859230928678799175576150822952201848806616643615613562842355410104862578550863465661734839271290328348967522998634176499319107762583194718667771801067716614802322659239302476074096777926805529798115328');
|
||||
self::$twoe2048 = new \tgseclib\Math\BigInteger('32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230656');
|
||||
self::$twozerotwosixone = new \tgseclib\Math\BigInteger(20261);
|
||||
self::$zeroeight = new \tgseclib\Math\BigInteger('2147483648');
|
||||
// Check if we're in a console, for colorful log output
|
||||
try {
|
||||
self::$isatty = \defined('STDOUT') && \function_exists('posix_isatty') && \posix_isatty(STDOUT);
|
||||
self::$isatty = \defined(\STDOUT::class) && \function_exists('posix_isatty') && \posix_isatty(\STDOUT);
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
}
|
||||
self::$altervista = isset($_SERVER['SERVER_ADMIN']) && \strpos($_SERVER['SERVER_ADMIN'], 'altervista.org');
|
||||
self::$zerowebhost = isset($_SERVER['SERVER_ADMIN']) && \strpos($_SERVER['SERVER_ADMIN'], '000webhost.io');
|
||||
self::$can_getmypid = !self::$altervista && !self::$zerowebhost;
|
||||
self::$revision = @\file_get_contents(__DIR__.'/../../../.git/refs/heads/master');
|
||||
if (self::$revision) {
|
||||
self::$revision = \trim(self::$revision);
|
||||
$latest = @\file_get_contents('https://phar.madelineproto.xyz/release');
|
||||
if ($latest) {
|
||||
$latest = self::$revision === \trim($latest) ? '' : ' (AN UPDATE IS REQUIRED)';
|
||||
}
|
||||
self::$revision = 'Revision: '.self::$revision.$latest;
|
||||
}
|
||||
self::$can_parallel = false;
|
||||
if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && !(\class_exists(\Phar::class) && \Phar::running())) {
|
||||
try {
|
||||
$back = \debug_backtrace(0);
|
||||
\define('AMP_WORKER', 1);
|
||||
$promise = \Amp\File\get(\end($back)['file']);
|
||||
do {
|
||||
try {
|
||||
if (wait($promise)) {
|
||||
self::$can_parallel = true;
|
||||
break;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
if ($e->getMessage() !== 'Loop stopped without resolving the promise') {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
if (!self::$can_parallel && !\defined('AMP_WORKER')) {
|
||||
\define('AMP_WORKER', 1);
|
||||
}
|
||||
// Important, obtain root relative to caller script
|
||||
$backtrace = \debug_backtrace(0);
|
||||
self::$script_cwd = self::$cwd = \dirname(\end($backtrace)['file']);
|
||||
try {
|
||||
@ -306,8 +258,8 @@ class Magic
|
||||
self::$can_getcwd = true;
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
}
|
||||
// Even an empty handler is enough to catch ctrl+c
|
||||
if (\defined('SIGINT')) {
|
||||
// Define signal handlers
|
||||
if (\defined(\SIGINT::class)) {
|
||||
//if (function_exists('pcntl_async_signals')) pcntl_async_signals(true);
|
||||
try {
|
||||
Loop::unreference(Loop::onSignal(SIGINT, static function () {
|
||||
@ -321,32 +273,86 @@ class Magic
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
/*if (!self::$altervista && !self::$zerowebhost) {
|
||||
$DohConfig = new DoHConfig(
|
||||
[
|
||||
new Nameserver('https://mozilla.cloudflare-dns.com/dns-query'),
|
||||
new Nameserver('https://dns.google/resolve'),
|
||||
]
|
||||
);
|
||||
resolver(new Rfc8484StubResolver($DohConfig));
|
||||
}*/
|
||||
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
|
||||
try {
|
||||
\error_reporting(E_ALL);
|
||||
\ini_set('log_errors', 1);
|
||||
\ini_set('error_log', Magic::$script_cwd.'/MadelineProto.log');
|
||||
\error_log('Enabled PHP logging');
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
//$this->logger->logger('Could not enable PHP logging');
|
||||
}
|
||||
self::$initedLight = true;
|
||||
if ($light) {
|
||||
\define('AMP_WORKER', true);
|
||||
return;
|
||||
}
|
||||
$res = \json_decode(@\file_get_contents('https://rpc.madelineproto.xyz/v3.json'), true);
|
||||
if (isset($res['ok']) && $res['ok']) {
|
||||
RPCErrorException::$errorMethodMap = $res['result'];
|
||||
RPCErrorException::$descriptions += $res['human_result'];
|
||||
}
|
||||
self::$inited = true;
|
||||
}
|
||||
if (!\defined('\\tgseclib\\Crypt\\Common\\SymmetricKey::MODE_IGE') || \tgseclib\Crypt\Common\SymmetricKey::MODE_IGE !== 7) {
|
||||
throw new Exception(\danog\MadelineProto\Lang::$current_lang['phpseclib_fork']);
|
||||
}
|
||||
foreach (['xml', 'fileinfo', 'json', 'mbstring'] as $extension) {
|
||||
if (!\extension_loaded($extension)) {
|
||||
throw Exception::extension($extension);
|
||||
}
|
||||
}
|
||||
self::$has_thread = \class_exists(\Thread::class) && \method_exists(\Thread::class, 'getCurrentThread');
|
||||
self::$BIG_ENDIAN = \pack('L', 1) === \pack('N', 1);
|
||||
self::$bigint = PHP_INT_SIZE < 8;
|
||||
self::$ipv6 = (bool) \strlen(@\file_get_contents('http://ipv6.google.com', false, \stream_context_create(['http' => ['timeout' => 1]]))) > 0;
|
||||
\preg_match('/const V = (\\d+);/', @\file_get_contents('https://raw.githubusercontent.com/danog/MadelineProto/master/src/danog/MadelineProto/MTProto.php'), $matches);
|
||||
if (isset($matches[1]) && \danog\MadelineProto\MTProto::V < (int) $matches[1]) {
|
||||
throw new \danog\MadelineProto\Exception(\hex2bin(\danog\MadelineProto\Lang::$current_lang['v_error']), 0, null, 'MadelineProto', 1);
|
||||
}
|
||||
if (\class_exists('\\danog\\MadelineProto\\VoIP')) {
|
||||
if (!\defined('\\danog\\MadelineProto\\VoIP::PHP_LIBTGVOIP_VERSION') || !\in_array(\danog\MadelineProto\VoIP::PHP_LIBTGVOIP_VERSION, ['1.5.0'])) {
|
||||
throw new \danog\MadelineProto\Exception(\hex2bin(\danog\MadelineProto\Lang::$current_lang['v_tgerror']), 0, null, 'MadelineProto', 1);
|
||||
}
|
||||
}
|
||||
self::$emojis = \json_decode(self::JSON_EMOJIS);
|
||||
self::$zero = new \tgseclib\Math\BigInteger(0);
|
||||
self::$one = new \tgseclib\Math\BigInteger(1);
|
||||
self::$two = new \tgseclib\Math\BigInteger(2);
|
||||
self::$three = new \tgseclib\Math\BigInteger(3);
|
||||
self::$four = new \tgseclib\Math\BigInteger(4);
|
||||
self::$twoe1984 = new \tgseclib\Math\BigInteger('1751908409537131537220509645351687597690304110853111572994449976845956819751541616602568796259317428464425605223064365804210081422215355425149431390635151955247955156636234741221447435733643262808668929902091770092492911737768377135426590363166295684370498604708288556044687341394398676292971255828404734517580702346564613427770683056761383955397564338690628093211465848244049196353703022640400205739093118270803778352768276670202698397214556629204420309965547056893233608758387329699097930255380715679250799950923553703740673620901978370802540218870279314810722790539899334271514365444369275682816');
|
||||
self::$twoe2047 = new \tgseclib\Math\BigInteger('16158503035655503650357438344334975980222051334857742016065172713762327569433945446598600705761456731844358980460949009747059779575245460547544076193224141560315438683650498045875098875194826053398028819192033784138396109321309878080919047169238085235290822926018152521443787945770532904303776199561965192760957166694834171210342487393282284747428088017663161029038902829665513096354230157075129296432088558362971801859230928678799175576150822952201848806616643615613562842355410104862578550863465661734839271290328348967522998634176499319107762583194718667771801067716614802322659239302476074096777926805529798115328');
|
||||
self::$twoe2048 = new \tgseclib\Math\BigInteger('32317006071311007300714876688669951960444102669715484032130345427524655138867890893197201411522913463688717960921898019494119559150490921095088152386448283120630877367300996091750197750389652106796057638384067568276792218642619756161838094338476170470581645852036305042887575891541065808607552399123930385521914333389668342420684974786564569494856176035326322058077805659331026192708460314150258592864177116725943603718461857357598351152301645904403697613233287231227125684710820209725157101726931323469678542580656697935045997268352998638215525166389437335543602135433229604645318478604952148193555853611059596230656');
|
||||
self::$twozerotwosixone = new \tgseclib\Math\BigInteger(20261);
|
||||
self::$zeroeight = new \tgseclib\Math\BigInteger('2147483648');
|
||||
self::$altervista = isset($_SERVER['SERVER_ADMIN']) && \strpos($_SERVER['SERVER_ADMIN'], 'altervista.org');
|
||||
self::$zerowebhost = isset($_SERVER['SERVER_ADMIN']) && \strpos($_SERVER['SERVER_ADMIN'], '000webhost.io');
|
||||
self::$can_getmypid = !self::$altervista && !self::$zerowebhost;
|
||||
self::$revision = @\file_get_contents(__DIR__.'/../../../.git/refs/heads/master');
|
||||
if (self::$revision) {
|
||||
self::$revision = \trim(self::$revision);
|
||||
$latest = @\file_get_contents('https://phar.madelineproto.xyz/release');
|
||||
if ($latest) {
|
||||
$latest = self::$revision === \trim($latest) ? '' : ' (AN UPDATE IS REQUIRED)';
|
||||
}
|
||||
self::$revision = 'Revision: '.self::$revision.$latest;
|
||||
}
|
||||
self::$can_parallel = false;
|
||||
if ((PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') && !(\class_exists(\Phar::class) && \Phar::running())) {
|
||||
try {
|
||||
$back = \debug_backtrace(0);
|
||||
\define('AMP_WORKER', 1);
|
||||
$promise = \Amp\File\get(\end($back)['file']);
|
||||
do {
|
||||
try {
|
||||
if (wait($promise)) {
|
||||
self::$can_parallel = true;
|
||||
break;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
if ($e->getMessage() !== 'Loop stopped without resolving the promise') {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
} while (true);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
if (!self::$can_parallel && !\defined('AMP_WORKER')) {
|
||||
\define('AMP_WORKER', 1);
|
||||
}
|
||||
$res = \json_decode(@\file_get_contents('https://rpc.madelineproto.xyz/v3.json'), true);
|
||||
if (isset($res['ok']) && $res['ok']) {
|
||||
RPCErrorException::$errorMethodMap = $res['result'];
|
||||
RPCErrorException::$descriptions += $res['human_result'];
|
||||
}
|
||||
self::$inited = true;
|
||||
}
|
||||
/**
|
||||
* Check if this is a POSIX fork of the main PHP process.
|
||||
|
@ -43,7 +43,7 @@ class RPCErrorException extends \Exception
|
||||
self::$toReport = \array_slice(self::$toReport, -100);
|
||||
}
|
||||
self::$toReport []= [
|
||||
$method, $code, $error, time()
|
||||
$method, $code, $error, \time()
|
||||
];
|
||||
}
|
||||
if (!$description) {
|
||||
@ -53,24 +53,47 @@ class RPCErrorException extends \Exception
|
||||
}
|
||||
public function __toString()
|
||||
{
|
||||
$result = \sprintf(\danog\MadelineProto\Lang::$current_lang['rpc_tg_error'], self::localizeMessage($this->caller, $this->code, $this->message)." ({$this->code})", $this->rpc, $this->file, $this->line.PHP_EOL, \danog\MadelineProto\Magic::$revision.PHP_EOL.PHP_EOL).PHP_EOL.$this->getTLTrace().PHP_EOL;
|
||||
$this->localized ??= self::localizeMessage($this->caller, $this->code, $this->message);
|
||||
$result = \sprintf(\danog\MadelineProto\Lang::$current_lang['rpc_tg_error'], $this->localized." ({$this->code})", $this->rpc, $this->file, $this->line.PHP_EOL, \danog\MadelineProto\Magic::$revision.PHP_EOL.PHP_EOL).PHP_EOL.$this->getTLTrace().PHP_EOL;
|
||||
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
|
||||
$result = \str_replace(PHP_EOL, '<br>'.PHP_EOL, $result);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
/**
|
||||
* Get localized error name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLocalization(): string
|
||||
{
|
||||
$this->localized ??= self::localizeMessage($this->caller, $this->code, $this->message);
|
||||
return $this->localized;
|
||||
}
|
||||
/**
|
||||
* Set localized error name
|
||||
*
|
||||
* @param string $localization
|
||||
* @return void
|
||||
*/
|
||||
public function setLocalization(string $localization): void
|
||||
{
|
||||
$this->localized = $localization;
|
||||
}
|
||||
public function __construct($message = null, $code = 0, $caller = '', Exception $previous = null)
|
||||
{
|
||||
$this->rpc = $message;
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->prettifyTL($caller);
|
||||
$this->caller = $caller;
|
||||
$additional = [];
|
||||
foreach ($this->getTrace() as $level) {
|
||||
if (isset($level['function']) && $level['function'] === 'methodCall') {
|
||||
$this->line = $level['line'];
|
||||
$this->file = $level['file'];
|
||||
$additional = $level['args'];
|
||||
if (\is_string($caller)) {
|
||||
$this->prettifyTL($caller);
|
||||
$this->caller = $caller;
|
||||
$additional = [];
|
||||
foreach ($this->getTrace() as $level) {
|
||||
if (isset($level['function']) && $level['function'] === 'methodCall') {
|
||||
$this->line = $level['line'];
|
||||
$this->file = $level['file'];
|
||||
$additional = $level['args'];
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
@ -29,24 +29,6 @@ use function Amp\File\get;
|
||||
*/
|
||||
class Serialization
|
||||
{
|
||||
/**
|
||||
* List of session paths.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $paths = [];
|
||||
/**
|
||||
* Extract path components for serialization.
|
||||
*
|
||||
* @param string $file Session path
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function realpaths(string $file): array
|
||||
{
|
||||
$file = Tools::absolute($file);
|
||||
return ['file' => $file, 'lockfile' => $file.'.lock', 'tempfile' => $file.'.temp.session', 'session_lockfile' => $file.'.slock', ];
|
||||
}
|
||||
/**
|
||||
* Unserialize legacy session.
|
||||
*
|
||||
@ -58,8 +40,8 @@ class Serialization
|
||||
*/
|
||||
public static function legacyUnserialize(string $session): \Generator
|
||||
{
|
||||
$realpaths = self::realpaths($session);
|
||||
if (yield exists($realpaths['file'])) {
|
||||
$realpaths = new SessionPaths($session);
|
||||
if (yield exists($realpaths->getSessionPath())) {
|
||||
Logger::log('Waiting for exclusive session lock...');
|
||||
$warningId = Loop::delay(1000, static function () use (&$warningId) {
|
||||
Logger::log("It seems like the session is busy.");
|
||||
@ -68,7 +50,7 @@ class Serialization
|
||||
Loop::unreference($warningId);
|
||||
});
|
||||
Loop::unreference($warningId);
|
||||
$unlockGlobal = yield Tools::flock($realpaths['session_lockfile'], LOCK_EX, 1);
|
||||
$unlockGlobal = yield Tools::flock($realpaths->getSessionLockPath(), LOCK_EX, 1);
|
||||
Loop::cancel($warningId);
|
||||
$tempId = Shutdown::addCallback($unlockGlobal = static function () use ($unlockGlobal) {
|
||||
Logger::log("Unlocking exclusive session lock!");
|
||||
@ -78,10 +60,10 @@ class Serialization
|
||||
Logger::log("Got exclusive session lock!");
|
||||
|
||||
Logger::log('Waiting for shared lock of serialization lockfile...');
|
||||
$unlock = yield Tools::flock($realpaths['lockfile'], LOCK_SH);
|
||||
$unlock = yield Tools::flock($realpaths->getLockPath(), LOCK_SH);
|
||||
Logger::log('Shared lock acquired, deserializing...');
|
||||
try {
|
||||
$tounserialize = yield get($realpaths['file']);
|
||||
$tounserialize = yield get($realpaths->getSessionPath());
|
||||
} finally {
|
||||
$unlock();
|
||||
}
|
||||
|
120
src/danog/MadelineProto/SessionPaths.php
Normal file
120
src/danog/MadelineProto/SessionPaths.php
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Session paths 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;
|
||||
|
||||
/**
|
||||
* Session path information.
|
||||
*/
|
||||
class SessionPaths
|
||||
{
|
||||
/**
|
||||
* Session path.
|
||||
*/
|
||||
private string $sessionPath;
|
||||
/**
|
||||
* Global session lock path.
|
||||
*/
|
||||
private string $slockPath;
|
||||
/**
|
||||
* Session lock path.
|
||||
*/
|
||||
private string $lockPath;
|
||||
/**
|
||||
* IPC socket path.
|
||||
*/
|
||||
private string $ipcPath;
|
||||
/**
|
||||
* Temporary serialization path.
|
||||
*/
|
||||
private string $tempPath;
|
||||
/**
|
||||
* Construct session info from session name.
|
||||
*
|
||||
* @param string $session Session name
|
||||
*/
|
||||
public function __construct(string $session)
|
||||
{
|
||||
$session = Tools::absolute($session);
|
||||
$this->sessionPath = $session;
|
||||
$this->slockPath = "$session.slock";
|
||||
$this->lockPath = "$session.lock";
|
||||
$this->ipcPath = "$session.ipc";
|
||||
$this->tempPath = "$session.temp.session";
|
||||
}
|
||||
/**
|
||||
* Get session path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->sessionPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSessionPath(): string
|
||||
{
|
||||
return $this->sessionPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get global session lock path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSessionLockPath(): string
|
||||
{
|
||||
return $this->slockPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get lock path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLockPath(): string
|
||||
{
|
||||
return $this->lockPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get IPC socket path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIpcPath(): string
|
||||
{
|
||||
return $this->ipcPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get temporary serialization path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTempPath(): string
|
||||
{
|
||||
return $this->tempPath;
|
||||
}
|
||||
}
|
@ -108,6 +108,9 @@ class Snitch
|
||||
*/
|
||||
private function die(): void
|
||||
{
|
||||
die('Please do not remove madeline.phar, madeline.php and MadelineProto.log, or else MadelineProto will crash. If you have any problem with MadelineProto, report it to https://github.com/danog/MadelineProto or https://t.me/pwrtelegramgroup');
|
||||
Shutdown::removeCallback('restarter');
|
||||
$message = "Please do not remove madeline.phar, madeline.php and MadelineProto.log, or else MadelineProto will crash. If you have any problem with MadelineProto, report it to https://github.com/danog/MadelineProto or https://t.me/pwrtelegramgroup";
|
||||
Logger::log($message, Logger::FATAL_ERROR);
|
||||
die("$message\n");
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ trait PrettyException
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $tl_trace = '';
|
||||
public $tlTrace = '';
|
||||
/**
|
||||
* Method name.
|
||||
*
|
||||
@ -63,7 +63,18 @@ trait PrettyException
|
||||
*/
|
||||
public function getTLTrace(): string
|
||||
{
|
||||
return $this->tl_trace;
|
||||
return $this->tlTrace;
|
||||
}
|
||||
/**
|
||||
* Set TL trace.
|
||||
*
|
||||
* @param string $tlTrace TL trace
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setTLTrace(string $tlTrace): void
|
||||
{
|
||||
$this->tlTrace = $tlTrace;
|
||||
}
|
||||
/**
|
||||
* Generate async trace.
|
||||
@ -76,8 +87,8 @@ trait PrettyException
|
||||
public function prettifyTL(string $init = '', array $trace = null)
|
||||
{
|
||||
$this->method = $init;
|
||||
$previous_trace = $this->tl_trace;
|
||||
$this->tl_trace = '';
|
||||
$previous_trace = $this->tlTrace;
|
||||
$this->tlTrace = '';
|
||||
$eol = PHP_EOL;
|
||||
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
|
||||
$eol = '<br>'.PHP_EOL;
|
||||
@ -86,30 +97,30 @@ trait PrettyException
|
||||
foreach (\array_reverse($trace ?? $this->getTrace()) as $k => $frame) {
|
||||
if (isset($frame['function']) && \in_array($frame['function'], ['serializeParams', 'serializeObject'])) {
|
||||
if (($frame['args'][2] ?? '') !== '') {
|
||||
$this->tl_trace .= $tl ? "['".$frame['args'][2]."']" : "While serializing: \t".$frame['args'][2];
|
||||
$this->tlTrace .= $tl ? "['".$frame['args'][2]."']" : "While serializing: \t".$frame['args'][2];
|
||||
$tl = true;
|
||||
}
|
||||
} else {
|
||||
if ($tl) {
|
||||
$this->tl_trace .= $eol;
|
||||
$this->tlTrace .= $eol;
|
||||
}
|
||||
if (isset($frame['function']) && ($frame['function'] === 'handle_rpc_error' && $k === \count($this->getTrace()) - 1) || $frame['function'] === 'unserialize') {
|
||||
continue;
|
||||
}
|
||||
$this->tl_trace .= isset($frame['file']) ? \str_pad(\basename($frame['file']).'('.$frame['line'].'):', 20)."\t" : '';
|
||||
$this->tl_trace .= isset($frame['function']) ? $frame['function'].'(' : '';
|
||||
$this->tl_trace .= isset($frame['args']) ? \substr(\json_encode($frame['args']), 1, -1) : '';
|
||||
$this->tl_trace .= ')';
|
||||
$this->tl_trace .= $eol;
|
||||
$this->tlTrace .= isset($frame['file']) ? \str_pad(\basename($frame['file']).'('.$frame['line'].'):', 20)."\t" : '';
|
||||
$this->tlTrace .= isset($frame['function']) ? $frame['function'].'(' : '';
|
||||
$this->tlTrace .= isset($frame['args']) ? \substr(\json_encode($frame['args']), 1, -1) : '';
|
||||
$this->tlTrace .= ')';
|
||||
$this->tlTrace .= $eol;
|
||||
$tl = false;
|
||||
}
|
||||
}
|
||||
$this->tl_trace .= $init !== '' ? "['".$init."']" : '';
|
||||
$this->tl_trace = \implode($eol, \array_reverse(\explode($eol, $this->tl_trace)));
|
||||
$this->tlTrace .= $init !== '' ? "['".$init."']" : '';
|
||||
$this->tlTrace = \implode($eol, \array_reverse(\explode($eol, $this->tlTrace)));
|
||||
if ($previous_trace) {
|
||||
$this->tl_trace .= $eol.$eol;
|
||||
$this->tl_trace .= "Previous TL trace:{$eol}";
|
||||
$this->tl_trace .= $previous_trace;
|
||||
$this->tlTrace .= $eol.$eol;
|
||||
$this->tlTrace .= "Previous TL trace:{$eol}";
|
||||
$this->tlTrace .= $previous_trace;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -36,34 +36,12 @@ trait Loop
|
||||
*/
|
||||
private $stopLoop = false;
|
||||
/**
|
||||
* Start MadelineProto's update handling loop, or run the provided async callable.
|
||||
* Initialize self-restart hack.
|
||||
*
|
||||
* @param callable $callback Async callable to run
|
||||
*
|
||||
* @return mixed
|
||||
* @return void
|
||||
*/
|
||||
public function loop($callback = null): \Generator
|
||||
public function initSelfRestart(): void
|
||||
{
|
||||
if (\is_callable($callback)) {
|
||||
$this->logger->logger('Running async callable');
|
||||
return (yield $callback());
|
||||
}
|
||||
if ($callback instanceof Promise) {
|
||||
$this->logger->logger('Resolving async promise');
|
||||
return (yield $callback);
|
||||
}
|
||||
if (!$this->authorized) {
|
||||
$this->logger->logger('Not authorized, not starting event loop', \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
return false;
|
||||
}
|
||||
if (\in_array($this->settings['updates']['callback'], [['danog\\MadelineProto\\API', 'getUpdatesUpdateHandler'], 'getUpdatesUpdateHandler'])) {
|
||||
$this->logger->logger('Getupdates event handler is enabled, exiting from loop', \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
return false;
|
||||
}
|
||||
$this->logger->logger('Starting event loop');
|
||||
if (!\is_callable($this->loop_callback)) {
|
||||
$this->loop_callback = null;
|
||||
}
|
||||
static $inited = false;
|
||||
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' && !$inited) {
|
||||
$needs_restart = true;
|
||||
@ -104,12 +82,43 @@ trait Loop
|
||||
$this->closeConnection('Bot was started');
|
||||
$inited = true;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Start MadelineProto's update handling loop, or run the provided async callable.
|
||||
*
|
||||
* @param callable $callback Async callable to run
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function loop($callback = null): \Generator
|
||||
{
|
||||
if (\is_callable($callback)) {
|
||||
$this->logger->logger('Running async callable');
|
||||
return (yield $callback());
|
||||
}
|
||||
if ($callback instanceof Promise) {
|
||||
$this->logger->logger('Resolving async promise');
|
||||
return (yield $callback);
|
||||
}
|
||||
if (!$this->authorized) {
|
||||
$this->logger->logger('Not authorized, not starting event loop', \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
return false;
|
||||
}
|
||||
if (\in_array($this->settings['updates']['callback'], [['danog\\MadelineProto\\API', 'getUpdatesUpdateHandler'], 'getUpdatesUpdateHandler'])) {
|
||||
$this->logger->logger('Getupdates event handler is enabled, exiting from loop', \danog\MadelineProto\Logger::FATAL_ERROR);
|
||||
return false;
|
||||
}
|
||||
$this->logger->logger('Starting event loop');
|
||||
if (!\is_callable($this->loop_callback)) {
|
||||
$this->loop_callback = null;
|
||||
}
|
||||
if (!$this->settings['updates']['handle_updates']) {
|
||||
$this->settings['updates']['handle_updates'] = true;
|
||||
}
|
||||
if (!$this->settings['updates']['run_callback']) {
|
||||
$this->settings['updates']['run_callback'] = true;
|
||||
}
|
||||
$this->initSelfRestart();
|
||||
$this->startUpdateSystem();
|
||||
$this->logger->logger('Started update loop', \danog\MadelineProto\Logger::NOTICE);
|
||||
$this->stopLoop = false;
|
||||
|
@ -19,6 +19,7 @@
|
||||
|
||||
namespace danog\MadelineProto\Wrappers;
|
||||
|
||||
use danog\MadelineProto\MTProto;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
/**
|
||||
@ -33,7 +34,7 @@ trait Start
|
||||
*/
|
||||
public function start(): \Generator
|
||||
{
|
||||
if ($this->authorized === self::LOGGED_IN) {
|
||||
if (yield $this->getAuthorization() === MTProto::LOGGED_IN) {
|
||||
return yield from $this->fullGetSelf();
|
||||
}
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
@ -52,7 +53,7 @@ trait Start
|
||||
$this->serialize();
|
||||
return yield from $this->fullGetSelf();
|
||||
}
|
||||
if ($this->authorized === self::NOT_LOGGED_IN) {
|
||||
if ($this->authorized === MTProto::NOT_LOGGED_IN) {
|
||||
if (isset($_POST['phone_number'])) {
|
||||
yield from $this->webPhoneLogin();
|
||||
} elseif (isset($_POST['token'])) {
|
||||
@ -60,26 +61,26 @@ trait Start
|
||||
} else {
|
||||
yield from $this->webEcho();
|
||||
}
|
||||
} elseif ($this->authorized === self::WAITING_CODE) {
|
||||
} elseif ($this->authorized === MTProto::WAITING_CODE) {
|
||||
if (isset($_POST['phone_code'])) {
|
||||
yield from $this->webCompletePhoneLogin();
|
||||
} else {
|
||||
yield from $this->webEcho("You didn't provide a phone code!");
|
||||
}
|
||||
} elseif ($this->authorized === self::WAITING_PASSWORD) {
|
||||
} elseif ($this->authorized === MTProto::WAITING_PASSWORD) {
|
||||
if (isset($_POST['password'])) {
|
||||
yield from $this->webComplete2faLogin();
|
||||
} else {
|
||||
yield from $this->webEcho("You didn't provide the password!");
|
||||
}
|
||||
} elseif ($this->authorized === self::WAITING_SIGNUP) {
|
||||
} elseif ($this->authorized === MTProto::WAITING_SIGNUP) {
|
||||
if (isset($_POST['first_name'])) {
|
||||
yield from $this->webCompleteSignup();
|
||||
} else {
|
||||
yield from $this->webEcho("You didn't provide the first name!");
|
||||
}
|
||||
}
|
||||
if ($this->authorized === self::LOGGED_IN) {
|
||||
if ($this->authorized === MTProto::LOGGED_IN) {
|
||||
$this->serialize();
|
||||
return yield from $this->fullGetSelf();
|
||||
}
|
||||
|
@ -1,9 +1,22 @@
|
||||
<?php
|
||||
|
||||
\define('MADELINE_PHP', __FILE__);
|
||||
|
||||
function ___install_madeline()
|
||||
{
|
||||
if (\count(\debug_backtrace(0)) === 1) {
|
||||
die('You must include this file in another PHP script'.PHP_EOL);
|
||||
if (isset($GLOBALS['argv']) && !empty($GLOBALS['argv'])) {
|
||||
$arguments = $GLOBALS['argv'];
|
||||
} elseif (isset($_GET['argv']) && !empty($_GET['argv'])) {
|
||||
$arguments = $_GET['argv'];
|
||||
} else {
|
||||
$arguments = [];
|
||||
}
|
||||
if (isset($arguments[1]) && $arguments[1] === 'madeline-ipc') {
|
||||
\define(\MADELINE_WORKER_START::class, $arguments[2]);
|
||||
} else {
|
||||
die('You must include this file in another PHP script'.PHP_EOL);
|
||||
}
|
||||
}
|
||||
$old = false;
|
||||
if (PHP_MAJOR_VERSION === 5) {
|
||||
|
Loading…
Reference in New Issue
Block a user