Implement IPC
This commit is contained in:
parent
7d29bd84bc
commit
c8cdf328c0
|
@ -92,7 +92,7 @@ class API extends InternalDoc
|
||||||
private $wrapper;
|
private $wrapper;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Global session unlock callback
|
* Global session unlock callback.
|
||||||
*
|
*
|
||||||
* @var callback
|
* @var callback
|
||||||
*/
|
*/
|
||||||
|
@ -146,6 +146,7 @@ class API extends InternalDoc
|
||||||
|
|
||||||
unset($unserialized);
|
unset($unserialized);
|
||||||
|
|
||||||
|
$this->API->wrapper = $this->wrapper;
|
||||||
yield from $this->API->initAsynchronously();
|
yield from $this->API->initAsynchronously();
|
||||||
$this->APIFactory();
|
$this->APIFactory();
|
||||||
$this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
|
$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'];
|
$settings['app_info']['api_hash'] = $app['api_hash'];
|
||||||
}
|
}
|
||||||
$this->API = new MTProto($settings);
|
$this->API = new MTProto($settings);
|
||||||
|
$this->API->wrapper = $this->wrapper;
|
||||||
yield from $this->API->initAsynchronously();
|
yield from $this->API->initAsynchronously();
|
||||||
$this->APIFactory();
|
$this->APIFactory();
|
||||||
$this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
|
$this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
|
||||||
|
@ -186,14 +188,18 @@ class API extends InternalDoc
|
||||||
{
|
{
|
||||||
$this->init();
|
$this->init();
|
||||||
if (!$this->oldInstance) {
|
if (!$this->oldInstance) {
|
||||||
$this->logger->logger('Shutting down MadelineProto (API)');
|
$this->logger->logger('Shutting down MadelineProto ('.\explode('\\', \get_class($this))[2].')');
|
||||||
if ($this->API) {
|
if ($this->API) {
|
||||||
$this->API->destructing = true;
|
$this->API->destructing = true;
|
||||||
$this->API->unreference();
|
$this->API->unreference();
|
||||||
}
|
}
|
||||||
$this->destructing = true;
|
$this->destructing = true;
|
||||||
Tools::wait($this->wrapper->serialize());
|
if (isset($this->wrapper)) {
|
||||||
if ($this->unlock) ($this->unlock)();
|
Tools::wait($this->wrapper->serialize());
|
||||||
|
}
|
||||||
|
if ($this->unlock) {
|
||||||
|
($this->unlock)();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
$this->logger->logger('Shutting down MadelineProto (old deserialized instance of API)');
|
$this->logger->logger('Shutting down MadelineProto (old deserialized instance of API)');
|
||||||
}
|
}
|
||||||
|
|
|
@ -160,6 +160,18 @@ final class APIWrapper
|
||||||
return $this->factory;
|
return $this->factory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get IPC path.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getIpcPath(): string
|
||||||
|
{
|
||||||
|
return (new SessionPaths($this->session))->getIpcPath();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize session.
|
* Serialize session.
|
||||||
*
|
*
|
||||||
|
@ -171,6 +183,10 @@ final class APIWrapper
|
||||||
Logger::log("Not serializing, no session");
|
Logger::log("Not serializing, no session");
|
||||||
return new Success();
|
return new Success();
|
||||||
}
|
}
|
||||||
|
if ($this->API instanceof FastAPI) {
|
||||||
|
Logger::log("Not serializing, IPC client");
|
||||||
|
return new Success();
|
||||||
|
}
|
||||||
return Tools::callFork((function (): \Generator {
|
return Tools::callFork((function (): \Generator {
|
||||||
if (isset($this->API->flushSettings) && $this->API->flushSettings) {
|
if (isset($this->API->flushSettings) && $this->API->flushSettings) {
|
||||||
$this->API->flushSettings = false;
|
$this->API->flushSettings = false;
|
||||||
|
@ -183,9 +199,9 @@ final class APIWrapper
|
||||||
yield from $this->API->initAsynchronously();
|
yield from $this->API->initAsynchronously();
|
||||||
}
|
}
|
||||||
$this->serialized = \time();
|
$this->serialized = \time();
|
||||||
$realpaths = Serialization::realpaths($this->session);
|
$realpaths = new SessionPaths($this->session);
|
||||||
Logger::log('Waiting for exclusive lock of serialization lockfile...');
|
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');
|
Logger::log('Lock acquired, serializing');
|
||||||
try {
|
try {
|
||||||
if (!$this->gettingApiId) {
|
if (!$this->gettingApiId) {
|
||||||
|
@ -198,8 +214,8 @@ final class APIWrapper
|
||||||
$this->API->settings['logger']['logger_param'] = [$this->API, 'noop'];
|
$this->API->settings['logger']['logger_param'] = [$this->API, 'noop'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$wrote = yield put($realpaths['tempfile'], \serialize($this));
|
$wrote = yield put($realpaths->getTempPath(), \serialize($this));
|
||||||
yield renameAsync($realpaths['tempfile'], $realpaths['file']);
|
yield renameAsync($realpaths->getTempPath(), $realpaths->getSessionPath());
|
||||||
} finally {
|
} finally {
|
||||||
if (!$this->gettingApiId) {
|
if (!$this->gettingApiId) {
|
||||||
$this->API->settings['updates']['callback'] = $update_closure;
|
$this->API->settings['updates']['callback'] = $update_closure;
|
||||||
|
|
|
@ -168,7 +168,6 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
||||||
$name = $this->namespace.$name;
|
$name = $this->namespace.$name;
|
||||||
$aargs = isset($arguments[1]) && \is_array($arguments[1]) ? $arguments[1] : [];
|
$aargs = isset($arguments[1]) && \is_array($arguments[1]) ? $arguments[1] : [];
|
||||||
$aargs['apifactory'] = true;
|
$aargs['apifactory'] = true;
|
||||||
$aargs['datacenter'] = $this->API->datacenter->curdc;
|
|
||||||
$args = isset($arguments[0]) && \is_array($arguments[0]) ? $arguments[0] : [];
|
$args = isset($arguments[0]) && \is_array($arguments[0]) ? $arguments[0] : [];
|
||||||
return yield from $this->API->methodCallAsyncRead($name, $args, $aargs);
|
return yield from $this->API->methodCallAsyncRead($name, $args, $aargs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,9 @@ trait Start
|
||||||
*/
|
*/
|
||||||
private function APIStart(array $settings): \Generator
|
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') {
|
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||||
$stdout = getStdout();
|
$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)
|
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']);
|
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
|
||||||
\danog\MadelineProto\Magic::classExists();
|
\danog\MadelineProto\Magic::classExists();
|
||||||
$realpaths = Serialization::realpaths($session);
|
$realpaths = new SessionPaths($session);
|
||||||
$this->session = $realpaths['file'];
|
$this->session = $realpaths->getSessionPath();
|
||||||
foreach ($paths as $path => $settings) {
|
foreach ($paths as $path => $settings) {
|
||||||
$this->addInstance($path, $settings);
|
$this->addInstance($path, $settings);
|
||||||
}
|
}
|
||||||
if (\file_exists($realpaths['file'])) {
|
if (\file_exists($realpaths->getSessionPath())) {
|
||||||
if (!\file_exists($realpaths['lockfile'])) {
|
if (!\file_exists($realpaths->getLockPath())) {
|
||||||
\touch($realpaths['lockfile']);
|
\touch($realpaths->getLockPath());
|
||||||
\clearstatcache();
|
\clearstatcache();
|
||||||
}
|
}
|
||||||
$realpaths['lockfile'] = \fopen($realpaths['lockfile'], 'r');
|
$lock = \fopen($realpaths->getLockPath(), 'r');
|
||||||
\danog\MadelineProto\Logger::log('Waiting for shared lock of serialization lockfile...');
|
\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...');
|
\danog\MadelineProto\Logger::log('Shared lock acquired, deserializing...');
|
||||||
try {
|
try {
|
||||||
$tounserialize = \file_get_contents($realpaths['file']);
|
$tounserialize = \file_get_contents($realpaths->getSessionPath());
|
||||||
} finally {
|
} finally {
|
||||||
\flock($realpaths['lockfile'], LOCK_UN);
|
\flock($lock, LOCK_UN);
|
||||||
\fclose($realpaths['lockfile']);
|
\fclose($lock);
|
||||||
}
|
}
|
||||||
$deserialized = \unserialize($tounserialize);
|
$deserialized = \unserialize($tounserialize);
|
||||||
/*foreach ($deserialized['instance_paths'] as $path) {
|
/*foreach ($deserialized['instance_paths'] as $path) {
|
||||||
|
@ -115,21 +115,21 @@ class CombinedAPI
|
||||||
$filename = $this->session;
|
$filename = $this->session;
|
||||||
}
|
}
|
||||||
Logger::log(\danog\MadelineProto\Lang::$current_lang['serializing_madelineproto']);
|
Logger::log(\danog\MadelineProto\Lang::$current_lang['serializing_madelineproto']);
|
||||||
$realpaths = Serialization::realpaths($filename);
|
$realpaths = new SessionPaths($filename);
|
||||||
if (!\file_exists($realpaths['lockfile'])) {
|
if (!\file_exists($realpaths->getLockPath())) {
|
||||||
\touch($realpaths['lockfile']);
|
\touch($realpaths->getLockPath());
|
||||||
\clearstatcache();
|
\clearstatcache();
|
||||||
}
|
}
|
||||||
$realpaths['lockfile'] = \fopen($realpaths['lockfile'], 'w');
|
$lock = \fopen($realpaths->getLockPath(), 'w');
|
||||||
\danog\MadelineProto\Logger::log('Waiting for exclusive lock of serialization lockfile...');
|
\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');
|
\danog\MadelineProto\Logger::log('Lock acquired, serializing');
|
||||||
try {
|
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]));
|
$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['tempfile'], $realpaths['file']);
|
\rename($realpaths->getTempPath(), $realpaths->getSessionPath());
|
||||||
} finally {
|
} finally {
|
||||||
\flock($realpaths['lockfile'], LOCK_UN);
|
\flock($lock, LOCK_UN);
|
||||||
\fclose($realpaths['lockfile']);
|
\fclose($lock);
|
||||||
}
|
}
|
||||||
$this->serialized = \time();
|
$this->serialized = \time();
|
||||||
return $wrote;
|
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) {
|
if ($mode === self::NO_LOGGER) {
|
||||||
$mode = (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') ? Logger::ECHO_LOGGER : Logger::FILE_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);
|
$level = \max($level, self::NOTICE);
|
||||||
$max_size = \max($max_size, 100 * 1024);
|
$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\DbPropertiesFabric;
|
||||||
use danog\MadelineProto\Db\DbPropertiesTrait;
|
use danog\MadelineProto\Db\DbPropertiesTrait;
|
||||||
use danog\MadelineProto\Db\Mysql;
|
use danog\MadelineProto\Db\Mysql;
|
||||||
|
use danog\MadelineProto\Ipc\Server;
|
||||||
use danog\MadelineProto\Loop\Generic\PeriodicLoop;
|
use danog\MadelineProto\Loop\Generic\PeriodicLoop;
|
||||||
use danog\MadelineProto\Loop\Update\FeedLoop;
|
use danog\MadelineProto\Loop\Update\FeedLoop;
|
||||||
use danog\MadelineProto\Loop\Update\SeqLoop;
|
use danog\MadelineProto\Loop\Update\SeqLoop;
|
||||||
|
@ -384,6 +385,12 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||||
* @var PeriodicLoop
|
* @var PeriodicLoop
|
||||||
*/
|
*/
|
||||||
private $rpcLoop;
|
private $rpcLoop;
|
||||||
|
/**
|
||||||
|
* IPC server.
|
||||||
|
*
|
||||||
|
* @var Server
|
||||||
|
*/
|
||||||
|
private $ipcServer;
|
||||||
/**
|
/**
|
||||||
* Feeder loops.
|
* Feeder loops.
|
||||||
*
|
*
|
||||||
|
@ -678,6 +685,15 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||||
{
|
{
|
||||||
return $this->datacenter->getDataCenterConnections();
|
return $this->datacenter->getDataCenterConnections();
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Get main DC ID.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getDataCenterId(): int
|
||||||
|
{
|
||||||
|
return $this->datacenter->curdc;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Prompt serialization of instance.
|
* Prompt serialization of instance.
|
||||||
*
|
*
|
||||||
|
@ -716,12 +732,17 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||||
if (!$this->rpcLoop) {
|
if (!$this->rpcLoop) {
|
||||||
$this->rpcLoop = new PeriodicLoop($this, [$this, 'rpcReport'], 'config', 60);
|
$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->callCheckerLoop->start();
|
||||||
$this->serializeLoop->start();
|
$this->serializeLoop->start();
|
||||||
$this->phoneConfigLoop->start();
|
$this->phoneConfigLoop->start();
|
||||||
$this->configLoop->start();
|
$this->configLoop->start();
|
||||||
$this->checkTosLoop->start();
|
$this->checkTosLoop->start();
|
||||||
$this->rpcLoop->start();
|
$this->rpcLoop->start();
|
||||||
|
$this->ipcServer->start();
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Stop all internal loops.
|
* Stop all internal loops.
|
||||||
|
@ -754,6 +775,10 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||||
$this->rpcLoop->signal(true);
|
$this->rpcLoop->signal(true);
|
||||||
$this->rpcLoop = null;
|
$this->rpcLoop = null;
|
||||||
}
|
}
|
||||||
|
if ($this->ipcServer) {
|
||||||
|
$this->ipcServer->signal(null);
|
||||||
|
$this->ipcServer = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Report RPC errors.
|
* Report RPC errors.
|
||||||
|
@ -1056,7 +1081,7 @@ class MTProto extends AsyncConstruct implements TLCallback
|
||||||
*/
|
*/
|
||||||
public static function parseSettings(array $settings, array $previousSettings = []): array
|
public static function parseSettings(array $settings, array $previousSettings = []): array
|
||||||
{
|
{
|
||||||
Magic::classExists();
|
//Magic::classExists();
|
||||||
$settings = \array_replace_recursive($previousSettings, $settings);
|
$settings = \array_replace_recursive($previousSettings, $settings);
|
||||||
if (isset($previousSettings['connection_settings']['default_dc'])) {
|
if (isset($previousSettings['connection_settings']['default_dc'])) {
|
||||||
$settings['connection_settings']['default_dc'] = $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'];
|
return $this->authorization['user'];
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Get authorization info.
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function getAuthorization(): int
|
||||||
|
{
|
||||||
|
return $this->authorized;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* IDs of peers where to report errors.
|
* IDs of peers where to report errors.
|
||||||
*
|
*
|
||||||
|
|
|
@ -19,20 +19,16 @@
|
||||||
|
|
||||||
namespace danog\MadelineProto\MTProtoSession;
|
namespace danog\MadelineProto\MTProtoSession;
|
||||||
|
|
||||||
use Amp\Deferred;
|
|
||||||
use Amp\Promise;
|
|
||||||
use danog\MadelineProto\Loop\Connection\WriteLoop;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages acknowledgement of messages.
|
* Manages acknowledgement of messages.
|
||||||
*/
|
*/
|
||||||
trait AckHandler
|
trait AckHandler
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Acknowledge outgoing message ID
|
* Acknowledge outgoing message ID.
|
||||||
*
|
*
|
||||||
* @param string|int $message_id Message Id
|
* @param string|int $message_id Message Id
|
||||||
*
|
*
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function ackOutgoingMessageId($message_id): bool
|
public function ackOutgoingMessageId($message_id): bool
|
||||||
|
@ -45,10 +41,10 @@ trait AckHandler
|
||||||
return true;
|
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
|
* @param string|int $message_id Message ID
|
||||||
*
|
*
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function gotResponseForOutgoingMessageId($message_id): bool
|
public function gotResponseForOutgoingMessageId($message_id): bool
|
||||||
|
@ -70,10 +66,10 @@ trait AckHandler
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Acknowledge incoming message ID
|
* Acknowledge incoming message ID.
|
||||||
*
|
*
|
||||||
* @param string|int $message_id Message ID
|
* @param string|int $message_id Message ID
|
||||||
*
|
*
|
||||||
* @return boolean
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
public function ackIncomingMessageId($message_id): bool
|
public function ackIncomingMessageId($message_id): bool
|
||||||
|
|
|
@ -19,21 +19,17 @@
|
||||||
|
|
||||||
namespace danog\MadelineProto\MTProtoSession;
|
namespace danog\MadelineProto\MTProtoSession;
|
||||||
|
|
||||||
use Amp\Loop;
|
|
||||||
use danog\MadelineProto\Logger;
|
|
||||||
use danog\MadelineProto\MTProto;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages responses.
|
* Manages responses.
|
||||||
*/
|
*/
|
||||||
trait Reliable
|
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 string|int $req_msg_id Message ID of msgs_state_req that initiated this
|
||||||
* @param array $msg_ids Message IDs to send info about
|
* @param array $msg_ids Message IDs to send info about
|
||||||
*
|
*
|
||||||
* @return \Generator
|
* @return \Generator
|
||||||
*/
|
*/
|
||||||
public function sendMsgsStateInfo($req_msg_id, array $msg_ids): \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;
|
$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'];
|
$botAPI = isset($request['botAPI']) && $request['botAPI'];
|
||||||
if (isset($response['_']) && !$this->isCdn() && $this->API->getTL()->getConstructors()->findByPredicate($response['_'])['type'] === 'Updates') {
|
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));
|
\danog\MadelineProto\Tools::callForkDefer($this->API->handleUpdates($response));
|
||||||
}
|
}
|
||||||
unset($request);
|
unset($request);
|
||||||
|
|
|
@ -128,11 +128,11 @@ class MinDatabase implements TLCallback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$frames = \array_reverse($frames);
|
$frames = \array_reverse($frames);
|
||||||
$tl_trace = \array_shift($frames);
|
$tlTrace = \array_shift($frames);
|
||||||
foreach ($frames as $frame) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
$peers = [];
|
$peers = [];
|
||||||
|
|
|
@ -160,11 +160,11 @@ class ReferenceDatabase implements TLCallback
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$frames = \array_reverse($frames);
|
$frames = \array_reverse($frames);
|
||||||
$tl_trace = \array_shift($frames);
|
$tlTrace = \array_shift($frames);
|
||||||
foreach ($frames as $frame) {
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
if (!isset($location['file_reference'])) {
|
if (!isset($location['file_reference'])) {
|
||||||
|
|
|
@ -95,7 +95,7 @@ trait UpdateHandler
|
||||||
if (!$params['timeout']) {
|
if (!$params['timeout']) {
|
||||||
$params['timeout'] = 0.001;
|
$params['timeout'] = 0.001;
|
||||||
}
|
}
|
||||||
yield \danog\MadelineProto\Tools::first([$this->waitUpdate(), \danog\MadelineProto\Tools::sleep($params['timeout'])]);
|
yield $this->waitUpdate();
|
||||||
}
|
}
|
||||||
if (empty($this->updates)) {
|
if (empty($this->updates)) {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
@ -19,14 +19,10 @@
|
||||||
|
|
||||||
namespace danog\MadelineProto;
|
namespace danog\MadelineProto;
|
||||||
|
|
||||||
use Amp\DoH\DoHConfig;
|
|
||||||
use Amp\DoH\Nameserver;
|
|
||||||
use Amp\DoH\Rfc8484StubResolver;
|
|
||||||
use Amp\Loop;
|
use Amp\Loop;
|
||||||
use Amp\Loop\Driver;
|
use Amp\Loop\Driver;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use function Amp\ByteStream\getStdin;
|
use function Amp\ByteStream\getStdin;
|
||||||
use function Amp\Dns\resolver;
|
|
||||||
use function Amp\Promise\wait;
|
use function Amp\Promise\wait;
|
||||||
|
|
||||||
class Magic
|
class Magic
|
||||||
|
@ -103,12 +99,18 @@ class Magic
|
||||||
* @var int
|
* @var int
|
||||||
*/
|
*/
|
||||||
public static $pid;
|
public static $pid;
|
||||||
|
/**
|
||||||
|
* Whether we've inited all light constants.
|
||||||
|
*
|
||||||
|
* @var boolean
|
||||||
|
*/
|
||||||
|
private static $initedLight = false;
|
||||||
/**
|
/**
|
||||||
* Whether we've inited all static constants.
|
* Whether we've inited all static constants.
|
||||||
*
|
*
|
||||||
* @var boolean
|
* @var boolean
|
||||||
*/
|
*/
|
||||||
public static $inited = false;
|
private static $inited = false;
|
||||||
/**
|
/**
|
||||||
* Bigint zero.
|
* Bigint zero.
|
||||||
*
|
*
|
||||||
|
@ -220,85 +222,35 @@ class Magic
|
||||||
/**
|
/**
|
||||||
* Initialize magic constants.
|
* Initialize magic constants.
|
||||||
*
|
*
|
||||||
|
* @param bool $light Use lightweight initialization routine
|
||||||
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public static function classExists()
|
public static function classExists(bool $light = false): void
|
||||||
{
|
{
|
||||||
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
|
if (self::$inited || (self::$initedLight && $light)) {
|
||||||
\set_exception_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionHandler']);
|
return;
|
||||||
if (!self::$inited) {
|
}
|
||||||
if (!\defined('\\tgseclib\\Crypt\\Common\\SymmetricKey::MODE_IGE') || \tgseclib\Crypt\Common\SymmetricKey::MODE_IGE !== 7) {
|
if (!self::$initedLight) {
|
||||||
throw new Exception(\danog\MadelineProto\Lang::$current_lang['phpseclib_fork']);
|
// Setup error reporting
|
||||||
}
|
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
|
||||||
foreach (['xml', 'fileinfo', 'json', 'mbstring'] as $extension) {
|
\set_exception_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionHandler']);
|
||||||
if (!\extension_loaded($extension)) {
|
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
|
||||||
throw Exception::extension($extension);
|
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');
|
// Check if we're in a console, for colorful log output
|
||||||
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');
|
|
||||||
try {
|
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) {
|
} catch (\danog\MadelineProto\Exception $e) {
|
||||||
}
|
}
|
||||||
self::$altervista = isset($_SERVER['SERVER_ADMIN']) && \strpos($_SERVER['SERVER_ADMIN'], 'altervista.org');
|
// Important, obtain root relative to caller script
|
||||||
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);
|
|
||||||
}
|
|
||||||
$backtrace = \debug_backtrace(0);
|
$backtrace = \debug_backtrace(0);
|
||||||
self::$script_cwd = self::$cwd = \dirname(\end($backtrace)['file']);
|
self::$script_cwd = self::$cwd = \dirname(\end($backtrace)['file']);
|
||||||
try {
|
try {
|
||||||
|
@ -306,8 +258,8 @@ class Magic
|
||||||
self::$can_getcwd = true;
|
self::$can_getcwd = true;
|
||||||
} catch (\danog\MadelineProto\Exception $e) {
|
} catch (\danog\MadelineProto\Exception $e) {
|
||||||
}
|
}
|
||||||
// Even an empty handler is enough to catch ctrl+c
|
// Define signal handlers
|
||||||
if (\defined('SIGINT')) {
|
if (\defined(\SIGINT::class)) {
|
||||||
//if (function_exists('pcntl_async_signals')) pcntl_async_signals(true);
|
//if (function_exists('pcntl_async_signals')) pcntl_async_signals(true);
|
||||||
try {
|
try {
|
||||||
Loop::unreference(Loop::onSignal(SIGINT, static function () {
|
Loop::unreference(Loop::onSignal(SIGINT, static function () {
|
||||||
|
@ -321,32 +273,86 @@ class Magic
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*if (!self::$altervista && !self::$zerowebhost) {
|
self::$initedLight = true;
|
||||||
$DohConfig = new DoHConfig(
|
if ($light) {
|
||||||
[
|
\define('AMP_WORKER', true);
|
||||||
new Nameserver('https://mozilla.cloudflare-dns.com/dns-query'),
|
return;
|
||||||
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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$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.
|
* 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 = \array_slice(self::$toReport, -100);
|
||||||
}
|
}
|
||||||
self::$toReport []= [
|
self::$toReport []= [
|
||||||
$method, $code, $error, time()
|
$method, $code, $error, \time()
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
if (!$description) {
|
if (!$description) {
|
||||||
|
@ -53,24 +53,47 @@ class RPCErrorException extends \Exception
|
||||||
}
|
}
|
||||||
public function __toString()
|
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') {
|
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
|
||||||
$result = \str_replace(PHP_EOL, '<br>'.PHP_EOL, $result);
|
$result = \str_replace(PHP_EOL, '<br>'.PHP_EOL, $result);
|
||||||
}
|
}
|
||||||
return $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)
|
public function __construct($message = null, $code = 0, $caller = '', Exception $previous = null)
|
||||||
{
|
{
|
||||||
$this->rpc = $message;
|
$this->rpc = $message;
|
||||||
parent::__construct($message, $code, $previous);
|
parent::__construct($message, $code, $previous);
|
||||||
$this->prettifyTL($caller);
|
if (\is_string($caller)) {
|
||||||
$this->caller = $caller;
|
$this->prettifyTL($caller);
|
||||||
$additional = [];
|
$this->caller = $caller;
|
||||||
foreach ($this->getTrace() as $level) {
|
$additional = [];
|
||||||
if (isset($level['function']) && $level['function'] === 'methodCall') {
|
foreach ($this->getTrace() as $level) {
|
||||||
$this->line = $level['line'];
|
if (isset($level['function']) && $level['function'] === 'methodCall') {
|
||||||
$this->file = $level['file'];
|
$this->line = $level['line'];
|
||||||
$additional = $level['args'];
|
$this->file = $level['file'];
|
||||||
|
$additional = $level['args'];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -29,24 +29,6 @@ use function Amp\File\get;
|
||||||
*/
|
*/
|
||||||
class Serialization
|
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.
|
* Unserialize legacy session.
|
||||||
*
|
*
|
||||||
|
@ -58,8 +40,8 @@ class Serialization
|
||||||
*/
|
*/
|
||||||
public static function legacyUnserialize(string $session): \Generator
|
public static function legacyUnserialize(string $session): \Generator
|
||||||
{
|
{
|
||||||
$realpaths = self::realpaths($session);
|
$realpaths = new SessionPaths($session);
|
||||||
if (yield exists($realpaths['file'])) {
|
if (yield exists($realpaths->getSessionPath())) {
|
||||||
Logger::log('Waiting for exclusive session lock...');
|
Logger::log('Waiting for exclusive session lock...');
|
||||||
$warningId = Loop::delay(1000, static function () use (&$warningId) {
|
$warningId = Loop::delay(1000, static function () use (&$warningId) {
|
||||||
Logger::log("It seems like the session is busy.");
|
Logger::log("It seems like the session is busy.");
|
||||||
|
@ -68,7 +50,7 @@ class Serialization
|
||||||
Loop::unreference($warningId);
|
Loop::unreference($warningId);
|
||||||
});
|
});
|
||||||
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);
|
Loop::cancel($warningId);
|
||||||
$tempId = Shutdown::addCallback($unlockGlobal = static function () use ($unlockGlobal) {
|
$tempId = Shutdown::addCallback($unlockGlobal = static function () use ($unlockGlobal) {
|
||||||
Logger::log("Unlocking exclusive session lock!");
|
Logger::log("Unlocking exclusive session lock!");
|
||||||
|
@ -78,10 +60,10 @@ class Serialization
|
||||||
Logger::log("Got exclusive session lock!");
|
Logger::log("Got exclusive session lock!");
|
||||||
|
|
||||||
Logger::log('Waiting for shared lock of serialization lockfile...');
|
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...');
|
Logger::log('Shared lock acquired, deserializing...');
|
||||||
try {
|
try {
|
||||||
$tounserialize = yield get($realpaths['file']);
|
$tounserialize = yield get($realpaths->getSessionPath());
|
||||||
} finally {
|
} finally {
|
||||||
$unlock();
|
$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
|
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
|
* @var string
|
||||||
*/
|
*/
|
||||||
public $tl_trace = '';
|
public $tlTrace = '';
|
||||||
/**
|
/**
|
||||||
* Method name.
|
* Method name.
|
||||||
*
|
*
|
||||||
|
@ -63,7 +63,18 @@ trait PrettyException
|
||||||
*/
|
*/
|
||||||
public function getTLTrace(): string
|
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.
|
* Generate async trace.
|
||||||
|
@ -76,8 +87,8 @@ trait PrettyException
|
||||||
public function prettifyTL(string $init = '', array $trace = null)
|
public function prettifyTL(string $init = '', array $trace = null)
|
||||||
{
|
{
|
||||||
$this->method = $init;
|
$this->method = $init;
|
||||||
$previous_trace = $this->tl_trace;
|
$previous_trace = $this->tlTrace;
|
||||||
$this->tl_trace = '';
|
$this->tlTrace = '';
|
||||||
$eol = PHP_EOL;
|
$eol = PHP_EOL;
|
||||||
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
|
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
|
||||||
$eol = '<br>'.PHP_EOL;
|
$eol = '<br>'.PHP_EOL;
|
||||||
|
@ -86,30 +97,30 @@ trait PrettyException
|
||||||
foreach (\array_reverse($trace ?? $this->getTrace()) as $k => $frame) {
|
foreach (\array_reverse($trace ?? $this->getTrace()) as $k => $frame) {
|
||||||
if (isset($frame['function']) && \in_array($frame['function'], ['serializeParams', 'serializeObject'])) {
|
if (isset($frame['function']) && \in_array($frame['function'], ['serializeParams', 'serializeObject'])) {
|
||||||
if (($frame['args'][2] ?? '') !== '') {
|
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;
|
$tl = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($tl) {
|
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') {
|
if (isset($frame['function']) && ($frame['function'] === 'handle_rpc_error' && $k === \count($this->getTrace()) - 1) || $frame['function'] === 'unserialize') {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$this->tl_trace .= isset($frame['file']) ? \str_pad(\basename($frame['file']).'('.$frame['line'].'):', 20)."\t" : '';
|
$this->tlTrace .= isset($frame['file']) ? \str_pad(\basename($frame['file']).'('.$frame['line'].'):', 20)."\t" : '';
|
||||||
$this->tl_trace .= isset($frame['function']) ? $frame['function'].'(' : '';
|
$this->tlTrace .= isset($frame['function']) ? $frame['function'].'(' : '';
|
||||||
$this->tl_trace .= isset($frame['args']) ? \substr(\json_encode($frame['args']), 1, -1) : '';
|
$this->tlTrace .= isset($frame['args']) ? \substr(\json_encode($frame['args']), 1, -1) : '';
|
||||||
$this->tl_trace .= ')';
|
$this->tlTrace .= ')';
|
||||||
$this->tl_trace .= $eol;
|
$this->tlTrace .= $eol;
|
||||||
$tl = false;
|
$tl = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->tl_trace .= $init !== '' ? "['".$init."']" : '';
|
$this->tlTrace .= $init !== '' ? "['".$init."']" : '';
|
||||||
$this->tl_trace = \implode($eol, \array_reverse(\explode($eol, $this->tl_trace)));
|
$this->tlTrace = \implode($eol, \array_reverse(\explode($eol, $this->tlTrace)));
|
||||||
if ($previous_trace) {
|
if ($previous_trace) {
|
||||||
$this->tl_trace .= $eol.$eol;
|
$this->tlTrace .= $eol.$eol;
|
||||||
$this->tl_trace .= "Previous TL trace:{$eol}";
|
$this->tlTrace .= "Previous TL trace:{$eol}";
|
||||||
$this->tl_trace .= $previous_trace;
|
$this->tlTrace .= $previous_trace;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,34 +36,12 @@ trait Loop
|
||||||
*/
|
*/
|
||||||
private $stopLoop = false;
|
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 void
|
||||||
*
|
|
||||||
* @return mixed
|
|
||||||
*/
|
*/
|
||||||
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;
|
static $inited = false;
|
||||||
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' && !$inited) {
|
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' && !$inited) {
|
||||||
$needs_restart = true;
|
$needs_restart = true;
|
||||||
|
@ -104,12 +82,43 @@ trait Loop
|
||||||
$this->closeConnection('Bot was started');
|
$this->closeConnection('Bot was started');
|
||||||
$inited = true;
|
$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']) {
|
if (!$this->settings['updates']['handle_updates']) {
|
||||||
$this->settings['updates']['handle_updates'] = true;
|
$this->settings['updates']['handle_updates'] = true;
|
||||||
}
|
}
|
||||||
if (!$this->settings['updates']['run_callback']) {
|
if (!$this->settings['updates']['run_callback']) {
|
||||||
$this->settings['updates']['run_callback'] = true;
|
$this->settings['updates']['run_callback'] = true;
|
||||||
}
|
}
|
||||||
|
$this->initSelfRestart();
|
||||||
$this->startUpdateSystem();
|
$this->startUpdateSystem();
|
||||||
$this->logger->logger('Started update loop', \danog\MadelineProto\Logger::NOTICE);
|
$this->logger->logger('Started update loop', \danog\MadelineProto\Logger::NOTICE);
|
||||||
$this->stopLoop = false;
|
$this->stopLoop = false;
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
|
|
||||||
namespace danog\MadelineProto\Wrappers;
|
namespace danog\MadelineProto\Wrappers;
|
||||||
|
|
||||||
|
use danog\MadelineProto\MTProto;
|
||||||
use danog\MadelineProto\Tools;
|
use danog\MadelineProto\Tools;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,7 +34,7 @@ trait Start
|
||||||
*/
|
*/
|
||||||
public function start(): \Generator
|
public function start(): \Generator
|
||||||
{
|
{
|
||||||
if ($this->authorized === self::LOGGED_IN) {
|
if (yield $this->getAuthorization() === MTProto::LOGGED_IN) {
|
||||||
return yield from $this->fullGetSelf();
|
return yield from $this->fullGetSelf();
|
||||||
}
|
}
|
||||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||||
|
@ -52,7 +53,7 @@ trait Start
|
||||||
$this->serialize();
|
$this->serialize();
|
||||||
return yield from $this->fullGetSelf();
|
return yield from $this->fullGetSelf();
|
||||||
}
|
}
|
||||||
if ($this->authorized === self::NOT_LOGGED_IN) {
|
if ($this->authorized === MTProto::NOT_LOGGED_IN) {
|
||||||
if (isset($_POST['phone_number'])) {
|
if (isset($_POST['phone_number'])) {
|
||||||
yield from $this->webPhoneLogin();
|
yield from $this->webPhoneLogin();
|
||||||
} elseif (isset($_POST['token'])) {
|
} elseif (isset($_POST['token'])) {
|
||||||
|
@ -60,26 +61,26 @@ trait Start
|
||||||
} else {
|
} else {
|
||||||
yield from $this->webEcho();
|
yield from $this->webEcho();
|
||||||
}
|
}
|
||||||
} elseif ($this->authorized === self::WAITING_CODE) {
|
} elseif ($this->authorized === MTProto::WAITING_CODE) {
|
||||||
if (isset($_POST['phone_code'])) {
|
if (isset($_POST['phone_code'])) {
|
||||||
yield from $this->webCompletePhoneLogin();
|
yield from $this->webCompletePhoneLogin();
|
||||||
} else {
|
} else {
|
||||||
yield from $this->webEcho("You didn't provide a phone code!");
|
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'])) {
|
if (isset($_POST['password'])) {
|
||||||
yield from $this->webComplete2faLogin();
|
yield from $this->webComplete2faLogin();
|
||||||
} else {
|
} else {
|
||||||
yield from $this->webEcho("You didn't provide the password!");
|
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'])) {
|
if (isset($_POST['first_name'])) {
|
||||||
yield from $this->webCompleteSignup();
|
yield from $this->webCompleteSignup();
|
||||||
} else {
|
} else {
|
||||||
yield from $this->webEcho("You didn't provide the first name!");
|
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();
|
$this->serialize();
|
||||||
return yield from $this->fullGetSelf();
|
return yield from $this->fullGetSelf();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,22 @@
|
||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
\define('MADELINE_PHP', __FILE__);
|
||||||
|
|
||||||
function ___install_madeline()
|
function ___install_madeline()
|
||||||
{
|
{
|
||||||
if (\count(\debug_backtrace(0)) === 1) {
|
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;
|
$old = false;
|
||||||
if (PHP_MAJOR_VERSION === 5) {
|
if (PHP_MAJOR_VERSION === 5) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user