Compare commits
23 Commits
Author | SHA1 | Date |
---|---|---|
Daniil Gentili | 1a984f7f77 | |
Daniil Gentili | 38da1a17a2 | |
Daniil Gentili | e9f21f60e4 | |
Daniil Gentili | a3ded27cb5 | |
Daniil Gentili | 047a007d02 | |
Daniil Gentili | 8f989688b2 | |
Daniil Gentili | 92a38cfd86 | |
Daniil Gentili | a53bfb8484 | |
Daniil Gentili | bb003f0df8 | |
Daniil Gentili | 71b4efbbd3 | |
Daniil Gentili | c17588b5be | |
Daniil Gentili | 6d28493775 | |
Daniil Gentili | f951a05897 | |
Daniil Gentili | a463823b58 | |
Daniil Gentili | fab3785168 | |
Daniil Gentili | f3134ad9a1 | |
Daniil Gentili | 0796c227dd | |
Daniil Gentili | 2da0e61f9a | |
Daniil Gentili | 8523a42803 | |
Daniil Gentili | 2daf47ef53 | |
Daniil Gentili | 17342b63ec | |
Daniil Gentili | 967472f671 | |
Daniil Gentili | 30fdd20ff5 |
|
@ -5,8 +5,7 @@ $config->getFinder()
|
|||
->in(__DIR__ . '/src')
|
||||
->in(__DIR__ . '/tests')
|
||||
->in(__DIR__ . '/examples')
|
||||
->in(__DIR__ . '/tools')
|
||||
->in(__DIR__);
|
||||
->in(__DIR__ . '/tools');
|
||||
|
||||
$cacheDir = getenv('TRAVIS') ? getenv('HOME') . '/.php-cs-fixer' : __DIR__;
|
||||
|
||||
|
|
|
@ -6,12 +6,14 @@
|
|||
"homepage": "https://docs.madelineproto.xyz",
|
||||
"keywords": ["telegram", "mtproto", "protocol", "bytes", "messenger", "client", "PHP", "video", "stickers", "audio", "files", "GB"],
|
||||
"conflict": {
|
||||
"krakjoe/pthreads-polyfill": "*"
|
||||
"krakjoe/pthreads-polyfill": "*",
|
||||
"ext-pthreads": "*"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.4.0",
|
||||
"danog/primemodule": "^1",
|
||||
"erusev/parsedown": "^1.7",
|
||||
"symfony/polyfill-mbstring": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-json": "*",
|
||||
"ext-xml": "*",
|
||||
|
@ -34,11 +36,11 @@
|
|||
"danog/magicalserializer": "^1.0",
|
||||
"league/uri": "^6",
|
||||
"danog/ipc": "^0.1",
|
||||
"tivie/htaccess-parser": "^0.2.3",
|
||||
"amphp/log": "^1.1",
|
||||
"danog/loop": "^0.1.0",
|
||||
"danog/tgseclib": "^3",
|
||||
"amphp/redis": "^1.0"
|
||||
"amphp/redis": "^1.0",
|
||||
"symfony/polyfill-php80": "^1.18"
|
||||
},
|
||||
"require-dev": {
|
||||
"vlucas/phpdotenv": "^3",
|
||||
|
|
|
@ -19,7 +19,12 @@
|
|||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use Amp\Promise;
|
||||
use Amp\Ipc\Sync\ChannelledSocket;
|
||||
use Amp\Loop;
|
||||
use danog\MadelineProto\Ipc\Client;
|
||||
use danog\MadelineProto\Ipc\Server;
|
||||
use danog\MadelineProto\Settings\Ipc as SettingsIpc;
|
||||
use danog\MadelineProto\Settings\Logger as SettingsLogger;
|
||||
|
||||
/**
|
||||
* Main API wrapper for MadelineProto.
|
||||
|
@ -30,18 +35,16 @@ class API extends InternalDoc
|
|||
use \danog\MadelineProto\ApiWrappers\Start;
|
||||
use \danog\MadelineProto\ApiWrappers\Templates;
|
||||
/**
|
||||
* Session path.
|
||||
* Session paths.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public string $session = '';
|
||||
public SessionPaths $session;
|
||||
|
||||
/**
|
||||
* Instance of MadelineProto.
|
||||
*
|
||||
* @var null|MTProto
|
||||
* @var null|MTProto|Client
|
||||
*/
|
||||
public $API;
|
||||
|
||||
|
@ -83,7 +86,6 @@ class API extends InternalDoc
|
|||
*/
|
||||
private bool $destructing = false;
|
||||
|
||||
|
||||
/**
|
||||
* API wrapper (to avoid circular references).
|
||||
*
|
||||
|
@ -92,27 +94,28 @@ class API extends InternalDoc
|
|||
private $wrapper;
|
||||
|
||||
/**
|
||||
* Global session unlock callback.
|
||||
* Unlock callback.
|
||||
*
|
||||
* @var callback
|
||||
* @var ?callable
|
||||
*/
|
||||
private $unlock;
|
||||
|
||||
private $unlock = null;
|
||||
|
||||
/**
|
||||
* Magic constructor function.
|
||||
*
|
||||
* @param string $session Session name
|
||||
* @param array $settings Settings
|
||||
* @param string $session Session name
|
||||
* @param array|Settings $settings Settings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __magic_construct(string $session, array $settings = []): void
|
||||
public function __magic_construct(string $session, $settings = []): void
|
||||
{
|
||||
Magic::classExists(true);
|
||||
$settings = Settings::parseFromLegacy($settings);
|
||||
$this->session = new SessionPaths($session);
|
||||
$this->wrapper = new APIWrapper($this, $this->exportNamespace());
|
||||
|
||||
Magic::classExists();
|
||||
$this->setInitPromise($this->__construct_async($session, $settings));
|
||||
$this->setInitPromise($this->internalInitAPI($settings));
|
||||
foreach (\get_class_vars(APIFactory::class) as $key => $var) {
|
||||
if (\in_array($key, ['namespace', 'API', 'lua', 'async', 'asyncAPIPromise', 'methods'])) {
|
||||
continue;
|
||||
|
@ -125,19 +128,90 @@ class API extends InternalDoc
|
|||
/**
|
||||
* Async constructor function.
|
||||
*
|
||||
* @param string $session Session name
|
||||
* @param array $settings Settings
|
||||
* @param Settings|SettingsEmpty|SettingsIpc $settings Settings
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function __construct_async(string $session, array $settings = []): \Generator
|
||||
private function internalInitAPI(SettingsAbstract $settings): \Generator
|
||||
{
|
||||
Logger::constructorFromSettings($settings);
|
||||
$this->session = $session = Tools::absolute($session);
|
||||
[$unserialized, $this->unlock] = yield from Serialization::legacyUnserialize($session);
|
||||
if ($unserialized) {
|
||||
Logger::constructorFromSettings($settings instanceof Settings
|
||||
? $settings->getLogger()
|
||||
: ($settings instanceof SettingsLogger ? $settings : new SettingsLogger));
|
||||
|
||||
if (yield from $this->connectToMadelineProto($settings)) {
|
||||
return; // OK
|
||||
}
|
||||
if (!$settings instanceof Settings) {
|
||||
$newSettings = new Settings;
|
||||
$newSettings->merge($settings);
|
||||
$settings = $newSettings;
|
||||
}
|
||||
|
||||
$appInfo = $settings->getAppInfo();
|
||||
if (!$appInfo->hasApiInfo()) {
|
||||
$app = yield from $this->APIStart($settings);
|
||||
if (!$app) {
|
||||
$this->forceInit(true);
|
||||
die();
|
||||
}
|
||||
$appInfo->setApiId($app['api_id']);
|
||||
$appInfo->setApiHash($app['api_hash']);
|
||||
}
|
||||
$this->API = new MTProto($settings, $this->wrapper);
|
||||
yield from $this->API->initAsynchronously();
|
||||
$this->APIFactory();
|
||||
$this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconnect to full instance.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
protected function reconnectFull(): \Generator
|
||||
{
|
||||
if ($this->API instanceof Client) {
|
||||
yield $this->API->stopIpcServer();
|
||||
yield $this->API->disconnect();
|
||||
yield from $this->connectToMadelineProto(new SettingsEmpty, true);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Connect to MadelineProto.
|
||||
*
|
||||
* @param Settings|SettingsEmpty $settings Settings
|
||||
* @param bool $forceFull Whether to force full initialization
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
protected function connectToMadelineProto(SettingsAbstract $settings, bool $forceFull = false): \Generator
|
||||
{
|
||||
if ($settings instanceof SettingsIpc) {
|
||||
$forceFull = $forceFull || $settings->getForceFull();
|
||||
} elseif ($settings instanceof Settings) {
|
||||
$forceFull = $forceFull || $settings->getIpc()->getForceFull();
|
||||
}
|
||||
|
||||
[$unserialized, $this->unlock] = yield Tools::timeoutWithDefault(
|
||||
Serialization::unserialize($this->session, $forceFull),
|
||||
30000,
|
||||
[0, null]
|
||||
);
|
||||
if ($unserialized === 0) {
|
||||
// Timeout
|
||||
throw new \RuntimeException("Could not connect to MadelineProto, please check the logs for more details.");
|
||||
} elseif ($unserialized instanceof \Throwable) {
|
||||
// IPC server error, try fetching full session
|
||||
return yield from $this->connectToMadelineProto($settings, true);
|
||||
} elseif ($unserialized instanceof ChannelledSocket) {
|
||||
// Success, IPC client
|
||||
$this->API = new Client($unserialized, $this->session, Logger::$default);
|
||||
$this->APIFactory();
|
||||
return true;
|
||||
} elseif ($unserialized) {
|
||||
// Success, full session
|
||||
$unserialized->storage = $unserialized->storage ?? [];
|
||||
$unserialized->session = $session;
|
||||
$unserialized->session = $this->session;
|
||||
APIWrapper::link($this, $unserialized);
|
||||
APIWrapper::link($this->wrapper, $this);
|
||||
AbstractAPIFactory::link($this->wrapper->getFactory(), $this);
|
||||
|
@ -146,30 +220,17 @@ class API extends InternalDoc
|
|||
|
||||
unset($unserialized);
|
||||
|
||||
$this->API->wrapper = $this->wrapper;
|
||||
yield from $this->API->initAsynchronously();
|
||||
if ($settings instanceof SettingsIpc) {
|
||||
$settings = new SettingsEmpty;
|
||||
}
|
||||
yield from $this->API->wakeup($settings, $this->wrapper);
|
||||
$this->APIFactory();
|
||||
$this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($settings['app_info']['api_id']) || !$settings['app_info']['api_id']) {
|
||||
$app = (yield from $this->APIStart($settings));
|
||||
if (!$app) {
|
||||
$this->forceInit(true);
|
||||
die();
|
||||
}
|
||||
$settings['app_info']['api_id'] = $app['api_id'];
|
||||
$settings['app_info']['api_hash'] = $app['api_hash'];
|
||||
}
|
||||
$this->API = new MTProto($settings);
|
||||
$this->API->wrapper = $this->wrapper;
|
||||
yield from $this->API->initAsynchronously();
|
||||
$this->APIFactory();
|
||||
$this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wakeup function.
|
||||
*
|
||||
|
@ -189,13 +250,15 @@ class API extends InternalDoc
|
|||
$this->init();
|
||||
if (!$this->oldInstance) {
|
||||
$this->logger->logger('Shutting down MadelineProto ('.static::class.')');
|
||||
$this->destructing = true;
|
||||
if ($this->API) {
|
||||
$this->API->destructing = true;
|
||||
$this->API->unreference();
|
||||
}
|
||||
$this->destructing = true;
|
||||
if (isset($this->wrapper)) {
|
||||
if (isset($this->wrapper) && !Magic::$signaled) {
|
||||
$this->logger->logger('Prompting final serialization...');
|
||||
Tools::wait($this->wrapper->serialize());
|
||||
$this->logger->logger('Done final serialization!');
|
||||
}
|
||||
if ($this->unlock) {
|
||||
($this->unlock)();
|
||||
|
@ -212,16 +275,14 @@ class API extends InternalDoc
|
|||
private function APIFactory(): void
|
||||
{
|
||||
if ($this->API && $this->API->inited()) {
|
||||
foreach ($this->API->getMethodNamespaces() as $namespace) {
|
||||
if (!$this->{$namespace}) {
|
||||
$this->{$namespace} = $this->exportNamespace($namespace);
|
||||
if ($this->API instanceof MTProto) {
|
||||
foreach ($this->API->getMethodNamespaces() as $namespace) {
|
||||
if (!$this->{$namespace}) {
|
||||
$this->{$namespace} = $this->exportNamespace($namespace);
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->methods = self::getInternalMethodList($this->API);
|
||||
$this->API->wrapper = $this->wrapper;
|
||||
if ($this->API->event_handler && \class_exists($this->API->event_handler) && \is_subclass_of($this->API->event_handler, EventHandler::class)) {
|
||||
$this->API->setEventHandler($this->API->event_handler);
|
||||
}
|
||||
$this->methods = self::getInternalMethodList($this->API, MTProto::class);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,7 +314,7 @@ class API extends InternalDoc
|
|||
* @param API[] $instances Instances of madeline
|
||||
* @param string[]|string $eventHandler Event handler(s)
|
||||
*
|
||||
* @return Promise
|
||||
* @return void
|
||||
*/
|
||||
public static function startAndLoopMulti(array $instances, $eventHandler): void
|
||||
{
|
||||
|
@ -288,13 +349,30 @@ class API extends InternalDoc
|
|||
*/
|
||||
public function startAndLoopAsync(string $eventHandler): \Generator
|
||||
{
|
||||
$errors = [];
|
||||
$this->async(true);
|
||||
|
||||
if ($this->API instanceof Client) {
|
||||
yield $this->API->stopIpcServer();
|
||||
yield $this->API->disconnect();
|
||||
yield from $this->connectToMadelineProto(new SettingsEmpty, true);
|
||||
}
|
||||
|
||||
$started = false;
|
||||
while (true) {
|
||||
try {
|
||||
yield $this->start();
|
||||
$started = true;
|
||||
yield $this->setEventHandler($eventHandler);
|
||||
return yield from $this->API->loop();
|
||||
} catch (\Throwable $e) {
|
||||
$errors = [\time() => $errors[\time()] ?? 0];
|
||||
$errors[\time()]++;
|
||||
if ($errors[\time()] > 100 && (!$this->inited() || !$started)) {
|
||||
$this->logger->logger("More than 100 errors in a second and not inited, exiting!", Logger::FATAL_ERROR);
|
||||
return;
|
||||
}
|
||||
echo $e;
|
||||
$this->logger->logger((string) $e, Logger::FATAL_ERROR);
|
||||
$this->report("Surfaced: $e");
|
||||
}
|
||||
|
|
|
@ -20,25 +20,23 @@ namespace danog\MadelineProto;
|
|||
|
||||
use Amp\Promise;
|
||||
use Amp\Success;
|
||||
use danog\MadelineProto\Ipc\Client;
|
||||
|
||||
use function Amp\File\put;
|
||||
use function Amp\File\rename as renameAsync;
|
||||
use function Amp\File\open;
|
||||
|
||||
final class APIWrapper
|
||||
{
|
||||
/**
|
||||
* MTProto instance.
|
||||
*
|
||||
* @var ?MTProto
|
||||
* @var MTProto|null|Client
|
||||
*/
|
||||
private ?MTProto $API = null;
|
||||
private $API = null;
|
||||
|
||||
/**
|
||||
* Session path.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public string $session = '';
|
||||
public SessionPaths $session;
|
||||
|
||||
/**
|
||||
* Getting API ID flag.
|
||||
|
@ -169,62 +167,42 @@ final class APIWrapper
|
|||
*/
|
||||
public function getIpcPath(): string
|
||||
{
|
||||
return (new SessionPaths($this->session))->getIpcPath();
|
||||
return $this->session->getIpcPath();
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize session.
|
||||
*
|
||||
* @return Promise
|
||||
* @return Promise<bool>
|
||||
*/
|
||||
public function serialize(): Promise
|
||||
{
|
||||
if (!$this->session) {
|
||||
Logger::log("Not serializing, no session");
|
||||
return new Success();
|
||||
if ($this->API === null && !$this->gettingApiId) {
|
||||
return new Success(false);
|
||||
}
|
||||
if ($this->API instanceof FastAPI) {
|
||||
Logger::log("Not serializing, IPC client");
|
||||
return new Success();
|
||||
if ($this->API instanceof Client) {
|
||||
return new Success(false);
|
||||
}
|
||||
return Tools::callFork((function (): \Generator {
|
||||
if (isset($this->API->flushSettings) && $this->API->flushSettings) {
|
||||
$this->API->flushSettings = false;
|
||||
$this->API->__construct($this->API->settings);
|
||||
}
|
||||
if ($this->API === null && !$this->gettingApiId) {
|
||||
return false;
|
||||
}
|
||||
if ($this->API) {
|
||||
yield from $this->API->initAsynchronously();
|
||||
}
|
||||
$this->serialized = \time();
|
||||
$realpaths = new SessionPaths($this->session);
|
||||
Logger::log('Waiting for exclusive lock of serialization lockfile...');
|
||||
$unlock = yield Tools::flock($realpaths->getLockPath(), LOCK_EX);
|
||||
Logger::log('Lock acquired, serializing');
|
||||
try {
|
||||
if (!$this->gettingApiId) {
|
||||
$update_closure = $this->API->settings['updates']['callback'];
|
||||
if ($this->API->settings['updates']['callback'] instanceof \Closure) {
|
||||
$this->API->settings['updates']['callback'] = [$this->API, 'noop'];
|
||||
}
|
||||
$logger_closure = $this->API->settings['logger']['logger_param'];
|
||||
if ($this->API->settings['logger']['logger_param'] instanceof \Closure) {
|
||||
$this->API->settings['logger']['logger_param'] = [$this->API, 'noop'];
|
||||
}
|
||||
}
|
||||
$wrote = yield put($realpaths->getTempPath(), \serialize($this));
|
||||
yield renameAsync($realpaths->getTempPath(), $realpaths->getSessionPath());
|
||||
} finally {
|
||||
if (!$this->gettingApiId) {
|
||||
$this->API->settings['updates']['callback'] = $update_closure;
|
||||
$this->API->settings['logger']['logger_param'] = $logger_closure;
|
||||
}
|
||||
$unlock();
|
||||
|
||||
yield from $this->session->serialize(
|
||||
$this->API ? yield from $this->API->serializeSession($this) : $this,
|
||||
$this->session->getSessionPath()
|
||||
);
|
||||
|
||||
if ($this->API) {
|
||||
yield from $this->session->storeLightState($this->API);
|
||||
}
|
||||
Logger::log('Done serializing');
|
||||
return $wrote;
|
||||
|
||||
|
||||
// Truncate legacy session
|
||||
yield (yield open($this->session->getLegacySessionPath(), 'w'))->close();
|
||||
|
||||
Logger::log('Saved session!');
|
||||
return true;
|
||||
})());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
namespace danog\MadelineProto;
|
||||
|
||||
use danog\MadelineProto\Async\AsyncConstruct;
|
||||
use danog\MadelineProto\Ipc\Client;
|
||||
|
||||
abstract class AbstractAPIFactory extends AsyncConstruct
|
||||
{
|
||||
|
@ -31,14 +32,6 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
|||
* @var string
|
||||
*/
|
||||
private string $namespace = '';
|
||||
/**
|
||||
* MTProto instance.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @var MTProto
|
||||
*/
|
||||
public $API;
|
||||
/**
|
||||
* Whether lua is being used.
|
||||
*
|
||||
|
@ -61,6 +54,12 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
|||
* @var string[]
|
||||
*/
|
||||
protected array $methods = [];
|
||||
|
||||
/**
|
||||
* Main API instance.
|
||||
*/
|
||||
private API $mainAPI;
|
||||
|
||||
/**
|
||||
* Export APIFactory instance with the specified namespace.
|
||||
*
|
||||
|
@ -88,10 +87,21 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
|||
*/
|
||||
protected static function link(self $a, self $b): void
|
||||
{
|
||||
$a->API =& $b->API;
|
||||
$a->lua =& $b->lua;
|
||||
$a->async =& $b->async;
|
||||
$a->methods =& $b->methods;
|
||||
if ($b instanceof API) {
|
||||
$a->mainAPI = $b;
|
||||
$b->mainAPI = $b;
|
||||
} elseif ($a instanceof API) {
|
||||
$a->mainAPI = $a;
|
||||
$b->mainAPI = $a;
|
||||
} else {
|
||||
$a->mainAPI =& $b->mainAPI;
|
||||
}
|
||||
if (!$b->inited()) {
|
||||
$a->setInitPromise($b->initAsynchronously());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Enable or disable async.
|
||||
|
@ -162,14 +172,18 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
|||
public function __call_async(string $name, array $arguments): \Generator
|
||||
{
|
||||
yield from $this->initAsynchronously();
|
||||
|
||||
$lower_name = \strtolower($name);
|
||||
if ($this->namespace !== '' || !isset($this->methods[$lower_name])) {
|
||||
$name = $this->namespace.$name;
|
||||
$aargs = isset($arguments[1]) && \is_array($arguments[1]) ? $arguments[1] : [];
|
||||
$aargs['apifactory'] = true;
|
||||
$args = isset($arguments[0]) && \is_array($arguments[0]) ? $arguments[0] : [];
|
||||
return yield from $this->API->methodCallAsyncRead($name, $args, $aargs);
|
||||
return yield from $this->mainAPI->API->methodCallAsyncRead($name, $args, $aargs);
|
||||
}
|
||||
if ($lower_name === 'seteventhandler'
|
||||
|| ($lower_name === 'loop' && !isset($arguments[0]))
|
||||
) {
|
||||
yield from $this->mainAPI->reconnectFull();
|
||||
}
|
||||
$res = $this->methods[$lower_name](...$arguments);
|
||||
return $res instanceof \Generator ? yield from $res : yield $res;
|
||||
|
@ -177,8 +191,8 @@ abstract class AbstractAPIFactory extends AsyncConstruct
|
|||
/**
|
||||
* Get fully resolved method list for object, including snake_case and camelCase variants.
|
||||
*
|
||||
* @param API $value Value
|
||||
* @param string $class Custom class name
|
||||
* @param API|MTProto|Client $value Value
|
||||
* @param string $class Custom class name
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
namespace danog\MadelineProto;
|
||||
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Settings\TLSchema;
|
||||
use danog\MadelineProto\TL\TL;
|
||||
use danog\MadelineProto\TL\TLCallback;
|
||||
use phpDocumentor\Reflection\DocBlockFactory;
|
||||
|
@ -37,7 +38,9 @@ class AnnotationsBuilder
|
|||
$this->logger = $logger;
|
||||
}
|
||||
});
|
||||
$this->TL->init($settings['tl_schema']);
|
||||
$tlSchema = new TLSchema;
|
||||
$tlSchema->mergeArray($settings);
|
||||
$this->TL->init($tlSchema);
|
||||
$this->settings = $settings;
|
||||
$this->output = $output;
|
||||
}
|
||||
|
|
|
@ -19,7 +19,9 @@
|
|||
|
||||
namespace danog\MadelineProto\ApiWrappers;
|
||||
|
||||
use danog\MadelineProto\Lang;
|
||||
use danog\MadelineProto\MyTelegramOrgWrapper;
|
||||
use danog\MadelineProto\Settings;
|
||||
use danog\MadelineProto\Tools;
|
||||
use function Amp\ByteStream\getStdout;
|
||||
|
||||
|
@ -31,40 +33,46 @@ trait Start
|
|||
/**
|
||||
* Start API ID generation process.
|
||||
*
|
||||
* @param array $settings Settings
|
||||
* @param Settings $settings Settings
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function APIStart(array $settings): \Generator
|
||||
private function APIStart(Settings $settings): \Generator
|
||||
{
|
||||
if (\defined(\MADELINE_WORKER::class)) {
|
||||
throw new \danog\MadelineProto\Exception('Not inited!');
|
||||
}
|
||||
if ($this->getWebAPITemplate() === 'legacy') {
|
||||
$this->setWebAPITemplate($settings->getTemplates()->getHtmlTemplate());
|
||||
}
|
||||
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
|
||||
$stdout = getStdout();
|
||||
yield $stdout->write('You did not define a valid API ID/API hash. Do you want to define it now manually, or automatically? (m/a)
|
||||
Note that you can also provide the API parameters directly in the code using the settings: https://docs.madelineproto.xyz/docs/SETTINGS.html#settingsapp_infoapi_id'.PHP_EOL);
|
||||
if (\strpos(yield Tools::readLine('Your choice (m/a): '), 'm') !== false) {
|
||||
yield $stdout->write('1) Login to my.telegram.org
|
||||
2) Go to API development tools
|
||||
3) App title: your app\'s name, can be anything
|
||||
Short name: your app\'s short name, can be anything
|
||||
URL: your app/website\'s URL, or t.me/yourusername
|
||||
Platform: anything
|
||||
Description: Describe your app here
|
||||
4) Click on create application'.PHP_EOL);
|
||||
$app['api_id'] = yield Tools::readLine('5) Enter your API ID: ');
|
||||
$app['api_hash'] = yield Tools::readLine('6) Enter your API hash: ');
|
||||
$prepare = Lang::$current_lang['apiChooseManualAuto'].PHP_EOL;
|
||||
$prepare .= \sprintf(Lang::$current_lang['apiChooseManualAutoTip'], 'https://docs.madelineproto.xyz/docs/SETTINGS.html#settingsapp_infoapi_id');
|
||||
$prepare .= PHP_EOL;
|
||||
yield $stdout->write($prepare);
|
||||
if (\strpos(yield Tools::readLine(Lang::$current_lang['apiChoosePrompt']), 'm') !== false) {
|
||||
yield $stdout->write("1) ".Lang::$current_lang['apiManualInstructions0'].PHP_EOL);
|
||||
yield $stdout->write("2) ".Lang::$current_lang['apiManualInstructions1'].PHP_EOL);
|
||||
yield $stdout->write("3) ");
|
||||
foreach (['App title', 'Short name', 'URL', 'Platform', 'Description'] as $k => $key) {
|
||||
yield $stdout->write($k ? " $key: " : "$key: ");
|
||||
yield $stdout->write(Lang::$current_lang["apiAppInstructionsManual$k"].PHP_EOL);
|
||||
}
|
||||
yield $stdout->write("4) ".Lang::$current_lang['apiManualInstructions2'].PHP_EOL);
|
||||
|
||||
$app['api_id'] = yield Tools::readLine("5) ".Lang::$current_lang['apiManualPrompt0']);
|
||||
$app['api_hash'] = yield Tools::readLine("6) ".Lang::$current_lang['apiManualPrompt1']);
|
||||
return $app;
|
||||
}
|
||||
$this->myTelegramOrgWrapper = new \danog\MadelineProto\MyTelegramOrgWrapper($settings);
|
||||
yield from $this->myTelegramOrgWrapper->login(yield Tools::readLine('Enter a phone number that is already registered on Telegram: '));
|
||||
yield from $this->myTelegramOrgWrapper->completeLogin(yield Tools::readLine('Enter the verification code you received in telegram: '));
|
||||
yield from $this->myTelegramOrgWrapper->login(yield Tools::readLine(Lang::$current_lang['apiAutoPrompt0']));
|
||||
yield from $this->myTelegramOrgWrapper->completeLogin(yield Tools::readLine(Lang::$current_lang['apiAutoPrompt1']));
|
||||
if (!(yield from $this->myTelegramOrgWrapper->hasApp())) {
|
||||
$app_title = yield Tools::readLine('Enter the app\'s name, can be anything: ');
|
||||
$short_name = yield Tools::readLine('Enter the app\'s short name, can be anything: ');
|
||||
$url = yield Tools::readLine('Enter the app/website\'s URL, or t.me/yourusername: ');
|
||||
$description = yield Tools::readLine('Describe your app: ');
|
||||
$app_title = yield Tools::readLine(Lang::$current_lang['apiAppInstructionsAuto0']);
|
||||
$short_name = yield Tools::readLine(Lang::$current_lang['apiAppInstructionsAuto1']);
|
||||
$url = yield Tools::readLine(Lang::$current_lang['apiAppInstructionsAuto2']);
|
||||
$description = yield Tools::readLine(Lang::$current_lang['apiAppInstructionsAuto4']);
|
||||
$app = (yield from $this->myTelegramOrgWrapper->createApp(['app_title' => $app_title, 'app_shortname' => $short_name, 'app_url' => $url, 'app_platform' => 'web', 'app_desc' => $description]));
|
||||
} else {
|
||||
$app = (yield from $this->myTelegramOrgWrapper->getApp());
|
||||
|
@ -81,7 +89,7 @@ Note that you can also provide the API parameters directly in the code using the
|
|||
} elseif (isset($_POST['phone_number'])) {
|
||||
yield from $this->webAPIPhoneLogin($settings);
|
||||
} else {
|
||||
yield from $this->webAPIEcho();
|
||||
yield $this->webAPIEcho();
|
||||
}
|
||||
} elseif (!$this->myTelegramOrgWrapper->loggedIn()) {
|
||||
if (isset($_POST['code'])) {
|
||||
|
@ -89,7 +97,7 @@ Note that you can also provide the API parameters directly in the code using the
|
|||
if (yield from $this->myTelegramOrgWrapper->hasApp()) {
|
||||
return yield from $this->myTelegramOrgWrapper->getApp();
|
||||
}
|
||||
yield from $this->webAPIEcho();
|
||||
yield $this->webAPIEcho();
|
||||
} elseif (isset($_POST['api_id']) && isset($_POST['api_hash'])) {
|
||||
$app['api_id'] = (int) $_POST['api_id'];
|
||||
$app['api_hash'] = $_POST['api_hash'];
|
||||
|
@ -99,7 +107,7 @@ Note that you can also provide the API parameters directly in the code using the
|
|||
yield from $this->webAPIPhoneLogin($settings);
|
||||
} else {
|
||||
$this->myTelegramOrgWrapper = null;
|
||||
yield from $this->webAPIEcho();
|
||||
yield $this->webAPIEcho();
|
||||
}
|
||||
} else {
|
||||
if (isset($_POST['app_title'], $_POST['app_shortname'], $_POST['app_url'], $_POST['app_platform'], $_POST['app_desc'])) {
|
||||
|
@ -107,18 +115,18 @@ Note that you can also provide the API parameters directly in the code using the
|
|||
$this->gettingApiId = false;
|
||||
return $app;
|
||||
}
|
||||
yield from $this->webAPIEcho("You didn't provide all of the required parameters!");
|
||||
yield from $this->webAPIEcho(Lang::$current_lang['apiParamsError']);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private function webAPIPhoneLogin(array $settings): \Generator
|
||||
private function webAPIPhoneLogin(Settings $settings): \Generator
|
||||
{
|
||||
try {
|
||||
$this->myTelegramOrgWrapper = new MyTelegramOrgWrapper($settings);
|
||||
yield from $this->myTelegramOrgWrapper->login($_POST['phone_number']);
|
||||
yield from $this->webAPIEcho();
|
||||
yield $this->webAPIEcho();
|
||||
} catch (\Throwable $e) {
|
||||
yield from $this->webAPIEcho('ERROR: '.$e->getMessage().'. Try again.');
|
||||
yield from $this->webAPIEcho(\sprintf(Lang::$current_lang['apiError'], $e->getMessage()));
|
||||
}
|
||||
}
|
||||
private function webAPICompleteLogin(): \Generator
|
||||
|
@ -126,9 +134,9 @@ Note that you can also provide the API parameters directly in the code using the
|
|||
try {
|
||||
yield from $this->myTelegramOrgWrapper->completeLogin($_POST['code']);
|
||||
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
||||
yield from $this->webAPIEcho('ERROR: '.$e->getMessage().'. Try again.');
|
||||
yield from $this->webAPIEcho(\sprintf(Lang::$current_lang['apiError'], $e->getMessage()));
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
yield from $this->webAPIEcho('ERROR: '.$e->getMessage().'. Try again.');
|
||||
yield from $this->webAPIEcho(\sprintf(Lang::$current_lang['apiError'], $e->getMessage()));
|
||||
}
|
||||
}
|
||||
private function webAPICreateApp(): \Generator
|
||||
|
@ -139,9 +147,9 @@ Note that you can also provide the API parameters directly in the code using the
|
|||
$app = (yield from $this->myTelegramOrgWrapper->createApp($params));
|
||||
return $app;
|
||||
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
||||
yield from $this->webAPIEcho('ERROR: '.$e->getMessage().' Try again.');
|
||||
yield from $this->webAPIEcho(\sprintf(Lang::$current_lang['apiError'], $e->getMessage()));
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
yield from $this->webAPIEcho('ERROR: '.$e->getMessage().' Try again.');
|
||||
yield from $this->webAPIEcho(\sprintf(Lang::$current_lang['apiError'], $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,9 @@
|
|||
|
||||
namespace danog\MadelineProto\ApiWrappers;
|
||||
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Lang;
|
||||
|
||||
use function Amp\ByteStream\getOutputBufferStream;
|
||||
|
||||
trait Templates
|
||||
|
@ -28,7 +31,7 @@ trait Templates
|
|||
*
|
||||
* @var string
|
||||
*/
|
||||
private $webApiTemplate = '<!DOCTYPE html><html><head><title>MadelineProto</title></head><body><h1>MadelineProto</h1><p>%s</p><form method="POST">%s<button type="submit"/>Go</button></form></body></html>';
|
||||
private $webApiTemplate = 'legacy';
|
||||
/**
|
||||
* Generate page from template.
|
||||
*
|
||||
|
@ -39,7 +42,7 @@ trait Templates
|
|||
*/
|
||||
private function webAPIEchoTemplate(string $message, string $form): string
|
||||
{
|
||||
return \sprintf($this->webApiTemplate, $message, $form);
|
||||
return \sprintf($this->webApiTemplate, $message, $form, Lang::$current_lang['go']);
|
||||
}
|
||||
/**
|
||||
* Get web API login HTML template string.
|
||||
|
@ -64,72 +67,86 @@ trait Templates
|
|||
*
|
||||
* @param string $message Message to echo
|
||||
*
|
||||
* @return \Generator
|
||||
* @return Promise
|
||||
*/
|
||||
private function webAPIEcho(string $message = ''): \Generator
|
||||
private function webAPIEcho(string $message = ''): Promise
|
||||
{
|
||||
$stdout = getOutputBufferStream();
|
||||
$message = \htmlentities($message);
|
||||
if (!isset($this->myTelegramOrgWrapper)) {
|
||||
if (isset($_POST['type'])) {
|
||||
if ($_POST['type'] === 'manual') {
|
||||
yield $stdout->write($this->webAPIEchoTemplate('Enter your API ID and API hash<br><b>'.$message.'</b><ol>
|
||||
<li>Login to my.telegram.org</li>
|
||||
<li>Go to API development tools</li>
|
||||
<li>
|
||||
<ul>
|
||||
<li>App title: your app's name, can be anything</li>
|
||||
<li>Short name: your app's short name, only numbers and letters</li>
|
||||
<li>Platform: Web</li>
|
||||
<li>Description: describe your app here</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Click on create application</li>
|
||||
</ol>', '<input type="string" name="api_id" placeholder="API ID" required/><input type="string" name="api_hash" placeholder="API hash" required/>'));
|
||||
$title = \htmlentities(Lang::$current_lang['apiManualWeb']);
|
||||
$title .= "<br><b>$message</b>";
|
||||
$title .= "<ol>";
|
||||
$title .= "<li>".\htmlentities(Lang::$current_lang['apiManualInstructions0'])."</li>";
|
||||
$title .= "<li>".\htmlentities(Lang::$current_lang['apiManualInstructions1'])."</li>";
|
||||
$title .= "<li><ul>";
|
||||
foreach (['App title', 'Short name', 'URL', 'Platform', 'Description'] as $k => $key) {
|
||||
$title .= "<li>$key: ";
|
||||
$title .= \htmlentities(Lang::$current_lang["apiAppInstructionsManual$k"]);
|
||||
$title .= "</li>";
|
||||
}
|
||||
$title .= "</li></ul>";
|
||||
$title .= "<li>".\htmlentities(Lang::$current_lang['apiManualInstructions2'])."</li>";
|
||||
$title .= "</ol>";
|
||||
$form = '<input type="string" name="api_id" placeholder="API ID" required/>';
|
||||
$form .= '<input type="string" name="api_hash" placeholder="API hash" required/>';
|
||||
} else {
|
||||
yield $stdout->write($this->webAPIEchoTemplate('Enter a phone number that is <b>already registered</b> on telegram to get the API ID<br><b>'.$message.'</b>', '<input type="text" name="phone_number" placeholder="Phone number" required/>'));
|
||||
$title = Lang::$current_lang['apiAutoWeb'];
|
||||
$title .= "<br><b>$message</b>";
|
||||
$phone = \htmlentities(Lang::$current_lang['loginUserPhoneWeb']);
|
||||
$form = "<input type='text' name='phone_number' placeholder='$phone' required/>";
|
||||
}
|
||||
} else {
|
||||
if ($message) {
|
||||
$message = '<br><br>'.$message;
|
||||
}
|
||||
yield $stdout->write($this->webAPIEchoTemplate('Do you want to enter the API id and the API hash manually or automatically?<br>Note that you can also provide it directly in the code using the <a href="https://docs.madelineproto.xyz/docs/SETTINGS.html#settingsapp_infoapi_id">settings</a>.<b>'.$message.'</b>', '<select name="type"><option value="automatic">Automatically</option><option value="manual">Manually</option></select>'));
|
||||
$title = \htmlentities(Lang::$current_lang['apiChooseManualAutoWeb']);
|
||||
$title .= "<br>";
|
||||
$title .= \sprintf(Lang::$current_lang['apiChooseManualAutoTipWeb'], 'https://docs.madelineproto.xyz/docs/SETTINGS.html#settingsapp_infoapi_id');
|
||||
$title .= "<b>$message</b>";
|
||||
|
||||
$automatically = \htmlentities(Lang::$current_lang['apiChooseAutomaticallyWeb']);
|
||||
$manually = \htmlentities(Lang::$current_lang['apiChooseManuallyWeb']);
|
||||
|
||||
$form = "<select name='type'><option value='automatic'>$automatically</option><option value='manual'>$manually</option></select>";
|
||||
}
|
||||
} else {
|
||||
if (!$this->myTelegramOrgWrapper->loggedIn()) {
|
||||
yield $stdout->write($this->webAPIEchoTemplate('Enter your code<br><b>'.$message.'</b>', '<input type="text" name="code" placeholder="Code" required/>'));
|
||||
$title = \htmlentities(Lang::$current_lang['loginUserCode']);
|
||||
$title .= "<br><b>$message</b>";
|
||||
|
||||
$code = \htmlentities(Lang::$current_lang['loginUserPhoneCodeWeb']);
|
||||
$form = "<input type='text' name='code' placeholder='$code' required/>";
|
||||
} else {
|
||||
yield $stdout->write($this->webAPIEchoTemplate('Enter the API info<br><b>'.$message.'</b>', '<input type="hidden" name="creating_app" value="yes" required/>
|
||||
Enter the app name, can be anything: <br><input type="text" name="app_title" required/><br>
|
||||
<br>Enter the app's short name, alphanumeric, 5-32 chars: <br><input type="text" name="app_shortname" required/><br>
|
||||
<br>Enter the app/website URL, or https://t.me/yourusername: <br><input type="text" name="app_url" required/><br>
|
||||
<br>Enter the app platform: <br>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="android" checked> Android
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="ios"> iOS
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="wp"> Windows Phone
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="bb"> BlackBerry
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="desktop"> Desktop
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="web"> Web
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="ubp"> Ubuntu phone
|
||||
</label>
|
||||
<label>
|
||||
<input type="radio" name="app_platform" value="other"> Other (specify in description)
|
||||
</label>
|
||||
<br><br>Enter the app description, can be anything: <br><textarea name="app_desc" required></textarea><br><br>
|
||||
'));
|
||||
$title = \htmlentities(Lang::$current_lang['apiAppWeb']);
|
||||
$title .= "<br><b>$message</b>";
|
||||
|
||||
$form = '<input type="hidden" name="creating_app" value="yes" required/>';
|
||||
foreach (['app_title', 'app_shortname', 'app_url', 'app_platform', 'app_desc'] as $field) {
|
||||
$desc = \htmlentities(Lang::$current_lang["apiAppInstructionsAuto$field"]);
|
||||
if ($field == 'app_platform') {
|
||||
$form .= "$desc<br>";
|
||||
foreach ([
|
||||
'android' => 'Android',
|
||||
'ios' => 'iOS',
|
||||
'wp' => 'Windows Phone',
|
||||
'bb' => 'BlackBerry',
|
||||
'desktop' => 'Desktop',
|
||||
'web' => 'Web',
|
||||
'ubp' => 'Ubuntu phone',
|
||||
'other' => \htmlentities(Lang::$current_lang['apiAppInstructionsAutoTypeOther'])
|
||||
] as $key => $desc) {
|
||||
$form .= "<label><input type='radio' name='app_platform' value='$key' checked> $desc</label>";
|
||||
}
|
||||
} elseif ($field === 'app_desc') {
|
||||
$form .= "$desc<br><textarea name='$field' required></textarea><br><br>";
|
||||
} else {
|
||||
$form .= "$desc<br><input type='text' name='$field' required/><br><br>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return getOutputBufferStream()->write($this->webAPIEchoTemplate($title, $form));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ class AsyncConstruct
|
|||
/**
|
||||
* Async init promise.
|
||||
*
|
||||
* @var Promise
|
||||
* @var Promise|null|boolean
|
||||
*/
|
||||
private $asyncInitPromise;
|
||||
/**
|
||||
|
@ -93,10 +93,9 @@ class AsyncConstruct
|
|||
$this->asyncInitPromise = Tools::call($promise);
|
||||
$this->asyncInitPromise->onResolve(
|
||||
function (?\Throwable $error, $result): void {
|
||||
if ($error) {
|
||||
throw $error;
|
||||
if (!$error) {
|
||||
$this->asyncInitPromise = null;
|
||||
}
|
||||
$this->asyncInitPromise = null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,223 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* CombinedAPI 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\Loop;
|
||||
use function Amp\Promise\all;
|
||||
|
||||
class CombinedAPI
|
||||
{
|
||||
use \danog\Serializable;
|
||||
public $session;
|
||||
public $instance_paths = [];
|
||||
public $instances = [];
|
||||
public $timeout = 5;
|
||||
public $serialization_interval = 30;
|
||||
public $serialized = 0;
|
||||
protected $async;
|
||||
public function __magic_construct($session, $paths = [])
|
||||
{
|
||||
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
|
||||
\danog\MadelineProto\Magic::classExists();
|
||||
$realpaths = new SessionPaths($session);
|
||||
$this->session = $realpaths->getSessionPath();
|
||||
foreach ($paths as $path => $settings) {
|
||||
$this->addInstance($path, $settings);
|
||||
}
|
||||
if (\file_exists($realpaths->getSessionPath())) {
|
||||
if (!\file_exists($realpaths->getLockPath())) {
|
||||
\touch($realpaths->getLockPath());
|
||||
\clearstatcache();
|
||||
}
|
||||
$lock = \fopen($realpaths->getLockPath(), 'r');
|
||||
\danog\MadelineProto\Logger::log('Waiting for shared lock of serialization lockfile...');
|
||||
\flock($lock, LOCK_SH);
|
||||
\danog\MadelineProto\Logger::log('Shared lock acquired, deserializing...');
|
||||
try {
|
||||
$tounserialize = \file_get_contents($realpaths->getSessionPath());
|
||||
} finally {
|
||||
\flock($lock, LOCK_UN);
|
||||
\fclose($lock);
|
||||
}
|
||||
$deserialized = \unserialize($tounserialize);
|
||||
/*foreach ($deserialized['instance_paths'] as $path) {
|
||||
$this->addInstance($path, isset($paths[$path]) ? $paths[$path] : []);
|
||||
}*/
|
||||
$this->event_handler = $deserialized['event_handler'];
|
||||
$this->event_handler_instance = $deserialized['event_handler_instance'];
|
||||
if ($this->event_handler !== null) {
|
||||
$this->setEventHandler($this->event_handler);
|
||||
}
|
||||
}
|
||||
foreach ($paths as $path => $settings) {
|
||||
$this->addInstance($path, $settings);
|
||||
}
|
||||
}
|
||||
public function addInstance($path, $settings = [])
|
||||
{
|
||||
if (isset($this->instances[$path]) && isset($this->instance_paths[$path])) {
|
||||
if (isset($this->event_handler_instance)) {
|
||||
$this->event_handler_instance->referenceInstance($path);
|
||||
}
|
||||
return;
|
||||
}
|
||||
\danog\MadelineProto\Logger::constructor(3);
|
||||
\danog\MadelineProto\Logger::log("INSTANTIATING {$path}...");
|
||||
$instance = new \danog\MadelineProto\API($path, $settings);
|
||||
$this->instance_paths[$path] = $path;
|
||||
$this->instances[$path] = $instance;
|
||||
if (isset($this->event_handler_instance)) {
|
||||
$this->event_handler_instance->referenceInstance($path);
|
||||
}
|
||||
}
|
||||
public function removeInstance($path)
|
||||
{
|
||||
if (isset($this->instance_paths[$path])) {
|
||||
unset($this->instance_paths[$path]);
|
||||
}
|
||||
if (isset($this->instances[$path])) {
|
||||
unset($this->instances[$path]);
|
||||
}
|
||||
if (isset($this->event_handler_instance)) {
|
||||
$this->event_handler_instance->removeInstance($path);
|
||||
}
|
||||
}
|
||||
public function __destruct()
|
||||
{
|
||||
$this->serialize();
|
||||
}
|
||||
public function serialize($filename = '')
|
||||
{
|
||||
/*foreach ($this->instances as $instance) {
|
||||
$instance->serialize();
|
||||
}*/
|
||||
if (\is_null($this->session)) {
|
||||
return;
|
||||
}
|
||||
if ($filename === '') {
|
||||
$filename = $this->session;
|
||||
}
|
||||
Logger::log(\danog\MadelineProto\Lang::$current_lang['serializing_madelineproto']);
|
||||
$realpaths = new SessionPaths($filename);
|
||||
if (!\file_exists($realpaths->getLockPath())) {
|
||||
\touch($realpaths->getLockPath());
|
||||
\clearstatcache();
|
||||
}
|
||||
$lock = \fopen($realpaths->getLockPath(), 'w');
|
||||
\danog\MadelineProto\Logger::log('Waiting for exclusive lock of serialization lockfile...');
|
||||
\flock($lock, LOCK_EX);
|
||||
\danog\MadelineProto\Logger::log('Lock acquired, serializing');
|
||||
try {
|
||||
$wrote = \file_put_contents($realpaths->getTempPath(), \serialize(['event_handler' => $this->event_handler, 'event_handler_instance' => $this->event_handler_instance, 'instance_paths' => $this->instance_paths]));
|
||||
\rename($realpaths->getTempPath(), $realpaths->getSessionPath());
|
||||
} finally {
|
||||
\flock($lock, LOCK_UN);
|
||||
\fclose($lock);
|
||||
}
|
||||
$this->serialized = \time();
|
||||
return $wrote;
|
||||
}
|
||||
public $event_handler;
|
||||
private $event_handler_instance;
|
||||
private $event_handler_methods = [];
|
||||
public function getEventHandler()
|
||||
{
|
||||
return $this->event_handler_instance;
|
||||
}
|
||||
public function setEventHandler($event_handler)
|
||||
{
|
||||
if (!\class_exists($event_handler) || !\is_subclass_of($event_handler, '\\danog\\MadelineProto\\CombinedEventHandler')) {
|
||||
throw new \danog\MadelineProto\Exception('Wrong event handler was defined');
|
||||
}
|
||||
$this->event_handler = $event_handler;
|
||||
if (!$this->event_handler_instance instanceof $this->event_handler) {
|
||||
$class_name = $this->event_handler;
|
||||
$this->event_handler_instance = new $class_name($this);
|
||||
} else {
|
||||
$this->event_handler_instance->__construct($this);
|
||||
}
|
||||
$this->event_handler_methods = [];
|
||||
foreach (\get_class_methods($this->event_handler) as $method) {
|
||||
if ($method === 'onLoop') {
|
||||
$this->loop_callback = [$this->event_handler_instance, 'onLoop'];
|
||||
} elseif ($method === 'onAny') {
|
||||
foreach (\end($this->instances)->API->getTL()->getConstructors()->by_id as $constructor) {
|
||||
if ($constructor['type'] === 'Update' && !isset($this->event_handler_methods[$constructor['predicate']])) {
|
||||
$this->event_handler_methods[$constructor['predicate']] = [$this->event_handler_instance, 'onAny'];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$method_name = \lcfirst(\substr($method, 2));
|
||||
$this->event_handler_methods[$method_name] = [$this->event_handler_instance, $method];
|
||||
}
|
||||
}
|
||||
}
|
||||
public function eventUpdateHandler($update, $instance)
|
||||
{
|
||||
if (isset($this->event_handler_methods[$update['_']])) {
|
||||
return $this->event_handler_methods[$update['_']]($update, $instance);
|
||||
}
|
||||
}
|
||||
private $loop_callback;
|
||||
public function async($async)
|
||||
{
|
||||
$this->async = $async;
|
||||
foreach ($this->instances as $instance) {
|
||||
$instance->async($async);
|
||||
}
|
||||
}
|
||||
public function setLoopCallback($callback)
|
||||
{
|
||||
$this->loop_callback = $callback;
|
||||
}
|
||||
public function getUpdates($params = [])
|
||||
{
|
||||
}
|
||||
public function loop($max_forks = 0)
|
||||
{
|
||||
if (\is_callable($max_forks)) {
|
||||
return \danog\MadelineProto\Tools::wait($max_forks());
|
||||
}
|
||||
$loops = [];
|
||||
foreach ($this->instances as $path => $instance) {
|
||||
\danog\MadelineProto\Tools::wait($instance->initAsynchronously());
|
||||
if ($instance->API->authorized !== MTProto::LOGGED_IN) {
|
||||
continue;
|
||||
}
|
||||
if (!$instance->API->settings['updates']['handle_updates']) {
|
||||
$instance->API->settings['updates']['handle_updates'] = true;
|
||||
$instance->API->startUpdateSystem();
|
||||
}
|
||||
$instance->setCallback(function ($update) use ($path) {
|
||||
return $this->eventUpdateHandler($update, $path);
|
||||
}, ['async' => false]);
|
||||
if ($this->loop_callback !== null) {
|
||||
$instance->setLoopCallback($this->loop_callback, ['async' => false]);
|
||||
}
|
||||
$loops[] = \danog\MadelineProto\Tools::call($instance->loop(0, ['async' => true]));
|
||||
}
|
||||
Loop::repeat($this->serialization_interval * 1000, function () {
|
||||
\danog\MadelineProto\Logger::log('Serializing combined event handler');
|
||||
$this->serialize();
|
||||
});
|
||||
\danog\MadelineProto\Logger::log('Started update loop', \danog\MadelineProto\Logger::NOTICE);
|
||||
\danog\MadelineProto\Tools::wait(all($loops));
|
||||
}
|
||||
}
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* CombinedEventHandler 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;
|
||||
|
||||
abstract class CombinedEventHandler
|
||||
{
|
||||
private $CombinedAPI;
|
||||
public function __construct($CombinedAPI)
|
||||
{
|
||||
$this->CombinedAPI = $CombinedAPI;
|
||||
foreach ($CombinedAPI->instances as $path => $instance) {
|
||||
$this->referenceInstance($path);
|
||||
}
|
||||
}
|
||||
final public function __sleep()
|
||||
{
|
||||
$keys = \method_exists($this, '__magic_sleep') ? $this->__magic_sleep() : \get_object_vars($this);
|
||||
unset($keys['CombinedAPI']);
|
||||
if (isset($this->CombinedAPI) && $this->CombinedAPI instanceof CombinedAPI) {
|
||||
foreach ($this->CombinedAPI->instance_paths as $path) {
|
||||
unset($keys[$path]);
|
||||
}
|
||||
} else {
|
||||
foreach ($keys as $key => $value) {
|
||||
if ($value instanceof API && $key === $value->session) {
|
||||
unset($keys[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return \array_keys($keys);
|
||||
}
|
||||
final public function referenceInstance($path)
|
||||
{
|
||||
$this->{$path} = $this->CombinedAPI->instances[$path];
|
||||
}
|
||||
final public function removeInstance($path)
|
||||
{
|
||||
if (isset($this->{$path})) {
|
||||
unset($this->{$path});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -294,7 +294,7 @@ class Connection extends Session
|
|||
*/
|
||||
public function isHttp(): bool
|
||||
{
|
||||
return \in_array($this->ctx->getStreamName(), [HttpStream::getName(), HttpsStream::getName()]);
|
||||
return \in_array($this->ctx->getStreamName(), [HttpStream::class, HttpsStream::class]);
|
||||
}
|
||||
/**
|
||||
* Check if is a media connection.
|
||||
|
@ -348,7 +348,7 @@ class Connection extends Session
|
|||
if (!isset($this->waiter)) {
|
||||
$this->waiter = new HttpWaitLoop($this);
|
||||
}
|
||||
if (!isset($this->pinger) && ($this->ctx->hasStreamName(WssStream::getName()) || $this->ctx->hasStreamName(WsStream::getName()))) {
|
||||
if (!isset($this->pinger) && ($this->ctx->hasStreamName(WssStream::class) || $this->ctx->hasStreamName(WsStream::class))) {
|
||||
$this->pinger = new PingLoop($this);
|
||||
}
|
||||
foreach ($this->new_outgoing as $message_id) {
|
||||
|
|
|
@ -38,6 +38,7 @@ use Amp\Websocket\Client\Handshake;
|
|||
use Amp\Websocket\Client\Rfc6455Connector;
|
||||
use danog\MadelineProto\MTProto\PermAuthKey;
|
||||
use danog\MadelineProto\MTProto\TempAuthKey;
|
||||
use danog\MadelineProto\Settings\Connection as ConnectionSettings;
|
||||
use danog\MadelineProto\Stream\Common\BufferedRawStream;
|
||||
use danog\MadelineProto\Stream\Common\UdpBufferedStream;
|
||||
use danog\MadelineProto\Stream\ConnectionContext;
|
||||
|
@ -48,9 +49,6 @@ use danog\MadelineProto\Stream\MTProtoTransport\HttpStream;
|
|||
use danog\MadelineProto\Stream\MTProtoTransport\IntermediatePaddedStream;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\IntermediateStream;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\ObfuscatedStream;
|
||||
use danog\MadelineProto\Stream\Proxy\HttpProxy;
|
||||
use danog\MadelineProto\Stream\Proxy\SocksProxy;
|
||||
use danog\MadelineProto\Stream\StreamInterface;
|
||||
use danog\MadelineProto\Stream\Transport\DefaultStream;
|
||||
use danog\MadelineProto\Stream\Transport\WssStream;
|
||||
use danog\MadelineProto\Stream\Transport\WsStream;
|
||||
|
@ -70,7 +68,7 @@ class DataCenter
|
|||
/**
|
||||
* Current DC ID.
|
||||
*
|
||||
* @var string
|
||||
* @var string|int
|
||||
*/
|
||||
public $curdc = 0;
|
||||
/**
|
||||
|
@ -88,9 +86,9 @@ class DataCenter
|
|||
/**
|
||||
* Settings.
|
||||
*
|
||||
* @var array
|
||||
* @var ConnectionSettings
|
||||
*/
|
||||
private $settings = [];
|
||||
private $settings;
|
||||
/**
|
||||
* HTTP client.
|
||||
*
|
||||
|
@ -132,9 +130,14 @@ class DataCenter
|
|||
}
|
||||
public function __wakeup()
|
||||
{
|
||||
if (\is_array($this->settings)) {
|
||||
$settings = new ConnectionSettings;
|
||||
$settings->mergeArray(['connection_settings' => $this->settings]);
|
||||
$this->settings = $settings;
|
||||
}
|
||||
$array = [];
|
||||
foreach ($this->sockets as $id => $socket) {
|
||||
if ($socket instanceof Connection) {
|
||||
if ($socket instanceof \danog\MadelineProto\Connection) {
|
||||
if ($socket->temp_auth_key) {
|
||||
$array[$id]['tempAuthKey'] = $socket->temp_auth_key;
|
||||
}
|
||||
|
@ -185,19 +188,19 @@ class DataCenter
|
|||
/**
|
||||
* Constructor function.
|
||||
*
|
||||
* @param MTProto $API Main MTProto instance
|
||||
* @param array $dclist DC IP list
|
||||
* @param array $settings Settings
|
||||
* @param boolean $reconnectAll Whether to reconnect to all DCs or just to changed ones
|
||||
* @param CookieJar $jar Cookie jar
|
||||
* @param MTProto $API Main MTProto instance
|
||||
* @param array $dclist DC IP list
|
||||
* @param ConnectionSettings $settings Settings
|
||||
* @param boolean $reconnectAll Whether to reconnect to all DCs or just to changed ones
|
||||
* @param CookieJar $jar Cookie jar
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __magic_construct($API, array $dclist, array $settings, bool $reconnectAll = true, CookieJar $jar = null)
|
||||
public function __magic_construct($API, array $dclist, ConnectionSettings $settings, bool $reconnectAll = true, CookieJar $jar = null)
|
||||
{
|
||||
$this->API = $API;
|
||||
$changed = [];
|
||||
$changedSettings = $this->settings !== $settings;
|
||||
$changedSettings = $settings->hasChanged();
|
||||
if (!$reconnectAll) {
|
||||
$changed = [];
|
||||
$test = $API->getCachedConfig()['test_mode'] ?? false ? 'test' : 'main';
|
||||
|
@ -213,7 +216,6 @@ class DataCenter
|
|||
$this->settings = $settings;
|
||||
foreach ($this->sockets as $key => $socket) {
|
||||
if ($socket instanceof DataCenterConnection && !\strpos($key, '_bk')) {
|
||||
//$this->API->logger->logger(\sprintf(Lang::$current_lang['dc_con_stop'], $key), Logger::VERBOSE);
|
||||
if ($reconnectAll || isset($changed[$id])) {
|
||||
$this->API->logger->logger("Disconnecting all before reconnect!");
|
||||
$socket->needReconnect(true);
|
||||
|
@ -238,6 +240,7 @@ class DataCenter
|
|||
$this->webSocketConnnector = new Rfc6455Connector($this->HTTPClient);
|
||||
}
|
||||
}
|
||||
$this->settings->applyChanges();
|
||||
}
|
||||
/**
|
||||
* Set VoIP endpoints.
|
||||
|
@ -304,147 +307,116 @@ class DataCenter
|
|||
{
|
||||
$ctxs = [];
|
||||
$combos = [];
|
||||
$dc_config_number = isset($this->settings[$dc_number]) ? $dc_number : 'all';
|
||||
$test = $this->settings[$dc_config_number]['test_mode'] ? 'test' : 'main';
|
||||
$ipv6 = $this->settings[$dc_config_number]['ipv6'] ? 'ipv6' : 'ipv4';
|
||||
switch ($this->settings[$dc_config_number]['protocol']) {
|
||||
case 'abridged':
|
||||
case 'tcp_abridged':
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [AbridgedStream::getName(), []]];
|
||||
$test = $this->settings->getTestMode() ? 'test' : 'main';
|
||||
$ipv6 = $this->settings->getIpv6() ? 'ipv6' : 'ipv4';
|
||||
switch ($this->settings->getProtocol()) {
|
||||
case AbridgedStream::class:
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [AbridgedStream::class, []]];
|
||||
break;
|
||||
case 'intermediate':
|
||||
case 'tcp_intermediate':
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [IntermediateStream::getName(), []]];
|
||||
case IntermediateStream::class:
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [IntermediateStream::class, []]];
|
||||
break;
|
||||
case 'obfuscated2':
|
||||
$this->settings[$dc_config_number]['protocol'] = 'tcp_intermediate_padded';
|
||||
$this->settings[$dc_config_number]['obfuscated'] = true;
|
||||
// no break
|
||||
case 'intermediate_padded':
|
||||
case 'tcp_intermediate_padded':
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [IntermediatePaddedStream::getName(), []]];
|
||||
case IntermediatePaddedStream::class:
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [IntermediatePaddedStream::class, []]];
|
||||
break;
|
||||
case 'full':
|
||||
case 'tcp_full':
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [FullStream::getName(), []]];
|
||||
case FullStream::class:
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [FullStream::class, []]];
|
||||
break;
|
||||
case 'http':
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [HttpStream::getName(), []]];
|
||||
case HttpStream::class:
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [HttpStream::class, []]];
|
||||
break;
|
||||
case 'https':
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [HttpsStream::getName(), []]];
|
||||
case HttpsStream::class:
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [HttpsStream::class, []]];
|
||||
break;
|
||||
case 'udp':
|
||||
$default = [[DefaultStream::getName(), []], [UdpBufferedStream::getName(), []]];
|
||||
case UdpBufferedStream::class:
|
||||
$default = [[DefaultStream::class, []], [UdpBufferedStream::class, []]];
|
||||
break;
|
||||
default:
|
||||
throw new Exception(Lang::$current_lang['protocol_invalid']);
|
||||
}
|
||||
if ($this->settings[$dc_config_number]['obfuscated'] && !\in_array($default[2][0], [HttpsStream::getName(), HttpStream::getName()])) {
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], \end($default)];
|
||||
if ($this->settings->getObfuscated() && !\in_array($default[2][0], [HttpsStream::class, HttpStream::class])) {
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [ObfuscatedStream::class, []], \end($default)];
|
||||
}
|
||||
if ($this->settings[$dc_config_number]['transport'] && !\in_array($default[2][0], [HttpsStream::getName(), HttpStream::getName()])) {
|
||||
switch ($this->settings[$dc_config_number]['transport']) {
|
||||
case 'tcp':
|
||||
if ($this->settings[$dc_config_number]['obfuscated']) {
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], \end($default)];
|
||||
if ($this->settings->getTransport() && !\in_array($default[2][0], [HttpsStream::class, HttpStream::class])) {
|
||||
switch ($this->settings->getTransport()) {
|
||||
case DefaultStream::class:
|
||||
if ($this->settings->getObfuscated()) {
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []], [ObfuscatedStream::class, []], \end($default)];
|
||||
}
|
||||
break;
|
||||
case 'wss':
|
||||
$default = [[DefaultStream::getName(), []], [WssStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], \end($default)];
|
||||
case WssStream::class:
|
||||
$default = [[DefaultStream::class, []], [WssStream::class, []], [BufferedRawStream::class, []], [ObfuscatedStream::class, []], \end($default)];
|
||||
break;
|
||||
case 'ws':
|
||||
$default = [[DefaultStream::getName(), []], [WsStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), []], \end($default)];
|
||||
case WsStream::class:
|
||||
$default = [[DefaultStream::class, []], [WsStream::class, []], [BufferedRawStream::class, []], [ObfuscatedStream::class, []], \end($default)];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!$dc_number) {
|
||||
$default = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []]];
|
||||
$default = [[DefaultStream::class, []], [BufferedRawStream::class, []]];
|
||||
}
|
||||
$combos[] = $default;
|
||||
if (!isset($this->settings[$dc_config_number]['do_not_retry'])) {
|
||||
if (!$this->settings->getRetry()) {
|
||||
if (isset($this->dclist[$test][$ipv6][$dc_number]['tcpo_only']) && $this->dclist[$test][$ipv6][$dc_number]['tcpo_only'] || isset($this->dclist[$test][$ipv6][$dc_number]['secret'])) {
|
||||
$extra = isset($this->dclist[$test][$ipv6][$dc_number]['secret']) ? ['secret' => $this->dclist[$test][$ipv6][$dc_number]['secret']] : [];
|
||||
$combos[] = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [ObfuscatedStream::getName(), $extra], [IntermediatePaddedStream::getName(), []]];
|
||||
$combos[] = [[DefaultStream::class, []], [BufferedRawStream::class, []], [ObfuscatedStream::class, $extra], [IntermediatePaddedStream::class, []]];
|
||||
}
|
||||
if (\is_iterable($this->settings[$dc_config_number]['proxy'])) {
|
||||
$proxies = $this->settings[$dc_config_number]['proxy'];
|
||||
$proxy_extras = $this->settings[$dc_config_number]['proxy_extra'];
|
||||
} else {
|
||||
$proxies = [$this->settings[$dc_config_number]['proxy']];
|
||||
$proxy_extras = [$this->settings[$dc_config_number]['proxy_extra']];
|
||||
}
|
||||
foreach ($proxies as $key => $proxy) {
|
||||
// Convert old settings
|
||||
if ($proxy === '\\Socket') {
|
||||
$proxy = DefaultStream::getName();
|
||||
}
|
||||
if ($proxy === '\\SocksProxy') {
|
||||
$proxy = SocksProxy::getName();
|
||||
}
|
||||
if ($proxy === '\\HttpProxy') {
|
||||
$proxy = HttpProxy::getName();
|
||||
}
|
||||
if ($proxy === '\\MTProxySocket') {
|
||||
$proxy = ObfuscatedStream::getName();
|
||||
}
|
||||
if ($proxy === DefaultStream::getName()) {
|
||||
foreach ($this->settings->getProxies() as $proxy => $extras) {
|
||||
if (!$dc_number && $proxy === ObfuscatedStream::class) {
|
||||
continue;
|
||||
}
|
||||
if (!$dc_number && $proxy === ObfuscatedStream::getName()) {
|
||||
continue;
|
||||
}
|
||||
$extra = $proxy_extras[$key];
|
||||
if (!isset(\class_implements($proxy)[StreamInterface::class])) {
|
||||
throw new Exception(Lang::$current_lang['proxy_class_invalid']);
|
||||
}
|
||||
if ($proxy === ObfuscatedStream::getName() && \in_array(\strlen($extra['secret']), [17, 34])) {
|
||||
$combos[] = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [$proxy, $extra], [IntermediatePaddedStream::getName(), []]];
|
||||
}
|
||||
foreach ($combos as $k => $orig) {
|
||||
$combo = [];
|
||||
if ($proxy === ObfuscatedStream::getName()) {
|
||||
$combo = $orig;
|
||||
if ($combo[\count($combo) - 2][0] === ObfuscatedStream::getName()) {
|
||||
$combo[\count($combo) - 2][1] = $extra;
|
||||
} else {
|
||||
$mtproto = \end($combo);
|
||||
$combo[\count($combo) - 1] = [$proxy, $extra];
|
||||
$combo[] = $mtproto;
|
||||
}
|
||||
} else {
|
||||
if ($orig[1][0] === BufferedRawStream::getName()) {
|
||||
list($first, $second) = [\array_slice($orig, 0, 2), \array_slice($orig, 2)];
|
||||
$first[] = [$proxy, $extra];
|
||||
$combo = \array_merge($first, $second);
|
||||
} elseif (\in_array($orig[1][0], [WsStream::getName(), WssStream::getName()])) {
|
||||
list($first, $second) = [\array_slice($orig, 0, 1), \array_slice($orig, 1)];
|
||||
$first[] = [BufferedRawStream::getName(), []];
|
||||
$first[] = [$proxy, $extra];
|
||||
$combo = \array_merge($first, $second);
|
||||
}
|
||||
foreach ($extras as $extra) {
|
||||
if ($proxy === ObfuscatedStream::class && \in_array(\strlen($extra['secret']), [17, 34])) {
|
||||
$combos[] = [[DefaultStream::class, []], [BufferedRawStream::class, []], [$proxy, $extra], [IntermediatePaddedStream::class, []]];
|
||||
}
|
||||
foreach ($combos as $k => $orig) {
|
||||
$combo = [];
|
||||
if ($proxy === ObfuscatedStream::class) {
|
||||
$combo = $orig;
|
||||
if ($combo[\count($combo) - 2][0] === ObfuscatedStream::class) {
|
||||
$combo[\count($combo) - 2][1] = $extra;
|
||||
} else {
|
||||
$mtproto = \end($combo);
|
||||
$combo[\count($combo) - 1] = [$proxy, $extra];
|
||||
$combo[] = $mtproto;
|
||||
}
|
||||
} else {
|
||||
if ($orig[1][0] === BufferedRawStream::class) {
|
||||
list($first, $second) = [\array_slice($orig, 0, 2), \array_slice($orig, 2)];
|
||||
$first[] = [$proxy, $extra];
|
||||
$combo = \array_merge($first, $second);
|
||||
} elseif (\in_array($orig[1][0], [WsStream::class, WssStream::class])) {
|
||||
list($first, $second) = [\array_slice($orig, 0, 1), \array_slice($orig, 1)];
|
||||
$first[] = [BufferedRawStream::class, []];
|
||||
$first[] = [$proxy, $extra];
|
||||
$combo = \array_merge($first, $second);
|
||||
}
|
||||
}
|
||||
\array_unshift($combos, $combo);
|
||||
//unset($combos[$k]);
|
||||
}
|
||||
\array_unshift($combos, $combo);
|
||||
//unset($combos[$k]);
|
||||
}
|
||||
}
|
||||
if ($dc_number) {
|
||||
$combos[] = [[DefaultStream::getName(), []], [BufferedRawStream::getName(), []], [HttpsStream::getName(), []]];
|
||||
$combos[] = [[DefaultStream::class, []], [BufferedRawStream::class, []], [HttpsStream::class, []]];
|
||||
}
|
||||
$combos = \array_unique($combos, SORT_REGULAR);
|
||||
}
|
||||
/* @var $context \Amp\ConnectContext */
|
||||
$context = $context ?? (new ConnectContext())->withMaxAttempts(1)->withConnectTimeout(1000 * $this->settings[$dc_config_number]['timeout']);
|
||||
$context = $context ?? (new ConnectContext())->withMaxAttempts(1)->withConnectTimeout(1000 * $this->settings->getTimeout());
|
||||
foreach ($combos as $combo) {
|
||||
foreach ([true, false] as $useDoH) {
|
||||
$ipv6Combos = [$this->settings[$dc_config_number]['ipv6'] ? 'ipv6' : 'ipv4', $this->settings[$dc_config_number]['ipv6'] ? 'ipv4' : 'ipv6'];
|
||||
$ipv6Combos = [
|
||||
$this->settings->getIpv6() ? 'ipv6' : 'ipv4',
|
||||
$this->settings->getIpv6() ? 'ipv4' : 'ipv6'
|
||||
];
|
||||
foreach ($ipv6Combos as $ipv6) {
|
||||
// This is only for non-MTProto connections
|
||||
if (!$dc_number) {
|
||||
/* @var $ctx \danog\MadelineProto\Stream\ConnectionContext */
|
||||
$ctx = (new ConnectionContext())->setSocketContext($context)->setUri($uri)->setIpv6($ipv6 === 'ipv6');
|
||||
foreach ($combo as $stream) {
|
||||
if ($stream[0] === DefaultStream::getName() && $stream[1] === []) {
|
||||
if ($stream[0] === DefaultStream::class && $stream[1] === []) {
|
||||
$stream[1] = $useDoH ? new DoHConnector($this, $ctx) : $this->dnsConnector;
|
||||
}
|
||||
$ctx->addStream(...$stream);
|
||||
|
@ -466,7 +438,7 @@ class DataCenter
|
|||
$port = $this->dclist[$test][$ipv6][$dc_number]['port'];
|
||||
foreach (\array_unique([$port, 443, 80, 88, 5222]) as $port) {
|
||||
$stream = \end($combo)[0];
|
||||
if ($stream === HttpsStream::getName()) {
|
||||
if ($stream === HttpsStream::class) {
|
||||
if (\strpos($dc_number, '_cdn') !== false) {
|
||||
continue;
|
||||
}
|
||||
|
@ -474,33 +446,33 @@ class DataCenter
|
|||
if (\strpos($dc_number, '_media') !== false) {
|
||||
$subdomain .= '-1';
|
||||
}
|
||||
$path = $this->settings[$dc_config_number]['test_mode'] ? 'apiw_test1' : 'apiw1';
|
||||
$path = $this->settings->getTestMode() ? 'apiw_test1' : 'apiw1';
|
||||
$uri = 'tcp://'.$subdomain.'.web.telegram.org:'.$port.'/'.$path;
|
||||
} elseif ($stream === HttpStream::getName()) {
|
||||
} elseif ($stream === HttpStream::class) {
|
||||
$uri = 'tcp://'.$address.':'.$port.'/api';
|
||||
} else {
|
||||
$uri = 'tcp://'.$address.':'.$port;
|
||||
}
|
||||
if ($combo[1][0] === WssStream::getName()) {
|
||||
if ($combo[1][0] === WssStream::class) {
|
||||
$subdomain = $this->dclist['ssl_subdomains'][\preg_replace('/\\D+/', '', $dc_number)];
|
||||
if (\strpos($dc_number, '_media') !== false) {
|
||||
$subdomain .= '-1';
|
||||
}
|
||||
$path = $this->settings[$dc_config_number]['test_mode'] ? 'apiws_test' : 'apiws';
|
||||
$path = $this->settings->getTestMode() ? 'apiws_test' : 'apiws';
|
||||
$uri = 'tcp://'.$subdomain.'.web.telegram.org:'.$port.'/'.$path;
|
||||
} elseif ($combo[1][0] === WsStream::getName()) {
|
||||
} elseif ($combo[1][0] === WsStream::class) {
|
||||
$subdomain = $this->dclist['ssl_subdomains'][\preg_replace('/\\D+/', '', $dc_number)];
|
||||
if (\strpos($dc_number, '_media') !== false) {
|
||||
$subdomain .= '-1';
|
||||
}
|
||||
$path = $this->settings[$dc_config_number]['test_mode'] ? 'apiws_test' : 'apiws';
|
||||
$path = $this->settings->getTestMode() ? 'apiws_test' : 'apiws';
|
||||
//$uri = 'tcp://' . $subdomain . '.web.telegram.org:' . $port . '/' . $path;
|
||||
$uri = 'tcp://'.$address.':'.$port.'/'.$path;
|
||||
}
|
||||
/* @var $ctx \danog\MadelineProto\Stream\ConnectionContext */
|
||||
$ctx = (new ConnectionContext())->setDc($dc_number)->setTest($this->settings[$dc_config_number]['test_mode'])->setSocketContext($context)->setUri($uri)->setIpv6($ipv6 === 'ipv6');
|
||||
$ctx = (new ConnectionContext())->setDc($dc_number)->setTest($this->settings->getTestMode())->setSocketContext($context)->setUri($uri)->setIpv6($ipv6 === 'ipv6');
|
||||
foreach ($combo as $stream) {
|
||||
if ($stream[0] === DefaultStream::getName() && $stream[1] === []) {
|
||||
if ($stream[0] === DefaultStream::class && $stream[1] === []) {
|
||||
$stream[1] = $useDoH ? new DoHConnector($this, $ctx) : $this->dnsConnector;
|
||||
}
|
||||
if (\in_array($stream[0], [WsStream::class, WssStream::class]) && $stream[1] === []) {
|
||||
|
@ -675,8 +647,8 @@ class DataCenter
|
|||
*/
|
||||
public function getDcs($all = true): array
|
||||
{
|
||||
$test = $this->settings['all']['test_mode'] ? 'test' : 'main';
|
||||
$ipv6 = $this->settings['all']['ipv6'] ? 'ipv6' : 'ipv4';
|
||||
$test = $this->settings->getTestMode() ? 'test' : 'main';
|
||||
$ipv6 = $this->settings->getIpv6() ? 'ipv6' : 'ipv4';
|
||||
return $all ? \array_keys((array) $this->dclist[$test][$ipv6]) : \array_keys((array) $this->sockets);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ use danog\MadelineProto\Loop\Generic\PeriodicLoopInternal;
|
|||
use danog\MadelineProto\MTProto\AuthKey;
|
||||
use danog\MadelineProto\MTProto\PermAuthKey;
|
||||
use danog\MadelineProto\MTProto\TempAuthKey;
|
||||
use danog\MadelineProto\Settings\Connection as ConnectionSettings;
|
||||
use danog\MadelineProto\Stream\ConnectionContext;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\HttpsStream;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\HttpStream;
|
||||
|
@ -361,10 +362,10 @@ class DataCenterConnection implements JsonSerializable
|
|||
$this->ctx = $ctx->getCtx();
|
||||
$this->datacenter = $ctx->getDc();
|
||||
$media = $ctx->isMedia() || $ctx->isCDN();
|
||||
$count = $media ? $this->API->settings['connection_settings']['media_socket_count']['min'] : 1;
|
||||
$count = $media ? $this->API->getSettings()->getConnection()->getMinMediaSocketCount() : 1;
|
||||
if ($count > 1) {
|
||||
if (!$this->robinLoop) {
|
||||
$this->robinLoop = new PeriodicLoopInternal($this->API, [$this, 'even'], "robin loop DC {$this->datacenter}", $this->API->settings['connection_settings']['robin_period'] * 1000);
|
||||
$this->robinLoop = new PeriodicLoopInternal($this->API, [$this, 'even'], "robin loop DC {$this->datacenter}", $this->API->getSettings()->getConnection()->getRobinPeriod() * 1000);
|
||||
}
|
||||
$this->robinLoop->start();
|
||||
}
|
||||
|
@ -558,7 +559,7 @@ class DataCenterConnection implements JsonSerializable
|
|||
$count += 50;
|
||||
}
|
||||
} elseif ($min < 100) {
|
||||
$max = $this->isMedia() || $this->isCDN() ? $this->API->settings['connection_settings']['media_socket_count']['max'] : 1;
|
||||
$max = $this->isMedia() || $this->isCDN() ? $this->API->getSettings()->getConnection()->getMaxMediaSocketCount() : 1;
|
||||
if (\count($this->availableConnections) < $max) {
|
||||
$this->connectMore(2);
|
||||
} else {
|
||||
|
@ -619,7 +620,7 @@ class DataCenterConnection implements JsonSerializable
|
|||
*/
|
||||
public function isHttp(): bool
|
||||
{
|
||||
return \in_array($this->ctx->getStreamName(), [HttpStream::getName(), HttpsStream::getName()]);
|
||||
return \in_array($this->ctx->getStreamName(), [HttpStream::class, HttpsStream::class]);
|
||||
}
|
||||
/**
|
||||
* Check if is connected directly by IP address.
|
||||
|
@ -628,7 +629,7 @@ class DataCenterConnection implements JsonSerializable
|
|||
*/
|
||||
public function byIPAddress(): bool
|
||||
{
|
||||
return !$this->ctx->hasStreamName(WssStream::getName()) && !$this->ctx->hasStreamName(HttpsStream::getName());
|
||||
return !$this->ctx->hasStreamName(WssStream::class) && !$this->ctx->hasStreamName(HttpsStream::class);
|
||||
}
|
||||
/**
|
||||
* Check if is a media connection.
|
||||
|
@ -651,12 +652,20 @@ class DataCenterConnection implements JsonSerializable
|
|||
/**
|
||||
* Get DC-specific settings.
|
||||
*
|
||||
* @return array
|
||||
* @return ConnectionSettings
|
||||
*/
|
||||
public function getSettings(): array
|
||||
public function getSettings(): ConnectionSettings
|
||||
{
|
||||
$dc_config_number = isset($this->API->settings['connection_settings'][$this->datacenter]) ? $this->datacenter : 'all';
|
||||
return $this->API->settings['connection_settings'][$dc_config_number];
|
||||
return $this->API->getSettings()->getConnection();
|
||||
}
|
||||
/**
|
||||
* Get global settings.
|
||||
*
|
||||
* @return Settings
|
||||
*/
|
||||
public function getGenericSettings(): Settings
|
||||
{
|
||||
return $this->API->getSettings();
|
||||
}
|
||||
/**
|
||||
* JSON serialize function.
|
||||
|
|
|
@ -16,10 +16,18 @@ trait ArrayCacheTrait
|
|||
*/
|
||||
protected array $ttlValues = [];
|
||||
|
||||
/**
|
||||
* TTL interval.
|
||||
*/
|
||||
protected int $ttl = 5 * 60;
|
||||
/**
|
||||
* TTL cleanup interval.
|
||||
*/
|
||||
private int $ttlCheckInterval = 60;
|
||||
|
||||
protected string $ttl = '+5 minutes';
|
||||
private string $ttlCheckInterval = '+1 minute';
|
||||
|
||||
/**
|
||||
* Cache cleanup watcher ID.
|
||||
*/
|
||||
private ?string $cacheCleanupId = null;
|
||||
|
||||
protected function getCache(string $key, $default = null)
|
||||
|
@ -55,7 +63,7 @@ trait ArrayCacheTrait
|
|||
|
||||
protected function startCacheCleanupLoop(): void
|
||||
{
|
||||
$this->cacheCleanupId = Loop::repeat(\strtotime($this->ttlCheckInterval, 0) * 1000, fn () => $this->cleanupCache());
|
||||
$this->cacheCleanupId = Loop::repeat($this->ttlCheckInterval * 1000, fn () => $this->cleanupCache());
|
||||
}
|
||||
protected function stopCacheCleanupLoop(): void
|
||||
{
|
||||
|
|
|
@ -3,42 +3,52 @@
|
|||
namespace danog\MadelineProto\Db;
|
||||
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Settings\Database\Memory;
|
||||
use danog\MadelineProto\Settings\Database\Mysql;
|
||||
use danog\MadelineProto\Settings\Database\Postgres;
|
||||
use danog\MadelineProto\Settings\Database\Redis;
|
||||
use danog\MadelineProto\Settings\DatabaseAbstract;
|
||||
|
||||
class DbPropertiesFactory
|
||||
{
|
||||
/**
|
||||
* @param array $dbSettings
|
||||
* @param DatabaseAbstract $dbSettings
|
||||
* @param string $namePrefix
|
||||
* @param string $propertyType
|
||||
* @param string|array $propertyType
|
||||
* @param string $name
|
||||
* @param $value
|
||||
*
|
||||
* @return Promise<DbType>
|
||||
*
|
||||
* @uses \danog\MadelineProto\Db\MemoryArray
|
||||
* @uses \danog\MadelineProto\Db\SharedMemoryArray
|
||||
* @uses \danog\MadelineProto\Db\MysqlArray
|
||||
* @uses \danog\MadelineProto\Db\PostgresArray
|
||||
* @uses \danog\MadelineProto\Db\RedisArray
|
||||
*/
|
||||
public static function get(array $dbSettings, string $namePrefix, string $propertyType, string $name, $value = null): Promise
|
||||
public static function get(DatabaseAbstract $dbSettings, string $namePrefix, $propertyType, string $name, $value = null): Promise
|
||||
{
|
||||
$class = __NAMESPACE__;
|
||||
$config = $propertyType['config'] ?? [];
|
||||
$propertyType = \is_array($propertyType) ? $propertyType['type'] : $propertyType;
|
||||
$propertyType = \strtolower($propertyType);
|
||||
$class = !($config['enableCache'] ?? true) && !$dbSettings instanceof Memory
|
||||
? __NAMESPACE__.'\\NullCache'
|
||||
: __NAMESPACE__;
|
||||
|
||||
switch (\strtolower($dbSettings['type'])) {
|
||||
case 'memory':
|
||||
switch (true) {
|
||||
case $dbSettings instanceof Memory:
|
||||
$class .= '\\Memory';
|
||||
break;
|
||||
case 'mysql':
|
||||
case $dbSettings instanceof Mysql:
|
||||
$class .= '\\Mysql';
|
||||
break;
|
||||
case 'postgres':
|
||||
case $dbSettings instanceof Postgres:
|
||||
$class .= '\\Postgres';
|
||||
break;
|
||||
case 'redis':
|
||||
case $dbSettings instanceof Redis:
|
||||
$class .= '\\Redis';
|
||||
break;
|
||||
default:
|
||||
throw new \InvalidArgumentException("Unknown dbType: {$dbSettings['type']}");
|
||||
throw new \InvalidArgumentException("Unknown dbType: ".\get_class($dbSettings));
|
||||
|
||||
}
|
||||
|
||||
|
@ -51,6 +61,6 @@ class DbPropertiesFactory
|
|||
throw new \InvalidArgumentException("Unknown $propertyType: {$propertyType}");
|
||||
}
|
||||
|
||||
return $class::getInstance($name, $value, $namePrefix, $dbSettings[$dbSettings['type']]??[]);
|
||||
return $class::getInstance($name, $value, $namePrefix, $dbSettings);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ trait DbPropertiesTrait
|
|||
if (empty(static::$dbProperties)) {
|
||||
throw new \LogicException(static::class.' must have $dbProperties');
|
||||
}
|
||||
$dbSettings = $MadelineProto->settings['db'];
|
||||
$dbSettings = $MadelineProto->settings->getDb();
|
||||
$prefix = static::getSessionId($MadelineProto);
|
||||
|
||||
foreach (static::$dbProperties as $property => $type) {
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace danog\MadelineProto\Db;
|
||||
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Settings\DatabaseAbstract;
|
||||
|
||||
interface DbType
|
||||
{
|
||||
|
@ -10,9 +11,9 @@ interface DbType
|
|||
* @param string $name
|
||||
* @param null $value
|
||||
* @param string $tablePrefix
|
||||
* @param array $settings
|
||||
* @param DatabaseAbstract $settings
|
||||
*
|
||||
* @return Promise<self>
|
||||
*/
|
||||
public static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): Promise;
|
||||
public static function getInstance(string $name, $value = null, string $tablePrefix = '', $settings): Promise;
|
||||
}
|
||||
|
|
|
@ -4,8 +4,9 @@ namespace danog\MadelineProto\Db\Driver;
|
|||
|
||||
use Amp\Mysql\ConnectionConfig;
|
||||
use Amp\Mysql\Pool;
|
||||
use Amp\Sql\Common\ConnectionPool;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Settings\Database\Mysql as DatabaseMysql;
|
||||
|
||||
use function Amp\Mysql\Pool;
|
||||
|
||||
class Mysql
|
||||
|
@ -29,23 +30,17 @@ class Mysql
|
|||
*
|
||||
* @return \Generator<Pool>
|
||||
*/
|
||||
public static function getConnection(
|
||||
string $host = '127.0.0.1',
|
||||
int $port = 3306,
|
||||
string $user = 'root',
|
||||
string $password = '',
|
||||
string $db = 'MadelineProto',
|
||||
int $maxConnections = ConnectionPool::DEFAULT_MAX_CONNECTIONS,
|
||||
int $idleTimeout = ConnectionPool::DEFAULT_IDLE_TIMEOUT
|
||||
): \Generator {
|
||||
$dbKey = "$host:$port:$db";
|
||||
public static function getConnection(DatabaseMysql $settings): \Generator
|
||||
{
|
||||
$dbKey = $settings->getKey();
|
||||
if (empty(static::$connections[$dbKey])) {
|
||||
$config = ConnectionConfig::fromString(
|
||||
"host={$host} port={$port} user={$user} password={$password} db={$db}"
|
||||
);
|
||||
$config = ConnectionConfig::fromString("host=".\str_replace("tcp://", "", $settings->getUri()))
|
||||
->withUser($settings->getUsername())
|
||||
->withPassword($settings->getPassword())
|
||||
->withDatabase($settings->getDatabase());
|
||||
|
||||
yield from static::createDb($config);
|
||||
static::$connections[$dbKey] = pool($config, $maxConnections, $idleTimeout);
|
||||
static::$connections[$dbKey] = pool($config, $settings->getMaxConnections(), $settings->getIdleTimeout());
|
||||
}
|
||||
|
||||
return static::$connections[$dbKey];
|
||||
|
|
|
@ -4,8 +4,9 @@ namespace danog\MadelineProto\Db\Driver;
|
|||
|
||||
use Amp\Postgres\ConnectionConfig;
|
||||
use Amp\Postgres\Pool;
|
||||
use Amp\Sql\Common\ConnectionPool;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Settings\Database\Postgres as DatabasePostgres;
|
||||
|
||||
use function Amp\Postgres\Pool;
|
||||
|
||||
class Postgres
|
||||
|
@ -29,23 +30,17 @@ class Postgres
|
|||
*
|
||||
* @return \Generator<Pool>
|
||||
*/
|
||||
public static function getConnection(
|
||||
string $host = '127.0.0.1',
|
||||
int $port = 5432,
|
||||
string $user = 'root',
|
||||
string $password = '',
|
||||
string $db = 'MadelineProto',
|
||||
int $maxConnections = ConnectionPool::DEFAULT_MAX_CONNECTIONS,
|
||||
int $idleTimeout = ConnectionPool::DEFAULT_IDLE_TIMEOUT
|
||||
): \Generator {
|
||||
$dbKey = "$host:$port:$db";
|
||||
public static function getConnection(DatabasePostgres $settings): \Generator
|
||||
{
|
||||
$dbKey = $settings->getKey();
|
||||
if (empty(static::$connections[$dbKey])) {
|
||||
$config = ConnectionConfig::fromString(
|
||||
"host={$host} port={$port} user={$user} password={$password} db={$db}"
|
||||
);
|
||||
$config = ConnectionConfig::fromString("host=".\str_replace("tcp://", "", $settings->getUri()))
|
||||
->withUser($settings->getUsername())
|
||||
->withPassword($settings->getPassword())
|
||||
->withDatabase($settings->getDatabase());
|
||||
|
||||
yield from static::createDb($config);
|
||||
static::$connections[$dbKey] = pool($config, $maxConnections, $idleTimeout);
|
||||
static::$connections[$dbKey] = pool($config, $settings->getMaxConnections(), $settings->getIdleTimeout());
|
||||
}
|
||||
|
||||
return static::$connections[$dbKey];
|
||||
|
|
|
@ -5,6 +5,7 @@ namespace danog\MadelineProto\Db\Driver;
|
|||
use Amp\Redis\Config;
|
||||
use Amp\Redis\Redis as RedisRedis;
|
||||
use Amp\Redis\RemoteExecutorFactory;
|
||||
use danog\MadelineProto\Settings\Database\Redis as DatabaseRedis;
|
||||
|
||||
class Redis
|
||||
{
|
||||
|
@ -27,17 +28,13 @@ class Redis
|
|||
*
|
||||
* @return \Generator<RedisRedis>
|
||||
*/
|
||||
public static function getConnection(
|
||||
string $host = '127.0.0.1',
|
||||
int $port = 6379,
|
||||
string $password = '',
|
||||
int $db = 0
|
||||
): \Generator {
|
||||
$dbKey = "$host:$port:$db";
|
||||
public static function getConnection(DatabaseRedis $settings): \Generator
|
||||
{
|
||||
$dbKey = $settings->getKey();
|
||||
if (empty(static::$connections[$dbKey])) {
|
||||
$config = Config::fromUri(
|
||||
"{$host}:{$port}?password={$password}&db={$db}"
|
||||
);
|
||||
$config = Config::fromUri($settings->getUri())
|
||||
->withPassword($settings->getPassword())
|
||||
->withDatabase($settings->getDatabase());
|
||||
|
||||
static::$connections[$dbKey] = new RedisRedis((new RemoteExecutorFactory($config))->createQueryExecutor());
|
||||
yield static::$connections[$dbKey]->ping();
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
namespace danog\MadelineProto\Db;
|
||||
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\SettingsAbstract;
|
||||
use ReflectionClass;
|
||||
|
||||
abstract class DriverArray implements DbArray
|
||||
{
|
||||
|
@ -14,12 +16,22 @@ abstract class DriverArray implements DbArray
|
|||
}
|
||||
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
if (isset($this->settings) && \is_array($this->settings)) {
|
||||
$clazz = (new ReflectionClass($this))->getProperty('dbSettings')->getType()->getName();
|
||||
/** @var SettingsAbstract */
|
||||
$this->dbSettings = new $clazz;
|
||||
$this->dbSettings->mergeArray($this->settings);
|
||||
unset($this->settings);
|
||||
}
|
||||
}
|
||||
public function offsetExists($index): bool
|
||||
{
|
||||
throw new \RuntimeException('Native isset not support promises. Use isset method');
|
||||
}
|
||||
|
||||
abstract protected function initConnection(array $settings): \Generator;
|
||||
abstract public function initConnection($settings): \Generator;
|
||||
|
||||
/**
|
||||
* @param self $new
|
||||
|
|
|
@ -6,6 +6,8 @@ use Amp\Producer;
|
|||
use Amp\Promise;
|
||||
use Amp\Success;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Settings\Database\Memory;
|
||||
|
||||
use function Amp\call;
|
||||
|
||||
class MemoryArray extends \ArrayIterator implements DbArray
|
||||
|
@ -15,7 +17,16 @@ class MemoryArray extends \ArrayIterator implements DbArray
|
|||
parent::__construct((array) $array, $flags | self::STD_PROP_LIST);
|
||||
}
|
||||
|
||||
public static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): Promise
|
||||
/**
|
||||
* Get instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param mixed $value
|
||||
* @param string $tablePrefix
|
||||
* @param Memory $settings
|
||||
* @return Promise
|
||||
*/
|
||||
public static function getInstance(string $name, $value = null, string $tablePrefix = '', $settings): Promise
|
||||
{
|
||||
return call(static function () use ($value) {
|
||||
if ($value instanceof MemoryArray) {
|
||||
|
@ -23,6 +34,9 @@ class MemoryArray extends \ArrayIterator implements DbArray
|
|||
}
|
||||
if ($value instanceof DbArray) {
|
||||
Logger::log("Loading database to memory. Please wait.", Logger::WARNING);
|
||||
if ($value instanceof DriverArray) {
|
||||
yield from $value->initConnection($value->dbSettings);
|
||||
}
|
||||
$value = yield $value->getArrayCopy();
|
||||
}
|
||||
return new static($value);
|
||||
|
|
|
@ -9,17 +9,22 @@ use Amp\Sql\ResultSet;
|
|||
use Amp\Success;
|
||||
use danog\MadelineProto\Db\Driver\Mysql;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Settings\Database\Mysql as DatabaseMysql;
|
||||
|
||||
use function Amp\call;
|
||||
|
||||
class MysqlArray extends SqlArray
|
||||
{
|
||||
protected string $table;
|
||||
protected array $settings;
|
||||
protected DatabaseMysql $dbSettings;
|
||||
private Pool $db;
|
||||
|
||||
// Legacy
|
||||
protected array $settings;
|
||||
|
||||
public function __sleep(): array
|
||||
{
|
||||
return ['table', 'settings'];
|
||||
return ['table', 'dbSettings'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -177,18 +182,16 @@ class MysqlArray extends SqlArray
|
|||
return null;
|
||||
}
|
||||
|
||||
protected function initConnection(array $settings): \Generator
|
||||
/**
|
||||
* Initialize connection.
|
||||
*
|
||||
* @param DatabaseMysql $settings
|
||||
* @return \Generator
|
||||
*/
|
||||
public function initConnection($settings): \Generator
|
||||
{
|
||||
if (!isset($this->db)) {
|
||||
$this->db = yield from Mysql::getConnection(
|
||||
$settings['host'],
|
||||
$settings['port'],
|
||||
$settings['user'],
|
||||
$settings['password'],
|
||||
$settings['database'],
|
||||
$settings['max_connections'],
|
||||
$settings['idle_timeout']
|
||||
);
|
||||
$this->db = yield from Mysql::getConnection($settings);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,7 +242,7 @@ class MysqlArray extends SqlArray
|
|||
private function request(string $query, array $params = []): Promise
|
||||
{
|
||||
return call(function () use ($query, $params) {
|
||||
Logger::log([$query, $params], Logger::VERBOSE);
|
||||
//Logger::log([$query, $params], Logger::VERBOSE);
|
||||
|
||||
if (empty($this->db) || !$this->db->isAlive()) {
|
||||
Logger::log('No database connection', Logger::WARNING);
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Db\NullCache;
|
||||
|
||||
use danog\MadelineProto\Settings\Database\Mysql;
|
||||
|
||||
class MysqlArray extends Mysql
|
||||
{
|
||||
use NullCacheTrait;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Db\NullCache;
|
||||
|
||||
trait NullCacheTrait
|
||||
{
|
||||
protected function getCache(string $key, $default = null)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Save item in cache.
|
||||
*
|
||||
* @param string $key
|
||||
* @param $value
|
||||
*/
|
||||
protected function setCache(string $key, $value): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove key from cache.
|
||||
*
|
||||
* @param string $key
|
||||
*/
|
||||
protected function unsetCache(string $key): void
|
||||
{
|
||||
}
|
||||
|
||||
protected function startCacheCleanupLoop(): void
|
||||
{
|
||||
}
|
||||
protected function stopCacheCleanupLoop(): void
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Db\NullCache;
|
||||
|
||||
use danog\MadelineProto\Settings\Database\Postgres;
|
||||
|
||||
class PostgresArray extends Postgres
|
||||
{
|
||||
use NullCacheTrait;
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Db\NullCache;
|
||||
|
||||
use danog\MadelineProto\Db\RedisArray as DbRedisArray;
|
||||
|
||||
class RedisArray extends DbRedisArray
|
||||
{
|
||||
use NullCacheTrait;
|
||||
}
|
|
@ -9,26 +9,29 @@ use Amp\Sql\ResultSet;
|
|||
use Amp\Success;
|
||||
use danog\MadelineProto\Db\Driver\Postgres;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Settings\Database\Postgres as DatabasePostgres;
|
||||
|
||||
use function Amp\call;
|
||||
|
||||
class PostgresArray extends SqlArray
|
||||
{
|
||||
protected string $table;
|
||||
protected array $settings;
|
||||
public DatabasePostgres $dbSettings;
|
||||
private Pool $db;
|
||||
|
||||
protected function initConnection(array $settings): \Generator
|
||||
// Legacy
|
||||
protected array $settings;
|
||||
|
||||
/**
|
||||
* Initialize connection.
|
||||
*
|
||||
* @param DatabasePostgres $settings
|
||||
* @return \Generator
|
||||
*/
|
||||
public function initConnection($settings): \Generator
|
||||
{
|
||||
if (!isset($this->db)) {
|
||||
$this->db = yield from Postgres::getConnection(
|
||||
$settings['host'],
|
||||
$settings['port'],
|
||||
$settings['user'],
|
||||
$settings['password'],
|
||||
$settings['database'],
|
||||
$settings['max_connections'],
|
||||
$settings['idle_timeout']
|
||||
);
|
||||
$this->db = yield from Postgres::getConnection($settings);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,7 +110,7 @@ class PostgresArray extends SqlArray
|
|||
|
||||
public function __sleep()
|
||||
{
|
||||
return ['table', 'settings'];
|
||||
return ['table', 'dbSettings'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,6 +8,7 @@ use Amp\Redis\Redis as RedisRedis;
|
|||
use Amp\Success;
|
||||
use danog\MadelineProto\Db\Driver\Redis as Redis;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Settings\Database\Redis as DatabaseRedis;
|
||||
use Generator;
|
||||
|
||||
use function Amp\call;
|
||||
|
@ -15,9 +16,12 @@ use function Amp\call;
|
|||
class RedisArray extends SqlArray
|
||||
{
|
||||
protected string $table;
|
||||
protected array $settings;
|
||||
public DatabaseRedis $dbSettings;
|
||||
private RedisRedis $db;
|
||||
|
||||
// Legacy
|
||||
protected array $settings;
|
||||
|
||||
protected function prepareTable(): Generator
|
||||
{
|
||||
yield new Success;
|
||||
|
@ -39,22 +43,24 @@ class RedisArray extends SqlArray
|
|||
}
|
||||
}
|
||||
|
||||
protected function initConnection(array $settings): \Generator
|
||||
/**
|
||||
* Initialize connection.
|
||||
*
|
||||
* @param DatabaseRedis $settings
|
||||
* @return \Generator
|
||||
*/
|
||||
public function initConnection($settings): \Generator
|
||||
{
|
||||
if (!isset($this->db)) {
|
||||
$this->db = yield from Redis::getConnection(
|
||||
$settings['host'],
|
||||
$settings['port'],
|
||||
$settings['password'],
|
||||
$settings['database']
|
||||
);
|
||||
$this->db = yield from Redis::getConnection($settings);
|
||||
}
|
||||
}
|
||||
|
||||
public function __sleep()
|
||||
{
|
||||
return ['table', 'settings'];
|
||||
return ['table', 'dbSettings'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get redis key name.
|
||||
*
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
namespace danog\MadelineProto\Db;
|
||||
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Settings\Database\DatabaseAbstract;
|
||||
|
||||
use function Amp\call;
|
||||
|
||||
|
@ -22,11 +23,11 @@ abstract class SqlArray extends DriverArray
|
|||
* @param string $name
|
||||
* @param DbArray|array|null $value
|
||||
* @param string $tablePrefix
|
||||
* @param array $settings
|
||||
* @param DatabaseAbstract $settings
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public static function getInstance(string $name, $value = null, string $tablePrefix = '', array $settings = []): Promise
|
||||
public static function getInstance(string $name, $value = null, string $tablePrefix = '', $settings): Promise
|
||||
{
|
||||
$tableName = "{$tablePrefix}_{$name}";
|
||||
if ($value instanceof static && $value->table === $tableName) {
|
||||
|
@ -36,8 +37,8 @@ abstract class SqlArray extends DriverArray
|
|||
$instance->table = $tableName;
|
||||
}
|
||||
|
||||
$instance->settings = $settings;
|
||||
$instance->ttl = $settings['cache_ttl'] ?? $instance->ttl;
|
||||
$instance->dbSettings = $settings;
|
||||
$instance->ttl = $settings->getCacheTtl();
|
||||
|
||||
$instance->startCacheCleanupLoop();
|
||||
|
||||
|
@ -48,7 +49,7 @@ abstract class SqlArray extends DriverArray
|
|||
// Skip migrations if its same object
|
||||
if ($instance !== $value) {
|
||||
if ($value instanceof DriverArray) {
|
||||
yield from $value->initConnection($value->settings);
|
||||
yield from $value->initConnection($value->dbSettings);
|
||||
}
|
||||
yield from static::renameTmpTable($instance, $value);
|
||||
yield from static::migrateDataToDb($instance, $value);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use danog\MadelineProto\Settings\TLSchema;
|
||||
use danog\MadelineProto\TL\TL;
|
||||
|
||||
// This code was written a few years ago: it is garbage, and has to be rewritten
|
||||
|
@ -48,7 +49,9 @@ class DocsBuilder
|
|||
$this->logger = $logger;
|
||||
}
|
||||
});
|
||||
$this->TL->init($settings['tl_schema']);
|
||||
$new = new TLSchema;
|
||||
$new->mergeArray($settings);
|
||||
$this->TL->init($new);
|
||||
if (isset($settings['tl_schema']['td']) && !isset($settings['tl_schema']['telegram'])) {
|
||||
$this->td = true;
|
||||
}
|
||||
|
|
|
@ -22,18 +22,19 @@ namespace danog\MadelineProto;
|
|||
/**
|
||||
* Event handler.
|
||||
*/
|
||||
class EventHandler extends InternalDoc
|
||||
abstract class EventHandler extends InternalDoc
|
||||
{
|
||||
/**
|
||||
* Constructor.
|
||||
* Internal constructor.
|
||||
*
|
||||
* @param APIWrapper|null $MadelineProto MadelineProto instance
|
||||
* @internal
|
||||
*
|
||||
* @param APIWrapper $MadelineProto MadelineProto instance
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(?APIWrapper $MadelineProto)
|
||||
public function initInternal(APIWrapper $MadelineProto): void
|
||||
{
|
||||
if (!$MadelineProto) {
|
||||
return;
|
||||
}
|
||||
self::link($this, $MadelineProto->getFactory());
|
||||
$this->API =& $MadelineProto->getAPI();
|
||||
foreach ($this->API->getMethodNamespaces() as $namespace) {
|
||||
|
|
|
@ -19,151 +19,4 @@
|
|||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use Amp\File\StatCache;
|
||||
use Amp\Ipc\Sync\ChannelledSocket;
|
||||
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;
|
||||
use function Amp\File\unlink;
|
||||
use function Amp\Ipc\connect;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
if (!$client = yield from $this->checkInit($session, $settings)) {
|
||||
try {
|
||||
yield unlink($session->getIpcPath());
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
StatCache::clear($session->getIpcPath());
|
||||
Server::startMe($session);
|
||||
$inited = false;
|
||||
$this->logger->logger("Waiting for IPC server to start...");
|
||||
for ($x = 0; $x < 30; $x++) {
|
||||
yield Tools::sleep(1);
|
||||
StatCache::clear($session->getIpcPath());
|
||||
if ($client = yield from $this->checkInit($session, $settings)) {
|
||||
$inited = true;
|
||||
break;
|
||||
}
|
||||
Server::startMe($session);
|
||||
}
|
||||
if (!$client) {
|
||||
throw new Exception("The IPC server isn't running, please check logs!");
|
||||
}
|
||||
}
|
||||
$this->API = new Client($client, $this->logger);
|
||||
$this->methods = self::getInternalMethodList($this->API, MTProto::class);
|
||||
$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()) === Server::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);
|
||||
Logger::log("Destroying temporary MadelineProto...");
|
||||
while (\gc_collect_cycles());
|
||||
Logger::log("Destroyed temporary MadelineProto!");
|
||||
return null; // Should start IPC server
|
||||
}
|
||||
return yield from $this->tryConnect($session->getIpcPath());
|
||||
}
|
||||
/**
|
||||
* Try connecting to IPC socket.
|
||||
*
|
||||
* @param string $ipcPath IPC path
|
||||
*
|
||||
* @return \Generator<ChannelledSocket|null>
|
||||
*/
|
||||
private function tryConnect(string $ipcPath): \Generator
|
||||
{
|
||||
Logger::log("Trying to connect to IPC socket...");
|
||||
try {
|
||||
\clearstatcache(true, $ipcPath);
|
||||
return yield connect($ipcPath);
|
||||
} catch (\Throwable $e) {
|
||||
$e = $e->getMessage();
|
||||
Logger::log("$e while connecting to IPC socket");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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!");
|
||||
}
|
||||
}
|
||||
\class_alias(API::class, '\\danog\\MadelineProto\\FastAPI');
|
||||
|
|
|
@ -4568,12 +4568,11 @@ class InternalDoc extends APIFactory
|
|||
/**
|
||||
* Cleanup memory and session file.
|
||||
*
|
||||
* @return self
|
||||
* @return void
|
||||
*/
|
||||
public function cleanup(): \danog\MadelineProto\API
|
||||
public function cleanup(): void
|
||||
{
|
||||
$this->API->cleanup();
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* Close connection with client, connected via web.
|
||||
|
@ -4848,15 +4847,17 @@ class InternalDoc extends APIFactory
|
|||
* Asynchronously lock a file
|
||||
* Resolves with a callbable that MUST eventually be called in order to release the lock.
|
||||
*
|
||||
* @param string $file File to lock
|
||||
* @param integer $operation Locking mode
|
||||
* @param float $polling Polling interval
|
||||
* @param string $file File to lock
|
||||
* @param integer $operation Locking mode
|
||||
* @param float $polling Polling interval
|
||||
* @param ?Promise $token Cancellation token
|
||||
* @param ?callable $failureCb Failure callback, called only once if the first locking attempt fails.
|
||||
*
|
||||
* @return Promise<callable>
|
||||
* @return Promise<?callable>
|
||||
*/
|
||||
public function flock(string $file, int $operation, float $polling = 0.1)
|
||||
public function flock(string $file, int $operation, float $polling = 0.1, ?\Amp\Promise $token = null, $failureCb = null)
|
||||
{
|
||||
return \danog\MadelineProto\Tools::flock($file, $operation, $polling);
|
||||
return \danog\MadelineProto\Tools::flock($file, $operation, $polling, $token, $failureCb);
|
||||
}
|
||||
/**
|
||||
* Convert bot API channel ID to MTProto channel ID.
|
||||
|
@ -5132,8 +5133,6 @@ class InternalDoc extends APIFactory
|
|||
}
|
||||
/**
|
||||
* Get logger.
|
||||
*
|
||||
* @return Logger
|
||||
*/
|
||||
public function getLogger(): \danog\MadelineProto\Logger
|
||||
{
|
||||
|
@ -5208,6 +5207,13 @@ class InternalDoc extends APIFactory
|
|||
{
|
||||
return $this->__call(__FUNCTION__, [$data, $extra]);
|
||||
}
|
||||
/**
|
||||
* Get PSR logger.
|
||||
*/
|
||||
public function getPsrLogger(): \Psr\Log\LoggerInterface
|
||||
{
|
||||
return $this->API->getPsrLogger();
|
||||
}
|
||||
/**
|
||||
* Get full info about peer (including full list of channel members), returns a Chat object.
|
||||
*
|
||||
|
@ -5247,11 +5253,11 @@ class InternalDoc extends APIFactory
|
|||
return \danog\MadelineProto\MTProto::getSessionId($madelineProto);
|
||||
}
|
||||
/**
|
||||
* Return current settings array.
|
||||
* Return current settings.
|
||||
*
|
||||
* @return array
|
||||
* @return Settings
|
||||
*/
|
||||
public function getSettings(): array
|
||||
public function getSettings(): \danog\MadelineProto\Settings
|
||||
{
|
||||
return $this->API->getSettings();
|
||||
}
|
||||
|
@ -5326,6 +5332,21 @@ class InternalDoc extends APIFactory
|
|||
{
|
||||
return $this->API->hasSecretChat($chat);
|
||||
}
|
||||
/**
|
||||
* Checks private property exists in an object.
|
||||
*
|
||||
* @param object $obj Object
|
||||
* @param string $var Attribute name
|
||||
*
|
||||
* @psalm-suppress InvalidScope
|
||||
*
|
||||
* @return bool
|
||||
* @access public
|
||||
*/
|
||||
public function hasVar($obj, string $var): bool
|
||||
{
|
||||
return \danog\MadelineProto\Tools::hasVar($obj, $var);
|
||||
}
|
||||
/**
|
||||
* Import authorization.
|
||||
*
|
||||
|
@ -5348,7 +5369,13 @@ class InternalDoc extends APIFactory
|
|||
{
|
||||
return \danog\MadelineProto\Tools::inflateStripped($stripped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize database instance.
|
||||
*
|
||||
* @param MTProto $MadelineProto
|
||||
* @param boolean $reset
|
||||
* @return \Generator
|
||||
*/
|
||||
public function initDb(\danog\MadelineProto\MTProto $MadelineProto, bool $reset = false, array $extra = [])
|
||||
{
|
||||
return $this->__call(__FUNCTION__, [$MadelineProto, $reset, $extra]);
|
||||
|
@ -5382,6 +5409,15 @@ class InternalDoc extends APIFactory
|
|||
{
|
||||
return \danog\MadelineProto\Tools::isArrayOrAlike($var);
|
||||
}
|
||||
/**
|
||||
* Whether we're an IPC client instance.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isIpc(): bool
|
||||
{
|
||||
return $this->API->isIpc();
|
||||
}
|
||||
/**
|
||||
* Check whether provided bot API ID is a channel.
|
||||
*
|
||||
|
@ -5418,7 +5454,7 @@ class InternalDoc extends APIFactory
|
|||
/**
|
||||
* Start MadelineProto's update handling loop, or run the provided async callable.
|
||||
*
|
||||
* @param callable $callback Async callable to run
|
||||
* @param callable|null $callback Async callable to run
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
|
@ -5654,13 +5690,14 @@ class InternalDoc extends APIFactory
|
|||
/**
|
||||
* Report an error to the previously set peer.
|
||||
*
|
||||
* @param string $message Error to report
|
||||
* @param string $message Error to report
|
||||
* @param string $parseMode Parse mode
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function report(string $message, array $extra = [])
|
||||
public function report(string $message, string $parseMode = '', array $extra = [])
|
||||
{
|
||||
return $this->__call(__FUNCTION__, [$message, $extra]);
|
||||
return $this->__call(__FUNCTION__, [$message, $parseMode, $extra]);
|
||||
}
|
||||
/**
|
||||
* Request VoIP call.
|
||||
|
@ -5774,13 +5811,13 @@ class InternalDoc extends APIFactory
|
|||
/**
|
||||
* Set event handler.
|
||||
*
|
||||
* @param string|EventHandler $event_handler Event handler
|
||||
* @param class-string<EventHandler> $eventHandler Event handler
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function setEventHandler($event_handler, array $extra = [])
|
||||
public function setEventHandler(string $eventHandler, array $extra = [])
|
||||
{
|
||||
return $this->__call(__FUNCTION__, [$event_handler, $extra]);
|
||||
return $this->__call(__FUNCTION__, [$eventHandler, $extra]);
|
||||
}
|
||||
/**
|
||||
* Set NOOP update handler, ignoring all updates.
|
||||
|
@ -5841,6 +5878,13 @@ class InternalDoc extends APIFactory
|
|||
{
|
||||
$this->API->setWebhook($hook_url, $pem_path);
|
||||
}
|
||||
/**
|
||||
* Set API wrapper needed for triggering serialization functions.
|
||||
*/
|
||||
public function setWrapper(\danog\MadelineProto\APIWrapper $wrapper): void
|
||||
{
|
||||
$this->API->setWrapper($wrapper);
|
||||
}
|
||||
/**
|
||||
* Setup logger.
|
||||
*
|
||||
|
@ -5853,11 +5897,11 @@ class InternalDoc extends APIFactory
|
|||
/**
|
||||
* Asynchronously sleep.
|
||||
*
|
||||
* @param int $time Number of seconds to sleep for
|
||||
* @param int|float $time Number of seconds to sleep for
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function sleep(int $time)
|
||||
public function sleep($time)
|
||||
{
|
||||
return \danog\MadelineProto\Tools::sleep($time);
|
||||
}
|
||||
|
@ -5937,6 +5981,27 @@ class InternalDoc extends APIFactory
|
|||
{
|
||||
return \danog\MadelineProto\Tools::timeout($promise, $timeout);
|
||||
}
|
||||
/**
|
||||
* Creates an artificial timeout for any `Promise`.
|
||||
*
|
||||
* If the promise is resolved before the timeout expires, the result is returned
|
||||
*
|
||||
* If the timeout expires before the promise is resolved, a default value is returned
|
||||
*
|
||||
* @template TReturn
|
||||
*
|
||||
* @param Promise<TReturn>|\Generator $promise Promise to which the timeout is applied.
|
||||
* @param int $timeout Timeout in milliseconds.
|
||||
* @param TReturn $default
|
||||
*
|
||||
* @return Promise<TReturn>
|
||||
*
|
||||
* @throws \TypeError If $promise is not an instance of \Amp\Promise or \React\Promise\PromiseInterface.
|
||||
*/
|
||||
public function timeoutWithDefault($promise, int $timeout, $default = null)
|
||||
{
|
||||
return \danog\MadelineProto\Tools::timeoutWithDefault($promise, $timeout, $default);
|
||||
}
|
||||
/**
|
||||
* Convert to camelCase.
|
||||
*
|
||||
|
@ -6063,14 +6128,13 @@ class InternalDoc extends APIFactory
|
|||
/**
|
||||
* Parse, update and store settings.
|
||||
*
|
||||
* @param array $settings Settings
|
||||
* @param bool $reinit Whether to reinit the instance
|
||||
* @param SettingsAbstract $settings Settings
|
||||
*
|
||||
* @return void
|
||||
* @return \Generator
|
||||
*/
|
||||
public function updateSettings(array $settings, bool $reinit = true, array $extra = [])
|
||||
public function updateSettings(\danog\MadelineProto\SettingsAbstract $settings, array $extra = [])
|
||||
{
|
||||
return $this->__call(__FUNCTION__, [$settings, $reinit, $extra]);
|
||||
return $this->__call(__FUNCTION__, [$settings, $extra]);
|
||||
}
|
||||
/**
|
||||
* Upload file.
|
||||
|
|
|
@ -18,83 +18,53 @@
|
|||
|
||||
namespace danog\MadelineProto\Ipc;
|
||||
|
||||
use Amp\Deferred;
|
||||
use Amp\Ipc\Sync\ChannelledSocket;
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\API;
|
||||
use danog\MadelineProto\Exception;
|
||||
use danog\MadelineProto\FileCallbackInterface;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\MTProtoTools\FilesLogic;
|
||||
use danog\MadelineProto\SessionPaths;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
/**
|
||||
* IPC client.
|
||||
*/
|
||||
class Client
|
||||
class Client extends ClientAbstract
|
||||
{
|
||||
use \danog\MadelineProto\Wrappers\Start;
|
||||
use \danog\MadelineProto\Wrappers\Templates;
|
||||
use FilesLogic;
|
||||
|
||||
/**
|
||||
* IPC server socket.
|
||||
* Session.
|
||||
*/
|
||||
private ChannelledSocket $server;
|
||||
/**
|
||||
* Requests promise array.
|
||||
*/
|
||||
private array $requests = [];
|
||||
/**
|
||||
* Logger instance.
|
||||
*/
|
||||
public Logger $logger;
|
||||
protected SessionPaths $session;
|
||||
/**
|
||||
* Constructor function.
|
||||
*
|
||||
* @param ChannelledSocket $socket IPC client socket
|
||||
* @param Logger $logger Logger
|
||||
* @param ChannelledSocket $socket IPC client socket
|
||||
* @param SessionPaths $session Session paths
|
||||
* @param Logger $logger Logger
|
||||
*/
|
||||
public function __construct(ChannelledSocket $server, Logger $logger)
|
||||
public function __construct(ChannelledSocket $server, SessionPaths $session, Logger $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
$this->server = $server;
|
||||
Tools::callFork($this->loop());
|
||||
$this->session = $session;
|
||||
Tools::callFork($this->loopInternal());
|
||||
}
|
||||
/**
|
||||
* Logger.
|
||||
* Run the provided async callable.
|
||||
*
|
||||
* @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.
|
||||
* @param callable $callback Async callable to run
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function loop(): \Generator
|
||||
public function loop(callable $callback): \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);
|
||||
}
|
||||
}
|
||||
return yield $callback();
|
||||
}
|
||||
/**
|
||||
* Unreference.
|
||||
|
@ -103,23 +73,175 @@ class Client
|
|||
*/
|
||||
public function unreference(): void
|
||||
{
|
||||
if (isset($this->server)) {
|
||||
Tools::wait($this->server->disconnect());
|
||||
}
|
||||
Tools::wait($this->disconnect());
|
||||
}
|
||||
/**
|
||||
* Call function.
|
||||
* Stop IPC server instance.
|
||||
*
|
||||
* @param string $function Function name
|
||||
* @param array $arguments Arguments
|
||||
* @internal
|
||||
*/
|
||||
public function stopIpcServer(): Promise
|
||||
{
|
||||
$this->run = false;
|
||||
return $this->server->send(Server::SHUTDOWN);
|
||||
}
|
||||
/**
|
||||
* Restart IPC server instance.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function restartIpcServer(): Promise
|
||||
{
|
||||
return $this->server->send(Server::SHUTDOWN);
|
||||
}
|
||||
/**
|
||||
* Whether we're an IPC client instance.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isIpc(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file from URL.
|
||||
*
|
||||
* @param string|FileCallbackInterface $url URL of file
|
||||
* @param integer $size Size of file
|
||||
* @param string $fileName File name
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
* @param boolean $encrypted Whether to encrypt file for secret chats
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function __call(string $function, array $arguments): \Generator
|
||||
public function uploadFromUrl($url, int $size = 0, string $fileName = '', $cb = null, bool $encrypted = false): \Generator
|
||||
{
|
||||
$this->requests []= $deferred = new Deferred;
|
||||
yield $this->server->send([$function, $arguments]);
|
||||
return yield $deferred->promise();
|
||||
if (\is_object($url) && $url instanceof FileCallbackInterface) {
|
||||
$cb = $url;
|
||||
$url = $url->getFile();
|
||||
}
|
||||
$params = [$url, $size, $fileName, &$cb, $encrypted];
|
||||
$wrapper = yield from Wrapper::create($params, $this->session, $this->logger);
|
||||
$wrapper->wrap($cb, false);
|
||||
return yield from $this->__call('uploadFromUrl', $wrapper);
|
||||
}
|
||||
/**
|
||||
* Upload file from callable.
|
||||
*
|
||||
* The callable must accept two parameters: int $offset, int $size
|
||||
* The callable must return a string with the contest of the file at the specified offset and size.
|
||||
*
|
||||
* @param mixed $callable Callable
|
||||
* @param integer $size File size
|
||||
* @param string $mime Mime type
|
||||
* @param string $fileName File name
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
* @param boolean $seekable Whether chunks can be fetched out of order
|
||||
* @param boolean $encrypted Whether to encrypt file for secret chats
|
||||
*
|
||||
* @return \Generator<array>
|
||||
*/
|
||||
public function uploadFromCallable(callable $callable, int $size, string $mime, string $fileName = '', $cb = null, bool $seekable = true, bool $encrypted = false): \Generator
|
||||
{
|
||||
if (\is_object($callable) && $callable instanceof FileCallbackInterface) {
|
||||
$cb = $callable;
|
||||
$callable = $callable->getFile();
|
||||
}
|
||||
$params = [&$callable, $size, $mime, $fileName, &$cb, $seekable, $encrypted];
|
||||
$wrapper = yield from Wrapper::create($params, $this->session, $this->logger);
|
||||
$wrapper->wrap($cb, false);
|
||||
$wrapper->wrap($callable, false);
|
||||
return yield from $this->__call('uploadFromCallable', $wrapper);
|
||||
}
|
||||
/**
|
||||
* Reupload telegram file.
|
||||
*
|
||||
* @param mixed $media Telegram file
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
* @param boolean $encrypted Whether to encrypt file for secret chats
|
||||
*
|
||||
* @return \Generator<array>
|
||||
*/
|
||||
public function uploadFromTgfile($media, $cb = null, bool $encrypted = false): \Generator
|
||||
{
|
||||
if (\is_object($media) && $media instanceof FileCallbackInterface) {
|
||||
$cb = $media;
|
||||
$media = $media->getFile();
|
||||
}
|
||||
$params = [$media, &$cb, $encrypted];
|
||||
$wrapper = yield from Wrapper::create($params, $this->session, $this->logger);
|
||||
$wrapper->wrap($cb, false);
|
||||
return yield from $this->__call('uploadFromTgfile', $wrapper);
|
||||
}
|
||||
/**
|
||||
* Download file to directory.
|
||||
*
|
||||
* @param mixed $messageMedia File to download
|
||||
* @param string|FileCallbackInterface $dir Directory where to download the file
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
*
|
||||
* @return \Generator<string> Downloaded file path
|
||||
*/
|
||||
public function downloadToDir($messageMedia, $dir, $cb = null): \Generator
|
||||
{
|
||||
if (\is_object($dir) && $dir instanceof FileCallbackInterface) {
|
||||
$cb = $dir;
|
||||
$dir = $dir->getFile();
|
||||
}
|
||||
$params = [$messageMedia, $dir, &$cb];
|
||||
$wrapper = yield from Wrapper::create($params, $this->session, $this->logger);
|
||||
$wrapper->wrap($cb, false);
|
||||
return yield from $this->__call('downloadToDir', $wrapper);
|
||||
}
|
||||
/**
|
||||
* Download file.
|
||||
*
|
||||
* @param mixed $messageMedia File to download
|
||||
* @param string|FileCallbackInterface $file Downloaded file path
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
*
|
||||
* @return \Generator<string> Downloaded file path
|
||||
*/
|
||||
public function downloadToFile($messageMedia, $file, $cb = null): \Generator
|
||||
{
|
||||
if (\is_object($file) && $file instanceof FileCallbackInterface) {
|
||||
$cb = $file;
|
||||
$file = $file->getFile();
|
||||
}
|
||||
$params = [$messageMedia, $file, &$cb];
|
||||
$wrapper = yield from Wrapper::create($params, $this->session, $this->logger);
|
||||
$wrapper->wrap($cb, false);
|
||||
return yield from $this->__call('downloadToFile', $wrapper);
|
||||
}
|
||||
/**
|
||||
* Download file to callable.
|
||||
* The callable must accept two parameters: string $payload, int $offset
|
||||
* The callable will be called (possibly out of order, depending on the value of $seekable).
|
||||
* The callable should return the number of written bytes.
|
||||
*
|
||||
* @param mixed $messageMedia File to download
|
||||
* @param callable|FileCallbackInterface $callable Chunk callback
|
||||
* @param callable $cb Status callback (DEPRECATED, use FileCallbackInterface)
|
||||
* @param bool $seekable Whether the callable can be called out of order
|
||||
* @param int $offset Offset where to start downloading
|
||||
* @param int $end Offset where to stop downloading (inclusive)
|
||||
* @param int $part_size Size of each chunk
|
||||
*
|
||||
* @return \Generator<bool>
|
||||
*/
|
||||
public function downloadToCallable($messageMedia, callable $callable, $cb = null, bool $seekable = true, int $offset = 0, int $end = -1, int $part_size = null): \Generator
|
||||
{
|
||||
$messageMedia = (yield from $this->getDownloadInfo($messageMedia));
|
||||
if (\is_object($callable) && $callable instanceof FileCallbackInterface) {
|
||||
$cb = $callable;
|
||||
$callable = $callable->getFile();
|
||||
}
|
||||
$params = [$messageMedia, &$callable, &$cb, $seekable, $offset, $end, $part_size, ];
|
||||
$wrapper = yield from Wrapper::create($params, $this->session, $this->logger);
|
||||
$wrapper->wrap($callable, false);
|
||||
$wrapper->wrap($cb, false);
|
||||
return yield from $this->__call('downloadToCallable', $wrapper);
|
||||
}
|
||||
/**
|
||||
* Placeholder.
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
<?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 Amp\Promise;
|
||||
use danog\MadelineProto\Logger;
|
||||
|
||||
use function Amp\Ipc\connect;
|
||||
|
||||
/**
|
||||
* IPC client.
|
||||
*/
|
||||
abstract class ClientAbstract
|
||||
{
|
||||
/**
|
||||
* IPC server socket.
|
||||
*/
|
||||
protected ChannelledSocket $server;
|
||||
/**
|
||||
* Requests promise array.
|
||||
*
|
||||
* @var Deferred[]
|
||||
*/
|
||||
private array $requests = [];
|
||||
/**
|
||||
* Wrappers array.
|
||||
*
|
||||
* @var Wrapper[]
|
||||
*/
|
||||
private array $wrappers = [];
|
||||
/**
|
||||
* Whether to run loop.
|
||||
*/
|
||||
protected bool $run = true;
|
||||
/**
|
||||
* Logger instance.
|
||||
*/
|
||||
public Logger $logger;
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
protected function loopInternal(): \Generator
|
||||
{
|
||||
do {
|
||||
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 (isset($this->wrappers[$id])) {
|
||||
yield $this->wrappers[$id]->disconnect();
|
||||
unset($this->wrappers[$id]);
|
||||
}
|
||||
if ($payload instanceof ExitFailure) {
|
||||
$promise->fail($payload->getException());
|
||||
} else {
|
||||
$promise->resolve($payload);
|
||||
}
|
||||
unset($promise);
|
||||
}
|
||||
}
|
||||
if ($this->run) {
|
||||
$this->logger("Reconnecting to IPC server!");
|
||||
yield $this->server->disconnect();
|
||||
if ($this instanceof Client) {
|
||||
Server::startMe($this->session);
|
||||
$this->server = yield connect($this->session->getIpcPath());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} while ($this->run);
|
||||
}
|
||||
/**
|
||||
* Disconnect cleanly from main instance.
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public function disconnect(): \Generator
|
||||
{
|
||||
$this->run = false;
|
||||
yield $this->server->disconnect();
|
||||
foreach ($this->wrappers as $w) {
|
||||
yield from $w->disconnect();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Call function.
|
||||
*
|
||||
* @param string|int $function Function name
|
||||
* @param array|Wrapper $arguments Arguments
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function __call($function, $arguments): \Generator
|
||||
{
|
||||
$this->requests []= $deferred = new Deferred;
|
||||
if ($arguments instanceof Wrapper) {
|
||||
$this->wrappers[\count($this->requests) - 1] = $arguments;
|
||||
}
|
||||
yield $this->server->send([$function, $arguments]);
|
||||
return yield $deferred->promise();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc;
|
||||
|
||||
/**
|
||||
* IPC state class.
|
||||
*/
|
||||
final class IpcState
|
||||
{
|
||||
/**
|
||||
* Startup time.
|
||||
*/
|
||||
private float $startupTime;
|
||||
/**
|
||||
* Startup ID.
|
||||
*/
|
||||
private int $startupId;
|
||||
/**
|
||||
* Exception.
|
||||
*/
|
||||
private ?ExitFailure $exception;
|
||||
/**
|
||||
* Construct.
|
||||
*
|
||||
* @param integer $startupId
|
||||
* @param \Throwable $exception
|
||||
*/
|
||||
public function __construct(int $startupId, \Throwable $exception = null)
|
||||
{
|
||||
$this->startupTime = \microtime(true);
|
||||
$this->startupId = $startupId;
|
||||
$this->exception = $exception ? new ExitFailure($exception) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get startup time.
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function getStartupTime(): float
|
||||
{
|
||||
return $this->startupTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get startup ID.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getStartupId(): int
|
||||
{
|
||||
return $this->startupId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exception.
|
||||
*
|
||||
* @return ?\Throwable
|
||||
*/
|
||||
public function getException(): ?\Throwable
|
||||
{
|
||||
return $this->exception ? $this->exception->getException() : null;
|
||||
}
|
||||
}
|
|
@ -2,17 +2,25 @@
|
|||
|
||||
namespace danog\MadelineProto\Ipc\Runner;
|
||||
|
||||
use danog\MadelineProto\Logger;
|
||||
|
||||
final class ProcessRunner extends RunnerAbstract
|
||||
{
|
||||
/** @var string|null Cached path to located PHP binary. */
|
||||
private static $binaryPath;
|
||||
|
||||
/**
|
||||
* Resources.
|
||||
*/
|
||||
private static array $resources = [];
|
||||
/**
|
||||
* Runner.
|
||||
*
|
||||
* @param string $session Session path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function start(string $session): void
|
||||
public static function start(string $session, int $request): void
|
||||
{
|
||||
if (\PHP_SAPI === "cli") {
|
||||
$binary = \PHP_BINARY;
|
||||
|
@ -28,16 +36,23 @@ final class ProcessRunner extends RunnerAbstract
|
|||
|
||||
$runner = self::getScriptPath();
|
||||
|
||||
if (\strtolower(\substr(PHP_OS, 0, 3)) === 'win') {
|
||||
$binary = \str_replace("Program Files", "PROGRA~1", $binary);
|
||||
// Pray there are no spaces in the name, escapeshellarg would help but windows doesn't want quotes in the program name
|
||||
} else {
|
||||
$binary = \escapeshellarg($binary);
|
||||
}
|
||||
$command = \implode(" ", [
|
||||
'nohup',
|
||||
\escapeshellarg($binary),
|
||||
$binary,
|
||||
self::formatOptions($options),
|
||||
$runner,
|
||||
'madeline-ipc',
|
||||
\escapeshellarg($session),
|
||||
'&>/dev/null &'
|
||||
$request
|
||||
]);
|
||||
\proc_close(\proc_open($command, [], $foo));
|
||||
Logger::log("Starting process with $command");
|
||||
|
||||
self::$resources []= \proc_open($command, [], $foo);
|
||||
}
|
||||
private static function locateBinary(): string
|
||||
{
|
||||
|
|
|
@ -60,8 +60,9 @@ abstract class RunnerAbstract
|
|||
* Runner.
|
||||
*
|
||||
* @param string $session Session path
|
||||
* @param int $startup ID
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public static function start(string $session): void;
|
||||
abstract public static function start(string $session, int $startupId): void;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
namespace danog\MadelineProto\Ipc\Runner;
|
||||
|
||||
use Amp\ByteStream\ResourceOutputStream;
|
||||
use Amp\Parallel\Context\ContextException;
|
||||
use danog\MadelineProto\Magic;
|
||||
|
||||
|
@ -15,21 +14,18 @@ final class WebRunner extends RunnerAbstract
|
|||
* Resources.
|
||||
*/
|
||||
private static array $resources = [];
|
||||
/**
|
||||
* Socket.
|
||||
*
|
||||
* @var ResourceOutputStream
|
||||
*/
|
||||
private $res;
|
||||
|
||||
/**
|
||||
* Start.
|
||||
*
|
||||
* @param string $session Session path
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function start(string $session): void
|
||||
public static function start(string $session, int $id): void
|
||||
{
|
||||
if (!isset($_SERVER['SERVER_NAME'])) {
|
||||
throw new ContextException("Could not initialize web runner!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!self::$runPath) {
|
||||
|
@ -79,7 +75,7 @@ final class WebRunner extends RunnerAbstract
|
|||
}
|
||||
|
||||
$params = [
|
||||
'argv' => ['madeline-ipc', $session],
|
||||
'argv' => ['madeline-ipc', $session, $id],
|
||||
'cwd' => Magic::getcwd()
|
||||
];
|
||||
|
||||
|
|
|
@ -16,12 +16,13 @@
|
|||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
use Amp\Deferred;
|
||||
use danog\MadelineProto\API;
|
||||
use danog\MadelineProto\Ipc\IpcState;
|
||||
use danog\MadelineProto\Ipc\Server;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Magic;
|
||||
use danog\MadelineProto\SessionPaths;
|
||||
use danog\MadelineProto\Settings\Ipc;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
(static function (): void {
|
||||
|
@ -48,6 +49,13 @@ use danog\MadelineProto\Tools;
|
|||
\define(\MADELINE_WORKER_TYPE::class, \array_shift($arguments));
|
||||
\define(\MADELINE_WORKER_ARGS::class, $arguments);
|
||||
}
|
||||
|
||||
if (\defined(\SIGHUP::class)) {
|
||||
try {
|
||||
\pcntl_signal(SIGHUP, fn () => null);
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
}
|
||||
if (!\class_exists(API::class)) {
|
||||
$paths = [
|
||||
\dirname(__DIR__, 7)."/autoload.php",
|
||||
|
@ -82,29 +90,37 @@ use danog\MadelineProto\Tools;
|
|||
}
|
||||
\define(\MADELINE_WORKER::class, 1);
|
||||
|
||||
$runnerId = \MADELINE_WORKER_ARGS[1];
|
||||
$session = new SessionPaths($ipcPath);
|
||||
|
||||
try {
|
||||
Magic::classExists();
|
||||
Magic::$script_cwd = $_GET['cwd'] ?? Magic::getcwd();
|
||||
$API = new API($ipcPath);
|
||||
|
||||
$API = new API($ipcPath, (new Ipc)->setForceFull(true));
|
||||
$API->init();
|
||||
if ($API->hasEventHandler()) {
|
||||
unset($API);
|
||||
\gc_collect_cycles();
|
||||
Logger::log("Session has event handler, can't start IPC server like this!");
|
||||
$ipc = (new SessionPaths($ipcPath))->getIpcPath();
|
||||
@\unlink($ipc);
|
||||
\file_put_contents($ipc, Server::EVENT_HANDLER);
|
||||
} else {
|
||||
$API->initSelfRestart();
|
||||
Tools::wait((new Deferred)->promise());
|
||||
$API->initSelfRestart();
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
Tools::wait($session->storeIpcState(new IpcState($runnerId)));
|
||||
Tools::wait(Server::waitShutdown());
|
||||
return;
|
||||
} catch (\Throwable $e) {
|
||||
Logger::log((string) $e, Logger::FATAL_ERROR);
|
||||
Tools::wait($API->report("Surfaced: $e"));
|
||||
}
|
||||
}
|
||||
} 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, Server::NOT_INITED);
|
||||
Logger::log("$e", Logger::FATAL_ERROR);
|
||||
Logger::log("Got exception in IPC server, exiting...", Logger::FATAL_ERROR);
|
||||
$ipc = Tools::wait($session->getIpcState());
|
||||
if (!($ipc && $ipc->getStartupId() === $runnerId && !$ipc->getException())) {
|
||||
Logger::log("Reporting error!");
|
||||
Tools::wait($session->storeIpcState(new IpcState($runnerId, $e)));
|
||||
Logger::log("Reported error!");
|
||||
} else {
|
||||
Logger::log("Not reporting error!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,13 +18,17 @@
|
|||
|
||||
namespace danog\MadelineProto\Ipc;
|
||||
|
||||
use Amp\Deferred;
|
||||
use Amp\Ipc\IpcServer;
|
||||
use Amp\Ipc\Sync\ChannelledSocket;
|
||||
use Amp\Promise;
|
||||
use danog\Loop\SignalLoop;
|
||||
use danog\MadelineProto\Ipc\Runner\ProcessRunner;
|
||||
use danog\MadelineProto\Ipc\Runner\WebRunner;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Loop\InternalLoop;
|
||||
use danog\MadelineProto\SessionPaths;
|
||||
use danog\MadelineProto\Settings\Ipc;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
/**
|
||||
|
@ -34,47 +38,105 @@ class Server extends SignalLoop
|
|||
{
|
||||
use InternalLoop;
|
||||
/**
|
||||
* Session not initialized, should initialize.
|
||||
* Shutdown server.
|
||||
*/
|
||||
const NOT_INITED = 'not inited';
|
||||
const SHUTDOWN = 0;
|
||||
/**
|
||||
* Session uses event handler, should start from main event handler file.
|
||||
* Boolean to shut down worker, if started.
|
||||
*/
|
||||
const EVENT_HANDLER = 'event';
|
||||
private static bool $shutdown = false;
|
||||
/**
|
||||
* Deferred to shut down worker, if started.
|
||||
*/
|
||||
private static ?Deferred $shutdownDeferred = null;
|
||||
/**
|
||||
* IPC server.
|
||||
*/
|
||||
private IpcServer $server;
|
||||
protected IpcServer $server;
|
||||
/**
|
||||
* Callback IPC server.
|
||||
*/
|
||||
private ServerCallback $callback;
|
||||
/**
|
||||
* IPC settings.
|
||||
*/
|
||||
private Ipc $settings;
|
||||
/**
|
||||
* Set IPC path.
|
||||
*
|
||||
* @param string $path IPC path
|
||||
* @param SessionPaths $session Session
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setIpcPath(string $path): void
|
||||
public function setIpcPath(SessionPaths $session): void
|
||||
{
|
||||
$this->server = new IpcServer($path);
|
||||
self::$shutdownDeferred = new Deferred;
|
||||
$this->server = new IpcServer($session->getIpcPath());
|
||||
$this->callback = new ServerCallback($this->API);
|
||||
$this->callback->setIpcPath($session);
|
||||
}
|
||||
public function start(): bool
|
||||
{
|
||||
return $this instanceof ServerCallback ? parent::start() : $this->callback->start() && parent::start();
|
||||
}
|
||||
/**
|
||||
* Start IPC server in background.
|
||||
*
|
||||
* @param string $session Session path
|
||||
* @param SessionPaths $session Session path
|
||||
*
|
||||
* @return void
|
||||
* @return Promise
|
||||
*/
|
||||
public static function startMe(string $session): void
|
||||
public static function startMe(SessionPaths $session): Promise
|
||||
{
|
||||
$id = Tools::randomInt(2000000000);
|
||||
try {
|
||||
Logger::log("Starting IPC server $session (process)");
|
||||
ProcessRunner::start($session);
|
||||
WebRunner::start($session);
|
||||
return;
|
||||
ProcessRunner::start($session, $id);
|
||||
WebRunner::start($session, $id);
|
||||
return Tools::call(self::monitor($session, $id));
|
||||
} catch (\Throwable $e) {
|
||||
Logger::log($e);
|
||||
}
|
||||
Logger::log("Starting IPC server $session (web)");
|
||||
WebRunner::start($session);
|
||||
try {
|
||||
Logger::log("Starting IPC server $session (web)");
|
||||
WebRunner::start($session, $id);
|
||||
} catch (\Throwable $e) {
|
||||
Logger::log($e);
|
||||
}
|
||||
return Tools::call(self::monitor($session, $id));
|
||||
}
|
||||
/**
|
||||
* Monitor session.
|
||||
*
|
||||
* @param SessionPaths $session
|
||||
* @param int $id
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private static function monitor(SessionPaths $session, int $id): \Generator
|
||||
{
|
||||
while (true) {
|
||||
$state = yield $session->getIpcState();
|
||||
if ($state && $state->getStartupId() === $id) {
|
||||
if ($e = $state->getException()) {
|
||||
Logger::log("IPC server got exception $e");
|
||||
return $e;
|
||||
}
|
||||
Logger::log("IPC server started successfully!");
|
||||
return true;
|
||||
}
|
||||
yield Tools::sleep(1);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Wait for shutdown.
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
public static function waitShutdown(): Promise
|
||||
{
|
||||
return self::$shutdownDeferred->promise();
|
||||
}
|
||||
/**
|
||||
* Main loop.
|
||||
|
@ -87,24 +149,35 @@ class Server extends SignalLoop
|
|||
Tools::callFork($this->clientLoop($socket));
|
||||
}
|
||||
$this->server->close();
|
||||
if (isset($this->callback)) {
|
||||
$this->callback->signal(null);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Client handler loop.
|
||||
*
|
||||
* @param ChannelledSocket $socket Client
|
||||
*
|
||||
* @return \Generator
|
||||
* @return \Generator|Promise
|
||||
*/
|
||||
private function clientLoop(ChannelledSocket $socket): \Generator
|
||||
protected function clientLoop(ChannelledSocket $socket)
|
||||
{
|
||||
$this->API->logger("Accepted IPC client connection!");
|
||||
|
||||
$id = 0;
|
||||
$payload = null;
|
||||
try {
|
||||
while ($payload = yield $socket->receive()) {
|
||||
Tools::callFork($this->clientRequest($socket, $id++, $payload));
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
} finally {
|
||||
yield $socket->disconnect();
|
||||
if ($payload === self::SHUTDOWN) {
|
||||
$this->signal(null);
|
||||
if (self::$shutdownDeferred) {
|
||||
self::$shutdownDeferred->resolve();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
@ -112,17 +185,26 @@ class Server extends SignalLoop
|
|||
*
|
||||
* @param ChannelledSocket $socket Socket
|
||||
* @param integer $id Request ID
|
||||
* @param array $payload Payload
|
||||
* @param array|Wrapper $payload Payload
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function clientRequest(ChannelledSocket $socket, int $id, $payload): \Generator
|
||||
private function clientRequest(ChannelledSocket $socket, int $id, $payload): \Generator
|
||||
{
|
||||
try {
|
||||
if ($payload[1] instanceof Wrapper) {
|
||||
$wrapper = $payload[1];
|
||||
$payload[1] = $this->callback->unwrap($wrapper);
|
||||
}
|
||||
$result = $this->API->{$payload[0]}(...$payload[1]);
|
||||
$result = $result instanceof \Generator ? yield from $result : yield $result;
|
||||
} catch (\Throwable $e) {
|
||||
$this->API->logger("Got error while calling IPC method: $e", Logger::ERROR);
|
||||
$result = new ExitFailure($e);
|
||||
} finally {
|
||||
if (isset($wrapper)) {
|
||||
yield $wrapper->disconnect();
|
||||
}
|
||||
}
|
||||
try {
|
||||
yield $socket->send([$id, $result]);
|
||||
|
@ -144,4 +226,18 @@ class Server extends SignalLoop
|
|||
{
|
||||
return "IPC server";
|
||||
}
|
||||
|
||||
/**
|
||||
* Set IPC settings.
|
||||
*
|
||||
* @param Ipc $settings IPC settings
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setSettings(Ipc $settings): self
|
||||
{
|
||||
$this->settings = $settings;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,97 @@
|
|||
<?php
|
||||
/**
|
||||
* IPC callback server.
|
||||
*
|
||||
* 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 Amp\Loop;
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Exception;
|
||||
use danog\MadelineProto\SessionPaths;
|
||||
|
||||
/**
|
||||
* IPC callback server.
|
||||
*/
|
||||
class ServerCallback extends Server
|
||||
{
|
||||
/**
|
||||
* Timeout watcher list, indexed by socket ID.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private $watcherList = [];
|
||||
/**
|
||||
* Timeout watcher list, indexed by socket ID.
|
||||
*
|
||||
* @var array<int, ChannelledSocket>
|
||||
*/
|
||||
private $socketList = [];
|
||||
/**
|
||||
* Counter.
|
||||
*/
|
||||
private int $id = 0;
|
||||
/**
|
||||
* Set IPC path.
|
||||
*
|
||||
* @param SessionPaths $session Session
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setIpcPath(SessionPaths $session): void
|
||||
{
|
||||
$this->server = new IpcServer($session->getIpcCallbackPath());
|
||||
}
|
||||
/**
|
||||
* Client handler loop.
|
||||
*
|
||||
* @param ChannelledSocket $socket Client
|
||||
*
|
||||
* @return Promise
|
||||
*/
|
||||
protected function clientLoop(ChannelledSocket $socket)
|
||||
{
|
||||
$id = $this->id++;
|
||||
$this->API->logger("Accepted IPC callback connection, assigning ID $id!");
|
||||
$this->socketList[$id] = $socket;
|
||||
$this->watcherList[$id] = Loop::delay(30*1000, function () use ($id) {
|
||||
unset($this->watcherList[$id], $this->socketList[$id]);
|
||||
});
|
||||
|
||||
return $socket->send($id);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unwrap value.
|
||||
*
|
||||
* @param Wrapper $wrapper
|
||||
* @return mixed
|
||||
*/
|
||||
protected function unwrap(Wrapper $wrapper)
|
||||
{
|
||||
$id = $wrapper->getRemoteId();
|
||||
if (!isset($this->socketList[$id])) {
|
||||
throw new Exception("IPC timeout, could not find callback socket!");
|
||||
}
|
||||
$socket = $this->socketList[$id];
|
||||
Loop::cancel($this->watcherList[$id]);
|
||||
unset($this->watcherList[$id], $this->socketList[$id]);
|
||||
return $wrapper->unwrap($socket);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,213 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc;
|
||||
|
||||
use Amp\ByteStream\InputStream as ByteStreamInputStream;
|
||||
use Amp\ByteStream\OutputStream as ByteStreamOutputStream;
|
||||
use Amp\Ipc\Sync\ChannelledSocket;
|
||||
use Amp\Parallel\Sync\ExitFailure;
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Ipc\Wrapper\InputStream;
|
||||
use danog\MadelineProto\Ipc\Wrapper\Obj;
|
||||
use danog\MadelineProto\Ipc\Wrapper\OutputStream;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\SessionPaths;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
use function Amp\Ipc\connect;
|
||||
|
||||
/**
|
||||
* Callback payload wrapper.
|
||||
*/
|
||||
class Wrapper extends ClientAbstract
|
||||
{
|
||||
/**
|
||||
* Payload data.
|
||||
*
|
||||
* @var mixed
|
||||
*/
|
||||
private $data;
|
||||
/**
|
||||
* Callbacks.
|
||||
*
|
||||
* @var callable[]
|
||||
*/
|
||||
private array $callbacks = [];
|
||||
/**
|
||||
* Callbacks IDs.
|
||||
*
|
||||
* @var (int|array{0: class-string<Obj>, array<string, int>})[]
|
||||
*/
|
||||
private array $callbackIds = [];
|
||||
/**
|
||||
* Callback ID.
|
||||
*/
|
||||
private int $id = 0;
|
||||
/**
|
||||
* Remote socket ID.
|
||||
*/
|
||||
private int $remoteId = 0;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param mixed $data Payload data
|
||||
* @param SessionPaths $ipc IPC URI
|
||||
*
|
||||
* @return \Generator<int, Promise<ChannelledSocket>|Promise<mixed>, mixed, Wrapper>
|
||||
*/
|
||||
public static function create(&$data, SessionPaths $session, Logger $logger): \Generator
|
||||
{
|
||||
$instance = new self;
|
||||
$instance->data = &$data;
|
||||
$instance->logger = $logger;
|
||||
$instance->run = false;
|
||||
|
||||
$logger->logger("Connecting to callback IPC server...");
|
||||
$instance->server = yield connect($session->getIpcCallbackPath());
|
||||
$logger->logger("Connected to callback IPC server!");
|
||||
|
||||
$instance->remoteId = yield $instance->server->receive();
|
||||
$logger->logger("Got ID {$instance->remoteId} from callback IPC server!");
|
||||
|
||||
Tools::callFork($instance->receiverLoop());
|
||||
return $instance;
|
||||
}
|
||||
/**
|
||||
* Serialization function.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function __sleep(): array
|
||||
{
|
||||
return ['data', 'callbackIds', 'remoteId'];
|
||||
}
|
||||
/**
|
||||
* Wrap a certain callback object.
|
||||
*
|
||||
* @param object|callable $callback Callback to wrap
|
||||
* @param bool $wrapObjects Whether to wrap object methods, too
|
||||
*
|
||||
* @param-out int $callback Callback ID
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function wrap(&$callback, bool $wrapObjects = true): void
|
||||
{
|
||||
if (\is_object($callback) && $wrapObjects) {
|
||||
$ids = [];
|
||||
foreach (\get_class_methods($callback) as $method) {
|
||||
$id = $this->id++;
|
||||
$this->callbacks[$id] = [$callback, $method];
|
||||
$ids[$method] = $id;
|
||||
}
|
||||
$class = Obj::class;
|
||||
if ($callback instanceof ByteStreamInputStream) {
|
||||
$class = InputStream::class;
|
||||
} elseif ($callback instanceof ByteStreamOutputStream) {
|
||||
$class = OutputStream::class;
|
||||
}
|
||||
if ($class !== Obj::class && \method_exists($callback, 'seek')) {
|
||||
$class = "Seekable$class";
|
||||
}
|
||||
$callback = [$class, $ids]; // Will be re-filled later
|
||||
$this->callbackIds[] = &$callback;
|
||||
} elseif (\is_callable($callback)) {
|
||||
$id = $this->id++;
|
||||
$this->callbacks[$id] = self::copy($callback);
|
||||
$callback = $id;
|
||||
$this->callbackIds[] = &$callback;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get copy of data.
|
||||
*
|
||||
* @param mixed $data
|
||||
* @return mixed
|
||||
*/
|
||||
private static function copy($data)
|
||||
{
|
||||
return $data;
|
||||
}
|
||||
/**
|
||||
* Receiver loop.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function receiverLoop(): \Generator
|
||||
{
|
||||
$id = 0;
|
||||
$payload = null;
|
||||
try {
|
||||
while ($payload = yield $this->server->receive()) {
|
||||
Tools::callFork($this->clientRequest($id++, $payload));
|
||||
}
|
||||
} finally {
|
||||
yield $this->server->disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle client request.
|
||||
*
|
||||
* @param integer $id Request ID
|
||||
* @param array $payload Payload
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private function clientRequest(int $id, $payload): \Generator
|
||||
{
|
||||
try {
|
||||
$result = $this->callbacks[$payload[0]](...$payload[1]);
|
||||
$result = $result instanceof \Generator ? yield from $result : yield $result;
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->logger("Got error while calling reverse IPC method: $e", Logger::ERROR);
|
||||
$result = new ExitFailure($e);
|
||||
}
|
||||
try {
|
||||
yield $this->server->send([$id, $result]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->logger("Got error while trying to send result of reverse method: $e", Logger::ERROR);
|
||||
try {
|
||||
yield $this->server->send([$id, new ExitFailure($e)]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->logger("Got error while trying to send error of error of reverse method: $e", Logger::ERROR);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get remote socket ID.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRemoteId(): int
|
||||
{
|
||||
return $this->remoteId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set socket and unwrap data.
|
||||
*
|
||||
* @param ChannelledSocket $server Socket.
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function unwrap(ChannelledSocket $server)
|
||||
{
|
||||
$this->server = $server;
|
||||
Tools::callFork($this->loopInternal());
|
||||
|
||||
foreach ($this->callbackIds as &$id) {
|
||||
if (\is_int($id)) {
|
||||
$id = fn (...$args): \Generator => $this->__call($id, $args);
|
||||
} else {
|
||||
[$class, $ids] = $id;
|
||||
$id = new $class($this, $ids);
|
||||
}
|
||||
}
|
||||
return $this->data;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
use danog\MadelineProto\FileCallbackInterface;
|
||||
|
||||
class FileCallback extends Obj implements FileCallbackInterface
|
||||
{
|
||||
/**
|
||||
* Get file.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getFile()
|
||||
{
|
||||
return $this->__call('getFile');
|
||||
}
|
||||
/**
|
||||
* Invoke callback.
|
||||
*
|
||||
* @param int $percent Percent
|
||||
* @param int $speed Speed in mbps
|
||||
* @param int $time Time
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __invoke(...$args)
|
||||
{
|
||||
return $this->__call('__invoke', $args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
use Amp\ByteStream\InputStream as AmpInputStream;
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
class InputStream extends Obj implements AmpInputStream
|
||||
{
|
||||
/**
|
||||
* Reads data from the stream.
|
||||
*
|
||||
* @return Promise Resolves with a string when new data is available or `null` if the stream has closed.
|
||||
*
|
||||
* @psalm-return Promise<string|null>
|
||||
*
|
||||
* @throws PendingReadError Thrown if another read operation is still pending.
|
||||
*/
|
||||
public function read(): Promise
|
||||
{
|
||||
return Tools::call($this->__call('read'));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
use danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
/**
|
||||
* Generic callback wrapper object.
|
||||
*/
|
||||
class Obj
|
||||
{
|
||||
/**
|
||||
* Method list.
|
||||
*
|
||||
* @var array<string, int>
|
||||
*/
|
||||
private array $methods = [];
|
||||
/**
|
||||
* Wrapper.
|
||||
*/
|
||||
private Wrapper $wrapper;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Wrapper $wrapper
|
||||
* @param array $methods
|
||||
*/
|
||||
public function __construct(Wrapper $wrapper, array $methods)
|
||||
{
|
||||
$this->wrapper = $wrapper;
|
||||
$this->methods = $methods;
|
||||
}
|
||||
/**
|
||||
* Call method.
|
||||
*
|
||||
* @param string $name
|
||||
* @param array $arguments
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function __call(string $name, array $arguments = []): \Generator
|
||||
{
|
||||
return $this->wrapper->__call($this->methods[$name], $arguments);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
use Amp\ByteStream\OutputStream as AmpOutputStream;
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
class OutputStream extends Obj implements AmpOutputStream
|
||||
{
|
||||
/**
|
||||
* Writes data to the stream.
|
||||
*
|
||||
* @param string $data Bytes to write.
|
||||
*
|
||||
* @return Promise Succeeds once the data has been successfully written to the stream.
|
||||
*
|
||||
* @throws ClosedException If the stream has already been closed.
|
||||
* @throws StreamException If writing to the stream fails.
|
||||
*/
|
||||
public function write(string $data): Promise
|
||||
{
|
||||
return Tools::call($this->__call('write', [$data]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the stream as no longer writable. Optionally writes a final data chunk before. Note that this is not the
|
||||
* same as forcefully closing the stream. This method waits for all pending writes to complete before closing the
|
||||
* stream. Socket streams implementing this interface should only close the writable side of the stream.
|
||||
*
|
||||
* @param string $finalData Bytes to write.
|
||||
*
|
||||
* @return Promise Succeeds once the data has been successfully written to the stream.
|
||||
*
|
||||
* @throws ClosedException If the stream has already been closed.
|
||||
* @throws StreamException If writing to the stream fails.
|
||||
*/
|
||||
public function end(string $finalData = ""): Promise
|
||||
{
|
||||
return Tools::call($this->__call('write', [$finalData]));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
class SeekableInputStream extends InputStream
|
||||
{
|
||||
use SeekableTrait;
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
class SeekableOutputStream extends OutputStream
|
||||
{
|
||||
use SeekableTrait;
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
trait SeekableTrait
|
||||
{
|
||||
/**
|
||||
* Set the handle's internal pointer position.
|
||||
*
|
||||
* $whence values:
|
||||
*
|
||||
* SEEK_SET - Set position equal to offset bytes.
|
||||
* SEEK_CUR - Set position to current location plus offset.
|
||||
* SEEK_END - Set position to end-of-file plus offset.
|
||||
*
|
||||
* @param int $position
|
||||
* @param int $whence
|
||||
* @return \Amp\Promise<int> New offset position.
|
||||
*/
|
||||
public function seek(int $position, int $whence = \SEEK_SET): Promise
|
||||
{
|
||||
return Tools::call($this->__call('seek', [$position, $whence]));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
use danog\MadelineProto\Ipc\Wrapper;
|
||||
|
||||
trait WrapMethodTrait
|
||||
{
|
||||
abstract public function __call($name, $args);
|
||||
public function wrap(...$args): \Generator
|
||||
{
|
||||
$new = yield from Wrapper::create($args, $this->session->getIpcCallbackPath(), $this->logger);
|
||||
foreach ($args as &$arg) {
|
||||
$new->wrap($arg);
|
||||
}
|
||||
return $this->__call(__FUNCTION__, $new);
|
||||
}
|
||||
}
|
|
@ -12,7 +12,7 @@
|
|||
* @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
|
||||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||||
*/
|
||||
|
||||
namespace danog\MadelineProto;
|
||||
|
@ -23,27 +23,13 @@ class Lang
|
|||
'it' =>
|
||||
[
|
||||
'phpseclib_fork' => 'Per favore installa questo fork di phpseclib: https://github.com/danog/phpseclib',
|
||||
'inst_dc' => 'Istanziamento dei DataCenter...',
|
||||
'load_rsa' => 'Caricamento delle chiavi RSA...',
|
||||
'TL_translation' => 'Translazione degli schemi TL...',
|
||||
'dh_prime_check_0' => 'Esecuzione dei check dh_prime (0/3)...',
|
||||
'nearest_dc' => 'Siamo in %s, il DC più vicino è %d.',
|
||||
'serialization_ofd' => 'La serializzazione non è aggiornata, reistanziamento dell\'oggetto in corso!',
|
||||
'getupdates_deserialization' => 'Ottenimento aggiornamenti dopo deserializzazione...',
|
||||
'shutdown_reader_pool' => 'Chiusura pool di lettura, %d thread rimasti',
|
||||
'threading_on' => 'IL THREADING È ABILITATO',
|
||||
'socket_reader' => 'Lettore socket su DC %s: ',
|
||||
'socket_status_1' => 'CREAZIONE',
|
||||
'socket_status_2' => 'INVIO',
|
||||
'socket_status_3' => 'ATTESA',
|
||||
'socket_status_4' => 'PRONTO',
|
||||
'socket_status_5' => 'AVVIATO',
|
||||
'api_not_set' => 'Devi specificare una chiave ed un ID API, ottienili su https://my.telegram.org',
|
||||
'session_corrupted' => 'La sessione si è corrotta!',
|
||||
'resetSession_seqno' => 'Resettando ID sessione e numero di sequenza sul DC %s...',
|
||||
'gen_perm_auth_key' => 'Generando chiave di autorizzazione permanente per il DC %s...',
|
||||
'gen_temp_auth_key' => 'Generando chiave di autorizzazione temporanea per il DC %s...',
|
||||
'copy_auth_dcs' => 'Copiando autorizzazione dal DC %s al DC %s...',
|
||||
'write_client_info' => 'Scrittura info sul client (eseguendo nel contempo il metodo %s)...',
|
||||
'config_updated' => 'La configurazione è stata aggiornata!',
|
||||
'length_not_4' => 'La lunghezza non è uguale a 4',
|
||||
|
@ -57,29 +43,12 @@ class Lang
|
|||
'encode_double_error' => 'Non sono riuscito a codificare il numero a virgola mobile fornito',
|
||||
'file_not_exist' => 'Il file specificato non esiste',
|
||||
'deserialization_error' => 'C\'è stato un errore durante la deserializzazione',
|
||||
'rsa_init' => 'Istanziamento di \\tgseclib\\Crypt\\RSA in corso...',
|
||||
'loading_key' => 'Caricamento della chiave in corso...',
|
||||
'computing_fingerprint' => 'Calcolo del fingerprint in corso...',
|
||||
'rsa_encrypting' => 'Criptando con chiave RSA...',
|
||||
'rpc_tg_error' => 'Telegram ha ritornato un errore RPC: %s (%s), causato da %s:%s%sTL trace:',
|
||||
'v_error' => '506572206661766f726520616767696f726e612071756573746120696e7374616c6c617a696f6e65206469204d6164656c696e6550726f746f20636f6e206769742070756c6c206520636f6d706f73657220757064617465',
|
||||
'v_tgerror' => '506572206661766f726520616767696f726e61207068702d6c69627467766f6970',
|
||||
'no_mode_specified' => 'Nessuna modalità di logging è stata specificata!',
|
||||
'constructor_function_uncalled' => 'Il metodo costruttore non è stato ancora chiamato! Per favore chiama il metodo costruttore prima di usare questo metodo.',
|
||||
'proxy_class_invalid' => 'È stata specificata una classe proxy errata!',
|
||||
'socket_con_error' => 'Connessione fallita.',
|
||||
'protocol_not_implemented' => 'Questo protocollo non è stato ancora implementato.',
|
||||
'protocol_invalid' => 'È stato fornito un protocollo non valido',
|
||||
'nothing_in_socket' => 'Non c\'è niente nel socket!',
|
||||
'wrong_length_read' => 'ATTENZIONE: Non sono stati letti abbastanza byte (dovevo leggere %s, ho letto %s)!',
|
||||
'no_data_in_socket' => 'Non ci sono dati nel socket!',
|
||||
'dc_con_start' => 'Connessione al DC %s in corso...',
|
||||
'dc_con_stop' => 'Disconnessione dal DC %s in corso...',
|
||||
'dc_con_test_start' => 'Connessione al DC %s (server %s, %s, %s)...',
|
||||
'script_not_exist' => 'Lo script fornito non esiste',
|
||||
'apifactory_start' => 'Sto avviando la fabbrica di API...',
|
||||
'madelineproto_ready' => 'MadelineProto è pronto!',
|
||||
'logout_error' => 'C\'è stato un errore durante il logout!',
|
||||
'logout_ok' => 'Il logout è stato eseguito correttamente!',
|
||||
'already_loggedIn' => 'Questa istanza di MadelineProto è già loggata, prima faccio il logout...',
|
||||
'login_ok' => 'Il login è stato eseguito correttamente!',
|
||||
|
@ -96,10 +65,8 @@ class Lang
|
|||
'signing_up' => 'Mi sto registrando su telegram come utente normale...',
|
||||
'signup_ok' => 'Mi sono registrato su Telegram!',
|
||||
'2fa_uncalled' => 'Non sto aspettando la password, chiama prima le funzioni phoneLogin e completePhoneLogin!',
|
||||
'getting_dialogs' => 'Sto ottenendo la lista delle chat...',
|
||||
'libtgvoip_required' => 'È necessario installare l\'estensione php-libtgvoip per accettare e gestire chiamate vocali, vistate https://docs.madelineproto.xyz per più info.',
|
||||
'peer_not_in_db' => 'Questo utente/gruppo/canale non è presente nel database interno MadelineProto',
|
||||
'calling_user' => 'Sto chiamando %s...',
|
||||
'generating_a' => 'Sto generando a...',
|
||||
'generating_g_a' => 'Sto generando g_a...',
|
||||
'call_error_1' => 'Impossibile trovare ed accettare la chiamata %s',
|
||||
|
@ -135,62 +102,138 @@ class Lang
|
|||
'sec_peer_not_in_db' => 'La chat segreta non è presente nel database interno MadelineProto',
|
||||
'stream_handle_invalid' => 'Il valore fornito non è uno stream',
|
||||
'length_too_big' => 'Il valore fornito è troppo lungo',
|
||||
'deserialize_not_str' => 'Il valore generato non è una stringa',
|
||||
'type_extract_error_id' => 'Non sono riuscito ad estrarre il tipo %s con ID %s',
|
||||
'vector_invalid' => 'ID vettore non valido: ',
|
||||
'constructor_not_found' => 'Costruttore non trovato per tipo: ',
|
||||
'rand_bytes_too_small' => 'random_bytes è troppo corto!',
|
||||
'botapi_conversion_error' => 'NOn sono risucito a convertire %s in un oggetto bot API',
|
||||
'botapi_conversion_error' => 'Non sono risucito a convertire %s in un oggetto bot API',
|
||||
'non_text_conversion' => 'Non posso ancora convertire messaggi media',
|
||||
'last_byte_invalid' => 'L\'ultimo byte non è valido',
|
||||
'file_type_invalid' => 'È stato fornito un tipo file errato',
|
||||
'recreate_temp_auth_key' => 'Sono stato costretto a rigenerare la chiave di autorizzazione temporanea',
|
||||
'resetting_auth_key' => 'ATTENZIONE: Sto resettando la chiave temporanea...',
|
||||
'shutting_down_reader_pool' => 'Chisura pool di lettura',
|
||||
'shutting_down_handler_pool' => 'Chiusura pool di gestione per DC %s, %d thread rimasti',
|
||||
'secret_chat_skipping' => 'Non ho la chat segreta %s nel database, ignorando messaggio',
|
||||
'fingerprint_mismatch' => 'Fingerprint della chiave non valido',
|
||||
'msg_data_length_too_big' => 'message_data_length è troppo grande',
|
||||
'length_not_divisible_16' => 'La lunghezza dei dati decifrati non è divisibile per 16',
|
||||
'msg_key_mismatch' => 'msg_key non valido',
|
||||
'rand_bytes_too_short' => 'random_bytes è troppo corto!',
|
||||
'resending_unsupported' => 'IL riinvio di messaggi non è ancora supportato',
|
||||
'unrecognized_dec_msg' => 'È stato ricevuto un messaggio decifrato sconosciuto: ',
|
||||
'serializing_madelineproto' => 'Sto serializzando MadelineProto...',
|
||||
'req_pq' => 'Sto richiedendo pq...',
|
||||
'done' => 'Fatto!',
|
||||
'cdn_reupload' => 'Il file non è disponibile sul nostro CDN, richiedo la copia!',
|
||||
'stored_on_cdn' => 'Il file è scaricabile tramite CDN!',
|
||||
'apiAppInstructionsAuto0' => 'Inserisci il nome dell\'app, può essere qualsiasi cosa: ',
|
||||
'apiAppInstructionsAuto1' => 'Inserisci il nome ridotto dell\'app, caratteri alfanumerici: ',
|
||||
'apiAppInstructionsAuto2' => 'Inserisci il sito internet dell\'app, oppure t.me/username: ',
|
||||
'apiAppInstructionsAuto3' => 'Inserisci la piattaforma dell\'app: ',
|
||||
'apiAppInstructionsAuto4' => 'Descrivi la tua app: ',
|
||||
'apiAppInstructionsManual0' => 'titolo dell\'app, può essere qualsiasi cosa',
|
||||
'apiAppInstructionsManual1' => 'il nome ridotto dell\'app, caratteri alfanumerici: ',
|
||||
'apiAppInstructionsManual2' => 'L\'indirizzo del tuo sito, oppure t.me/username',
|
||||
'apiAppInstructionsManual3' => 'Qualsiasi',
|
||||
'apiAppInstructionsManual4' => 'Descrivi la tua app',
|
||||
'apiAutoPrompt0' => 'Inserisci un numero di telefono che è già registrato su Telegram: ',
|
||||
'apiAutoPrompt1' => 'Inserisci il codice di verifica che hai ricevuto su Telegram: ',
|
||||
'apiChooseManualAutoTip' => 'Nota che puoi anche fornire i parametri API direttamente nelle impostazioni: %s',
|
||||
'apiChoosePrompt' => 'La tua scelta (m/a): ',
|
||||
'apiError' => 'ERRORE: %s. Prova ancora.',
|
||||
'apiManualInstructions0' => 'Effettua il login su my.telegram.org',
|
||||
'apiManualInstructions1' => 'Vai su API development tools',
|
||||
'apiManualInstructions2' => 'Clicca su create application',
|
||||
'apiManualPrompt0' => 'Inserisci il tuo API ID: ',
|
||||
'apiManualPrompt1' => 'Inserisci il tuo API hash: ',
|
||||
'apiParamsError' => 'Non hai fornito tutti i parametri richiesti!',
|
||||
'loginBot' => 'Inserisci il tuo bot token: ',
|
||||
'loginChoosePrompt' => 'Vuoi effettuare il login come utente o come bot (u/b)? ',
|
||||
'loginNoCode' => 'Non hai fornito un codice di verifica!',
|
||||
'loginNoName' => 'Non hai fornito un nome!',
|
||||
'loginNoPass' => 'Non hai fornito la password!',
|
||||
'loginUser' => 'Inserisci il tuo numero di telefono: ',
|
||||
'loginUserPass' => 'Inserisci la tua password (suggerimento %s): ',
|
||||
'loginUserPassHint' => 'Suggerimento: %s',
|
||||
'loginUserPassWeb' => 'Inserisci la tua password: ',
|
||||
'loginUserCode' => 'Inserisci il codice di verifica: ',
|
||||
'signupFirstName' => 'Inserisci il tuo nome: ',
|
||||
'signupFirstNameWeb' => 'Nome',
|
||||
'signupLastName' => 'Inserisci il tuo cognome: ',
|
||||
'signupLastNameWeb' => 'Cognome',
|
||||
'signupWeb' => 'Registrazione',
|
||||
'go' => 'Vai',
|
||||
'loginChoosePromptWeb' => 'Vuoi effettuare il login come utente o come bot?',
|
||||
'loginOptionBot' => 'Bot',
|
||||
'loginOptionUser' => 'Utente',
|
||||
'loginBotTokenWeb' => 'Token del bot',
|
||||
'loginUserPhoneCodeWeb' => 'Codice di verifica',
|
||||
'loginUserPhoneWeb' => 'Numero di telefono',
|
||||
'apiAutoWeb' => 'Inserisci un numero di telefono <b>già registrato su Telegram</b> per ottenere l'API ID',
|
||||
'apiChooseManualAuto' => 'You did not define a valid API ID/API hash. Do you want to define it now manually, or automatically? (m/a)',
|
||||
'apiManualWeb' => 'Inserisci il tuo API ID e API hash',
|
||||
'apiAppInstructionsAutoTypeOther' => 'Altro (specificare nella descrizione)',
|
||||
'apiAppWeb' => 'Inserire informazioni API',
|
||||
'apiChooseAutomaticallyWeb' => 'Automaticamente',
|
||||
'apiChooseManualAutoTipWeb' => 'Nota che puoi anche fornire i parametri API direttamente nelle <a target="_blank" href="%s">impostazioni</a>.',
|
||||
'apiChooseManualAutoWeb' => 'Vuoi configurare il tuo API ID/hash manualmente o automaticamente?',
|
||||
'apiChooseManuallyWeb' => 'Manualmente',
|
||||
],
|
||||
'en' =>
|
||||
[
|
||||
'req_pq' => 'Requesting pq...',
|
||||
'go' => 'Go',
|
||||
'apiChooseManualAuto' => 'Do you want to enter the API id and the API hash manually or automatically? (m/a)',
|
||||
'apiChooseManualAutoWeb' => 'Do you want to enter the API id and the API hash manually or automatically?',
|
||||
'apiChooseManualAutoTip' => 'Note that you can also provide them directly in the code using the settings: %s',
|
||||
'apiChooseManualAutoTipWeb' => 'Note that you can also provide them directly in the code using the <a target="_blank" href="%s">settings</a>.',
|
||||
'apiChoosePrompt' => 'Your choice (m/a): ',
|
||||
'apiChooseAutomaticallyWeb' => 'Automatically',
|
||||
'apiChooseManuallyWeb' => 'Manually',
|
||||
'apiManualInstructions0' => 'Login to my.telegram.org',
|
||||
'apiManualInstructions1' => 'Go to API development tools',
|
||||
'apiManualInstructions2' => 'Click on create application',
|
||||
'apiAppInstructionsManual0' => 'your app\'s name, can be anything',
|
||||
'apiAppInstructionsManual1' => 'your app\'s short name, alphanumeric',
|
||||
'apiAppInstructionsManual2' => 'your app/website\'s URL, or t.me/yourusername',
|
||||
'apiAppInstructionsManual3' => 'anything',
|
||||
'apiAppInstructionsManual4' => 'Describe your app here',
|
||||
'apiManualWeb' => 'Enter your API ID and API hash',
|
||||
'apiManualPrompt0' => 'Enter your API ID: ',
|
||||
'apiManualPrompt1' => 'Enter your API hash: ',
|
||||
'apiAutoWeb' => 'Enter a phone number that is <b>already registered</b> on telegram to get the API ID',
|
||||
'apiAutoPrompt0' => 'Enter a phone number that is already registered on Telegram: ',
|
||||
'apiAutoPrompt1' => 'Enter the verification code you received in Telegram: ',
|
||||
'apiAppWeb' => 'Enter API information',
|
||||
'apiAppInstructionsAuto0' => 'Enter the app\'s name, can be anything: ',
|
||||
'apiAppInstructionsAuto1' => 'Enter the app\'s short name, alphanumeric: ',
|
||||
'apiAppInstructionsAuto2' => 'Enter the app/website\'s URL, or t.me/yourusername: ',
|
||||
'apiAppInstructionsAuto3' => 'Enter the app platform: ',
|
||||
'apiAppInstructionsAuto4' => 'Describe your app: ',
|
||||
'apiAppInstructionsAutoTypeOther' => 'Other (specify in description)',
|
||||
'apiParamsError' => 'You didn\'t provide all of the required parameters!',
|
||||
'apiError' => 'ERROR: %s. Try again.',
|
||||
'loginChoosePrompt' => 'Do you want to login as user or bot (u/b)? ',
|
||||
'loginChoosePromptWeb' => 'Do you want to login as user or bot?',
|
||||
'loginOptionBot' => 'Bot',
|
||||
'loginOptionUser' => 'User',
|
||||
'loginBot' => 'Enter your bot token: ',
|
||||
'loginUser' => 'Enter your phone number: ',
|
||||
'loginUserCode' => 'Enter the code: ',
|
||||
'loginUserPass' => 'Enter your password (hint %s): ',
|
||||
'loginUserPassWeb' => 'Enter your password: ',
|
||||
'loginUserPassHint' => 'Hint: %s',
|
||||
'signupFirstName' => 'Enter your first name: ',
|
||||
'signupLastName' => 'Enter your last name (can be empty): ',
|
||||
'signupWeb' => 'Sign up please',
|
||||
'signupFirstNameWeb' => 'First name',
|
||||
'signupLastNameWeb' => 'Last name',
|
||||
'loginNoCode' => 'You didn\'t provide a phone code!',
|
||||
'loginNoPass' => 'You didn\'t provide the password!',
|
||||
'loginNoName' => 'You didn\'t provide the first name!',
|
||||
'loginBotTokenWeb' => 'Bot token',
|
||||
'loginUserPhoneWeb' => 'Phone number',
|
||||
'loginUserPhoneCodeWeb' => 'Code',
|
||||
'done' => 'Done!',
|
||||
'cdn_reupload' => 'File is not stored on CDN, requesting reupload!',
|
||||
'stored_on_cdn' => 'File is stored on CDN!',
|
||||
'serializing_madelineproto' => 'Serializing MadelineProto...',
|
||||
'phpseclib_fork' => 'Please install this fork of phpseclib: https://github.com/danog/phpseclib',
|
||||
'inst_dc' => 'Istantiating DataCenter...',
|
||||
'load_rsa' => 'Loading RSA keys...',
|
||||
'TL_translation' => 'Translating TL schemas...',
|
||||
'dh_prime_check_0' => 'Executing dh_prime checks (0/3)...',
|
||||
'nearest_dc' => 'We\'re in %s, nearest DC is %d.',
|
||||
'serialization_ofd' => 'Serialization is out of date, reconstructing object!',
|
||||
'getupdates_deserialization' => 'Getting updates after deserialization...',
|
||||
'shutdown_reader_pool' => 'Shutting down reader pool, %d jobs left',
|
||||
'threading_on' => 'THREADING IS ENABLED',
|
||||
'socket_reader' => 'Socket reader on DC %s: ',
|
||||
'socket_status_1' => 'CREATING',
|
||||
'socket_status_2' => 'SUBMITTING',
|
||||
'socket_status_3' => 'WAITING',
|
||||
'socket_status_4' => 'READY',
|
||||
'socket_status_5' => 'WORKING',
|
||||
'api_not_set' => 'You must provide an api key and an api id, get your own @ my.telegram.org',
|
||||
'session_corrupted' => 'The session is corrupted!',
|
||||
'resetSession_seqno' => 'Resetting session id and seq_no in DC %s...',
|
||||
'gen_perm_auth_key' => 'Generating permanent authorization key for DC %s...',
|
||||
'gen_temp_auth_key' => 'Generating temporary authorization key for DC %s...',
|
||||
'copy_auth_dcs' => 'Copying authorization from DC %s to DC %s...',
|
||||
'write_client_info' => 'Writing client info (also executing %s)...',
|
||||
'config_updated' => 'Updated config!',
|
||||
'length_not_4' => 'Length is not equal to 4',
|
||||
|
@ -204,29 +247,12 @@ class Lang
|
|||
'encode_double_error' => 'Could not properly encode double',
|
||||
'file_not_exist' => 'File does not exist',
|
||||
'deserialization_error' => 'An error occurred on deserialization',
|
||||
'rsa_init' => 'Istantiating \\tgseclib\\Crypt\\RSA...',
|
||||
'loading_key' => 'Loading key...',
|
||||
'computing_fingerprint' => 'Computing fingerprint...',
|
||||
'rsa_encrypting' => 'Encrypting with rsa key...',
|
||||
'rpc_tg_error' => 'Telegram returned an RPC error: %s (%s), caused by %s:%s%sTL trace:',
|
||||
'v_error' => '506c656173652075706461746520746f20746865206c61746573742076657273696f6e206f66204d6164656c696e6550726f746f2e',
|
||||
'v_tgerror' => '506c6561736520757064617465207068702d6c69627467766f6970',
|
||||
'no_mode_specified' => 'No mode was specified!',
|
||||
'constructor_function_uncalled' => 'The constructor function wasn\'t called! Please call the constructor function before using this method.',
|
||||
'proxy_class_invalid' => 'Invalid proxy class provided!',
|
||||
'socket_con_error' => 'Connection: couldn\'t connect to socket.',
|
||||
'protocol_not_implemented' => 'Connection: This protocol isn\'t implemented yet.',
|
||||
'protocol_invalid' => 'Connection: invalid protocol specified.',
|
||||
'nothing_in_socket' => 'Nothing in the socket!',
|
||||
'wrong_length_read' => 'WARNING: Wrong length was read (should\'ve read %s, read %s)!',
|
||||
'no_data_in_socket' => 'No data in the socket!',
|
||||
'dc_con_start' => 'Connecting to DC %s...',
|
||||
'dc_con_stop' => 'Disconnecting from DC %s...',
|
||||
'dc_con_test_start' => 'Connecting to DC %s (%s server, %s, %s)...',
|
||||
'script_not_exist' => 'Provided script does not exist',
|
||||
'apifactory_start' => 'Running APIFactory...',
|
||||
'madelineproto_ready' => 'MadelineProto is ready!',
|
||||
'logout_error' => 'An error occurred while logging out!',
|
||||
'logout_ok' => 'Logged out successfully!',
|
||||
'already_loggedIn' => 'This instance of MadelineProto is already logged in. Logging out first...',
|
||||
'login_ok' => 'Logged in successfully!',
|
||||
|
@ -243,10 +269,8 @@ class Lang
|
|||
'signing_up' => 'Signing up as a normal user...',
|
||||
'signup_ok' => 'Signed up in successfully!',
|
||||
'2fa_uncalled' => 'I\'m not waiting for the password! Please call the phoneLogin and the completePhoneLogin methods first!',
|
||||
'getting_dialogs' => 'Getting dialogs...',
|
||||
'libtgvoip_required' => 'The php-libtgvoip extension is required to accept and manage calls. See daniil.it/MadelineProto for more info.',
|
||||
'peer_not_in_db' => 'This peer is not present in the internal peer database',
|
||||
'calling_user' => 'Calling %s...',
|
||||
'generating_a' => 'Generating a...',
|
||||
'generating_g_a' => 'Generating g_a...',
|
||||
'call_error_1' => 'Could not find and accept call %s',
|
||||
|
@ -276,65 +300,91 @@ class Lang
|
|||
'long_not_64' => 'Given value is not 64 bytes long',
|
||||
'array_invalid' => 'You didn\'t provide a valid array',
|
||||
'predicate_not_set' => 'Predicate (value under _) was not set!',
|
||||
'type_extract_error' => 'Could not extract type "%s"',
|
||||
'type_extract_error' => 'Could not extract type "%s", you should update MadelineProto!',
|
||||
'method_not_found' => 'Could not find method: ',
|
||||
'params_missing' => 'Missing required parameter',
|
||||
'sec_peer_not_in_db' => 'This secret peer is not present in the internal peer database',
|
||||
'stream_handle_invalid' => 'An invalid stream handle was provided.',
|
||||
'length_too_big' => 'Length is too big',
|
||||
'deserialize_not_str' => 'Deserialize: Generated value isn\'t a string',
|
||||
'type_extract_error_id' => 'Could not extract type: %s with id %s',
|
||||
'vector_invalid' => 'Invalid vector constructor: ',
|
||||
'type_extract_error_id' => 'Could not extract type: %s with id %s, you should update MadelineProto!',
|
||||
'constructor_not_found' => 'Constructor not found for type: ',
|
||||
'rand_bytes_too_small' => 'Random_bytes is too small!',
|
||||
'botapi_conversion_error' => 'Can\'t convert %s to a bot API object',
|
||||
'non_text_conversion' => 'Can\'t convert non text messages yet!',
|
||||
'last_byte_invalid' => 'Invalid last byte',
|
||||
'file_type_invalid' => 'Invalid file type detected (%s)',
|
||||
'recreate_temp_auth_key' => 'I had to recreate the temporary authorization key',
|
||||
'resetting_auth_key' => 'WARNING: Resetting auth key...',
|
||||
'shutting_down_reader_pool' => 'Shutting down reader pool ',
|
||||
'shutting_down_handler_pool' => 'Shutting down handler pool for dc %s, %d jobs left',
|
||||
'secret_chat_skipping' => 'I do not have the secret chat %s in the database, skipping message...',
|
||||
'fingerprint_mismatch' => 'Key fingerprint mismatch',
|
||||
'msg_data_length_too_big' => 'Message_data_length is too big',
|
||||
'length_not_divisible_16' => 'Length of decrypted data is not divisible by 16',
|
||||
'msg_key_mismatch' => 'Msg_key mismatch',
|
||||
'rand_bytes_too_short' => 'Random_bytes is too short!',
|
||||
'resending_unsupported' => 'Resending of messages is not yet supported',
|
||||
'unrecognized_dec_msg' => 'Unrecognized decrypted message received: ',
|
||||
],
|
||||
];
|
||||
|
||||
// THIS WILL BE OVERWRITTEN BY $lang["en"]
|
||||
public static $current_lang = [
|
||||
'req_pq' => 'Requesting pq...',
|
||||
'go' => 'Go',
|
||||
'apiChooseManualAuto' => 'Do you want to enter the API id and the API hash manually or automatically? (m/a)',
|
||||
'apiChooseManualAutoWeb' => 'Do you want to enter the API id and the API hash manually or automatically?',
|
||||
'apiChooseManualAutoTip' => 'Note that you can also provide them directly in the code using the settings: %s',
|
||||
'apiChooseManualAutoTipWeb' => 'Note that you can also provide them directly in the code using the <a href="%s">settings</a>.',
|
||||
'apiChoosePrompt' => 'Your choice (m/a): ',
|
||||
'apiChooseAutomaticallyWeb' => 'Automatically',
|
||||
'apiChooseManuallyWeb' => 'Manually',
|
||||
'apiManualInstructions0' => 'Login to my.telegram.org',
|
||||
'apiManualInstructions1' => 'Go to API development tools',
|
||||
'apiManualInstructions2' => 'Click on create application',
|
||||
'apiAppInstructionsManual0' => 'your app\'s name, can be anything',
|
||||
'apiAppInstructionsManual1' => 'your app\'s short name, alphanumeric',
|
||||
'apiAppInstructionsManual2' => 'your app/website\'s URL, or t.me/yourusername',
|
||||
'apiAppInstructionsManual3' => 'anything',
|
||||
'apiAppInstructionsManual4' => 'Describe your app here',
|
||||
'apiManualWeb' => 'Enter your API ID and API hash',
|
||||
'apiManualPrompt0' => 'Enter your API ID: ',
|
||||
'apiManualPrompt1' => 'Enter your API hash: ',
|
||||
'apiAutoWeb' => 'Enter a phone number that is <b>already registered</b> on telegram to get the API ID',
|
||||
'apiAutoPrompt0' => 'Enter a phone number that is already registered on Telegram: ',
|
||||
'apiAutoPrompt1' => 'Enter the verification code you received in Telegram: ',
|
||||
'apiAppWeb' => 'Enter API information',
|
||||
'apiAppInstructionsAuto0' => 'Enter the app\'s name, can be anything: ',
|
||||
'apiAppInstructionsAuto1' => 'Enter the app\'s short name, alphanumeric: ',
|
||||
'apiAppInstructionsAuto2' => 'Enter the app/website\'s URL, or t.me/yourusername: ',
|
||||
'apiAppInstructionsAuto3' => 'Enter the app platform: ',
|
||||
'apiAppInstructionsAuto4' => 'Describe your app: ',
|
||||
'apiAppInstructionsAutoTypeOther' => 'Other (specify in description)',
|
||||
'apiParamsError' => 'You didn\'t provide all of the required parameters!',
|
||||
'apiError' => 'ERROR: %s. Try again.',
|
||||
'loginChoosePrompt' => 'Do you want to login as user or bot (u/b)? ',
|
||||
'loginChoosePromptWeb' => 'Do you want to login as user or bot?',
|
||||
'loginOptionBot' => 'Bot',
|
||||
'loginOptionUser' => 'User',
|
||||
'loginBot' => 'Enter your bot token: ',
|
||||
'loginUser' => 'Enter your phone number: ',
|
||||
'loginUserCode' => 'Enter the code: ',
|
||||
'loginUserPass' => 'Enter your password (hint %s): ',
|
||||
'loginUserPassWeb' => 'Enter your password: ',
|
||||
'loginUserPassHint' => 'Hint: %s',
|
||||
'signupFirstName' => 'Enter your first name: ',
|
||||
'signupLastName' => 'Enter your last name (can be empty): ',
|
||||
'signupWeb' => 'Sign up please',
|
||||
'signupFirstNameWeb' => 'First name',
|
||||
'signupLastNameWeb' => 'Last name',
|
||||
'loginNoCode' => 'You didn\'t provide a phone code!',
|
||||
'loginNoPass' => 'You didn\'t provide the password!',
|
||||
'loginNoName' => 'You didn\'t provide the first name!',
|
||||
'loginBotTokenWeb' => 'Bot token',
|
||||
'loginUserPhoneWeb' => 'Phone number',
|
||||
'loginUserPhoneCodeWeb' => 'Code',
|
||||
'done' => 'Done!',
|
||||
'cdn_reupload' => 'File is not stored on CDN, requesting reupload!',
|
||||
'stored_on_cdn' => 'File is stored on CDN!',
|
||||
'serializing_madelineproto' => 'Serializing MadelineProto...',
|
||||
'phpseclib_fork' => 'Please install this fork of phpseclib: https://github.com/danog/phpseclib',
|
||||
'inst_dc' => 'Istantiating DataCenter...',
|
||||
'load_rsa' => 'Loading RSA keys...',
|
||||
'TL_translation' => 'Translating TL schemas...',
|
||||
'dh_prime_check_0' => 'Executing dh_prime checks (0/3)...',
|
||||
'nearest_dc' => 'We\'re in %s, nearest DC is %d.',
|
||||
'serialization_ofd' => 'Serialization is out of date, reconstructing object!',
|
||||
'getupdates_deserialization' => 'Getting updates after deserialization...',
|
||||
'shutdown_reader_pool' => 'Shutting down reader pool, %d jobs left',
|
||||
'threading_on' => 'THREADING IS ENABLED',
|
||||
'socket_reader' => 'Socket reader on DC %s: ',
|
||||
'socket_status_1' => 'CREATING',
|
||||
'socket_status_2' => 'SUBMITTING',
|
||||
'socket_status_3' => 'WAITING',
|
||||
'socket_status_4' => 'READY',
|
||||
'socket_status_5' => 'WORKING',
|
||||
'api_not_set' => 'You must provide an api key and an api id, get your own @ my.telegram.org',
|
||||
'session_corrupted' => 'The session is corrupted!',
|
||||
'resetSession_seqno' => 'Resetting session id and seq_no in DC %s...',
|
||||
'gen_perm_auth_key' => 'Generating permanent authorization key for DC %s...',
|
||||
'gen_temp_auth_key' => 'Generating temporary authorization key for DC %s...',
|
||||
'copy_auth_dcs' => 'Copying authorization from DC %s to DC %s...',
|
||||
'write_client_info' => 'Writing client info (also executing %s)...',
|
||||
'config_updated' => 'Updated config!',
|
||||
'length_not_4' => 'Length is not equal to 4',
|
||||
|
@ -348,29 +398,12 @@ class Lang
|
|||
'encode_double_error' => 'Could not properly encode double',
|
||||
'file_not_exist' => 'File does not exist',
|
||||
'deserialization_error' => 'An error occurred on deserialization',
|
||||
'rsa_init' => 'Istantiating \\tgseclib\\Crypt\\RSA...',
|
||||
'loading_key' => 'Loading key...',
|
||||
'computing_fingerprint' => 'Computing fingerprint...',
|
||||
'rsa_encrypting' => 'Encrypting with rsa key...',
|
||||
'rpc_tg_error' => 'Telegram returned an RPC error: %s (%s), caused by %s:%s%sTL trace:',
|
||||
'v_error' => '506c656173652075706461746520746f20746865206c61746573742076657273696f6e206f66204d6164656c696e6550726f746f2e',
|
||||
'v_tgerror' => '506c6561736520757064617465207068702d6c69627467766f6970',
|
||||
'no_mode_specified' => 'No mode was specified!',
|
||||
'constructor_function_uncalled' => 'The constructor function wasn\'t called! Please call the constructor function before using this method.',
|
||||
'proxy_class_invalid' => 'Invalid proxy class provided!',
|
||||
'socket_con_error' => 'Connection: couldn\'t connect to socket.',
|
||||
'protocol_not_implemented' => 'Connection: This protocol isn\'t implemented yet.',
|
||||
'protocol_invalid' => 'Connection: invalid protocol specified.',
|
||||
'nothing_in_socket' => 'Nothing in the socket!',
|
||||
'wrong_length_read' => 'WARNING: Wrong length was read (should\'ve read %s, read %s)!',
|
||||
'no_data_in_socket' => 'No data in the socket!',
|
||||
'dc_con_start' => 'Connecting to DC %s...',
|
||||
'dc_con_stop' => 'Disconnecting from DC %s...',
|
||||
'dc_con_test_start' => 'Connecting to DC %s (%s server, %s, %s)...',
|
||||
'script_not_exist' => 'Provided script does not exist',
|
||||
'apifactory_start' => 'Running APIFactory...',
|
||||
'madelineproto_ready' => 'MadelineProto is ready!',
|
||||
'logout_error' => 'An error occurred while logging out!',
|
||||
'logout_ok' => 'Logged out successfully!',
|
||||
'already_loggedIn' => 'This instance of MadelineProto is already logged in. Logging out first...',
|
||||
'login_ok' => 'Logged in successfully!',
|
||||
|
@ -387,10 +420,8 @@ class Lang
|
|||
'signing_up' => 'Signing up as a normal user...',
|
||||
'signup_ok' => 'Signed up in successfully!',
|
||||
'2fa_uncalled' => 'I\'m not waiting for the password! Please call the phoneLogin and the completePhoneLogin methods first!',
|
||||
'getting_dialogs' => 'Getting dialogs...',
|
||||
'libtgvoip_required' => 'The php-libtgvoip extension is required to accept and manage calls. See daniil.it/MadelineProto for more info.',
|
||||
'peer_not_in_db' => 'This peer is not present in the internal peer database',
|
||||
'calling_user' => 'Calling %s...',
|
||||
'generating_a' => 'Generating a...',
|
||||
'generating_g_a' => 'Generating g_a...',
|
||||
'call_error_1' => 'Could not find and accept call %s',
|
||||
|
@ -420,32 +451,22 @@ class Lang
|
|||
'long_not_64' => 'Given value is not 64 bytes long',
|
||||
'array_invalid' => 'You didn\'t provide a valid array',
|
||||
'predicate_not_set' => 'Predicate (value under _) was not set!',
|
||||
'type_extract_error' => 'Could not extract type "%s"',
|
||||
'type_extract_error' => 'Could not extract type "%s", you should update MadelineProto!',
|
||||
'method_not_found' => 'Could not find method: ',
|
||||
'params_missing' => 'Missing required parameter',
|
||||
'sec_peer_not_in_db' => 'This secret peer is not present in the internal peer database',
|
||||
'stream_handle_invalid' => 'An invalid stream handle was provided.',
|
||||
'length_too_big' => 'Length is too big',
|
||||
'deserialize_not_str' => 'Deserialize: Generated value isn\'t a string',
|
||||
'type_extract_error_id' => 'Could not extract type: %s with id %s',
|
||||
'vector_invalid' => 'Invalid vector constructor: ',
|
||||
'type_extract_error_id' => 'Could not extract type: %s with id %s, you should update MadelineProto!',
|
||||
'constructor_not_found' => 'Constructor not found for type: ',
|
||||
'rand_bytes_too_small' => 'Random_bytes is too small!',
|
||||
'botapi_conversion_error' => 'Can\'t convert %s to a bot API object',
|
||||
'non_text_conversion' => 'Can\'t convert non text messages yet!',
|
||||
'last_byte_invalid' => 'Invalid last byte',
|
||||
'file_type_invalid' => 'Invalid file type detected (%s)',
|
||||
'recreate_temp_auth_key' => 'I had to recreate the temporary authorization key',
|
||||
'resetting_auth_key' => 'WARNING: Resetting auth key...',
|
||||
'shutting_down_reader_pool' => 'Shutting down reader pool ',
|
||||
'shutting_down_handler_pool' => 'Shutting down handler pool for dc %s, %d jobs left',
|
||||
'secret_chat_skipping' => 'I do not have the secret chat %s in the database, skipping message...',
|
||||
'fingerprint_mismatch' => 'Key fingerprint mismatch',
|
||||
'msg_data_length_too_big' => 'Message_data_length is too big',
|
||||
'length_not_divisible_16' => 'Length of decrypted data is not divisible by 16',
|
||||
'msg_key_mismatch' => 'Msg_key mismatch',
|
||||
'rand_bytes_too_short' => 'Random_bytes is too short!',
|
||||
'resending_unsupported' => 'Resending of messages is not yet supported',
|
||||
'unrecognized_dec_msg' => 'Unrecognized decrypted message received: ',
|
||||
];
|
||||
}
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
/**
|
||||
* IPC light state.
|
||||
*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Light state.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class LightState
|
||||
{
|
||||
/**
|
||||
* Event handler class name.
|
||||
*
|
||||
* @var null|class-string<EventHandler>
|
||||
*/
|
||||
private ?string $eventHandler = null;
|
||||
|
||||
public function __construct(MTProto $API)
|
||||
{
|
||||
if ($API->hasEventHandler()) {
|
||||
$this->eventHandler = \get_class($API->getEventHandler());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether we can start IPC.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function canStartIpc(): bool
|
||||
{
|
||||
return !$this->eventHandler || \class_exists($this->eventHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event handler class name.
|
||||
*
|
||||
* @return null|class-string<EventHandler>
|
||||
*/
|
||||
public function getEventHandler(): ?string
|
||||
{
|
||||
return $this->eventHandler;
|
||||
}
|
||||
}
|
|
@ -22,6 +22,8 @@ namespace danog\MadelineProto;
|
|||
use Amp\ByteStream\ResourceOutputStream;
|
||||
use Amp\Failure;
|
||||
use Amp\Loop;
|
||||
use danog\MadelineProto\Settings\Logger as SettingsLogger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
use function Amp\ByteStream\getStderr;
|
||||
use function Amp\ByteStream\getStdout;
|
||||
|
@ -44,7 +46,7 @@ class Logger
|
|||
/**
|
||||
* Optional logger parameter.
|
||||
*
|
||||
* @var mixed
|
||||
* @var null|string|callable
|
||||
*/
|
||||
public $optional = null;
|
||||
/**
|
||||
|
@ -80,7 +82,7 @@ class Logger
|
|||
/**
|
||||
* Default logger instance.
|
||||
*
|
||||
* @var self
|
||||
* @var ?self
|
||||
*/
|
||||
public static $default;
|
||||
/**
|
||||
|
@ -93,116 +95,106 @@ class Logger
|
|||
* Log rotation loop ID.
|
||||
*/
|
||||
private string $rotateId = '';
|
||||
/**
|
||||
* PSR logger.
|
||||
*/
|
||||
private PsrLogger $psr;
|
||||
/**
|
||||
* Ultra verbose logging.
|
||||
*/
|
||||
const ULTRA_VERBOSE = 5;
|
||||
/**
|
||||
* Verbose logging.
|
||||
*/
|
||||
const VERBOSE = 4;
|
||||
/**
|
||||
* Notice logging.
|
||||
*/
|
||||
const NOTICE = 3;
|
||||
/**
|
||||
* Warning logging.
|
||||
*/
|
||||
const WARNING = 2;
|
||||
/**
|
||||
* Error logging.
|
||||
*/
|
||||
const ERROR = 1;
|
||||
/**
|
||||
* Log only fatal errors.
|
||||
*/
|
||||
const FATAL_ERROR = 0;
|
||||
|
||||
/**
|
||||
* Disable logger (DEPRECATED).
|
||||
*/
|
||||
const NO_LOGGER = 0;
|
||||
/**
|
||||
* Default logger (syslog).
|
||||
*/
|
||||
const DEFAULT_LOGGER = 1;
|
||||
/**
|
||||
* File logger.
|
||||
*/
|
||||
const FILE_LOGGER = 2;
|
||||
/**
|
||||
* Echo logger.
|
||||
*/
|
||||
const ECHO_LOGGER = 3;
|
||||
/**
|
||||
* Callable logger.
|
||||
*/
|
||||
const CALLABLE_LOGGER = 4;
|
||||
|
||||
const LEVEL_ULTRA_VERBOSE = self::ULTRA_VERBOSE;
|
||||
const LEVEL_VERBOSE = self::VERBOSE;
|
||||
const LEVEL_NOTICE = self::NOTICE;
|
||||
const LEVEL_WARNING = self::WARNING;
|
||||
const LEVEL_ERROR = self::ERROR;
|
||||
const LEVEL_FATAL = self::FATAL_ERROR;
|
||||
|
||||
const LOGGER_DEFAULT = self::DEFAULT_LOGGER;
|
||||
const LOGGER_ECHO = self::ECHO_LOGGER;
|
||||
const LOGGER_FILE = self::FILE_LOGGER;
|
||||
const LOGGER_CALLABLE = self::CALLABLE_LOGGER;
|
||||
/**
|
||||
* Construct global static logger from MadelineProto settings.
|
||||
*
|
||||
* @param array $settings Settings array
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function constructorFromSettings(array $settings)
|
||||
{
|
||||
if (!self::$default) {
|
||||
// The getLogger function will automatically init the static logger, but we'll do it again anyway
|
||||
self::$default = self::getLoggerFromSettings(MTProto::parseSettings($settings));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get logger from MadelineProto settings.
|
||||
*
|
||||
* @param array $settings Settings array
|
||||
* @param string $prefix Optional prefix for log messages
|
||||
* @param SettingsLogger $settings Settings instance
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public static function getLoggerFromSettings(array $settings, string $prefix = ''): self
|
||||
public static function constructorFromSettings(SettingsLogger $settings): self
|
||||
{
|
||||
if (!isset($settings['logger']['logger_param']) && isset($settings['logger']['param'])) {
|
||||
$settings['logger']['logger_param'] = $settings['logger']['param'];
|
||||
}
|
||||
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' && isset($settings['logger']['logger_param']) && $settings['logger']['logger_param'] === 'MadelineProto.log') {
|
||||
$settings['logger']['logger_param'] = Magic::$script_cwd.'/MadelineProto.log';
|
||||
}
|
||||
$logger = new self($settings['logger']['logger'], $settings['logger']['logger_param'] ?? '', $prefix, $settings['logger']['logger_level'] ?? Logger::VERBOSE, $settings['logger']['max_size'] ?? 100 * 1024 * 1024);
|
||||
if (!self::$default) {
|
||||
self::$default = $logger;
|
||||
}
|
||||
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
|
||||
try {
|
||||
\error_reporting(E_ALL);
|
||||
\ini_set('log_errors', 1);
|
||||
\ini_set('error_log', $settings['logger']['logger'] === self::FILE_LOGGER ? $settings['logger']['logger_param'] : Magic::$script_cwd.'/MadelineProto.log');
|
||||
\error_log('Enabled PHP logging');
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
$logger->logger('Could not enable PHP logging');
|
||||
}
|
||||
}
|
||||
return $logger;
|
||||
return self::$default = new self($settings);
|
||||
}
|
||||
/**
|
||||
* Construct global logger.
|
||||
*
|
||||
* @param int $mode One of the logger constants
|
||||
* @param mixed $optional Optional parameter for logger
|
||||
* @param string $prefix Prefix for log messages
|
||||
* @param int $level Default logging level
|
||||
* @param int $max_size Maximum size for logfile
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function constructor(int $mode, $optional = null, string $prefix = '', int $level = self::NOTICE, int $max_size = 100 * 1024 * 1024)
|
||||
{
|
||||
self::$default = new self($mode, $optional, $prefix, $level, $max_size);
|
||||
}
|
||||
/**
|
||||
* Construct global logger.
|
||||
*
|
||||
* @param int $mode One of the logger constants
|
||||
* @param mixed $optional Optional parameter for logger
|
||||
* @param string $prefix Prefix for log messages
|
||||
* @param int $level Default logging level
|
||||
* @param int $max_size Maximum size for logfile
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(int $mode, $optional = null, string $prefix = '', int $level = self::NOTICE, int $max_size = 10 * 1024 * 1024)
|
||||
{
|
||||
if ($mode === null) {
|
||||
throw new Exception(\danog\MadelineProto\Lang::$current_lang['no_mode_specified']);
|
||||
}
|
||||
if ($mode === self::NO_LOGGER) {
|
||||
$mode = (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') ? Logger::ECHO_LOGGER : Logger::FILE_LOGGER;
|
||||
}
|
||||
if (\defined(\MADELINE_WORKER::class)) {
|
||||
$mode = Logger::FILE_LOGGER;
|
||||
}
|
||||
$level = \max($level, self::NOTICE);
|
||||
$max_size = \max($max_size, 100 * 1024);
|
||||
|
||||
$this->mode = $mode;
|
||||
$this->optional = $mode == self::FILE_LOGGER ? Tools::absolute($optional) : $optional;
|
||||
/**
|
||||
* Construct logger.
|
||||
*
|
||||
* @param SettingsLogger $settings
|
||||
* @param string $prefix
|
||||
*/
|
||||
public function __construct(SettingsLogger $settings, string $prefix = '')
|
||||
{
|
||||
$this->psr = new PsrLogger($this);
|
||||
$this->prefix = $prefix === '' ? '' : ', '.$prefix;
|
||||
$this->level = $level;
|
||||
if ($this->mode === self::FILE_LOGGER && !\file_exists(\pathinfo($this->optional, PATHINFO_DIRNAME))) {
|
||||
$this->optional = Magic::$script_cwd.'/MadelineProto.log';
|
||||
}
|
||||
if ($this->mode === self::FILE_LOGGER && !\preg_match('/\\.log$/', $this->optional)) {
|
||||
$this->optional .= '.log';
|
||||
}
|
||||
if ($mode === self::FILE_LOGGER && $max_size !== -1 && \file_exists($this->optional) && \filesize($this->optional) > $max_size) {
|
||||
\unlink($this->optional);
|
||||
|
||||
$this->mode = $settings->getType();
|
||||
$this->optional = $settings->getExtra();
|
||||
$this->level = $settings->getLevel();
|
||||
|
||||
$maxSize = $settings->getMaxSize();
|
||||
|
||||
if ($this->mode === self::FILE_LOGGER) {
|
||||
if (!\file_exists(\pathinfo($this->optional, PATHINFO_DIRNAME))) {
|
||||
$this->optional = Magic::$script_cwd.'/MadelineProto.log';
|
||||
}
|
||||
if (!str_ends_with($this->optional, '.log')) {
|
||||
$this->optional .= '.log';
|
||||
}
|
||||
if ($maxSize !== -1 && \file_exists($this->optional) && \filesize($this->optional) > $maxSize) {
|
||||
\unlink($this->optional);
|
||||
}
|
||||
}
|
||||
$this->colors[self::ULTRA_VERBOSE] = \implode(';', [self::FOREGROUND['light_gray'], self::SET['dim']]);
|
||||
$this->colors[self::VERBOSE] = \implode(';', [self::FOREGROUND['green'], self::SET['bold']]);
|
||||
|
@ -219,16 +211,16 @@ class Logger
|
|||
} elseif ($this->mode === self::FILE_LOGGER) {
|
||||
Snitch::logFile($this->optional);
|
||||
$this->stdout = new ResourceOutputStream(\fopen($this->optional, 'a'));
|
||||
if ($max_size !== -1) {
|
||||
if ($maxSize !== -1) {
|
||||
$this->rotateId = Loop::repeat(
|
||||
10*1000,
|
||||
function () use ($max_size) {
|
||||
function () use ($maxSize) {
|
||||
\clearstatcache(true, $this->optional);
|
||||
if (\file_exists($this->optional) && \filesize($this->optional) >= $max_size) {
|
||||
if (\file_exists($this->optional) && \filesize($this->optional) >= $maxSize) {
|
||||
$this->stdout = null;
|
||||
\unlink($this->optional);
|
||||
$this->stdout = new ResourceOutputStream(\fopen($this->optional, 'a'));
|
||||
$this->logger("Automatically truncated logfile to $max_size");
|
||||
$this->logger("Automatically truncated logfile to $maxSize");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -244,6 +236,22 @@ class Logger
|
|||
$this->stdout = getStderr();
|
||||
}
|
||||
}
|
||||
|
||||
if (!self::$default) {
|
||||
self::$default = $this;
|
||||
}
|
||||
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
|
||||
try {
|
||||
\error_reporting(E_ALL);
|
||||
\ini_set('log_errors', "1");
|
||||
\ini_set('error_log', $this->mode === self::FILE_LOGGER
|
||||
? $this->optional
|
||||
: Magic::$script_cwd.'/MadelineProto.log');
|
||||
\error_log('Enabled PHP logging');
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
$this->logger('Could not enable PHP logging');
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Destructor function.
|
||||
|
@ -325,4 +333,14 @@ class Logger
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PSR logger.
|
||||
*
|
||||
* @return LoggerInterface
|
||||
*/
|
||||
public function getPsrLogger(): LoggerInterface
|
||||
{
|
||||
return $this->psr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ class CheckLoop extends ResumableSignalLoop
|
|||
$datacenter = $this->datacenter;
|
||||
$connection = $this->connection;
|
||||
$shared = $this->datacenterConnection;
|
||||
$timeout = $shared->getSettings()['timeout'];
|
||||
$timeout = $shared->getSettings()->getTimeout();
|
||||
$timeoutMs = $timeout * 1000;
|
||||
$timeoutResend = $timeout * $timeout;
|
||||
// Typically 25 seconds, good enough
|
||||
|
|
|
@ -40,7 +40,7 @@ class PingLoop extends ResumableSignalLoop
|
|||
$datacenter = $this->datacenter;
|
||||
$connection = $this->connection;
|
||||
$shared = $this->datacenterConnection;
|
||||
$timeout = $shared->getSettings()['timeout'];
|
||||
$timeout = $shared->getSettings()->getTimeout();
|
||||
$timeoutMs = $timeout * 1000;
|
||||
while (true) {
|
||||
while (!$shared->hasTempAuthKey()) {
|
||||
|
|
|
@ -165,7 +165,7 @@ class WriteLoop extends ResumableSignalLoop
|
|||
unset($connection->pending_outgoing[$k]);
|
||||
continue;
|
||||
}
|
||||
if ($shared->getSettings()['pfs'] && !$shared->isBound() && !$connection->isCDN() && !\in_array($message['_'], ['http_wait', 'auth.bindTempAuthKey']) && $message['method']) {
|
||||
if ($shared->getGenericSettings()->getAuth()->getPfs() && !$shared->isBound() && !$connection->isCDN() && !\in_array($message['_'], ['http_wait', 'auth.bindTempAuthKey']) && $message['method']) {
|
||||
$API->logger->logger("Skipping {$message['_']} due to unbound keys in DC {$datacenter}");
|
||||
$skipped = true;
|
||||
continue;
|
||||
|
@ -210,7 +210,7 @@ class WriteLoop extends ResumableSignalLoop
|
|||
if (!$shared->getTempAuthKey()->isInited() && $message['_'] !== 'auth.bindTempAuthKey' && !$inited) {
|
||||
$inited = true;
|
||||
$API->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['write_client_info'], $message['_']), \danog\MadelineProto\Logger::NOTICE);
|
||||
$MTmessage['body'] = (yield from $API->getTL()->serializeMethod('invokeWithLayer', ['layer' => $API->settings['tl_schema']['layer'], 'query' => yield from $API->getTL()->serializeMethod('initConnection', ['api_id' => $API->settings['app_info']['api_id'], 'api_hash' => $API->settings['app_info']['api_hash'], 'device_model' => !$connection->isCDN() ? $API->settings['app_info']['device_model'] : 'n/a', 'system_version' => !$connection->isCDN() ? $API->settings['app_info']['system_version'] : 'n/a', 'app_version' => $API->settings['app_info']['app_version'], 'system_lang_code' => $API->settings['app_info']['lang_code'], 'lang_code' => $API->settings['app_info']['lang_code'], 'lang_pack' => $API->settings['app_info']['lang_pack'], 'proxy' => $connection->getCtx()->getInputClientProxy(), 'query' => $MTmessage['body']])]));
|
||||
$MTmessage['body'] = (yield from $API->getTL()->serializeMethod('invokeWithLayer', ['layer' => $API->settings->getSchema()->getLayer(), 'query' => yield from $API->getTL()->serializeMethod('initConnection', ['api_id' => $API->settings->getAppInfo()->getApiId(), 'api_hash' => $API->settings->getAppInfo()->getApiHash(), 'device_model' => !$connection->isCDN() ? $API->settings->getAppInfo()->getDeviceModel() : 'n/a', 'system_version' => !$connection->isCDN() ? $API->settings->getAppInfo()->getSystemVersion() : 'n/a', 'app_version' => $API->settings->getAppInfo()->getAppVersion(), 'system_lang_code' => $API->settings->getAppInfo()->getLangCode(), 'lang_code' => $API->settings->getAppInfo()->getLangCode(), 'lang_pack' => $API->settings->getAppInfo()->getLangPack(), 'proxy' => $connection->getCtx()->getInputClientProxy(), 'query' => $MTmessage['body']])]));
|
||||
} else {
|
||||
if (isset($message['queue'])) {
|
||||
if (!isset($connection->call_queue[$message['queue']])) {
|
||||
|
@ -218,7 +218,7 @@ class WriteLoop extends ResumableSignalLoop
|
|||
}
|
||||
$MTmessage['body'] = (yield from $API->getTL()->serializeMethod('invokeAfterMsgs', ['msg_ids' => $connection->call_queue[$message['queue']], 'query' => $MTmessage['body']]));
|
||||
$connection->call_queue[$message['queue']][$message_id] = $message_id;
|
||||
if (\count($connection->call_queue[$message['queue']]) > $API->settings['msg_array_limit']['call_queue']) {
|
||||
if (\count($connection->call_queue[$message['queue']]) > $API->settings->getRpc()->getLimitCallQueue()) {
|
||||
\reset($connection->call_queue[$message['queue']]);
|
||||
$key = \key($connection->call_queue[$message['queue']]);
|
||||
unset($connection->call_queue[$message['queue']][$key]);
|
||||
|
|
|
@ -75,17 +75,14 @@ class FeedLoop extends ResumableSignalLoop
|
|||
{
|
||||
$API = $this->API;
|
||||
$this->updater = $API->updaters[$this->channelId];
|
||||
if (!$this->API->settings['updates']['handle_updates']) {
|
||||
return false;
|
||||
}
|
||||
while (!$this->API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
|
||||
while (!$API->hasAllAuth()) {
|
||||
if (yield $this->waitSignal($this->pause())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->state = $this->channelId === self::GENERIC ? yield from $API->loadUpdateState() : $API->loadChannelState($this->channelId);
|
||||
while (true) {
|
||||
while (!$this->API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
|
||||
while (!$API->hasAllAuth()) {
|
||||
if (yield $this->waitSignal($this->pause())) {
|
||||
return;
|
||||
}
|
||||
|
@ -93,9 +90,6 @@ class FeedLoop extends ResumableSignalLoop
|
|||
if (yield $this->waitSignal($this->pause())) {
|
||||
return;
|
||||
}
|
||||
if (!$this->API->settings['updates']['handle_updates']) {
|
||||
return;
|
||||
}
|
||||
$API->logger->logger("Resumed {$this}");
|
||||
while ($this->incomingUpdates) {
|
||||
$updates = $this->incomingUpdates;
|
||||
|
|
|
@ -51,17 +51,14 @@ class SeqLoop extends ResumableSignalLoop
|
|||
{
|
||||
$API = $this->API;
|
||||
$this->feeder = $API->feeders[FeedLoop::GENERIC];
|
||||
if (!$this->API->settings['updates']['handle_updates']) {
|
||||
return false;
|
||||
}
|
||||
while (!$this->API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
|
||||
while (!$API->hasAllAuth()) {
|
||||
if (yield $this->waitSignal($this->pause())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->state = (yield from $API->loadUpdateState());
|
||||
while (true) {
|
||||
while (!$this->API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
|
||||
while (!$API->hasAllAuth()) {
|
||||
if (yield $this->waitSignal($this->pause())) {
|
||||
return;
|
||||
}
|
||||
|
@ -69,9 +66,6 @@ class SeqLoop extends ResumableSignalLoop
|
|||
if (yield $this->waitSignal($this->pause())) {
|
||||
return;
|
||||
}
|
||||
if (!$this->API->settings['updates']['handle_updates']) {
|
||||
return;
|
||||
}
|
||||
while ($this->incomingUpdates) {
|
||||
$updates = $this->incomingUpdates;
|
||||
$this->incomingUpdates = [];
|
||||
|
|
|
@ -69,17 +69,17 @@ class UpdateLoop extends ResumableSignalLoop
|
|||
{
|
||||
$API = $this->API;
|
||||
$feeder = $this->feeder = $API->feeders[$this->channelId];
|
||||
while (!$API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
|
||||
while (!$API->hasAllAuth()) {
|
||||
if (yield $this->waitSignal($this->pause())) {
|
||||
$API->logger->logger("Exiting {$this} due to signal");
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->state = $state = $this->channelId === self::GENERIC ? yield from $API->loadUpdateState() : $API->loadChannelState($this->channelId);
|
||||
$timeout = $API->settings['updates']['getdifference_interval'] * 1000;
|
||||
$timeout = 30 * 1000;
|
||||
$first = true;
|
||||
while (true) {
|
||||
while (!$API->settings['updates']['handle_updates'] || !$API->hasAllAuth()) {
|
||||
while (!$API->hasAllAuth()) {
|
||||
if (yield $this->waitSignal($this->pause())) {
|
||||
$API->logger->logger("Exiting {$this} due to signal");
|
||||
return;
|
||||
|
@ -160,7 +160,7 @@ class UpdateLoop extends ResumableSignalLoop
|
|||
}
|
||||
} else {
|
||||
$API->logger->logger('Resumed and fetching normal difference...', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
$difference = yield from $API->methodCallAsyncRead('updates.getDifference', ['pts' => $state->pts(), 'date' => $state->date(), 'qts' => $state->qts()], ['datacenter' => $API->settings['connection_settings']['default_dc']]);
|
||||
$difference = yield from $API->methodCallAsyncRead('updates.getDifference', ['pts' => $state->pts(), 'date' => $state->date(), 'qts' => $state->qts()], $API->settings->getDefaultDcParams());
|
||||
$API->logger->logger('Got '.$difference['_'], \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
switch ($difference['_']) {
|
||||
case 'updates.differenceEmpty':
|
||||
|
|
|
@ -22,7 +22,7 @@ namespace danog\MadelineProto;
|
|||
class Lua
|
||||
{
|
||||
use \danog\Serializable;
|
||||
public $MadelineProto;
|
||||
public API $MadelineProto;
|
||||
protected $Lua;
|
||||
protected $script;
|
||||
public function __magic_construct($script, $MadelineProto)
|
||||
|
@ -31,8 +31,7 @@ class Lua
|
|||
throw new Exception(\danog\MadelineProto\Lang::$current_lang['script_not_exist']);
|
||||
}
|
||||
$this->MadelineProto = $MadelineProto;
|
||||
$this->MadelineProto->settings['updates']['handle_updates'] = true;
|
||||
$this->MadelineProto->API->datacenter->sockets[$this->MadelineProto->settings['connection_settings']['default_dc']]->startUpdateLoop();
|
||||
$this->MadelineProto->API->datacenter->sockets[$this->MadelineProto->getSettings()->getConnection()->getDefaultDc()]->startUpdateLoop();
|
||||
$this->script = $script;
|
||||
$this->__wakeup();
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -19,8 +19,12 @@
|
|||
|
||||
namespace danog\MadelineProto\MTProtoSession;
|
||||
|
||||
use danog\MadelineProto\DataCenterConnection;
|
||||
|
||||
/**
|
||||
* Manages acknowledgement of messages.
|
||||
*
|
||||
* @property DataCenterConnection $shared
|
||||
*/
|
||||
trait AckHandler
|
||||
{
|
||||
|
@ -89,9 +93,8 @@ trait AckHandler
|
|||
*/
|
||||
public function hasPendingCalls(): bool
|
||||
{
|
||||
$settings = $this->shared->getSettings();
|
||||
$timeout = $settings['timeout'];
|
||||
$pfs = $settings['pfs'];
|
||||
$timeout = $this->shared->getSettings()->getTimeout();
|
||||
$pfs = $this->shared->getGenericSettings()->getAuth()->getPfs();
|
||||
$unencrypted = !$this->shared->hasTempAuthKey();
|
||||
$notBound = !$this->shared->isBound();
|
||||
$pfsNotBound = $pfs && $notBound;
|
||||
|
@ -113,9 +116,10 @@ trait AckHandler
|
|||
public function getPendingCalls(): array
|
||||
{
|
||||
$settings = $this->shared->getSettings();
|
||||
$dropTimeout = $settings['drop_timeout'];
|
||||
$timeout = $settings['timeout'];
|
||||
$pfs = $settings['pfs'];
|
||||
$global = $this->shared->getGenericSettings();
|
||||
$dropTimeout = $global->getRpc()->getRpcTimeout();
|
||||
$timeout = $settings->getTimeout();
|
||||
$pfs = $global->getAuth()->getPfs();
|
||||
$unencrypted = !$this->shared->hasTempAuthKey();
|
||||
$notBound = !$this->shared->isBound();
|
||||
$pfsNotBound = $pfs && $notBound;
|
||||
|
|
|
@ -20,10 +20,13 @@
|
|||
namespace danog\MadelineProto\MTProtoSession\MsgIdHandler;
|
||||
|
||||
use danog\MadelineProto\MTProtoSession\MsgIdHandler;
|
||||
use danog\MadelineProto\MTProtoSession\Session;
|
||||
use tgseclib\Math\BigInteger;
|
||||
|
||||
/**
|
||||
* Manages message ids.
|
||||
*
|
||||
* @property Session $session
|
||||
*/
|
||||
class MsgIdHandler32 extends MsgIdHandler
|
||||
{
|
||||
|
@ -66,7 +69,7 @@ class MsgIdHandler32 extends MsgIdHandler
|
|||
if ($newMessageId->compare($key = $this->getMaxId($incoming = false)) <= 0) {
|
||||
throw new \danog\MadelineProto\Exception('Given message id ('.$newMessageId.') is lower than or equal to the current limit ('.$key.'). Consider syncing your date.', 1);
|
||||
}
|
||||
if (\count($this->session->outgoing_messages) > $this->session->API->settings['msg_array_limit']['outgoing']) {
|
||||
if (\count($this->session->outgoing_messages) > $this->session->API->settings->getRpc()->getLimitOutgoing()) {
|
||||
\reset($this->session->outgoing_messages);
|
||||
$key = \key($this->session->outgoing_messages);
|
||||
if (!isset($this->session->outgoing_messages[$key]['promise'])) {
|
||||
|
@ -89,7 +92,7 @@ class MsgIdHandler32 extends MsgIdHandler
|
|||
$this->session->API->logger->logger('WARNING: Given message id ('.$newMessageId.') is lower than or equal to the current limit ('.$key.'). Consider syncing your date.', \danog\MadelineProto\Logger::WARNING);
|
||||
}
|
||||
}
|
||||
if (\count($this->session->incoming_messages) > $this->session->API->settings['msg_array_limit']['incoming']) {
|
||||
if (\count($this->session->incoming_messages) > $this->session->API->settings->getRpc()->getLimitIncoming()) {
|
||||
\reset($this->session->incoming_messages);
|
||||
$key = \key($this->session->incoming_messages);
|
||||
if (!isset($this->session->incoming_messages[$key]['promise'])) {
|
||||
|
|
|
@ -65,7 +65,7 @@ class MsgIdHandler64 extends MsgIdHandler
|
|||
if ($newMessageId <= $this->maxOutgoingId) {
|
||||
throw new \danog\MadelineProto\Exception('Given message id ('.$newMessageId.') is lower than or equal to the current limit ('.$this->maxOutgoingId.'). Consider syncing your date.');
|
||||
}
|
||||
if (\count($this->session->outgoing_messages) > $this->session->API->settings['msg_array_limit']['outgoing']) {
|
||||
if (\count($this->session->outgoing_messages) > $this->session->API->settings->getRpc()->getLimitOutgoing()) {
|
||||
\reset($this->session->outgoing_messages);
|
||||
$key = \key($this->session->outgoing_messages);
|
||||
if (!isset($this->session->outgoing_messages[$key]['promise'])) {
|
||||
|
@ -88,7 +88,7 @@ class MsgIdHandler64 extends MsgIdHandler
|
|||
$this->session->API->logger->logger('WARNING: Given message id ('.$newMessageId.') is lower than or equal to the current limit ('.$key.'). Consider syncing your date.', \danog\MadelineProto\Logger::WARNING);
|
||||
}
|
||||
}
|
||||
if (\count($this->session->incoming_messages) > $this->session->API->settings['msg_array_limit']['incoming']) {
|
||||
if (\count($this->session->incoming_messages) > $this->session->API->settings->getRpc()->getLimitIncoming()) {
|
||||
\reset($this->session->incoming_messages);
|
||||
$key = \key($this->session->incoming_messages);
|
||||
if (!isset($this->session->incoming_messages[$key]['promise'])) {
|
||||
|
|
|
@ -336,7 +336,7 @@ trait ResponseHandler
|
|||
$datacenter .= '_media';
|
||||
}
|
||||
if (isset($request['user_related']) && $request['user_related']) {
|
||||
$this->API->settings['connection_settings']['default_dc'] = $this->API->authorized_dc = $this->API->datacenter->curdc;
|
||||
$this->API->settings->setDefaultDc($this->API->authorized_dc = $this->API->datacenter->curdc);
|
||||
}
|
||||
Loop::defer([$this, 'methodRecall'], ['message_id' => $request_id, 'datacenter' => $datacenter]);
|
||||
//$this->API->methodRecall('', ['message_id' => $request_id, 'datacenter' => $datacenter, 'postpone' => true]);
|
||||
|
@ -422,7 +422,7 @@ trait ResponseHandler
|
|||
return;
|
||||
case 420:
|
||||
$seconds = \preg_replace('/[^0-9]+/', '', $response['error_message']);
|
||||
$limit = $request['FloodWaitLimit'] ?? $this->API->settings['flood_timeout']['wait_if_lt'];
|
||||
$limit = $request['FloodWaitLimit'] ?? $this->API->settings->getRPC()->getFloodTimeout();
|
||||
if (\is_numeric($seconds) && $seconds < $limit) {
|
||||
//$this->gotResponseForOutgoingMessageId($request_id);
|
||||
$this->logger->logger('Flood, waiting '.$seconds.' seconds before repeating async call of '.($request['_'] ?? '').'...', \danog\MadelineProto\Logger::NOTICE);
|
||||
|
@ -449,6 +449,9 @@ trait ResponseHandler
|
|||
$this->shared->getTempAuthKey()->setServerSalt($response['new_server_salt']);
|
||||
$this->methodRecall('', ['message_id' => $request_id, 'postpone' => true]);
|
||||
return;
|
||||
case 20:
|
||||
$this->methodRecall('', ['message_id' => $request_id, 'postpone' => true]);
|
||||
return;
|
||||
case 16:
|
||||
case 17:
|
||||
$this->time_delta = (int) (new \tgseclib\Math\BigInteger(\strrev($response_id), 256))->bitwise_rightShift(32)->subtract(new \tgseclib\Math\BigInteger(\time()))->toString();
|
||||
|
@ -478,7 +481,7 @@ trait ResponseHandler
|
|||
if (isset($response['_']) && !$this->isCdn() && $this->API->getTL()->getConstructors()->findByPredicate($response['_'])['type'] === 'Updates') {
|
||||
$body = [];
|
||||
if (isset($request['body']['peer'])) {
|
||||
$body['peer'] = $this->API->getID($request['body']['peer']);
|
||||
$body['peer'] = \is_string($request['body']['peer']) ? $request['body']['peer'] : $this->API->getId($request['body']['peer']);
|
||||
}
|
||||
if (isset($request['body']['message'])) {
|
||||
$body['message'] = (string) $request['body']['message'];
|
||||
|
|
|
@ -21,6 +21,8 @@ namespace danog\MadelineProto\MTProtoSession;
|
|||
|
||||
/**
|
||||
* Manages MTProto session-specific data.
|
||||
*
|
||||
* @property MTProto $API
|
||||
*/
|
||||
abstract class Session
|
||||
{
|
||||
|
|
|
@ -25,7 +25,9 @@ use danog\MadelineProto\MTProto;
|
|||
use danog\MadelineProto\MTProto\AuthKey;
|
||||
use danog\MadelineProto\MTProto\PermAuthKey;
|
||||
use danog\MadelineProto\MTProto\TempAuthKey;
|
||||
use danog\MadelineProto\Settings;
|
||||
use danog\PrimeModule;
|
||||
|
||||
use tgseclib\Math\BigInteger;
|
||||
|
||||
/**
|
||||
|
@ -33,6 +35,8 @@ use tgseclib\Math\BigInteger;
|
|||
*
|
||||
* https://core.telegram.org/mtproto/auth_key
|
||||
* https://core.telegram.org/mtproto/samples-auth_key
|
||||
*
|
||||
* @property Settings $settings Settings
|
||||
*/
|
||||
trait AuthKeyHandler
|
||||
{
|
||||
|
@ -63,9 +67,9 @@ trait AuthKeyHandler
|
|||
$connection = $this->datacenter->getAuthConnection($datacenter);
|
||||
$cdn = $connection->isCDN();
|
||||
$req_pq = $cdn ? 'req_pq' : 'req_pq_multi';
|
||||
for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) {
|
||||
for ($retry_id_total = 1; $retry_id_total <= $this->settings->getAuth()->getMaxAuthTries(); $retry_id_total++) {
|
||||
try {
|
||||
$this->logger->logger(\danog\MadelineProto\Lang::$current_lang['req_pq'], \danog\MadelineProto\Logger::VERBOSE);
|
||||
$this->logger->logger("Requesting pq...", \danog\MadelineProto\Logger::VERBOSE);
|
||||
/**
|
||||
* ***********************************************************************
|
||||
* Make pq request, DH exchange initiation.
|
||||
|
@ -264,7 +268,7 @@ trait AuthKeyHandler
|
|||
$this->logger->logger(\sprintf('Server-client time delta = %.1f s', $connection->time_delta), \danog\MadelineProto\Logger::VERBOSE);
|
||||
$this->checkPG($dh_prime, $g);
|
||||
$this->checkG($g_a, $dh_prime);
|
||||
for ($retry_id = 0; $retry_id <= $this->settings['max_tries']['authorization']; $retry_id++) {
|
||||
for ($retry_id = 0; $retry_id <= $this->settings->getAuth()->getMaxAuthTries(); $retry_id++) {
|
||||
$this->logger->logger('Generating b...', \danog\MadelineProto\Logger::VERBOSE);
|
||||
$b = new BigInteger(\danog\MadelineProto\Tools::random(256), 256);
|
||||
$this->logger->logger('Generating g_b...', \danog\MadelineProto\Logger::VERBOSE);
|
||||
|
@ -510,7 +514,7 @@ trait AuthKeyHandler
|
|||
{
|
||||
$datacenterConnection = $this->datacenter->getDataCenterConnection($datacenter);
|
||||
$connection = $datacenterConnection->getAuthConnection();
|
||||
for ($retry_id_total = 1; $retry_id_total <= $this->settings['max_tries']['authorization']; $retry_id_total++) {
|
||||
for ($retry_id_total = 1; $retry_id_total <= $this->settings->getAuth()->getMaxAuthTries(); $retry_id_total++) {
|
||||
try {
|
||||
$this->logger->logger('Binding authorization keys...', \danog\MadelineProto\Logger::VERBOSE);
|
||||
$nonce = \danog\MadelineProto\Tools::random(8);
|
||||
|
@ -669,19 +673,19 @@ trait AuthKeyHandler
|
|||
return;
|
||||
}
|
||||
}
|
||||
if ($this->datacenter->getDataCenterConnection($id)->getSettings()['pfs']) {
|
||||
if ($this->getSettings()->getAuth()->getPfs()) {
|
||||
if (!$cdn) {
|
||||
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
|
||||
//$authorized = $socket->authorized;
|
||||
//$socket->authorized = false;
|
||||
$socket->setTempAuthKey(null);
|
||||
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
|
||||
yield from $this->bindTempAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id);
|
||||
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings->getAuth()->getDefaultTempAuthKeyExpiresIn(), $id));
|
||||
yield from $this->bindTempAuthKey($this->settings->getAuth()->getDefaultTempAuthKeyExpiresIn(), $id);
|
||||
$this->config = yield from $connection->methodCallAsyncRead('help.getConfig', []);
|
||||
yield from $this->syncAuthorization($id);
|
||||
} elseif (!$socket->hasTempAuthKey()) {
|
||||
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
|
||||
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
|
||||
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings->getAuth()->getDefaultTempAuthKeyExpiresIn(), $id));
|
||||
}
|
||||
} else {
|
||||
if (!$cdn) {
|
||||
|
@ -690,7 +694,7 @@ trait AuthKeyHandler
|
|||
yield from $this->syncAuthorization($id);
|
||||
} elseif (!$socket->hasTempAuthKey()) {
|
||||
$this->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['gen_temp_auth_key'], $id), \danog\MadelineProto\Logger::NOTICE);
|
||||
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings['authorization']['default_temp_auth_key_expires_in'], $id));
|
||||
$socket->setTempAuthKey(yield from $this->createAuthKey($this->settings->getAuth()->getDefaultTempAuthKeyExpiresIn(), $id));
|
||||
}
|
||||
}
|
||||
} elseif (!$cdn) {
|
||||
|
|
|
@ -19,8 +19,12 @@
|
|||
|
||||
namespace danog\MadelineProto\MTProtoTools;
|
||||
|
||||
use danog\MadelineProto\Settings;
|
||||
|
||||
/**
|
||||
* Manages method and object calls.
|
||||
*
|
||||
* @property Settings $settings Settings
|
||||
*/
|
||||
trait CallHandler
|
||||
{
|
||||
|
|
|
@ -19,93 +19,35 @@
|
|||
|
||||
namespace danog\MadelineProto\MTProtoTools;
|
||||
|
||||
use Amp\ByteStream\InputStream;
|
||||
use Amp\ByteStream\IteratorStream;
|
||||
use Amp\ByteStream\OutputStream;
|
||||
use Amp\ByteStream\ResourceInputStream;
|
||||
use Amp\ByteStream\ResourceOutputStream;
|
||||
use Amp\ByteStream\StreamException;
|
||||
use Amp\Deferred;
|
||||
use Amp\File\BlockingFile;
|
||||
use Amp\File\Handle;
|
||||
use Amp\File\StatCache as StatCacheAsync;
|
||||
use Amp\Http\Client\Request;
|
||||
use Amp\Http\Server\Request as ServerRequest;
|
||||
use Amp\Http\Server\Response;
|
||||
use Amp\Http\Status;
|
||||
use Amp\Producer;
|
||||
use Amp\Promise;
|
||||
use Amp\Success;
|
||||
use danog\MadelineProto\Exception;
|
||||
use danog\MadelineProto\FileCallbackInterface;
|
||||
use danog\MadelineProto\Stream\Common\BufferedRawStream;
|
||||
use danog\MadelineProto\Stream\Common\SimpleBufferedRawStream;
|
||||
use danog\MadelineProto\Stream\ConnectionContext;
|
||||
use danog\MadelineProto\Stream\Transport\PremadeStream;
|
||||
use danog\MadelineProto\Settings;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
use tgseclib\Crypt\AES;
|
||||
|
||||
use const danog\Decoder\TYPES;
|
||||
|
||||
use function Amp\File\exists;
|
||||
use function Amp\File\open;
|
||||
use function Amp\File\stat as statAsync;
|
||||
|
||||
use function Amp\Promise\all;
|
||||
|
||||
/**
|
||||
* Manages upload and download of files.
|
||||
*
|
||||
* @property Settings $settings Settings
|
||||
*/
|
||||
trait Files
|
||||
{
|
||||
/**
|
||||
* Upload file.
|
||||
*
|
||||
* @param FileCallbackInterface|string|array $file File, URL or Telegram file to upload
|
||||
* @param string $fileName File name
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
* @param boolean $encrypted Whether to encrypt file for secret chats
|
||||
*
|
||||
* @return \Generator<array>
|
||||
*/
|
||||
public function upload($file, string $fileName = '', $cb = null, bool $encrypted = false): \Generator
|
||||
{
|
||||
if (\is_object($file) && $file instanceof FileCallbackInterface) {
|
||||
$cb = $file;
|
||||
$file = $file->getFile();
|
||||
}
|
||||
if (\is_string($file) || \is_object($file) && \method_exists($file, '__toString')) {
|
||||
if (\filter_var($file, FILTER_VALIDATE_URL)) {
|
||||
return yield from $this->uploadFromUrl($file, 0, $fileName, $cb, $encrypted);
|
||||
}
|
||||
} elseif (\is_array($file)) {
|
||||
return yield from $this->uploadFromTgfile($file, $cb, $encrypted);
|
||||
}
|
||||
if (\is_resource($file) || (\is_object($file) && $file instanceof InputStream)) {
|
||||
return yield from $this->uploadFromStream($file, 0, '', $fileName, $cb, $encrypted);
|
||||
}
|
||||
if (!$this->settings['upload']['allow_automatic_upload']) {
|
||||
return yield from $this->uploadFromUrl($file, 0, $fileName, $cb, $encrypted);
|
||||
}
|
||||
$file = Tools::absolute($file);
|
||||
if (!yield exists($file)) {
|
||||
throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['file_not_exist']);
|
||||
}
|
||||
if (empty($fileName)) {
|
||||
$fileName = \basename($file);
|
||||
}
|
||||
StatCacheAsync::clear($file);
|
||||
$size = (yield statAsync($file))['size'];
|
||||
if ($size > 512 * 1024 * 4000) {
|
||||
throw new \danog\MadelineProto\Exception('Given file is too big!');
|
||||
}
|
||||
$stream = yield open($file, 'rb');
|
||||
$mime = $this->getMimeFromFile($file);
|
||||
try {
|
||||
return yield from $this->uploadFromStream($stream, $size, $mime, $fileName, $cb, $encrypted);
|
||||
} finally {
|
||||
yield $stream->close();
|
||||
}
|
||||
}
|
||||
use FilesLogic;
|
||||
/**
|
||||
* Upload file from URL.
|
||||
*
|
||||
|
@ -149,90 +91,6 @@ trait Files
|
|||
}
|
||||
return yield from $this->uploadFromStream($stream, $size, $mime, $fileName, $cb, $encrypted);
|
||||
}
|
||||
/**
|
||||
* Upload file from stream.
|
||||
*
|
||||
* @param mixed $stream PHP resource or AMPHP async stream
|
||||
* @param integer $size File size
|
||||
* @param string $mime Mime type
|
||||
* @param string $fileName File name
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
* @param boolean $encrypted Whether to encrypt file for secret chats
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function uploadFromStream($stream, int $size, string $mime, string $fileName = '', $cb = null, bool $encrypted = false): \Generator
|
||||
{
|
||||
if (\is_object($stream) && $stream instanceof FileCallbackInterface) {
|
||||
$cb = $stream;
|
||||
$stream = $stream->getFile();
|
||||
}
|
||||
/* @var $stream \Amp\ByteStream\OutputStream */
|
||||
if (!\is_object($stream)) {
|
||||
$stream = new ResourceInputStream($stream);
|
||||
}
|
||||
if (!$stream instanceof InputStream) {
|
||||
throw new Exception("Invalid stream provided");
|
||||
}
|
||||
$seekable = false;
|
||||
if (\method_exists($stream, 'seek')) {
|
||||
try {
|
||||
yield $stream->seek(0);
|
||||
$seekable = true;
|
||||
} catch (StreamException $e) {
|
||||
}
|
||||
}
|
||||
$created = false;
|
||||
if ($stream instanceof Handle) {
|
||||
$callable = static function (int $offset, int $size) use ($stream, $seekable): \Generator {
|
||||
if ($seekable) {
|
||||
while ($stream->tell() !== $offset) {
|
||||
yield $stream->seek($offset);
|
||||
}
|
||||
}
|
||||
return yield $stream->read($size);
|
||||
};
|
||||
} else {
|
||||
if (!$stream instanceof BufferedRawStream) {
|
||||
$ctx = (new ConnectionContext())->addStream(PremadeStream::getName(), $stream)->addStream(SimpleBufferedRawStream::getName());
|
||||
$stream = (yield from $ctx->getStream());
|
||||
$created = true;
|
||||
}
|
||||
$callable = static function (int $offset, int $size) use ($stream): \Generator {
|
||||
$reader = yield $stream->getReadBuffer($l);
|
||||
try {
|
||||
return yield $reader->bufferRead($size);
|
||||
} catch (\danog\MadelineProto\NothingInTheSocketException $e) {
|
||||
$reader = yield $stream->getReadBuffer($size);
|
||||
return yield $reader->bufferRead($size);
|
||||
}
|
||||
};
|
||||
$seekable = false;
|
||||
}
|
||||
if (!$size && $seekable && \method_exists($stream, 'tell')) {
|
||||
yield $stream->seek(0, \SEEK_END);
|
||||
$size = yield $stream->tell();
|
||||
yield $stream->seek(0);
|
||||
} elseif (!$size) {
|
||||
$this->logger->logger("No content length for stream, caching first");
|
||||
$body = $stream;
|
||||
$stream = new BlockingFile(\fopen('php://temp', 'r+b'), 'php://temp', 'r+b');
|
||||
while (null !== ($chunk = yield $body->read())) {
|
||||
yield $stream->write($chunk);
|
||||
}
|
||||
$size = $stream->tell();
|
||||
if (!$size) {
|
||||
throw new Exception('Wrong size!');
|
||||
}
|
||||
yield $stream->seek(0);
|
||||
return yield from $this->uploadFromStream($stream, $size, $mime, $fileName, $cb, $encrypted);
|
||||
}
|
||||
$res = (yield from $this->uploadFromCallable($callable, $size, $mime, $fileName, $cb, $seekable, $encrypted));
|
||||
if ($created) {
|
||||
$stream->disconnect();
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
/**
|
||||
* Upload file from callable.
|
||||
*
|
||||
|
@ -263,12 +121,12 @@ trait Files
|
|||
$this->logger->logger('Upload status: '.$percent.'%', \danog\MadelineProto\Logger::NOTICE);
|
||||
};
|
||||
}
|
||||
$datacenter = $this->settings['connection_settings']['default_dc'];
|
||||
$datacenter = $this->settings->getDefaultDc();
|
||||
if ($this->datacenter->has($datacenter.'_media')) {
|
||||
$datacenter .= '_media';
|
||||
}
|
||||
$part_size = $this->settings['upload']['part_size'];
|
||||
$parallel_chunks = $this->settings['upload']['parallel_chunks'] ?? 4000;
|
||||
$part_size = 512 * 1024;
|
||||
$parallel_chunks = $this->settings->getFiles()->getUploadParallelChunks();
|
||||
$part_total_num = (int) \ceil($size / $part_size);
|
||||
$part_num = 0;
|
||||
$method = $size > 10 * 1024 * 1024 ? 'upload.saveBigFilePart' : 'upload.saveFilePart';
|
||||
|
@ -361,19 +219,6 @@ trait Files
|
|||
//\hash_final($ctx);
|
||||
return $constructor;
|
||||
}
|
||||
/**
|
||||
* Upload file to secret chat.
|
||||
*
|
||||
* @param FileCallbackInterface|string|array $file File, URL or Telegram file to upload
|
||||
* @param string $fileName File name
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
*
|
||||
* @return \Generator<array>
|
||||
*/
|
||||
public function uploadEncrypted($file, string $fileName = '', $cb = null): \Generator
|
||||
{
|
||||
return $this->upload($file, $fileName, $cb, true);
|
||||
}
|
||||
/**
|
||||
* Reupload telegram file.
|
||||
*
|
||||
|
@ -395,7 +240,7 @@ trait Files
|
|||
}
|
||||
$size = $media['size'];
|
||||
$mime = $media['mime'];
|
||||
$chunk_size = $this->settings['upload']['part_size'];
|
||||
$chunk_size = 512 * 1024;
|
||||
$bridge = new class($size, $chunk_size, $cb) {
|
||||
/**
|
||||
* Read promises.
|
||||
|
@ -903,106 +748,6 @@ trait Files
|
|||
throw new \danog\MadelineProto\Exception('Invalid constructor provided: '.$messageMedia['_']);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Download file to browser.
|
||||
*
|
||||
* Supports HEAD requests and content-ranges for parallel and resumed downloads.
|
||||
*
|
||||
* @param array|string $messageMedia File to download
|
||||
* @param callable $cb Status callback (can also use FileCallback)
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function downloadToBrowser($messageMedia, callable $cb = null): \Generator
|
||||
{
|
||||
if (\is_object($messageMedia) && $messageMedia instanceof FileCallbackInterface) {
|
||||
$cb = $messageMedia;
|
||||
$messageMedia = $messageMedia->getFile();
|
||||
}
|
||||
|
||||
$headers = [];
|
||||
if (isset($_SERVER['HTTP_RANGE'])) {
|
||||
$headers['range'] = $_SERVER['HTTP_RANGE'];
|
||||
}
|
||||
|
||||
$messageMedia = yield from $this->getDownloadInfo($messageMedia);
|
||||
$result = ResponseInfo::parseHeaders(
|
||||
$_SERVER['REQUEST_METHOD'],
|
||||
$headers,
|
||||
$messageMedia
|
||||
);
|
||||
|
||||
foreach ($result->getHeaders() as $key => $value) {
|
||||
if (\is_array($value)) {
|
||||
foreach ($value as $subValue) {
|
||||
\header("$key: $subValue", false);
|
||||
}
|
||||
} else {
|
||||
\header("$key: $value");
|
||||
}
|
||||
}
|
||||
\http_response_code($result->getCode());
|
||||
|
||||
if (!\in_array($result->getCode(), [Status::OK, Status::PARTIAL_CONTENT])) {
|
||||
yield Tools::echo($result->getCodeExplanation());
|
||||
} elseif ($result->shouldServe()) {
|
||||
if (\ob_get_level()) {
|
||||
\ob_end_flush();
|
||||
\ob_implicit_flush();
|
||||
}
|
||||
yield from $this->downloadToStream($messageMedia, \fopen('php://output', 'w'), $cb, ...$result->getServeRange());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Download file to amphp/http-server response.
|
||||
*
|
||||
* Supports HEAD requests and content-ranges for parallel and resumed downloads.
|
||||
*
|
||||
* @param array|string $messageMedia File to download
|
||||
* @param ServerRequest $request Request
|
||||
* @param callable $cb Status callback (can also use FileCallback)
|
||||
*
|
||||
* @return \Generator<Response> Returned response
|
||||
*/
|
||||
public function downloadToResponse($messageMedia, ServerRequest $request, callable $cb = null): \Generator
|
||||
{
|
||||
if (\is_object($messageMedia) && $messageMedia instanceof FileCallbackInterface) {
|
||||
$cb = $messageMedia;
|
||||
$messageMedia = $messageMedia->getFile();
|
||||
}
|
||||
|
||||
$messageMedia = yield from $this->getDownloadInfo($messageMedia);
|
||||
|
||||
$result = ResponseInfo::parseHeaders(
|
||||
$request->getMethod(),
|
||||
\array_map(fn (array $headers) => $headers[0], $request->getHeaders()),
|
||||
$messageMedia
|
||||
);
|
||||
|
||||
$body = null;
|
||||
if ($result->shouldServe()) {
|
||||
$body = new IteratorStream(
|
||||
new Producer(
|
||||
function (callable $emit) use (&$messageMedia, &$cb, &$result) {
|
||||
$emit = static function (string $payload) use ($emit): \Generator {
|
||||
yield $emit($payload);
|
||||
return \strlen($payload);
|
||||
};
|
||||
yield Tools::call($this->downloadToCallable($messageMedia, $emit, $cb, false, ...$result->getServeRange()));
|
||||
}
|
||||
)
|
||||
);
|
||||
} elseif (!\in_array($result->getCode(), [Status::OK, Status::PARTIAL_CONTENT])) {
|
||||
$body = $result->getCodeExplanation();
|
||||
}
|
||||
|
||||
$response = new Response($result->getCode(), $result->getHeaders(), $body);
|
||||
if ($result->shouldServe() && !empty($result->getHeaders()['Content-Length'])) {
|
||||
$response->setHeader('content-length', $result->getHeaders()['Content-Length']);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
/**
|
||||
* Download file to directory.
|
||||
*
|
||||
|
@ -1057,49 +802,6 @@ trait Files
|
|||
}
|
||||
return $file;
|
||||
}
|
||||
/**
|
||||
* Download file to stream.
|
||||
*
|
||||
* @param mixed $messageMedia File to download
|
||||
* @param mixed|FileCallbackInterface $stream Stream where to download file
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
* @param int $offset Offset where to start downloading
|
||||
* @param int $end Offset where to end download
|
||||
*
|
||||
* @return \Generator<bool>
|
||||
*/
|
||||
public function downloadToStream($messageMedia, $stream, $cb = null, int $offset = 0, int $end = -1): \Generator
|
||||
{
|
||||
$messageMedia = (yield from $this->getDownloadInfo($messageMedia));
|
||||
if (\is_object($stream) && $stream instanceof FileCallbackInterface) {
|
||||
$cb = $stream;
|
||||
$stream = $stream->getFile();
|
||||
}
|
||||
/** @var $stream \Amp\ByteStream\OutputStream */
|
||||
if (!\is_object($stream)) {
|
||||
$stream = new ResourceOutputStream($stream);
|
||||
}
|
||||
if (!$stream instanceof OutputStream) {
|
||||
throw new Exception("Invalid stream provided");
|
||||
}
|
||||
$seekable = false;
|
||||
if (\method_exists($stream, 'seek')) {
|
||||
try {
|
||||
yield $stream->seek($offset);
|
||||
$seekable = true;
|
||||
} catch (StreamException $e) {
|
||||
}
|
||||
}
|
||||
$callable = static function (string $payload, int $offset) use ($stream, $seekable): \Generator {
|
||||
if ($seekable) {
|
||||
while ($stream->tell() !== $offset) {
|
||||
yield $stream->seek($offset);
|
||||
}
|
||||
}
|
||||
return yield $stream->write($payload);
|
||||
};
|
||||
return yield from $this->downloadToCallable($messageMedia, $callable, $cb, $seekable, $offset, $end);
|
||||
}
|
||||
/**
|
||||
* Download file to callable.
|
||||
* The callable must accept two parameters: string $payload, int $offset
|
||||
|
@ -1134,9 +836,9 @@ trait Files
|
|||
if ($end === -1 && isset($messageMedia['size'])) {
|
||||
$end = $messageMedia['size'];
|
||||
}
|
||||
$part_size = $part_size ?? $this->settings['download']['part_size'];
|
||||
$parallel_chunks = $this->settings['download']['parallel_chunks'] ?? 4000;
|
||||
$datacenter = $messageMedia['InputFileLocation']['dc_id'] ?? $this->settings['connection_settings']['default_dc'];
|
||||
$part_size = $part_size ?? 1024 * 1024;
|
||||
$parallel_chunks = $this->settings->getFiles()->getDownloadParallelChunks();
|
||||
$datacenter = $messageMedia['InputFileLocation']['dc_id'] ?? $this->settings->getDefaultDc();
|
||||
if ($this->datacenter->has($datacenter.'_media')) {
|
||||
$datacenter .= '_media';
|
||||
}
|
||||
|
|
|
@ -0,0 +1,328 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\MTProtoTools;
|
||||
|
||||
use Amp\ByteStream\InputStream;
|
||||
use Amp\ByteStream\IteratorStream;
|
||||
use Amp\ByteStream\OutputStream;
|
||||
use Amp\ByteStream\ResourceInputStream;
|
||||
use Amp\ByteStream\ResourceOutputStream;
|
||||
use Amp\ByteStream\StreamException;
|
||||
use Amp\File\BlockingFile;
|
||||
|
||||
use Amp\File\Handle;
|
||||
use Amp\File\StatCache as StatCacheAsync;
|
||||
use Amp\Http\Client\Request;
|
||||
use Amp\Http\Server\Request as ServerRequest;
|
||||
use Amp\Http\Server\Response;
|
||||
use Amp\Http\Status;
|
||||
use Amp\Producer;
|
||||
use danog\MadelineProto\Exception;
|
||||
use danog\MadelineProto\FileCallbackInterface;
|
||||
use danog\MadelineProto\Stream\Common\BufferedRawStream;
|
||||
use danog\MadelineProto\Stream\Common\SimpleBufferedRawStream;
|
||||
use danog\MadelineProto\Stream\ConnectionContext;
|
||||
use danog\MadelineProto\Stream\Transport\PremadeStream;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
|
||||
use function Amp\File\exists;
|
||||
use function Amp\File\open;
|
||||
use function Amp\File\stat as statAsync;
|
||||
|
||||
trait FilesLogic
|
||||
{
|
||||
/**
|
||||
* Download file to browser.
|
||||
*
|
||||
* Supports HEAD requests and content-ranges for parallel and resumed downloads.
|
||||
*
|
||||
* @param array|string $messageMedia File to download
|
||||
* @param callable $cb Status callback (can also use FileCallback)
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function downloadToBrowser($messageMedia, callable $cb = null): \Generator
|
||||
{
|
||||
if (\is_object($messageMedia) && $messageMedia instanceof FileCallbackInterface) {
|
||||
$cb = $messageMedia;
|
||||
$messageMedia = $messageMedia->getFile();
|
||||
}
|
||||
|
||||
$headers = [];
|
||||
if (isset($_SERVER['HTTP_RANGE'])) {
|
||||
$headers['range'] = $_SERVER['HTTP_RANGE'];
|
||||
}
|
||||
|
||||
$messageMedia = yield from $this->getDownloadInfo($messageMedia);
|
||||
$result = ResponseInfo::parseHeaders(
|
||||
$_SERVER['REQUEST_METHOD'],
|
||||
$headers,
|
||||
$messageMedia
|
||||
);
|
||||
|
||||
foreach ($result->getHeaders() as $key => $value) {
|
||||
if (\is_array($value)) {
|
||||
foreach ($value as $subValue) {
|
||||
\header("$key: $subValue", false);
|
||||
}
|
||||
} else {
|
||||
\header("$key: $value");
|
||||
}
|
||||
}
|
||||
\http_response_code($result->getCode());
|
||||
|
||||
if (!\in_array($result->getCode(), [Status::OK, Status::PARTIAL_CONTENT])) {
|
||||
yield Tools::echo($result->getCodeExplanation());
|
||||
} elseif ($result->shouldServe()) {
|
||||
if (\ob_get_level()) {
|
||||
\ob_end_flush();
|
||||
\ob_implicit_flush();
|
||||
}
|
||||
yield from $this->downloadToStream($messageMedia, \fopen('php://output', 'w'), $cb, ...$result->getServeRange());
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Download file to stream.
|
||||
*
|
||||
* @param mixed $messageMedia File to download
|
||||
* @param mixed|FileCallbackInterface $stream Stream where to download file
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
* @param int $offset Offset where to start downloading
|
||||
* @param int $end Offset where to end download
|
||||
*
|
||||
* @return \Generator<bool>
|
||||
*/
|
||||
public function downloadToStream($messageMedia, $stream, $cb = null, int $offset = 0, int $end = -1): \Generator
|
||||
{
|
||||
$messageMedia = yield from $this->getDownloadInfo($messageMedia);
|
||||
if (\is_object($stream) && $stream instanceof FileCallbackInterface) {
|
||||
$cb = $stream;
|
||||
$stream = $stream->getFile();
|
||||
}
|
||||
/** @var $stream \Amp\ByteStream\OutputStream */
|
||||
if (!\is_object($stream)) {
|
||||
$stream = new ResourceOutputStream($stream);
|
||||
}
|
||||
if (!$stream instanceof OutputStream) {
|
||||
throw new Exception("Invalid stream provided");
|
||||
}
|
||||
$seekable = false;
|
||||
if (\method_exists($stream, 'seek')) {
|
||||
try {
|
||||
yield $stream->seek($offset);
|
||||
$seekable = true;
|
||||
} catch (StreamException $e) {
|
||||
}
|
||||
}
|
||||
$callable = static function (string $payload, int $offset) use ($stream, $seekable): \Generator {
|
||||
if ($seekable) {
|
||||
while ($stream->tell() !== $offset) {
|
||||
yield $stream->seek($offset);
|
||||
}
|
||||
}
|
||||
return yield $stream->write($payload);
|
||||
};
|
||||
return yield from $this->downloadToCallable($messageMedia, $callable, $cb, $seekable, $offset, $end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download file to amphp/http-server response.
|
||||
*
|
||||
* Supports HEAD requests and content-ranges for parallel and resumed downloads.
|
||||
*
|
||||
* @param array|string $messageMedia File to download
|
||||
* @param ServerRequest $request Request
|
||||
* @param callable $cb Status callback (can also use FileCallback)
|
||||
*
|
||||
* @return \Generator<Response> Returned response
|
||||
*/
|
||||
public function downloadToResponse($messageMedia, ServerRequest $request, callable $cb = null): \Generator
|
||||
{
|
||||
if (\is_object($messageMedia) && $messageMedia instanceof FileCallbackInterface) {
|
||||
$cb = $messageMedia;
|
||||
$messageMedia = $messageMedia->getFile();
|
||||
}
|
||||
|
||||
$messageMedia = yield from $this->getDownloadInfo($messageMedia);
|
||||
|
||||
$result = ResponseInfo::parseHeaders(
|
||||
$request->getMethod(),
|
||||
\array_map(fn (array $headers) => $headers[0], $request->getHeaders()),
|
||||
$messageMedia
|
||||
);
|
||||
|
||||
$body = null;
|
||||
if ($result->shouldServe()) {
|
||||
$body = new IteratorStream(
|
||||
new Producer(
|
||||
function (callable $emit) use (&$messageMedia, &$cb, &$result) {
|
||||
$emit = static function (string $payload) use ($emit): \Generator {
|
||||
yield $emit($payload);
|
||||
return \strlen($payload);
|
||||
};
|
||||
yield Tools::call($this->downloadToCallable($messageMedia, $emit, $cb, false, ...$result->getServeRange()));
|
||||
}
|
||||
)
|
||||
);
|
||||
} elseif (!\in_array($result->getCode(), [Status::OK, Status::PARTIAL_CONTENT])) {
|
||||
$body = $result->getCodeExplanation();
|
||||
}
|
||||
|
||||
$response = new Response($result->getCode(), $result->getHeaders(), $body);
|
||||
if ($result->shouldServe() && !empty($result->getHeaders()['Content-Length'])) {
|
||||
$response->setHeader('content-length', $result->getHeaders()['Content-Length']);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file to secret chat.
|
||||
*
|
||||
* @param FileCallbackInterface|string|array $file File, URL or Telegram file to upload
|
||||
* @param string $fileName File name
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
*
|
||||
* @return \Generator<array>
|
||||
*/
|
||||
public function uploadEncrypted($file, string $fileName = '', $cb = null): \Generator
|
||||
{
|
||||
return $this->upload($file, $fileName, $cb, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file.
|
||||
*
|
||||
* @param FileCallbackInterface|string|array $file File, URL or Telegram file to upload
|
||||
* @param string $fileName File name
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
* @param boolean $encrypted Whether to encrypt file for secret chats
|
||||
*
|
||||
* @return \Generator<array>
|
||||
*/
|
||||
public function upload($file, string $fileName = '', $cb = null, bool $encrypted = false): \Generator
|
||||
{
|
||||
if (\is_object($file) && $file instanceof FileCallbackInterface) {
|
||||
$cb = $file;
|
||||
$file = $file->getFile();
|
||||
}
|
||||
if (\is_string($file) || \is_object($file) && \method_exists($file, '__toString')) {
|
||||
if (\filter_var($file, FILTER_VALIDATE_URL)) {
|
||||
return yield from $this->uploadFromUrl($file, 0, $fileName, $cb, $encrypted);
|
||||
}
|
||||
} elseif (\is_array($file)) {
|
||||
return yield from $this->uploadFromTgfile($file, $cb, $encrypted);
|
||||
}
|
||||
if (\is_resource($file) || (\is_object($file) && $file instanceof InputStream)) {
|
||||
return yield from $this->uploadFromStream($file, 0, '', $fileName, $cb, $encrypted);
|
||||
}
|
||||
if (!$this->settings->getFiles()->getAllowAutomaticUpload()) {
|
||||
return yield from $this->uploadFromUrl($file, 0, $fileName, $cb, $encrypted);
|
||||
}
|
||||
$file = Tools::absolute($file);
|
||||
if (!yield exists($file)) {
|
||||
throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['file_not_exist']);
|
||||
}
|
||||
if (empty($fileName)) {
|
||||
$fileName = \basename($file);
|
||||
}
|
||||
StatCacheAsync::clear($file);
|
||||
$size = (yield statAsync($file))['size'];
|
||||
if ($size > 512 * 1024 * 4000) {
|
||||
throw new \danog\MadelineProto\Exception('Given file is too big!');
|
||||
}
|
||||
$stream = yield open($file, 'rb');
|
||||
$mime = $this->getMimeFromFile($file);
|
||||
try {
|
||||
return yield from $this->uploadFromStream($stream, $size, $mime, $fileName, $cb, $encrypted);
|
||||
} finally {
|
||||
yield $stream->close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file from stream.
|
||||
*
|
||||
* @param mixed $stream PHP resource or AMPHP async stream
|
||||
* @param integer $size File size
|
||||
* @param string $mime Mime type
|
||||
* @param string $fileName File name
|
||||
* @param callable $cb Callback (DEPRECATED, use FileCallbackInterface)
|
||||
* @param boolean $encrypted Whether to encrypt file for secret chats
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function uploadFromStream($stream, int $size, string $mime, string $fileName = '', $cb = null, bool $encrypted = false): \Generator
|
||||
{
|
||||
if (\is_object($stream) && $stream instanceof FileCallbackInterface) {
|
||||
$cb = $stream;
|
||||
$stream = $stream->getFile();
|
||||
}
|
||||
/* @var $stream \Amp\ByteStream\OutputStream */
|
||||
if (!\is_object($stream)) {
|
||||
$stream = new ResourceInputStream($stream);
|
||||
}
|
||||
if (!$stream instanceof InputStream) {
|
||||
throw new Exception("Invalid stream provided");
|
||||
}
|
||||
$seekable = false;
|
||||
if (\method_exists($stream, 'seek')) {
|
||||
try {
|
||||
yield $stream->seek(0);
|
||||
$seekable = true;
|
||||
} catch (StreamException $e) {
|
||||
}
|
||||
}
|
||||
$created = false;
|
||||
if ($stream instanceof Handle) {
|
||||
$callable = static function (int $offset, int $size) use ($stream, $seekable): \Generator {
|
||||
if ($seekable) {
|
||||
while ($stream->tell() !== $offset) {
|
||||
yield $stream->seek($offset);
|
||||
}
|
||||
}
|
||||
return yield $stream->read($size);
|
||||
};
|
||||
} else {
|
||||
if (!$stream instanceof BufferedRawStream) {
|
||||
$ctx = (new ConnectionContext())->addStream(PremadeStream::class, $stream)->addStream(SimpleBufferedRawStream::class);
|
||||
$stream = (yield from $ctx->getStream());
|
||||
$created = true;
|
||||
}
|
||||
$callable = static function (int $offset, int $size) use ($stream): \Generator {
|
||||
$reader = yield $stream->getReadBuffer($l);
|
||||
try {
|
||||
return yield $reader->bufferRead($size);
|
||||
} catch (\danog\MadelineProto\NothingInTheSocketException $e) {
|
||||
$reader = yield $stream->getReadBuffer($size);
|
||||
return yield $reader->bufferRead($size);
|
||||
}
|
||||
};
|
||||
$seekable = false;
|
||||
}
|
||||
if (!$size && $seekable && \method_exists($stream, 'tell')) {
|
||||
yield $stream->seek(0, \SEEK_END);
|
||||
$size = yield $stream->tell();
|
||||
yield $stream->seek(0);
|
||||
} elseif (!$size) {
|
||||
$this->logger->logger("No content length for stream, caching first");
|
||||
$body = $stream;
|
||||
$stream = new BlockingFile(\fopen('php://temp', 'r+b'), 'php://temp', 'r+b');
|
||||
while (null !== ($chunk = yield $body->read())) {
|
||||
yield $stream->write($chunk);
|
||||
}
|
||||
$size = $stream->tell();
|
||||
if (!$size) {
|
||||
throw new Exception('Wrong size!');
|
||||
}
|
||||
yield $stream->seek(0);
|
||||
return yield from $this->uploadFromStream($stream, $size, $mime, $fileName, $cb, $encrypted);
|
||||
}
|
||||
$res = (yield from $this->uploadFromCallable($callable, $size, $mime, $fileName, $cb, $seekable, $encrypted));
|
||||
if ($created) {
|
||||
$stream->disconnect();
|
||||
}
|
||||
return $res;
|
||||
}
|
||||
}
|
|
@ -23,11 +23,14 @@ use Amp\Http\Client\Request;
|
|||
use danog\Decoder\FileId;
|
||||
use danog\Decoder\PhotoSizeSource\PhotoSizeSourceDialogPhoto;
|
||||
use danog\MadelineProto\Db\DbArray;
|
||||
use danog\MadelineProto\Settings;
|
||||
|
||||
use const danog\Decoder\PROFILE_PHOTO;
|
||||
|
||||
/**
|
||||
* Manages peers.
|
||||
*
|
||||
* @property Settings $settings Settings
|
||||
*/
|
||||
trait PeerHandler
|
||||
{
|
||||
|
@ -127,7 +130,7 @@ trait PeerHandler
|
|||
$user['access_hash'] = $existingChat['access_hash'];
|
||||
}
|
||||
}
|
||||
$this->chats[$user['id']] = $user;
|
||||
yield $this->chats->offsetSet($user['id'], $user);
|
||||
$this->cachePwrChat($user['id'], false, true);
|
||||
}
|
||||
$this->cacheChatUsername($user['id'], $user);
|
||||
|
@ -156,8 +159,8 @@ trait PeerHandler
|
|||
$existingChat = yield $this->chats[-$chat['id']];
|
||||
if (!$existingChat || $existingChat !== $chat) {
|
||||
$this->logger->logger("Updated chat -{$chat['id']}", \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
$this->chats[-$chat['id']] = $chat;
|
||||
$this->cachePwrChat(-$chat['id'], $this->settings['peer']['full_fetch'], true);
|
||||
yield $this->chats->offsetSet(-$chat['id'], $chat);
|
||||
$this->cachePwrChat(-$chat['id'], $this->getSettings()->getPeer()->getFullFetch(), true);
|
||||
}
|
||||
$this->cacheChatUsername(-$chat['id'], $chat);
|
||||
break;
|
||||
|
@ -194,10 +197,10 @@ trait PeerHandler
|
|||
}
|
||||
$chat = $newchat;
|
||||
}
|
||||
$this->chats[$bot_api_id] = $chat;
|
||||
yield $this->chats->offsetSet($bot_api_id, $chat);
|
||||
$fullChat = yield $this->full_chats[$bot_api_id];
|
||||
if ($this->settings['peer']['full_fetch'] && (!$fullChat || $fullChat['full']['participants_count'] !== (yield from $this->getFullInfo($bot_api_id))['full']['participants_count'])) {
|
||||
$this->cachePwrChat($bot_api_id, $this->settings['peer']['full_fetch'], true);
|
||||
if ($this->getSettings()->getPeer()->getFullFetch() && (!$fullChat || $fullChat['full']['participants_count'] !== (yield from $this->getFullInfo($bot_api_id))['full']['participants_count'])) {
|
||||
$this->cachePwrChat($bot_api_id, $this->getSettings()->getPeer()->getFullFetch(), true);
|
||||
}
|
||||
}
|
||||
$this->cacheChatUsername($bot_api_id, $chat);
|
||||
|
@ -545,7 +548,7 @@ trait PeerHandler
|
|||
}
|
||||
}
|
||||
}
|
||||
if (!isset($this->settings['pwr']['requests']) || $this->settings['pwr']['requests'] === true && $recursive) {
|
||||
if ($this->settings->getPwr()->getRequests() && $recursive) {
|
||||
$dbres = [];
|
||||
try {
|
||||
$dbres = \json_decode(yield from $this->datacenter->fileGetContents('https://id.pwrtelegram.xyz/db/getusername?id='.$id), true);
|
||||
|
@ -582,7 +585,7 @@ trait PeerHandler
|
|||
}
|
||||
if ($id === 'support') {
|
||||
if (!$this->supportUser) {
|
||||
yield from $this->methodCallAsyncRead('help.getSupport', [], ['datacenter' => $this->settings['connection_settings']['default_dc']]);
|
||||
yield from $this->methodCallAsyncRead('help.getSupport', [], $this->settings->getDefaultDcParams());
|
||||
}
|
||||
return yield from $this->getInfo($this->supportUser);
|
||||
}
|
||||
|
@ -704,7 +707,7 @@ trait PeerHandler
|
|||
public function getFullInfo($id): \Generator
|
||||
{
|
||||
$partial = (yield from $this->getInfo($id));
|
||||
if (\time() - (yield from $this->fullChatLastUpdated($partial['bot_api_id'])) < (isset($this->settings['peer']['full_info_cache_time']) ? $this->settings['peer']['full_info_cache_time'] : 0)) {
|
||||
if (\time() - (yield from $this->fullChatLastUpdated($partial['bot_api_id'])) < $this->getSettings()->getPeer()->getFullInfoCacheTime()) {
|
||||
return \array_merge($partial, yield $this->full_chats[$partial['bot_api_id']]);
|
||||
}
|
||||
switch ($partial['type']) {
|
||||
|
@ -1008,8 +1011,7 @@ trait PeerHandler
|
|||
}
|
||||
private function storeDb($res, $force = false): \Generator
|
||||
{
|
||||
$settings = isset($this->settings['connection_settings'][$this->datacenter->curdc]) ? $this->settings['connection_settings'][$this->datacenter->curdc] : $this->settings['connection_settings']['all'];
|
||||
if (!isset($this->settings['pwr']) || $this->settings['pwr']['pwr'] === false || $settings['test_mode']) {
|
||||
if (!$this->settings->getPwr()->getDbToken() || $this->settings->getConnection()->getTestMode()) {
|
||||
return;
|
||||
}
|
||||
if (!empty($res)) {
|
||||
|
@ -1030,7 +1032,7 @@ trait PeerHandler
|
|||
//$path = '/tmp/ids'.hash('sha256', $payload);
|
||||
//file_put_contents($path, $payload);
|
||||
$id = isset($this->authorization['user']['username']) ? $this->authorization['user']['username'] : $this->authorization['user']['id'];
|
||||
$request = new Request('https://id.pwrtelegram.xyz/db'.$this->settings['pwr']['db_token'].'/addnewmadeline?d=pls&from='.$id, 'POST');
|
||||
$request = new Request('https://id.pwrtelegram.xyz/db'.$this->settings->getPwr()->getDbToken().'/addnewmadeline?d=pls&from='.$id, 'POST');
|
||||
$request->setHeader('content-type', 'application/json');
|
||||
$request->setBody($payload);
|
||||
$result = yield (yield $this->datacenter->getHTTPClient()->request($request))->getBody()->buffer();
|
||||
|
@ -1050,9 +1052,13 @@ trait PeerHandler
|
|||
*/
|
||||
public function resolveUsername(string $username): \Generator
|
||||
{
|
||||
$username = \str_replace('@', '', $username);
|
||||
if (!$username) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
$this->caching_simple_username[$username] = true;
|
||||
$res = yield from $this->methodCallAsyncRead('contacts.resolveUsername', ['username' => \str_replace('@', '', $username)], ['datacenter' => $this->datacenter->curdc]);
|
||||
$res = yield from $this->methodCallAsyncRead('contacts.resolveUsername', ['username' => $username], ['datacenter' => $this->datacenter->curdc]);
|
||||
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
||||
$this->logger->logger('Username resolution failed with error '.$e->getMessage(), \danog\MadelineProto\Logger::ERROR);
|
||||
if (\strpos($e->rpc, 'FLOOD_WAIT_') === 0 || $e->rpc === 'AUTH_KEY_UNREGISTERED' || $e->rpc === 'USERNAME_INVALID') {
|
||||
|
|
|
@ -422,10 +422,10 @@ class ReferenceDatabase implements TLCallback
|
|||
$origin['peer'] = $this->API->getId($origin['peer']);
|
||||
}
|
||||
if ($origin['peer'] < 0) {
|
||||
yield from $this->API->methodCallAsyncRead('channels.getMessages', ['channel' => $origin['peer'], 'id' => [$origin['msg_id']]], ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
|
||||
yield from $this->API->methodCallAsyncRead('channels.getMessages', ['channel' => $origin['peer'], 'id' => [$origin['msg_id']]], $this->API->getSettings()->getDefaultDcParams());
|
||||
break;
|
||||
}
|
||||
yield from $this->API->methodCallAsyncRead('messages.getMessages', ['id' => [$origin['msg_id']]], ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
|
||||
yield from $this->API->methodCallAsyncRead('messages.getMessages', ['id' => [$origin['msg_id']]], $this->API->getSettings()->getDefaultDcParams());
|
||||
break;
|
||||
// Peer + photo ID
|
||||
case self::PEER_PHOTO_ORIGIN:
|
||||
|
@ -438,25 +438,25 @@ class ReferenceDatabase implements TLCallback
|
|||
break;
|
||||
// Peer (default photo ID)
|
||||
case self::USER_PHOTO_ORIGIN:
|
||||
yield from $this->API->methodCallAsyncRead('photos.getUserPhotos', $origin, ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
|
||||
yield from $this->API->methodCallAsyncRead('photos.getUserPhotos', $origin, $this->API->getSettings()->getDefaultDcParams());
|
||||
break;
|
||||
case self::SAVED_GIFS_ORIGIN:
|
||||
yield from $this->API->methodCallAsyncRead('messages.getSavedGifs', $origin, ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
|
||||
yield from $this->API->methodCallAsyncRead('messages.getSavedGifs', $origin, $this->API->getSettings()->getDefaultDcParams());
|
||||
break;
|
||||
case self::STICKER_SET_ID_ORIGIN:
|
||||
yield from $this->API->methodCallAsyncRead('messages.getStickerSet', $origin, ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
|
||||
yield from $this->API->methodCallAsyncRead('messages.getStickerSet', $origin, $this->API->getSettings()->getDefaultDcParams());
|
||||
break;
|
||||
case self::STICKER_SET_RECENT_ORIGIN:
|
||||
yield from $this->API->methodCallAsyncRead('messages.getRecentStickers', $origin, ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
|
||||
yield from $this->API->methodCallAsyncRead('messages.getRecentStickers', $origin, $this->API->getSettings()->getDefaultDcParams());
|
||||
break;
|
||||
case self::STICKER_SET_FAVED_ORIGIN:
|
||||
yield from $this->API->methodCallAsyncRead('messages.getFavedStickers', $origin, ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
|
||||
yield from $this->API->methodCallAsyncRead('messages.getFavedStickers', $origin, $this->API->getSettings()->getDefaultDcParams());
|
||||
break;
|
||||
case self::STICKER_SET_EMOTICON_ORIGIN:
|
||||
yield from $this->API->methodCallAsyncRead('messages.getStickers', $origin, ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
|
||||
yield from $this->API->methodCallAsyncRead('messages.getStickers', $origin, $this->API->getSettings()->getDefaultDcParams());
|
||||
break;
|
||||
case self::WALLPAPER_ORIGIN:
|
||||
yield from $this->API->methodCallAsyncRead('account.getWallPapers', $origin, ['datacenter' => $this->API->settings['connection_settings']['default_dc']]);
|
||||
yield from $this->API->methodCallAsyncRead('account.getWallPapers', $origin, $this->API->getSettings()->getDefaultDcParams());
|
||||
break;
|
||||
default:
|
||||
throw new \danog\MadelineProto\Exception("Unknown origin type {$originType}");
|
||||
|
|
|
@ -20,59 +20,34 @@
|
|||
namespace danog\MadelineProto\MTProtoTools;
|
||||
|
||||
use Amp\Deferred;
|
||||
use Amp\Http\Client\Request;
|
||||
use Amp\Loop;
|
||||
use danog\MadelineProto\Logger;
|
||||
use danog\MadelineProto\Loop\Update\FeedLoop;
|
||||
use danog\MadelineProto\Loop\Update\UpdateLoop;
|
||||
use danog\MadelineProto\MTProto;
|
||||
use danog\MadelineProto\RPCErrorException;
|
||||
|
||||
use danog\MadelineProto\Settings;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
/**
|
||||
* Manages updates.
|
||||
*
|
||||
* @property Settings $settings Settings
|
||||
*/
|
||||
trait UpdateHandler
|
||||
{
|
||||
/**
|
||||
* Update handler callback.
|
||||
*
|
||||
* @var ?callable
|
||||
*/
|
||||
private $updateHandler;
|
||||
private $got_state = false;
|
||||
private $channels_state;
|
||||
public $updates = [];
|
||||
public $updates_key = 0;
|
||||
/**
|
||||
* PWR update handler.
|
||||
*
|
||||
* @param array $update Update
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function pwrUpdateHandler($update)
|
||||
{
|
||||
if (isset($this->settings['pwr']['updateHandler'])) {
|
||||
if (\is_array($this->settings['pwr']['updateHandler']) && $this->settings['pwr']['updateHandler'][0] === false) {
|
||||
$this->settings['pwr']['updateHandler'] = $this->settings['pwr']['updateHandler'][1];
|
||||
}
|
||||
if (\is_string($this->settings['pwr']['updateHandler'])) {
|
||||
return $this->{$this->settings['pwr']['updateHandler']}($update);
|
||||
}
|
||||
\in_array($this->settings['pwr']['updateHandler'], [['danog\\MadelineProto\\API', 'getUpdatesUpdateHandler'], 'getUpdatesUpdateHandler']) ? $this->getUpdatesUpdateHandler($update) : $this->settings['pwr']['updateHandler']($update);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Getupdates update handler.
|
||||
*
|
||||
* @param array $update Update
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function getUpdatesUpdateHandler(array $update): void
|
||||
{
|
||||
if (!$this->settings['updates']['handle_updates']) {
|
||||
return;
|
||||
}
|
||||
$this->updates[$this->updates_key++] = $update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get updates.
|
||||
*
|
||||
|
@ -84,23 +59,15 @@ trait UpdateHandler
|
|||
*/
|
||||
public function getUpdates($params = []): \Generator
|
||||
{
|
||||
if (!$this->settings['updates']['handle_updates']) {
|
||||
$this->settings['updates']['handle_updates'] = true;
|
||||
$this->startUpdateSystem();
|
||||
}
|
||||
if (!$this->settings['updates']['run_callback']) {
|
||||
$this->settings['updates']['run_callback'] = true;
|
||||
}
|
||||
$params = \array_merge(self::DEFAULT_GETUPDATES_PARAMS, $params);
|
||||
$this->updateHandler = MTProto::GETUPDATES_HANDLER;
|
||||
$params = MTProto::DEFAULT_GETUPDATES_PARAMS + $params;
|
||||
if (empty($this->updates)) {
|
||||
$this->update_deferred = new Deferred();
|
||||
if (!$params['timeout']) {
|
||||
$params['timeout'] = 0.001;
|
||||
}
|
||||
yield $this->waitUpdate();
|
||||
$params['timeout'] *= 1000;
|
||||
yield Tools::timeoutWithDefault($this->waitUpdate(), $params['timeout'] ?: 100000);
|
||||
}
|
||||
if (empty($this->updates)) {
|
||||
return [];
|
||||
return $this->updates;
|
||||
}
|
||||
if ($params['offset'] < 0) {
|
||||
$params['offset'] = \array_reverse(\array_keys((array) $this->updates))[\abs($params['offset']) - 1];
|
||||
|
@ -229,8 +196,8 @@ trait UpdateHandler
|
|||
*/
|
||||
public function getUpdatesState(): \Generator
|
||||
{
|
||||
$data = yield from $this->methodCallAsyncRead('updates.getState', [], ['datacenter' => $this->settings['connection_settings']['default_dc']]);
|
||||
yield from $this->getCdnConfig($this->settings['connection_settings']['default_dc']);
|
||||
$data = yield from $this->methodCallAsyncRead('updates.getState', [], $this->settings->getDefaultDcParams());
|
||||
yield from $this->getCdnConfig($this->settings->getDefaultDc());
|
||||
return $data;
|
||||
}
|
||||
/**
|
||||
|
@ -245,9 +212,6 @@ trait UpdateHandler
|
|||
*/
|
||||
public function handleUpdates($updates, $actual_updates = null): \Generator
|
||||
{
|
||||
if (!$this->settings['updates']['handle_updates']) {
|
||||
return;
|
||||
}
|
||||
if ($actual_updates) {
|
||||
$updates = $actual_updates;
|
||||
}
|
||||
|
@ -395,7 +359,7 @@ trait UpdateHandler
|
|||
return false;
|
||||
}
|
||||
$this->logger->logger('Applying qts: '.$update['qts'].' over current qts '.$cur_state->qts().', chat id: '.$update['message']['chat_id'], \danog\MadelineProto\Logger::VERBOSE);
|
||||
yield from $this->methodCallAsyncRead('messages.receivedQueue', ['max_qts' => $cur_state->qts($update['qts'])], ['datacenter' => $this->settings['connection_settings']['default_dc']]);
|
||||
yield from $this->methodCallAsyncRead('messages.receivedQueue', ['max_qts' => $cur_state->qts($update['qts'])], $this->settings->getDefaultDcParams());
|
||||
}
|
||||
yield from $this->handleEncryptedUpdate($update);
|
||||
return;
|
||||
|
@ -408,7 +372,7 @@ trait UpdateHandler
|
|||
if ($update['_'] === 'updateEncryption') {
|
||||
switch ($update['chat']['_']) {
|
||||
case 'encryptedChatRequested':
|
||||
if ($this->settings['secret_chats']['accept_chats'] === false || \is_array($this->settings['secret_chats']['accept_chats']) && !\in_array($update['chat']['admin_id'], $this->settings['secret_chats']['accept_chats'])) {
|
||||
if (!$this->settings->getSecretChats()->canAccept($update['chat']['admin_id'])) {
|
||||
return;
|
||||
}
|
||||
$this->logger->logger('Accepting secret chat '.$update['chat']['id'], \danog\MadelineProto\Logger::NOTICE);
|
||||
|
@ -439,7 +403,7 @@ trait UpdateHandler
|
|||
}
|
||||
//if ($update['_'] === 'updateServiceNotification' && strpos($update['type'], 'AUTH_KEY_DROP_') === 0) {
|
||||
//}
|
||||
if (!$this->settings['updates']['handle_updates']) {
|
||||
if (!$this->updateHandler) {
|
||||
return;
|
||||
}
|
||||
if (isset($update['message']['_']) && $update['message']['_'] === 'messageEmpty') {
|
||||
|
@ -448,42 +412,7 @@ trait UpdateHandler
|
|||
if (isset($update['message']['from_id']) && $update['message']['from_id'] === $this->authorization['user']['id']) {
|
||||
$update['message']['out'] = true;
|
||||
}
|
||||
//$this->logger->logger('Saving an update of type '.$update['_'].'...', \danog\MadelineProto\Logger::ULTRA_VERBOSE);
|
||||
if (isset($this->settings['pwr']['strict']) && $this->settings['pwr']['strict'] && isset($this->settings['pwr']['updateHandler'])) {
|
||||
$this->pwrUpdateHandler($update);
|
||||
} elseif ($this->settings['updates']['run_callback']) {
|
||||
$this->getUpdatesUpdateHandler($update);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Send update to webhook.
|
||||
*
|
||||
* @param array $update Update
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function pwrWebhook(array $update): void
|
||||
{
|
||||
$payload = \json_encode($update);
|
||||
//$this->logger->logger($update, $payload, json_last_error());
|
||||
if ($payload === '') {
|
||||
$this->logger->logger('EMPTY UPDATE');
|
||||
return;
|
||||
}
|
||||
\danog\MadelineProto\Tools::callFork((function () use ($payload): \Generator {
|
||||
$request = new Request($this->hook_url, 'POST');
|
||||
$request->setHeader('content-type', 'application/json');
|
||||
$request->setBody($payload);
|
||||
$result = yield (yield $this->datacenter->getHTTPClient()->request($request))->getBody()->buffer();
|
||||
$this->logger->logger('Result of webhook query is '.$result, \danog\MadelineProto\Logger::NOTICE);
|
||||
$result = \json_decode($result, true);
|
||||
if (\is_array($result) && isset($result['method']) && $result['method'] != '' && \is_string($result['method'])) {
|
||||
try {
|
||||
$this->logger->logger('Reverse webhook command returned', yield from $this->methodCallAsyncRead($result['method'], $result, ['datacenter' => $this->datacenter->curdc]));
|
||||
} catch (\Throwable $e) {
|
||||
$this->logger->logger("Reverse webhook command returned: {$e}");
|
||||
}
|
||||
}
|
||||
})());
|
||||
// First save to array, then once the feed loop signals resumal of loop, resume and handle
|
||||
$this->updates[$this->updates_key++] = $update;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ use Amp\Loop;
|
|||
use Amp\Loop\Driver;
|
||||
use ReflectionClass;
|
||||
use function Amp\ByteStream\getStdin;
|
||||
use function Amp\Log\hasColorSupport;
|
||||
use function Amp\Promise\wait;
|
||||
|
||||
class Magic
|
||||
|
@ -33,12 +34,6 @@ class Magic
|
|||
* @var array
|
||||
*/
|
||||
public static $storage = [];
|
||||
/**
|
||||
* Whether has threads.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public static $has_thread = false;
|
||||
/**
|
||||
* Whether this system is bigendian.
|
||||
*
|
||||
|
@ -63,6 +58,10 @@ class Magic
|
|||
* @var boolean
|
||||
*/
|
||||
public static $isFork = false;
|
||||
/**
|
||||
* Whether this is an IPC worker.
|
||||
*/
|
||||
public static bool $isIpcWorker = false;
|
||||
/**
|
||||
* Whether we can get our PID.
|
||||
*
|
||||
|
@ -235,6 +234,7 @@ class Magic
|
|||
// Setup error reporting
|
||||
\set_error_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionErrorHandler']);
|
||||
\set_exception_handler(['\\danog\\MadelineProto\\Exception', 'ExceptionHandler']);
|
||||
self::$isIpcWorker = \defined(\MADELINE_WORKER_TYPE::class) ? \MADELINE_WORKER_TYPE === 'madeline-ipc' : false;
|
||||
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg') {
|
||||
try {
|
||||
\error_reporting(E_ALL);
|
||||
|
@ -247,7 +247,7 @@ class Magic
|
|||
}
|
||||
// Check if we're in a console, for colorful log output
|
||||
try {
|
||||
self::$isatty = \defined(\STDOUT::class) && \function_exists('posix_isatty') && \posix_isatty(\STDOUT);
|
||||
self::$isatty = \defined(\STDOUT::class) && hasColorSupport();
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
}
|
||||
// Important, obtain root relative to caller script
|
||||
|
@ -266,11 +266,11 @@ class Magic
|
|||
\pcntl_signal(SIGINT, SIG_DFL);
|
||||
Loop::unreference(Loop::onSignal(SIGINT, static function () {
|
||||
Logger::log('Got sigint', Logger::FATAL_ERROR);
|
||||
Magic::shutdown(1);
|
||||
Magic::shutdown(self::$isIpcWorker ? 0 : 1);
|
||||
}));
|
||||
Loop::unreference(Loop::onSignal(SIGTERM, static function () {
|
||||
Logger::log('Got sigterm', Logger::FATAL_ERROR);
|
||||
Magic::shutdown(1);
|
||||
Magic::shutdown(self::$isIpcWorker ? 0 : 1);
|
||||
}));
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
@ -291,10 +291,9 @@ class Magic
|
|||
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;
|
||||
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);
|
||||
|
@ -413,6 +412,7 @@ class Magic
|
|||
$driver->unreference($key);
|
||||
}
|
||||
}
|
||||
MTProto::serializeAll();
|
||||
Loop::stop();
|
||||
die($code);
|
||||
}
|
||||
|
|
|
@ -48,7 +48,7 @@ class MyTelegramOrgWrapper
|
|||
/**
|
||||
* Settings.
|
||||
*/
|
||||
private array $settings = [];
|
||||
private Settings $settings;
|
||||
/**
|
||||
* Async setting.
|
||||
*/
|
||||
|
@ -79,11 +79,11 @@ class MyTelegramOrgWrapper
|
|||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $settings
|
||||
* @param array|Settings $settings
|
||||
*/
|
||||
public function __construct(array $settings = [])
|
||||
public function __construct($settings)
|
||||
{
|
||||
$this->settings = MTProto::parseSettings($settings, $this->settings);
|
||||
$this->settings = Settings::parseFromLegacy($settings);
|
||||
$this->__wakeup();
|
||||
}
|
||||
/**
|
||||
|
@ -93,23 +93,24 @@ class MyTelegramOrgWrapper
|
|||
*/
|
||||
public function __wakeup(): void
|
||||
{
|
||||
if ($this->settings === null) {
|
||||
$this->settings = [];
|
||||
if (!$this->settings) {
|
||||
$this->settings = new Settings;
|
||||
} elseif (\is_array($this->settings)) {
|
||||
$this->settings = Settings::parseFromLegacy($this->settings);
|
||||
}
|
||||
if (!$this->jar || !$this->jar instanceof InMemoryCookieJar) {
|
||||
$this->jar = new InMemoryCookieJar();
|
||||
}
|
||||
$this->settings = MTProto::parseSettings($this->settings);
|
||||
$this->datacenter = new DataCenter(new class($this->settings) {
|
||||
public function __construct($settings)
|
||||
$this->datacenter = new DataCenter(new class(new Logger($this->settings->getLogger())) {
|
||||
public function __construct(Logger $logger)
|
||||
{
|
||||
$this->logger = Logger::getLoggerFromSettings($settings);
|
||||
$this->logger = $logger;
|
||||
}
|
||||
public function getLogger()
|
||||
{
|
||||
return $this->logger;
|
||||
}
|
||||
}, [], $this->settings['connection_settings'], true, $this->jar);
|
||||
}, [], $this->settings->getConnection(), true, $this->jar);
|
||||
}
|
||||
/**
|
||||
* Login.
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use Psr\Log\AbstractLogger;
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
class PsrLogger extends AbstractLogger
|
||||
{
|
||||
private const LEVEL_MAP = [
|
||||
LogLevel::EMERGENCY => Logger::LEVEL_FATAL,
|
||||
LogLevel::ALERT => Logger::LEVEL_FATAL,
|
||||
LogLevel::CRITICAL => Logger::LEVEL_FATAL,
|
||||
LogLevel::ERROR => Logger::LEVEL_ERROR,
|
||||
LogLevel::WARNING => Logger::LEVEL_WARNING,
|
||||
LogLevel::NOTICE => Logger::LEVEL_NOTICE,
|
||||
LogLevel::INFO => Logger::LEVEL_VERBOSE,
|
||||
LogLevel::DEBUG => Logger::LEVEL_ULTRA_VERBOSE
|
||||
];
|
||||
/**
|
||||
* Logger.
|
||||
*/
|
||||
private Logger $logger;
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param Logger $logger
|
||||
*/
|
||||
public function __construct(Logger $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
/**
|
||||
* Logs with an arbitrary level.
|
||||
*
|
||||
* @param mixed $level
|
||||
* @param string $message
|
||||
* @param mixed[] $context
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \Psr\Log\InvalidArgumentException
|
||||
*/
|
||||
public function log($level, $message, array $context = [])
|
||||
{
|
||||
$this->logger->logger($message, self::LEVEL_MAP[$level]);
|
||||
}
|
||||
}
|
|
@ -55,12 +55,9 @@ class RSA
|
|||
*/
|
||||
public function load(TL $TL, string $rsa_key): \Generator
|
||||
{
|
||||
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['rsa_init'], Logger::ULTRA_VERBOSE);
|
||||
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['loading_key'], Logger::ULTRA_VERBOSE);
|
||||
$key = \tgseclib\Crypt\RSA::load($rsa_key);
|
||||
$this->n = Tools::getVar($key, 'modulus');
|
||||
$this->e = Tools::getVar($key, 'exponent');
|
||||
\danog\MadelineProto\Logger::log(\danog\MadelineProto\Lang::$current_lang['computing_fingerprint'], Logger::ULTRA_VERBOSE);
|
||||
$this->fp = \substr(\sha1((yield from $TL->serializeObject(['type' => 'bytes'], $this->n->toBytes(), 'key')).(yield from $TL->serializeObject(['type' => 'bytes'], $this->e->toBytes(), 'key')), true), -8);
|
||||
return $this;
|
||||
}
|
||||
|
|
|
@ -135,7 +135,7 @@ trait MessageHandler
|
|||
throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['msg_data_length_too_big']);
|
||||
}
|
||||
if ($message_key != \substr(\sha1(\substr($decrypted_data, 0, 4 + $message_data_length), true), -16)) {
|
||||
throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['msg_key_mismatch']);
|
||||
throw new \danog\MadelineProto\SecurityException('Msg_key mismatch');
|
||||
}
|
||||
if (\strlen($decrypted_data) - 4 - $message_data_length > 15) {
|
||||
throw new \danog\MadelineProto\SecurityException('difference between message_data_length and the length of the remaining decrypted buffer is too big');
|
||||
|
@ -155,7 +155,7 @@ trait MessageHandler
|
|||
throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['msg_data_length_too_big']);
|
||||
}
|
||||
if ($message_key != \substr(\hash('sha256', \substr($this->secret_chats[$chat_id][$old ? 'old_key' : 'key']['auth_key'], 88 + ($this->secret_chats[$chat_id]['admin'] ? 8 : 0), 32).$decrypted_data, true), 8, 16)) {
|
||||
throw new \danog\MadelineProto\SecurityException(\danog\MadelineProto\Lang::$current_lang['msg_key_mismatch']);
|
||||
throw new \danog\MadelineProto\SecurityException('Msg_key mismatch');
|
||||
}
|
||||
if (\strlen($decrypted_data) - 4 - $message_data_length < 12) {
|
||||
throw new \danog\MadelineProto\SecurityException('padding is too small');
|
||||
|
|
|
@ -26,9 +26,6 @@ trait ResponseHandler
|
|||
{
|
||||
private function handleDecryptedUpdate($update): \Generator
|
||||
{
|
||||
/*if (isset($update['message']['decrypted_message']['random_bytes']) && strlen($update['message']['decrypted_message']['random_bytes']) < 15) {
|
||||
throw new \danog\MadelineProto\ResponseException(\danog\MadelineProto\Lang::$current_lang['rand_bytes_too_short']);
|
||||
}*/
|
||||
// already checked in TL.php
|
||||
switch ($update['message']['decrypted_message']['_']) {
|
||||
case 'decryptedMessageService':
|
||||
|
@ -92,7 +89,7 @@ trait ResponseHandler
|
|||
}
|
||||
break;
|
||||
default:
|
||||
throw new \danog\MadelineProto\ResponseException(\danog\MadelineProto\Lang::$current_lang['unrecognized_dec_msg'].\var_export($update, true));
|
||||
throw new \danog\MadelineProto\ResponseException('Unrecognized decrypted message received: '.\var_export($update, true));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,121 +19,301 @@
|
|||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use Amp\Deferred;
|
||||
use Amp\Loop;
|
||||
use Amp\Promise;
|
||||
use danog\MadelineProto\Db\DriverArray;
|
||||
use danog\MadelineProto\Ipc\Server;
|
||||
use danog\MadelineProto\MTProtoSession\Session;
|
||||
|
||||
use function Amp\File\exists;
|
||||
use function Amp\File\get;
|
||||
use function Amp\Ipc\connect;
|
||||
|
||||
/**
|
||||
* Manages serialization of the MadelineProto instance.
|
||||
*/
|
||||
class Serialization
|
||||
abstract class Serialization
|
||||
{
|
||||
/**
|
||||
* Unserialize legacy session.
|
||||
* Header for session files.
|
||||
*/
|
||||
const PHP_HEADER = '<?php __HALT_COMPILER();';
|
||||
/**
|
||||
* Serialization version.
|
||||
*/
|
||||
const VERSION = 1;
|
||||
|
||||
/**
|
||||
* Unserialize session.
|
||||
*
|
||||
* @param string $session Session name
|
||||
* Logic for deserialization is as follows.
|
||||
* - If the session is unlocked
|
||||
* - Try starting IPC server:
|
||||
* - Fetch light state
|
||||
* - If don't need event handler
|
||||
* - Unlock
|
||||
* - Fork
|
||||
* - Lock (fork)
|
||||
* - Deserialize full (fork)
|
||||
* - Start IPC server (fork)
|
||||
* - Store IPC state (fork)
|
||||
* - If need event handler
|
||||
* - If have event handler class
|
||||
* - Deserialize full
|
||||
* - Start IPC server
|
||||
* - Store IPC state
|
||||
* - Else Fallthrough
|
||||
* - Wait for a new IPC state for a maximum of 30 seconds, then throw
|
||||
* - Execute original request via IPC
|
||||
*
|
||||
* - If the session is locked
|
||||
* - In parallel (concurrent):
|
||||
* - The IPC server should be running, connect
|
||||
* - Try starting full session
|
||||
* - Fetch light state
|
||||
* - If don't need event handler
|
||||
* - Wait lock
|
||||
* - Unlock
|
||||
* - Fork
|
||||
* - Lock (fork)
|
||||
* - Deserialize full (fork)
|
||||
* - Start IPC server (fork)
|
||||
* - Store IPC state (fork)
|
||||
* - If need event handler and have event handler class
|
||||
* - Wait lock
|
||||
* - Deserialize full
|
||||
* - Start IPC server
|
||||
* - Store IPC state
|
||||
* - Wait for a new IPC session for a maximum of 30 seconds, then throw
|
||||
* - Execute original request via IPC
|
||||
*
|
||||
*
|
||||
*
|
||||
* - If receiving a startAndLoop or setEventHandler request on an IPC session:
|
||||
* - Shutdown remote IPC server
|
||||
* - Deserialize full
|
||||
* - Start IPC server
|
||||
* - Store IPC state
|
||||
*
|
||||
* @param SessionPaths $session Session name
|
||||
* @param bool $forceFull Whether to force full session deserialization
|
||||
*
|
||||
* @internal
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public static function legacyUnserialize(string $session): \Generator
|
||||
public static function unserialize(SessionPaths $session, bool $forceFull = false): \Generator
|
||||
{
|
||||
$realpaths = new SessionPaths($session);
|
||||
if (yield exists($realpaths->getSessionPath())) {
|
||||
Logger::log('Waiting for exclusive session lock...');
|
||||
$warningId = Loop::delay(1000, static function () use (&$warningId) {
|
||||
Logger::log("It seems like the session is busy.");
|
||||
if (\defined(\MADELINE_WORKER::class)) {
|
||||
Logger::log("Exiting since we're in a worker");
|
||||
Magic::shutdown(1);
|
||||
}
|
||||
Logger::log("Telegram does not support starting multiple instances of the same session, make sure no other instance of the session is running.");
|
||||
$warningId = Loop::repeat(5000, fn () => Logger::log('Still waiting for exclusive session lock...'));
|
||||
Loop::unreference($warningId);
|
||||
});
|
||||
Loop::unreference($warningId);
|
||||
$unlockGlobal = yield Tools::flock($realpaths->getSessionLockPath(), LOCK_EX, 1);
|
||||
Loop::cancel($warningId);
|
||||
$tempId = Shutdown::addCallback($unlockGlobal = static function () use ($unlockGlobal) {
|
||||
Logger::log("Unlocking exclusive session lock!");
|
||||
$unlockGlobal();
|
||||
Logger::log("Unlocked exclusive session lock!");
|
||||
});
|
||||
Logger::log("Got exclusive session lock!");
|
||||
if (yield exists($session->getSessionPath())) {
|
||||
// Is new session
|
||||
$isNew = true;
|
||||
} elseif (yield exists($session->getLegacySessionPath())) {
|
||||
// Is old session
|
||||
$isNew = false;
|
||||
} else {
|
||||
// No session exists yet, lock for when we create it
|
||||
return [null, yield from Tools::flockGenerator($session->getLockPath(), LOCK_EX, 1)];
|
||||
}
|
||||
|
||||
Logger::log('Waiting for shared lock of serialization lockfile...');
|
||||
$unlock = yield Tools::flock($realpaths->getLockPath(), LOCK_SH);
|
||||
Logger::log('Shared lock acquired, deserializing...');
|
||||
try {
|
||||
$tounserialize = yield get($realpaths->getSessionPath());
|
||||
} finally {
|
||||
//Logger::log('Waiting for exclusive session lock...');
|
||||
$warningId = Loop::delay(1000, static function () use (&$warningId) {
|
||||
Logger::log("It seems like the session is busy.");
|
||||
if (\defined(\MADELINE_WORKER::class)) {
|
||||
Logger::log("Exiting since we're in a worker");
|
||||
Magic::shutdown(1);
|
||||
}
|
||||
Logger::log("Telegram does not support starting multiple instances of the same session, make sure no other instance of the session is running.");
|
||||
$warningId = Loop::repeat(5000, fn () => Logger::log('Still waiting for exclusive session lock...'));
|
||||
Loop::unreference($warningId);
|
||||
});
|
||||
Loop::unreference($warningId);
|
||||
|
||||
$lightState = null;
|
||||
$cancelFlock = new Deferred;
|
||||
$cancelIpc = new Deferred;
|
||||
$canContinue = true;
|
||||
$ipcSocket = null;
|
||||
$unlock = yield from Tools::flockGenerator($session->getLockPath(), LOCK_EX, 1, $cancelFlock->promise(), $forceFull ? null : static function () use ($session, $cancelFlock, $cancelIpc, &$canContinue, &$ipcSocket, &$lightState) {
|
||||
$ipcSocket = Tools::call(self::tryConnect($session->getIpcPath(), $cancelIpc->promise(), $cancelFlock));
|
||||
$session->getLightState()->onResolve(static function (?\Throwable $e, ?LightState $res) use ($cancelFlock, &$canContinue, &$lightState) {
|
||||
if ($res) {
|
||||
$lightState = $res;
|
||||
if (!$res->canStartIpc()) {
|
||||
$canContinue = false;
|
||||
$cancelFlock->resolve(true);
|
||||
}
|
||||
} else {
|
||||
$lightState = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
Loop::cancel($warningId);
|
||||
|
||||
if (!$unlock) { // Canceled, don't have lock
|
||||
return $ipcSocket;
|
||||
}
|
||||
if (!$canContinue) { // Have lock, can't use it
|
||||
Logger::log("Session has event handler, but it's not started.", Logger::ERROR);
|
||||
Logger::log("We don't have access to the event handler class, so we can't start it.", Logger::ERROR);
|
||||
Logger::log("Please start the event handler or unset it to use the IPC server.", Logger::ERROR);
|
||||
$unlock();
|
||||
return $ipcSocket;
|
||||
}
|
||||
|
||||
try {
|
||||
/** @var LightState */
|
||||
$lightState ??= yield $session->getLightState();
|
||||
} catch (\Throwable $e) {
|
||||
}
|
||||
|
||||
if ($lightState && !$forceFull) {
|
||||
if (!$class = $lightState->getEventHandler()) {
|
||||
// Unlock and fork
|
||||
$unlock();
|
||||
$cancelIpc->resolve(Server::startMe($session));
|
||||
return $ipcSocket ?? yield from self::tryConnect($session->getIpcPath(), $cancelIpc->promise());
|
||||
} elseif (!\class_exists($class)) {
|
||||
// Have lock, can't use it
|
||||
$unlock();
|
||||
Logger::log("Session has event handler, but it's not started.", Logger::ERROR);
|
||||
Logger::log("We don't have access to the event handler class, so we can't start it.", Logger::ERROR);
|
||||
Logger::log("Please start the event handler or unset it to use the IPC server.", Logger::ERROR);
|
||||
return $ipcSocket ?? yield from self::tryConnect($session->getIpcPath(), $cancelIpc->promise());
|
||||
}
|
||||
Magic::classExists();
|
||||
}
|
||||
|
||||
$tempId = Shutdown::addCallback($unlock = static function () use ($unlock) {
|
||||
Logger::log("Unlocking exclusive session lock!");
|
||||
$unlock();
|
||||
Logger::log("Unlocked exclusive session lock!");
|
||||
});
|
||||
Logger::log("Got exclusive session lock!");
|
||||
|
||||
if ($isNew) {
|
||||
$unserialized = yield from $session->unserialize();
|
||||
if ($unserialized instanceof DriverArray) {
|
||||
Logger::log("Extracting session from database...");
|
||||
yield from $unserialized->initConnection($unserialized->dbSettings);
|
||||
$unserialized = yield $unserialized['data'];
|
||||
if (!$unserialized) {
|
||||
throw new Exception("Could not extract session from database!");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$unserialized = yield from self::legacyUnserialize($session->getLegacySessionPath());
|
||||
}
|
||||
|
||||
if ($unserialized === false) {
|
||||
throw new Exception(\danog\MadelineProto\Lang::$current_lang['deserialization_error']);
|
||||
}
|
||||
|
||||
Shutdown::removeCallback($tempId);
|
||||
return [$unserialized, $unlock];
|
||||
}
|
||||
|
||||
/**
|
||||
* Try connecting to IPC socket.
|
||||
*
|
||||
* @param string $ipcPath IPC path
|
||||
* @param Promise $cancelConnect Cancelation token (triggers cancellation of connection)
|
||||
* @param ?Deferred $cancelFull Cancelation token source (can trigger cancellation of full unserialization)
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
private static function tryConnect(string $ipcPath, Promise $cancelConnect, ?Deferred $cancelFull = null): \Generator
|
||||
{
|
||||
for ($x = 0; $x < 30; $x++) {
|
||||
Logger::log("Trying to connect to IPC socket...");
|
||||
try {
|
||||
$unserialized = \unserialize($tounserialize);
|
||||
} catch (\danog\MadelineProto\Bug74586Exception $e) {
|
||||
\class_exists('\\Volatile');
|
||||
$tounserialize = \str_replace('O:26:"danog\\MadelineProto\\Button":', 'O:35:"danog\\MadelineProto\\TL\\Types\\Button":', $tounserialize);
|
||||
foreach (['RSA', 'TL\\TLMethods', 'TL\\TLConstructors', 'MTProto', 'API', 'DataCenter', 'Connection', 'TL\\Types\\Button', 'TL\\Types\\Bytes', 'APIFactory'] as $class) {
|
||||
\class_exists('\\danog\\MadelineProto\\'.$class);
|
||||
}
|
||||
$unserialized = \danog\Serialization::unserialize($tounserialize);
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
if ($e->getFile() === 'MadelineProto' && $e->getLine() === 1) {
|
||||
throw $e;
|
||||
}
|
||||
if (@\constant("MADELINEPROTO_TEST") === 'pony') {
|
||||
throw $e;
|
||||
}
|
||||
\class_exists('\\Volatile');
|
||||
foreach (['RSA', 'TL\\TLMethods', 'TL\\TLConstructors', 'MTProto', 'API', 'DataCenter', 'Connection', 'TL\\Types\\Button', 'TL\\Types\\Bytes', 'APIFactory'] as $class) {
|
||||
\class_exists('\\danog\\MadelineProto\\'.$class);
|
||||
}
|
||||
$changed = false;
|
||||
if (\strpos($tounserialize, 'O:26:"danog\\MadelineProto\\Button":') !== false) {
|
||||
Logger::log("SUBBING BUTTONS!");
|
||||
$tounserialize = \str_replace('O:26:"danog\\MadelineProto\\Button":', 'O:35:"danog\\MadelineProto\\TL\\Types\\Button":', $tounserialize);
|
||||
$changed = true;
|
||||
}
|
||||
if (\strpos($e->getMessage(), "Erroneous data format for unserializing 'phpseclib\\Math\\BigInteger'") === 0) {
|
||||
Logger::log("SUBBING BIGINTEGOR!");
|
||||
$tounserialize = \str_replace('phpseclib\\Math\\BigInteger', 'phpseclib\\Math\\BigIntegor', $tounserialize);
|
||||
$changed = true;
|
||||
}
|
||||
if (\strpos($tounserialize, 'C:25:"phpseclib\\Math\\BigInteger"') !== false) {
|
||||
Logger::log("SUBBING TGSECLIB old!");
|
||||
$tounserialize = \str_replace('C:25:"phpseclib\\Math\\BigInteger"', 'C:24:"tgseclib\\Math\\BigInteger"', $tounserialize);
|
||||
$changed = true;
|
||||
}
|
||||
if (\strpos($tounserialize, 'C:26:"phpseclib3\\Math\\BigInteger"') !== false) {
|
||||
Logger::log("SUBBING TGSECLIB!");
|
||||
$tounserialize = \str_replace('C:26:"phpseclib3\\Math\\BigInteger"', 'C:24:"tgseclib\\Math\\BigInteger"', $tounserialize);
|
||||
$changed = true;
|
||||
}
|
||||
Logger::log((string) $e, Logger::ERROR);
|
||||
if (!$changed) {
|
||||
throw $e;
|
||||
}
|
||||
try {
|
||||
$unserialized = \danog\Serialization::unserialize($tounserialize);
|
||||
} catch (\Throwable $e) {
|
||||
$unserialized = \unserialize($tounserialize);
|
||||
\clearstatcache(true, $ipcPath);
|
||||
$socket = yield connect($ipcPath);
|
||||
Logger::log("Connected to IPC socket!");
|
||||
if ($cancelFull) {
|
||||
$cancelFull->resolve(true);
|
||||
}
|
||||
return [$socket, null];
|
||||
} catch (\Throwable $e) {
|
||||
Logger::log((string) $e, Logger::ERROR);
|
||||
throw $e;
|
||||
$e = $e->getMessage();
|
||||
Logger::log("$e while connecting to IPC socket");
|
||||
}
|
||||
if ($unserialized instanceof \danog\PlaceHolder) {
|
||||
$unserialized = \danog\Serialization::unserialize($tounserialize);
|
||||
if ($res = yield Tools::timeoutWithDefault($cancelConnect, 1000, null)) {
|
||||
if ($res instanceof \Throwable) {
|
||||
return [$res, null];
|
||||
}
|
||||
$cancelConnect = (new Deferred)->promise();
|
||||
}
|
||||
if ($unserialized === false) {
|
||||
throw new Exception(\danog\MadelineProto\Lang::$current_lang['deserialization_error']);
|
||||
}
|
||||
Shutdown::removeCallback($tempId);
|
||||
return [$unserialized, $unlockGlobal];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize legacy session.
|
||||
*
|
||||
* @param string $session
|
||||
* @return \Generator
|
||||
*/
|
||||
private static function legacyUnserialize(string $session): \Generator
|
||||
{
|
||||
$tounserialize = yield get($session);
|
||||
|
||||
try {
|
||||
$unserialized = \unserialize($tounserialize);
|
||||
} catch (\danog\MadelineProto\Bug74586Exception $e) {
|
||||
\class_exists('\\Volatile');
|
||||
$tounserialize = \str_replace('O:26:"danog\\MadelineProto\\Button":', 'O:35:"danog\\MadelineProto\\TL\\Types\\Button":', $tounserialize);
|
||||
foreach (['RSA', 'TL\\TLMethods', 'TL\\TLConstructors', 'MTProto', 'API', 'DataCenter', 'Connection', 'TL\\Types\\Button', 'TL\\Types\\Bytes', 'APIFactory'] as $class) {
|
||||
\class_exists('\\danog\\MadelineProto\\'.$class);
|
||||
}
|
||||
$unserialized = \danog\Serialization::unserialize($tounserialize);
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
if ($e->getFile() === 'MadelineProto' && $e->getLine() === 1) {
|
||||
throw $e;
|
||||
}
|
||||
if (@\constant("MADELINEPROTO_TEST") === 'pony') {
|
||||
throw $e;
|
||||
}
|
||||
\class_exists('\\Volatile');
|
||||
foreach (['RSA', 'TL\\TLMethods', 'TL\\TLConstructors', 'MTProto', 'API', 'DataCenter', 'Connection', 'TL\\Types\\Button', 'TL\\Types\\Bytes', 'APIFactory'] as $class) {
|
||||
\class_exists('\\danog\\MadelineProto\\'.$class);
|
||||
}
|
||||
$changed = false;
|
||||
if (\strpos($tounserialize, 'O:26:"danog\\MadelineProto\\Button":') !== false) {
|
||||
Logger::log("SUBBING BUTTONS!");
|
||||
$tounserialize = \str_replace('O:26:"danog\\MadelineProto\\Button":', 'O:35:"danog\\MadelineProto\\TL\\Types\\Button":', $tounserialize);
|
||||
$changed = true;
|
||||
}
|
||||
if (\strpos($e->getMessage(), "Erroneous data format for unserializing 'phpseclib\\Math\\BigInteger'") === 0) {
|
||||
Logger::log("SUBBING BIGINTEGOR!");
|
||||
$tounserialize = \str_replace('phpseclib\\Math\\BigInteger', 'phpseclib\\Math\\BigIntegor', $tounserialize);
|
||||
$changed = true;
|
||||
}
|
||||
if (\strpos($tounserialize, 'C:25:"phpseclib\\Math\\BigInteger"') !== false) {
|
||||
Logger::log("SUBBING TGSECLIB old!");
|
||||
$tounserialize = \str_replace('C:25:"phpseclib\\Math\\BigInteger"', 'C:24:"tgseclib\\Math\\BigInteger"', $tounserialize);
|
||||
$changed = true;
|
||||
}
|
||||
if (\strpos($tounserialize, 'C:26:"phpseclib3\\Math\\BigInteger"') !== false) {
|
||||
Logger::log("SUBBING TGSECLIB!");
|
||||
$tounserialize = \str_replace('C:26:"phpseclib3\\Math\\BigInteger"', 'C:24:"tgseclib\\Math\\BigInteger"', $tounserialize);
|
||||
$changed = true;
|
||||
}
|
||||
Logger::log((string) $e, Logger::ERROR);
|
||||
if (!$changed) {
|
||||
throw $e;
|
||||
}
|
||||
try {
|
||||
$unserialized = \danog\Serialization::unserialize($tounserialize);
|
||||
} catch (\Throwable $e) {
|
||||
$unserialized = \unserialize($tounserialize);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
Logger::log((string) $e, Logger::ERROR);
|
||||
throw $e;
|
||||
}
|
||||
if ($unserialized instanceof \danog\PlaceHolder) {
|
||||
$unserialized = \danog\Serialization::unserialize($tounserialize);
|
||||
}
|
||||
|
||||
return $unserialized;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,19 +19,28 @@
|
|||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use Amp\File\StatCache;
|
||||
use Amp\Promise;
|
||||
use Amp\Success;
|
||||
use danog\MadelineProto\Ipc\IpcState;
|
||||
|
||||
use function Amp\File\exists;
|
||||
use function Amp\File\open;
|
||||
use function Amp\File\rename;
|
||||
|
||||
/**
|
||||
* Session path information.
|
||||
*/
|
||||
class SessionPaths
|
||||
{
|
||||
/**
|
||||
* Legacy session path.
|
||||
*/
|
||||
private string $legacySessionPath;
|
||||
/**
|
||||
* Session path.
|
||||
*/
|
||||
private string $sessionPath;
|
||||
/**
|
||||
* Global session lock path.
|
||||
*/
|
||||
private string $slockPath;
|
||||
/**
|
||||
* Session lock path.
|
||||
*/
|
||||
|
@ -41,9 +50,22 @@ class SessionPaths
|
|||
*/
|
||||
private string $ipcPath;
|
||||
/**
|
||||
* Temporary serialization path.
|
||||
* IPC callback socket path.
|
||||
*/
|
||||
private string $tempPath;
|
||||
private string $ipcCallbackPath;
|
||||
/**
|
||||
* IPC light state path.
|
||||
*/
|
||||
private string $ipcStatePath;
|
||||
/**
|
||||
* Light state path.
|
||||
*/
|
||||
private string $lightStatePath;
|
||||
/**
|
||||
* Light state.
|
||||
*/
|
||||
private ?LightState $lightState = null;
|
||||
|
||||
/**
|
||||
* Construct session info from session name.
|
||||
*
|
||||
|
@ -52,12 +74,60 @@ class SessionPaths
|
|||
public function __construct(string $session)
|
||||
{
|
||||
$session = Tools::absolute($session);
|
||||
$this->sessionPath = $session;
|
||||
$this->slockPath = "$session.slock";
|
||||
$this->legacySessionPath = $session;
|
||||
$this->sessionPath = "$session.safe.php";
|
||||
$this->lightStatePath = "$session.lightState.php";
|
||||
$this->lockPath = "$session.lock";
|
||||
$this->ipcPath = "$session.ipc";
|
||||
$this->tempPath = "$session.temp.session";
|
||||
$this->ipcCallbackPath = "$session.callback.ipc";
|
||||
$this->ipcStatePath = "$session.ipcState.php";
|
||||
}
|
||||
/**
|
||||
* Serialize object to file.
|
||||
*
|
||||
* @param object $object
|
||||
* @param string $path
|
||||
* @return \Generator
|
||||
*/
|
||||
public function serialize(object $object, string $path): \Generator
|
||||
{
|
||||
$file = yield open("$path.temp.php", 'bw+');
|
||||
yield $file->write(Serialization::PHP_HEADER);
|
||||
yield $file->write(\chr(Serialization::VERSION));
|
||||
yield $file->write(\serialize($object));
|
||||
yield $file->close();
|
||||
|
||||
yield rename("$path.temp.php", $path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize new object.
|
||||
*
|
||||
* @param string $path Object path, defaults to session path
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function unserialize(string $path = ''): \Generator
|
||||
{
|
||||
$path = $path ?: $this->sessionPath;
|
||||
|
||||
StatCache::clear($path);
|
||||
if (!yield exists($path)) {
|
||||
return null;
|
||||
}
|
||||
$headerLen = \strlen(Serialization::PHP_HEADER) + 1;
|
||||
|
||||
$file = yield open($path, 'rb');
|
||||
$size = yield \stat($path);
|
||||
$size = $size['size'] ?? $headerLen;
|
||||
|
||||
yield $file->seek($headerLen); // Skip version for now
|
||||
$unserialized = \unserialize((yield $file->read($size - $headerLen)) ?? '');
|
||||
yield $file->close();
|
||||
|
||||
return $unserialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session path.
|
||||
*
|
||||
|
@ -65,7 +135,17 @@ class SessionPaths
|
|||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->sessionPath;
|
||||
return $this->legacySessionPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get legacy session path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLegacySessionPath(): string
|
||||
{
|
||||
return $this->legacySessionPath;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -78,16 +158,6 @@ class SessionPaths
|
|||
return $this->sessionPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get global session lock path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSessionLockPath(): string
|
||||
{
|
||||
return $this->slockPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get lock path.
|
||||
*
|
||||
|
@ -109,12 +179,83 @@ class SessionPaths
|
|||
}
|
||||
|
||||
/**
|
||||
* Get temporary serialization path.
|
||||
* Get IPC light state path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTempPath(): string
|
||||
public function getIpcStatePath(): string
|
||||
{
|
||||
return $this->tempPath;
|
||||
return $this->ipcStatePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get IPC state.
|
||||
*
|
||||
* @return Promise<?IpcState>
|
||||
*/
|
||||
public function getIpcState(): Promise
|
||||
{
|
||||
return Tools::call($this->unserialize($this->ipcStatePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Store IPC state.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function storeIpcState(IpcState $state): \Generator
|
||||
{
|
||||
return $this->serialize($state, $this->getIpcStatePath());
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get light state path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLightStatePath(): string
|
||||
{
|
||||
return $this->lightStatePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get light state.
|
||||
*
|
||||
* @return Promise<LightState>
|
||||
*/
|
||||
public function getLightState(): Promise
|
||||
{
|
||||
if ($this->lightState) {
|
||||
return new Success($this->lightState);
|
||||
}
|
||||
$promise = Tools::call($this->unserialize($this->lightStatePath));
|
||||
$promise->onResolve(function (?\Throwable $e, ?LightState $res) {
|
||||
if ($res) {
|
||||
$this->lightState = $res;
|
||||
}
|
||||
});
|
||||
return $promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store light state.
|
||||
*
|
||||
* @return \Generator
|
||||
*/
|
||||
public function storeLightState(MTProto $state): \Generator
|
||||
{
|
||||
$this->lightState = new LightState($state);
|
||||
return $this->serialize($this->lightState, $this->getLightStatePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get IPC callback socket path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getIpcCallbackPath(): string
|
||||
{
|
||||
return $this->ipcCallbackPath;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,603 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto;
|
||||
|
||||
use danog\MadelineProto\Settings\AppInfo;
|
||||
use danog\MadelineProto\Settings\Auth;
|
||||
use danog\MadelineProto\Settings\Connection;
|
||||
use danog\MadelineProto\Settings\Database\Memory as DatabaseMemory;
|
||||
use danog\MadelineProto\Settings\Database\Mysql;
|
||||
use danog\MadelineProto\Settings\Database\Postgres;
|
||||
use danog\MadelineProto\Settings\Database\Redis;
|
||||
use danog\MadelineProto\Settings\DatabaseAbstract;
|
||||
use danog\MadelineProto\Settings\Files;
|
||||
use danog\MadelineProto\Settings\Ipc;
|
||||
use danog\MadelineProto\Settings\Logger;
|
||||
use danog\MadelineProto\Settings\Peer;
|
||||
use danog\MadelineProto\Settings\Pwr;
|
||||
use danog\MadelineProto\Settings\RPC;
|
||||
use danog\MadelineProto\Settings\SecretChats;
|
||||
use danog\MadelineProto\Settings\Serialization;
|
||||
use danog\MadelineProto\Settings\Templates;
|
||||
use danog\MadelineProto\Settings\TLSchema;
|
||||
|
||||
class Settings extends SettingsAbstract
|
||||
{
|
||||
/**
|
||||
* App information.
|
||||
*/
|
||||
protected AppInfo $appInfo;
|
||||
/**
|
||||
* Cryptography settings.
|
||||
*/
|
||||
protected Auth $auth;
|
||||
/**
|
||||
* Connection settings.
|
||||
*/
|
||||
protected Connection $connection;
|
||||
/**
|
||||
* File management settings.
|
||||
*/
|
||||
protected Files $files;
|
||||
/**
|
||||
* IPC server settings.
|
||||
*/
|
||||
protected Ipc $ipc;
|
||||
/**
|
||||
* Logger settings.
|
||||
*/
|
||||
protected Logger $logger;
|
||||
/**
|
||||
* Peer database settings.
|
||||
*/
|
||||
protected Peer $peer;
|
||||
/**
|
||||
* PWRTelegram settings.
|
||||
*/
|
||||
protected Pwr $pwr;
|
||||
/**
|
||||
* RPC settings.
|
||||
*/
|
||||
protected RPC $rpc;
|
||||
/**
|
||||
* Secret chat settings.
|
||||
*/
|
||||
protected SecretChats $secretChats;
|
||||
/**
|
||||
* Serialization settings.
|
||||
*/
|
||||
protected Serialization $serialization;
|
||||
/**
|
||||
* TL schema settings.
|
||||
*/
|
||||
protected TLSchema $schema;
|
||||
/**
|
||||
* DatabaseAbstract settings.
|
||||
*/
|
||||
protected DatabaseAbstract $db;
|
||||
/**
|
||||
* Template settings.
|
||||
*/
|
||||
protected Templates $templates;
|
||||
|
||||
/**
|
||||
* Create settings object from possibly legacy settings array.
|
||||
*
|
||||
* @param SettingsAbstract|array $settings Settings
|
||||
*
|
||||
* @return SettingsAbstract
|
||||
*/
|
||||
public static function parseFromLegacy($settings): SettingsAbstract
|
||||
{
|
||||
if (\is_array($settings)) {
|
||||
if (empty($settings)) {
|
||||
return new SettingsEmpty;
|
||||
}
|
||||
$settingsNew = new Settings;
|
||||
$settingsNew->mergeArray($settings);
|
||||
return $settingsNew;
|
||||
}
|
||||
return $settings;
|
||||
}
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->appInfo = new AppInfo;
|
||||
$this->auth = new Auth;
|
||||
$this->connection = new Connection;
|
||||
$this->files = new Files;
|
||||
$this->logger = new Logger;
|
||||
$this->peer = new Peer;
|
||||
$this->pwr = new Pwr;
|
||||
$this->rpc = new RPC;
|
||||
$this->secretChats = new SecretChats;
|
||||
$this->serialization = new Serialization;
|
||||
$this->schema = new TLSchema;
|
||||
$this->db = new DatabaseMemory;
|
||||
$this->templates = new Templates;
|
||||
$this->ipc = new IPc;
|
||||
}
|
||||
/**
|
||||
* Merge legacy array settings.
|
||||
*
|
||||
* @param array $settings Settings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function mergeArray(array $settings): void
|
||||
{
|
||||
$this->appInfo->mergeArray($settings);
|
||||
$this->auth->mergeArray($settings);
|
||||
$this->connection->mergeArray($settings);
|
||||
$this->files->mergeArray($settings);
|
||||
$this->logger->mergeArray($settings);
|
||||
$this->peer->mergeArray($settings);
|
||||
$this->pwr->mergeArray($settings);
|
||||
$this->rpc->mergeArray($settings);
|
||||
$this->secretChats->mergeArray($settings);
|
||||
$this->serialization->mergeArray($settings);
|
||||
$this->schema->mergeArray($settings);
|
||||
|
||||
switch ($settings['db']['type'] ?? 'memory') {
|
||||
case 'memory':
|
||||
$this->db = new DatabaseMemory;
|
||||
break;
|
||||
case 'mysql':
|
||||
$this->db = new Mysql;
|
||||
break;
|
||||
case 'postgres':
|
||||
$this->db = new Postgres;
|
||||
break;
|
||||
case 'redis':
|
||||
$this->db = new Redis;
|
||||
break;
|
||||
}
|
||||
$this->db->mergeArray($settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge another instance of settings.
|
||||
*
|
||||
* @param SettingsAbstract $settings Settings
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function merge(SettingsAbstract $settings): void
|
||||
{
|
||||
if (!$settings instanceof self) {
|
||||
if ($settings instanceof AppInfo) {
|
||||
$this->appInfo->merge($settings);
|
||||
} elseif ($settings instanceof Auth) {
|
||||
$this->auth->merge($settings);
|
||||
} elseif ($settings instanceof Connection) {
|
||||
$this->connection->merge($settings);
|
||||
} elseif ($settings instanceof Files) {
|
||||
$this->files->merge($settings);
|
||||
} elseif ($settings instanceof Logger) {
|
||||
$this->logger->merge($settings);
|
||||
} elseif ($settings instanceof Peer) {
|
||||
$this->peer->merge($settings);
|
||||
} elseif ($settings instanceof Pwr) {
|
||||
$this->pwr->merge($settings);
|
||||
} elseif ($settings instanceof RPC) {
|
||||
$this->rpc->merge($settings);
|
||||
} elseif ($settings instanceof SecretChats) {
|
||||
$this->secretChats->merge($settings);
|
||||
} elseif ($settings instanceof Serialization) {
|
||||
$this->serialization->merge($settings);
|
||||
} elseif ($settings instanceof TLSchema) {
|
||||
$this->schema->merge($settings);
|
||||
} elseif ($settings instanceof Ipc) {
|
||||
$this->ipc->merge($settings);
|
||||
} elseif ($settings instanceof Templates) {
|
||||
$this->templates->merge($settings);
|
||||
} elseif ($settings instanceof DatabaseAbstract) {
|
||||
if (!$this->db instanceof $settings) {
|
||||
$this->db = $settings;
|
||||
} else {
|
||||
$this->db->merge($settings);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
$this->appInfo->merge($settings->appInfo);
|
||||
$this->auth->merge($settings->auth);
|
||||
$this->connection->merge($settings->connection);
|
||||
$this->files->merge($settings->files);
|
||||
$this->logger->merge($settings->logger);
|
||||
$this->peer->merge($settings->peer);
|
||||
$this->pwr->merge($settings->pwr);
|
||||
$this->rpc->merge($settings->rpc);
|
||||
$this->secretChats->merge($settings->secretChats);
|
||||
$this->serialization->merge($settings->serialization);
|
||||
$this->schema->merge($settings->schema);
|
||||
$this->ipc->merge($settings->ipc);
|
||||
$this->templates->merge($settings->templates);
|
||||
|
||||
if (!$this->db instanceof $settings->db) {
|
||||
$this->db = $settings->db;
|
||||
} else {
|
||||
$this->db->merge($settings->db);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default DC ID.
|
||||
*
|
||||
* @return integer
|
||||
*/
|
||||
public function getDefaultDc(): int
|
||||
{
|
||||
return $this->connection->getDefaultDc();
|
||||
}
|
||||
/**
|
||||
* Get default DC params.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaultDcParams(): array
|
||||
{
|
||||
return $this->connection->getDefaultDcParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default DC ID.
|
||||
*
|
||||
* @param int $dc DC ID
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setDefaultDc(int $dc): self
|
||||
{
|
||||
$this->connection->setDefaultDc($dc);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get app information.
|
||||
*
|
||||
* @return AppInfo
|
||||
*/
|
||||
public function getAppInfo(): AppInfo
|
||||
{
|
||||
return $this->appInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set app information.
|
||||
*
|
||||
* @param AppInfo $appInfo App information.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setAppInfo(AppInfo $appInfo): self
|
||||
{
|
||||
$this->appInfo = $appInfo;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get cryptography settings.
|
||||
*
|
||||
* @return Auth
|
||||
*/
|
||||
public function getAuth(): Auth
|
||||
{
|
||||
return $this->auth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cryptography settings.
|
||||
*
|
||||
* @param Auth $auth Cryptography settings.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setAuth(Auth $auth): self
|
||||
{
|
||||
$this->auth = $auth;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection settings.
|
||||
*
|
||||
* @return Connection
|
||||
*/
|
||||
public function getConnection(): Connection
|
||||
{
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set connection settings.
|
||||
*
|
||||
* @param Connection $connection Connection settings.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setConnection(Connection $connection): self
|
||||
{
|
||||
$this->connection = $connection;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file management settings.
|
||||
*
|
||||
* @return Files
|
||||
*/
|
||||
public function getFiles(): Files
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set file management settings.
|
||||
*
|
||||
* @param Files $files File management settings.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setFiles(Files $files): self
|
||||
{
|
||||
$this->files = $files;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logger settings.
|
||||
*
|
||||
* @return Logger
|
||||
*/
|
||||
public function getLogger(): Logger
|
||||
{
|
||||
return $this->logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set logger settings.
|
||||
*
|
||||
* @param Logger $logger Logger settings.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setLogger(Logger $logger): self
|
||||
{
|
||||
$this->logger = $logger;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get peer database settings.
|
||||
*
|
||||
* @return Peer
|
||||
*/
|
||||
public function getPeer(): Peer
|
||||
{
|
||||
return $this->peer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set peer database settings.
|
||||
*
|
||||
* @param Peer $peer Peer database settings.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setPeer(Peer $peer): self
|
||||
{
|
||||
$this->peer = $peer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get PWRTelegram settings.
|
||||
*
|
||||
* @return Pwr
|
||||
*/
|
||||
public function getPwr(): Pwr
|
||||
{
|
||||
return $this->pwr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set PWRTelegram settings.
|
||||
*
|
||||
* @param Pwr $pwr PWRTelegram settings.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setPwr(Pwr $pwr): self
|
||||
{
|
||||
$this->pwr = $pwr;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get RPC settings.
|
||||
*
|
||||
* @return RPC
|
||||
*/
|
||||
public function getRpc(): RPC
|
||||
{
|
||||
return $this->rpc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set RPC settings.
|
||||
*
|
||||
* @param RPC $rpc RPC settings.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setRpc(RPC $rpc): self
|
||||
{
|
||||
$this->rpc = $rpc;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get secret chat settings.
|
||||
*
|
||||
* @return SecretChats
|
||||
*/
|
||||
public function getSecretChats(): SecretChats
|
||||
{
|
||||
return $this->secretChats;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set secret chat settings.
|
||||
*
|
||||
* @param SecretChats $secretChats Secret chat settings.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setSecretChats(SecretChats $secretChats): self
|
||||
{
|
||||
$this->secretChats = $secretChats;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get serialization settings.
|
||||
*
|
||||
* @return Serialization
|
||||
*/
|
||||
public function getSerialization(): Serialization
|
||||
{
|
||||
return $this->serialization;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set serialization settings.
|
||||
*
|
||||
* @param Serialization $serialization Serialization settings.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setSerialization(Serialization $serialization): self
|
||||
{
|
||||
$this->serialization = $serialization;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get TL schema settings.
|
||||
*
|
||||
* @return TLSchema
|
||||
*/
|
||||
public function getSchema(): TLSchema
|
||||
{
|
||||
return $this->schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set TL schema settings.
|
||||
*
|
||||
* @param TLSchema $schema TL schema settings.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setSchema(TLSchema $schema): self
|
||||
{
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database settings.
|
||||
*
|
||||
* @return DatabaseAbstract
|
||||
*/
|
||||
public function getDb(): DatabaseAbstract
|
||||
{
|
||||
return $this->db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set database settings.
|
||||
*
|
||||
* @param DatabaseAbstract $db DatabaseAbstract settings.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setDb(DatabaseAbstract $db): self
|
||||
{
|
||||
$this->db = $db;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get IPC server settings.
|
||||
*
|
||||
* @return Ipc
|
||||
*/
|
||||
public function getIpc(): Ipc
|
||||
{
|
||||
return $this->ipc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set IPC server settings.
|
||||
*
|
||||
* @param Ipc $ipc IPC server settings.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setIpc(Ipc $ipc): self
|
||||
{
|
||||
$this->ipc = $ipc;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function applyChanges(): SettingsAbstract
|
||||
{
|
||||
foreach (\get_object_vars($this) as $setting) {
|
||||
if ($setting instanceof SettingsAbstract) {
|
||||
$setting->applyChanges();
|
||||
}
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get template settings.
|
||||
*
|
||||
* @return Templates
|
||||
*/
|
||||
public function getTemplates(): Templates
|
||||
{
|
||||
return $this->templates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set template settings.
|
||||
*
|
||||
* @param Templates $templates Template settings
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setTemplates(Templates $templates): self
|
||||
{
|
||||
$this->templates = $templates;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,301 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Settings;
|
||||
|
||||
use danog\MadelineProto\Exception;
|
||||
use danog\MadelineProto\Lang;
|
||||
use danog\MadelineProto\Magic;
|
||||
use danog\MadelineProto\MTProto;
|
||||
use danog\MadelineProto\SettingsAbstract;
|
||||
|
||||
class AppInfo extends SettingsAbstract
|
||||
{
|
||||
/**
|
||||
* API ID.
|
||||
*/
|
||||
protected int $apiId;
|
||||
/**
|
||||
* API hash.
|
||||
*/
|
||||
protected string $apiHash;
|
||||
/**
|
||||
* Device model.
|
||||
*/
|
||||
protected string $deviceModel;
|
||||
/**
|
||||
* System version.
|
||||
*/
|
||||
protected string $systemVersion;
|
||||
/**
|
||||
* App version.
|
||||
*/
|
||||
protected string $appVersion;
|
||||
/**
|
||||
* Language code.
|
||||
*/
|
||||
protected string $langCode = 'en';
|
||||
/**
|
||||
* Language pack.
|
||||
*/
|
||||
protected string $langPack = '';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
// Detect device model
|
||||
try {
|
||||
$this->deviceModel = \php_uname('s');
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
$this->deviceModel = 'Web server';
|
||||
}
|
||||
// Detect system version
|
||||
try {
|
||||
$this->systemVersion = \php_uname('r');
|
||||
} catch (\danog\MadelineProto\Exception $e) {
|
||||
$this->systemVersion = PHP_VERSION;
|
||||
}
|
||||
// Detect language
|
||||
Lang::$current_lang =& Lang::$lang[$this->langCode];
|
||||
if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
||||
$this->setLangCode(\substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2));
|
||||
} elseif (isset($_SERVER['LANG'])) {
|
||||
$this->setLangCode(\explode('_', $_SERVER['LANG'])[0]);
|
||||
}
|
||||
$this->init();
|
||||
}
|
||||
public function __wakeup()
|
||||
{
|
||||
$this->init();
|
||||
}
|
||||
public function init(): void
|
||||
{
|
||||
Magic::classExists(true);
|
||||
// Detect language pack
|
||||
if (isset(Lang::$lang[$this->langCode])) {
|
||||
Lang::$current_lang =& Lang::$lang[$this->langCode];
|
||||
}
|
||||
$this->appVersion = MTProto::RELEASE.' ('.MTProto::V.', '.\str_replace(' (AN UPDATE IS REQUIRED)', '', Magic::$revision).')';
|
||||
}
|
||||
|
||||
public function mergeArray(array $settings): void
|
||||
{
|
||||
foreach (self::toCamel([
|
||||
'api_id',
|
||||
'api_hash',
|
||||
'device_model',
|
||||
'system_version',
|
||||
'app_version',
|
||||
'lang_code',
|
||||
'lang_pack'
|
||||
]) as $object => $array) {
|
||||
if (isset($settings['app_info'][$array])) {
|
||||
$this->{$object}($settings['app_info'][$array]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the settings have API ID/hash information.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function hasApiInfo(): bool
|
||||
{
|
||||
return isset($this->apiHash, $this->apiId) && $this->apiId;
|
||||
}
|
||||
/**
|
||||
* Get API ID.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getApiId(): int
|
||||
{
|
||||
if (!isset($this->apiId)) {
|
||||
throw new Exception(Lang::$current_lang['api_not_set']);
|
||||
}
|
||||
return $this->apiId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set API ID.
|
||||
*
|
||||
* @param int $apiId API ID.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setApiId(int $apiId): self
|
||||
{
|
||||
$this->apiId = $apiId;
|
||||
if ($apiId === 6) {
|
||||
// TG DEV NOTICE: these app info spoofing measures were implemented for NON-MALICIOUS purposes.
|
||||
// All accounts registered with a custom API ID require manual verification through recover@telegram.org, to avoid instant permabans.
|
||||
// This makes usage of all MTProto libraries very difficult, at least for new users.
|
||||
// To help a bit, when the android API ID is used, the android app infos are spoofed too.
|
||||
// THE ANDROID API HASH IS NOT PRESENT IN THIS REPOSITORY, AND WILL NOT BE GIVEN TO EVERYONE.
|
||||
// This measure was NOT created with the intent to aid spammers, flooders, and other scum.
|
||||
//
|
||||
// I understand that automated account registration through headless libraries may indicate the creation of a botnet,
|
||||
// ...and I understand why these automatic bans were implemented in the first place.
|
||||
// Manual requests to activate numbers through recover@telegram.org will still be required for the majority of users of this library,
|
||||
// ...those that choose to user their own API ID for their application.
|
||||
//
|
||||
// To be honest, I wrote this feature just for me, since I honestly don't want to
|
||||
// ...go through the hassle of registering => recovering => logging in to every account I use for my services (mainly webradios and test userbots)
|
||||
$this->deviceModel = 'LGENexus 5';
|
||||
$this->systemVersion = 'SDK 28';
|
||||
$this->appVersion = '4.9.1 (13613)';
|
||||
$this->langPack = 'android';
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API hash.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getApiHash(): string
|
||||
{
|
||||
if (!isset($this->apiHash)) {
|
||||
throw new Exception(Lang::$current_lang['api_not_set']);
|
||||
}
|
||||
return $this->apiHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set API hash.
|
||||
*
|
||||
* @param string $apiHash API hash.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setApiHash(string $apiHash): self
|
||||
{
|
||||
$this->apiHash = $apiHash;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get device model.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDeviceModel(): string
|
||||
{
|
||||
return $this->deviceModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set device model.
|
||||
*
|
||||
* @param string $deviceModel Device model.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setDeviceModel(string $deviceModel): self
|
||||
{
|
||||
$this->deviceModel = $deviceModel;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get system version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getSystemVersion(): string
|
||||
{
|
||||
return $this->systemVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set system version.
|
||||
*
|
||||
* @param string $systemVersion System version.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setSystemVersion(string $systemVersion): self
|
||||
{
|
||||
$this->systemVersion = $systemVersion;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get app version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getAppVersion(): string
|
||||
{
|
||||
return $this->appVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set app version.
|
||||
*
|
||||
* @param string $appVersion App version.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setAppVersion(string $appVersion): self
|
||||
{
|
||||
$this->appVersion = $appVersion;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language code.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLangCode(): string
|
||||
{
|
||||
return $this->langCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set language code.
|
||||
*
|
||||
* @param string $langCode Language code.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setLangCode(string $langCode): self
|
||||
{
|
||||
$this->langCode = $langCode;
|
||||
if (isset(Lang::$lang[$this->langCode])) {
|
||||
Lang::$current_lang =& Lang::$lang[$this->langCode];
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get language pack.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLangPack(): string
|
||||
{
|
||||
return $this->langPack;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set language pack.
|
||||
*
|
||||
* @param string $langPack Language pack.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setLangPack(string $langPack): self
|
||||
{
|
||||
$this->langPack = $langPack;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Settings;
|
||||
|
||||
use danog\MadelineProto\SettingsAbstract;
|
||||
|
||||
class Auth extends SettingsAbstract
|
||||
{
|
||||
/**
|
||||
* Validity period of temporary keys.
|
||||
* Validity period of the binding of temporary and permanent keys.
|
||||
*/
|
||||
protected int $defaultTempAuthKeyExpiresIn = 1 * 24 * 60 * 60;
|
||||
/**
|
||||
* MTProto public keys array.
|
||||
*/
|
||||
protected array $rsaKeys = [
|
||||
"-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6\nlyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS\nan9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw\nEfzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+\n8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n\nSlv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB\n-----END RSA PUBLIC KEY-----",
|
||||
"-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAxq7aeLAqJR20tkQQMfRn+ocfrtMlJsQ2Uksfs7Xcoo77jAid0bRt\nksiVmT2HEIJUlRxfABoPBV8wY9zRTUMaMA654pUX41mhyVN+XoerGxFvrs9dF1Ru\nvCHbI02dM2ppPvyytvvMoefRoL5BTcpAihFgm5xCaakgsJ/tH5oVl74CdhQw8J5L\nxI/K++KJBUyZ26Uba1632cOiq05JBUW0Z2vWIOk4BLysk7+U9z+SxynKiZR3/xdi\nXvFKk01R3BHV+GUKM2RYazpS/P8v7eyKhAbKxOdRcFpHLlVwfjyM1VlDQrEZxsMp\nNTLYXb6Sce1Uov0YtNx5wEowlREH1WOTlwIDAQAB\n-----END RSA PUBLIC KEY-----",
|
||||
"-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAsQZnSWVZNfClk29RcDTJQ76n8zZaiTGuUsi8sUhW8AS4PSbPKDm+\nDyJgdHDWdIF3HBzl7DHeFrILuqTs0vfS7Pa2NW8nUBwiaYQmPtwEa4n7bTmBVGsB\n1700/tz8wQWOLUlL2nMv+BPlDhxq4kmJCyJfgrIrHlX8sGPcPA4Y6Rwo0MSqYn3s\ng1Pu5gOKlaT9HKmE6wn5Sut6IiBjWozrRQ6n5h2RXNtO7O2qCDqjgB2vBxhV7B+z\nhRbLbCmW0tYMDsvPpX5M8fsO05svN+lKtCAuz1leFns8piZpptpSCFn7bWxiA9/f\nx5x17D7pfah3Sy2pA+NDXyzSlGcKdaUmwQIDAQAB\n-----END RSA PUBLIC KEY-----",
|
||||
"-----BEGIN RSA PUBLIC KEY-----\nMIIBCgKCAQEAwqjFW0pi4reKGbkc9pK83Eunwj/k0G8ZTioMMPbZmW99GivMibwa\nxDM9RDWabEMyUtGoQC2ZcDeLWRK3W8jMP6dnEKAlvLkDLfC4fXYHzFO5KHEqF06i\nqAqBdmI1iBGdQv/OQCBcbXIWCGDY2AsiqLhlGQfPOI7/vvKc188rTriocgUtoTUc\n/n/sIUzkgwTqRyvWYynWARWzQg0I9olLBBC2q5RQJJlnYXZwyTL3y9tdb7zOHkks\nWV9IMQmZmyZh/N7sMbGWQpt4NMchGpPGeJ2e5gHBjDnlIf2p1yZOYeUYrdbwcS0t\nUiggS4UeE8TzIuXFQxw7fzEIlmhIaq3FnwIDAQAB\n-----END RSA PUBLIC KEY-----"
|
||||
];
|
||||
|
||||
/**
|
||||
* Whether to use PFS.
|
||||
*/
|
||||
protected bool $pfs;
|
||||
|
||||
/**
|
||||
* Max tries for generating auth key.
|
||||
*/
|
||||
protected int $maxAuthTries = 5;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->pfs = \extension_loaded('gmp');
|
||||
}
|
||||
public function mergeArray(array $settings): void
|
||||
{
|
||||
foreach (self::toCamel([
|
||||
'default_temp_auth_key_expires_in',
|
||||
'rsa_keys',
|
||||
]) as $object => $array) {
|
||||
if (isset($settings['authorization'][$array])) {
|
||||
$this->{$object}($settings['authorization'][$array]);
|
||||
}
|
||||
}
|
||||
if (isset($settings['connection_settings']['all']['pfs'])) {
|
||||
$this->setPfs($settings['connection_settings']['all']['pfs']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MTProto public keys array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getRsaKeys(): array
|
||||
{
|
||||
return $this->rsaKeys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set MTProto public keys array.
|
||||
*
|
||||
* @param array $rsaKeys MTProto public keys array.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setRsaKeys(array $rsaKeys): self
|
||||
{
|
||||
$this->rsaKeys = $rsaKeys;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get validity period of the binding of temporary and permanent keys.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDefaultTempAuthKeyExpiresIn(): int
|
||||
{
|
||||
return $this->defaultTempAuthKeyExpiresIn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set validity period of the binding of temporary and permanent keys.
|
||||
*
|
||||
* @param int $defaultTempAuthKeyExpiresIn Validity period of the binding of temporary and permanent keys.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setDefaultTempAuthKeyExpiresIn(int $defaultTempAuthKeyExpiresIn): self
|
||||
{
|
||||
$this->defaultTempAuthKeyExpiresIn = $defaultTempAuthKeyExpiresIn;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether to use PFS.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getPfs(): bool
|
||||
{
|
||||
return $this->pfs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to use PFS.
|
||||
*
|
||||
* @param bool $pfs Whether to use PFS
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setPfs(bool $pfs): self
|
||||
{
|
||||
$this->pfs = $pfs;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get max tries for generating auth key.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMaxAuthTries(): int
|
||||
{
|
||||
return $this->maxAuthTries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set max tries for generating auth key.
|
||||
*
|
||||
* @param int $maxAuthTries Max tries for generating auth key
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setMaxAuthTries(int $maxAuthTries): self
|
||||
{
|
||||
$this->maxAuthTries = $maxAuthTries;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,596 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Settings;
|
||||
|
||||
use danog\MadelineProto\Exception;
|
||||
use danog\MadelineProto\Magic;
|
||||
use danog\MadelineProto\SettingsAbstract;
|
||||
use danog\MadelineProto\Stream\Common\UdpBufferedStream;
|
||||
use danog\MadelineProto\Stream\MTProtoBufferInterface;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\AbridgedStream;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\FullStream;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\HttpsStream;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\HttpStream;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\IntermediatePaddedStream;
|
||||
use danog\MadelineProto\Stream\MTProtoTransport\ObfuscatedStream;
|
||||
use danog\MadelineProto\Stream\Proxy\HttpProxy;
|
||||
use danog\MadelineProto\Stream\Proxy\SocksProxy;
|
||||
use danog\MadelineProto\Stream\RawStreamInterface;
|
||||
use danog\MadelineProto\Stream\StreamInterface;
|
||||
use danog\MadelineProto\Stream\Transport\DefaultStream;
|
||||
use danog\MadelineProto\Stream\Transport\WssStream;
|
||||
use danog\MadelineProto\Stream\Transport\WsStream;
|
||||
|
||||
class Connection extends SettingsAbstract
|
||||
{
|
||||
/**
|
||||
* Minimum media socket count.
|
||||
*/
|
||||
protected int $minMediaSocketCount = 5;
|
||||
/**
|
||||
* Maximum media socket count.
|
||||
*/
|
||||
protected int $maxMediaSocketCount = 10;
|
||||
/**
|
||||
* Robin period (seconds).
|
||||
*/
|
||||
protected int $robinPeriod = 10;
|
||||
/**
|
||||
* Default DC ID.
|
||||
*/
|
||||
protected int $defaultDc = 2;
|
||||
/**
|
||||
* Default DC params.
|
||||
*/
|
||||
private array $defaultDcParams = ['datacenter' => 2];
|
||||
/**
|
||||
* Protocol identifier.
|
||||
*
|
||||
* @var class-string<MTProtoBufferInterface>
|
||||
*/
|
||||
protected string $protocol = AbridgedStream::class;
|
||||
/**
|
||||
* Transport identifier.
|
||||
*
|
||||
* @var class-string<RawStreamInterface>
|
||||
*/
|
||||
protected string $transport = DefaultStream::class;
|
||||
/**
|
||||
* Proxy identifiers.
|
||||
*
|
||||
* @var array<class-string<StreamInterface>, array>
|
||||
*/
|
||||
protected array $proxy = [];
|
||||
/**
|
||||
* Whether to use the obfuscated protocol.
|
||||
*/
|
||||
protected bool $obfuscated = false;
|
||||
|
||||
/**
|
||||
* Whether we're in test mode.
|
||||
*/
|
||||
protected bool $testMode = false;
|
||||
|
||||
/**
|
||||
* Whether to use ipv6.
|
||||
*/
|
||||
protected bool $ipv6;
|
||||
|
||||
/**
|
||||
* Connection timeout.
|
||||
*/
|
||||
protected int $timeout = 2;
|
||||
|
||||
/**
|
||||
* Whether to retry connection.
|
||||
*/
|
||||
protected bool $retry = true;
|
||||
|
||||
/**
|
||||
* Subdomains of web.telegram.org for https protocol.
|
||||
*/
|
||||
protected array $sslSubdomains = [
|
||||
1 => 'pluto',
|
||||
2 => 'venus',
|
||||
3 => 'aurora',
|
||||
4 => 'vesta',
|
||||
5 => 'flora',
|
||||
];
|
||||
|
||||
public function mergeArray(array $settings): void
|
||||
{
|
||||
if (isset($settings['connection']['ssl_subdomains'])) {
|
||||
$this->setSslSubdomains($settings['connection']['ssl_subdomains']);
|
||||
}
|
||||
$settings = $settings['connection_settings'] ?? [];
|
||||
if (isset($settings['media_socket_count']['min'])) {
|
||||
$this->setMinMediaSocketCount($settings['media_socket_count']['min']);
|
||||
}
|
||||
if (isset($settings['media_socket_count']['max'])) {
|
||||
$this->setMaxMediaSocketCount($settings['media_socket_count']['max']);
|
||||
}
|
||||
foreach (self::toCamel([
|
||||
'robin_period',
|
||||
'default_dc',
|
||||
'pfs'
|
||||
]) as $object => $array) {
|
||||
if (isset($settings[$array])) {
|
||||
$this->{$object}($settings[$array]);
|
||||
}
|
||||
}
|
||||
|
||||
$settings = $settings['all'] ?? [];
|
||||
foreach (self::toCamel([
|
||||
'test_mode',
|
||||
'ipv6',
|
||||
'timeout',
|
||||
'obfuscated',
|
||||
]) as $object => $array) {
|
||||
if (isset($settings[$array])) {
|
||||
$this->{$object}($settings[$array]);
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($settings['do_not_retry'])) {
|
||||
$this->setRetry(false);
|
||||
}
|
||||
if (isset($settings['proxy'])) {
|
||||
foreach (\is_iterable($settings['proxy']) ? $settings['proxy'] : [$settings['proxy']] as $key => $proxy) {
|
||||
if ($proxy === '\\Socket') {
|
||||
$proxy = DefaultStream::class;
|
||||
} elseif ($proxy === '\\SocksProxy') {
|
||||
$proxy = SocksProxy::class;
|
||||
} elseif ($proxy === '\\HttpProxy') {
|
||||
$proxy = HttpProxy::class;
|
||||
} elseif ($proxy === '\\MTProxySocket') {
|
||||
$proxy = ObfuscatedStream::class;
|
||||
}
|
||||
if ($proxy !== DefaultStream::class) {
|
||||
$this->addProxy($proxy, $settings['proxy_extra'][$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isset($settings['transport'])) {
|
||||
$transport = $settings['transport'];
|
||||
if ($transport === 'tcp') {
|
||||
$transport = DefaultStream::class;
|
||||
} elseif ($transport === 'ws') {
|
||||
$transport = WsStream::class;
|
||||
} elseif ($transport === 'wss') {
|
||||
$transport = WssStream::class;
|
||||
}
|
||||
$this->setTransport($transport);
|
||||
}
|
||||
if (isset($settings['protocol'])) {
|
||||
$protocol = $settings['protocol'];
|
||||
switch ($protocol) {
|
||||
case 'abridged':
|
||||
case 'tcp_abridged':
|
||||
$protocol = AbridgedStream::class;
|
||||
break;
|
||||
case 'intermediate':
|
||||
case 'tcp_intermediate':
|
||||
$protocol = AbridgedStream::class;
|
||||
break;
|
||||
case 'obfuscated2':
|
||||
$this->setObfuscated(true);
|
||||
// no break
|
||||
case 'intermediate_padded':
|
||||
case 'tcp_intermediate_padded':
|
||||
$protocol = IntermediatePaddedStream::class;
|
||||
break;
|
||||
case 'full':
|
||||
case 'tcp_full':
|
||||
$protocol = FullStream::class;
|
||||
break;
|
||||
case 'http':
|
||||
$protocol = HttpStream::class;
|
||||
break;
|
||||
case 'https':
|
||||
$protocol = HttpsStream::class;
|
||||
break;
|
||||
case 'udp':
|
||||
$protocol = UdpBufferedStream::class;
|
||||
break;
|
||||
}
|
||||
$this->setProtocol($protocol);
|
||||
}
|
||||
}
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->init();
|
||||
}
|
||||
public function __wakeup()
|
||||
{
|
||||
$this->init();
|
||||
}
|
||||
public function init(): void
|
||||
{
|
||||
Magic::classExists(true);
|
||||
|
||||
if (Magic::$altervista) {
|
||||
$this->addProxy(HttpProxy::class, ['address' => 'localhost', 'port' => 80]);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get protocol identifier.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getProtocol(): string
|
||||
{
|
||||
return $this->protocol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set protocol identifier.
|
||||
*
|
||||
* @param class-string<MTProtoBufferInterface> $protocol Protocol identifier
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setProtocol(string $protocol): self
|
||||
{
|
||||
if (!isset(\class_implements($protocol)[MTProtoBufferInterface::class])) {
|
||||
throw new Exception("An invalid protocol was specified!");
|
||||
}
|
||||
$this->protocol = $protocol;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether to use ipv6.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getIpv6(): bool
|
||||
{
|
||||
return $this->ipv6 ?? Magic::$ipv6;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to use ipv6.
|
||||
*
|
||||
* @param bool $ipv6 Whether to use ipv6
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setIpv6(bool $ipv6): self
|
||||
{
|
||||
$this->ipv6 = $ipv6;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subdomains of web.telegram.org for https protocol.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getSslSubdomains(): array
|
||||
{
|
||||
return $this->sslSubdomains;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set subdomains of web.telegram.org for https protocol.
|
||||
*
|
||||
* @param array $sslSubdomains Subdomains of web.telegram.org for https protocol.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setSslSubdomains(array $sslSubdomains): self
|
||||
{
|
||||
$this->sslSubdomains = $sslSubdomains;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get minimum media socket count.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMinMediaSocketCount(): int
|
||||
{
|
||||
return $this->minMediaSocketCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set minimum media socket count.
|
||||
*
|
||||
* @param int $minMediaSocketCount Minimum media socket count.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setMinMediaSocketCount(int $minMediaSocketCount): self
|
||||
{
|
||||
$this->minMediaSocketCount = $minMediaSocketCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maximum media socket count.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMaxMediaSocketCount(): int
|
||||
{
|
||||
return $this->maxMediaSocketCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set maximum media socket count.
|
||||
*
|
||||
* @param int $maxMediaSocketCount Maximum media socket count.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setMaxMediaSocketCount(int $maxMediaSocketCount): self
|
||||
{
|
||||
$this->maxMediaSocketCount = $maxMediaSocketCount;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get robin period (seconds).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getRobinPeriod(): int
|
||||
{
|
||||
return $this->robinPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set robin period (seconds).
|
||||
*
|
||||
* @param int $robinPeriod Robin period (seconds).
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setRobinPeriod(int $robinPeriod): self
|
||||
{
|
||||
$this->robinPeriod = $robinPeriod;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default DC ID.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDefaultDc(): int
|
||||
{
|
||||
return $this->defaultDc;
|
||||
}
|
||||
/**
|
||||
* Get default DC params.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getDefaultDcParams(): array
|
||||
{
|
||||
return $this->defaultDcParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set default DC ID.
|
||||
*
|
||||
* @param int $defaultDc Default DC ID.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setDefaultDc(int $defaultDc): self
|
||||
{
|
||||
$this->defaultDc = $defaultDc;
|
||||
$this->defaultDcParams = ['datacenter' => $defaultDc];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get proxy identifiers.
|
||||
*
|
||||
* @return array<class-string<StreamInterface>, array>
|
||||
*/
|
||||
public function getProxies(): array
|
||||
{
|
||||
return $this->proxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add proxy identifier to list.
|
||||
*
|
||||
* @param class-string<StreamInterface> $proxy Proxy identifier
|
||||
* @param array $extra Extra
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function addProxy(string $proxy, array $extra = []): self
|
||||
{
|
||||
if (!isset(\class_implements($proxy)[StreamInterface::class])) {
|
||||
throw new Exception("An invalid proxy class was specified!");
|
||||
}
|
||||
if (!isset($this->proxy[$proxy])) {
|
||||
$this->proxy[$proxy] = [];
|
||||
}
|
||||
$this->proxy[$proxy][] = $extra;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set proxies.
|
||||
*
|
||||
* @param array $proxies Proxies
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setProxy(array $proxies): self
|
||||
{
|
||||
$this->proxy = $proxies;
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* Clear proxies.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function clearProxies(): self
|
||||
{
|
||||
$this->proxy = [];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove specific proxy pair.
|
||||
*
|
||||
* @param string $proxy
|
||||
* @param array $extra
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function removeProxy(string $proxy, array $extra): self
|
||||
{
|
||||
if (!isset($this->proxy[$proxy])) {
|
||||
return $this;
|
||||
}
|
||||
if (false === $index = \array_search($extra, $this->proxy[$proxy])) {
|
||||
return $this;
|
||||
}
|
||||
unset($this->proxy[$proxy][$index]);
|
||||
if (empty($this->proxy[$proxy])) {
|
||||
unset($this->proxy[$proxy]);
|
||||
}
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* Get whether to use the obfuscated protocol.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getObfuscated(): bool
|
||||
{
|
||||
return $this->obfuscated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to use the obfuscated protocol.
|
||||
*
|
||||
* @param bool $obfuscated Whether to use the obfuscated protocol.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setObfuscated(bool $obfuscated): self
|
||||
{
|
||||
$this->obfuscated = $obfuscated;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether we're in test mode.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getTestMode(): bool
|
||||
{
|
||||
return $this->testMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether we're in test mode.
|
||||
*
|
||||
* @param bool $testMode Whether we're in test mode.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setTestMode(bool $testMode): self
|
||||
{
|
||||
$this->testMode = $testMode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get transport identifier.
|
||||
*
|
||||
* @return class-string<RawStreamInterface>
|
||||
*/
|
||||
public function getTransport(): string
|
||||
{
|
||||
return $this->transport;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set transport identifier.
|
||||
*
|
||||
* @param class-string<RawStreamInterface> $transport Transport identifier.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setTransport(string $transport): self
|
||||
{
|
||||
if (!isset(\class_implements($transport)[RawStreamInterface::class])) {
|
||||
throw new Exception("An invalid transport was specified!");
|
||||
}
|
||||
$this->transport = $transport;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether to retry connection.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getRetry(): bool
|
||||
{
|
||||
return $this->retry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to retry connection.
|
||||
*
|
||||
* @param bool $retry Whether to retry connection.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setRetry(bool $retry): self
|
||||
{
|
||||
$this->retry = $retry;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection timeout.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getTimeout(): int
|
||||
{
|
||||
return $this->timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set connection timeout.
|
||||
*
|
||||
* @param int $timeout Connection timeout.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setTimeout(int $timeout): self
|
||||
{
|
||||
$this->timeout = $timeout;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Settings\Database;
|
||||
|
||||
use danog\MadelineProto\Settings\DatabaseAbstract as SettingsDatabaseAbstract;
|
||||
|
||||
/**
|
||||
* Base class for database backends.
|
||||
*/
|
||||
abstract class DatabaseAbstract extends SettingsDatabaseAbstract
|
||||
{
|
||||
/**
|
||||
* For how long to keep records in memory after last read, for cached backends.
|
||||
*/
|
||||
protected int $cacheTtl = 5 * 60;
|
||||
/**
|
||||
* Database password.
|
||||
*/
|
||||
protected string $password = '';
|
||||
|
||||
public function mergeArray(array $settings): void
|
||||
{
|
||||
foreach (self::toCamel([
|
||||
'database',
|
||||
'password',
|
||||
'cache_ttl'
|
||||
]) as $object => $array) {
|
||||
if (isset($settings[$array])) {
|
||||
if ($array === 'cache_ttl' && \is_string($settings[$array])) {
|
||||
$settings[$array] = \strtotime($settings[$array]);
|
||||
}
|
||||
$this->{$object}($settings[$array]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DB key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getKey(): string
|
||||
{
|
||||
$uri = \parse_url($this->getUri());
|
||||
$host = $uri['host'] ?? '';
|
||||
$port = $uri['port'] ?? '';
|
||||
return "$host:$port:".$this->getDatabase();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get for how long to keep records in memory after last read, for cached backends.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getCacheTtl(): int
|
||||
{
|
||||
return $this->cacheTtl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set for how long to keep records in memory after last read, for cached backends.
|
||||
*
|
||||
* @param int $cacheTtl For how long to keep records in memory after last read, for cached backends.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setCacheTtl(int $cacheTtl): self
|
||||
{
|
||||
$this->cacheTtl = $cacheTtl;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get password.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getPassword(): string
|
||||
{
|
||||
return $this->password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password.
|
||||
*
|
||||
* @param string $password Password.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setPassword(string $password): self
|
||||
{
|
||||
$this->password = $password;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database name/ID.
|
||||
*
|
||||
* @return string|int
|
||||
*/
|
||||
abstract public function getDatabase();
|
||||
/**
|
||||
* Get database URI.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getUri(): string;
|
||||
|
||||
/**
|
||||
* Set database name/ID.
|
||||
*
|
||||
* @param int|string $database
|
||||
* @return self
|
||||
*/
|
||||
abstract public function setDatabase($database): self;
|
||||
/**
|
||||
* Set database URI.
|
||||
*
|
||||
* @param string $uri
|
||||
* @return self
|
||||
*/
|
||||
abstract public function setUri(string $uri): self;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Settings\Database;
|
||||
|
||||
use danog\MadelineProto\Settings\DatabaseAbstract;
|
||||
|
||||
class Memory extends DatabaseAbstract
|
||||
{
|
||||
/**
|
||||
* Whether to cleanup the memory before serializing.
|
||||
*/
|
||||
protected bool $cleanup = false;
|
||||
|
||||
public function mergeArray(array $settings): void
|
||||
{
|
||||
if (isset($settings['serialization']['cleanup_before_serialization'])) {
|
||||
$this->setCleanup($settings['serialization']['cleanup_before_serialization']);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get whether to cleanup the memory before serializing.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getCleanup(): bool
|
||||
{
|
||||
return $this->cleanup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to cleanup the memory before serializing.
|
||||
*
|
||||
* @param bool $cleanup Whether to cleanup the memory before serializing.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setCleanup(bool $cleanup): self
|
||||
{
|
||||
$this->cleanup = $cleanup;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Settings\Database;
|
||||
|
||||
class Mysql extends SqlAbstract
|
||||
{
|
||||
public function mergeArray(array $settings): void
|
||||
{
|
||||
$settings = $settings['db']['mysql'] ?? [];
|
||||
if (isset($settings['host'])) {
|
||||
$this->setUri("tcp://".($settings['host']).(isset($settings['port']) ? ':'.($settings['port']) : ''));
|
||||
}
|
||||
parent::mergeArray($settings);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Settings\Database;
|
||||
|
||||
class Postgres extends SqlAbstract
|
||||
{
|
||||
public function mergeArray(array $settings): void
|
||||
{
|
||||
$settings = $settings['db']['postgres'] ?? [];
|
||||
if (isset($settings['host'])) {
|
||||
$this->setUri("tcp://".($settings['host']).(isset($settings['port']) ? ':'.($settings['port']) : ''));
|
||||
}
|
||||
parent::mergeArray($settings);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Settings\Database;
|
||||
|
||||
class Redis extends DatabaseAbstract
|
||||
{
|
||||
/**
|
||||
* Database number.
|
||||
*/
|
||||
protected int $database = 0;
|
||||
/**
|
||||
* Database URI.
|
||||
*/
|
||||
protected string $uri = 'redis://127.0.0.1';
|
||||
|
||||
public function mergeArray(array $settings): void
|
||||
{
|
||||
$settings = $settings['db']['redis'] ?? [];
|
||||
if (isset($settings['host'])) {
|
||||
$this->setUri($settings['host'].(isset($settings['port']) ? ':'.($settings['port']) : ''));
|
||||
}
|
||||
parent::mergeArray($settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database number.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDatabase(): int
|
||||
{
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set database number.
|
||||
*
|
||||
* @param int $database Database number.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setDatabase($database): self
|
||||
{
|
||||
$this->database = $database;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database URI.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUri(): string
|
||||
{
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set database URI.
|
||||
*
|
||||
* @param string $uri Database URI.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setUri(string $uri): self
|
||||
{
|
||||
$this->uri = $uri;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Settings\Database;
|
||||
|
||||
use danog\MadelineProto\Settings\Database;
|
||||
|
||||
abstract class SqlAbstract extends DatabaseAbstract
|
||||
{
|
||||
/**
|
||||
* Database name.
|
||||
*/
|
||||
protected string $database = 'MadelineProto';
|
||||
/**
|
||||
* Username.
|
||||
*/
|
||||
protected string $username = 'root';
|
||||
|
||||
/**
|
||||
* Maximum connection limit.
|
||||
*/
|
||||
protected int $maxConnections = 10;
|
||||
|
||||
/**
|
||||
* Idle timeout.
|
||||
*/
|
||||
protected int $idleTimeout = 60;
|
||||
|
||||
/**
|
||||
* Database URI.
|
||||
*/
|
||||
protected string $uri = 'tcp://127.0.0.1';
|
||||
|
||||
public function mergeArray(array $settings): void
|
||||
{
|
||||
foreach (self::toCamel([
|
||||
'max_connections',
|
||||
'idle_timeout',
|
||||
]) as $object => $array) {
|
||||
if (isset($settings[$array])) {
|
||||
$this->{$object}($settings[$array]);
|
||||
}
|
||||
}
|
||||
if (isset($settings['user'])) {
|
||||
$this->setUsername($settings['user']);
|
||||
}
|
||||
parent::mergeArray($settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maximum connection limit.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMaxConnections(): int
|
||||
{
|
||||
return $this->maxConnections;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set maximum connection limit.
|
||||
*
|
||||
* @param int $maxConnections Maximum connection limit.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setMaxConnections(int $maxConnections): self
|
||||
{
|
||||
$this->maxConnections = $maxConnections;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get idle timeout.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getIdleTimeout(): int
|
||||
{
|
||||
return $this->idleTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set idle timeout.
|
||||
*
|
||||
* @param int $idleTimeout Idle timeout.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setIdleTimeout(int $idleTimeout): self
|
||||
{
|
||||
$this->idleTimeout = $idleTimeout;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDatabase(): string
|
||||
{
|
||||
return $this->database;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set database name.
|
||||
*
|
||||
* @param string $database Database name.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setDatabase($database): self
|
||||
{
|
||||
$this->database = $database;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get username.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUsername(): string
|
||||
{
|
||||
return $this->username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set username.
|
||||
*
|
||||
* @param string $username Username.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setUsername(string $username): self
|
||||
{
|
||||
$this->username = $username;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database URI.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getUri(): string
|
||||
{
|
||||
return $this->uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set database URI.
|
||||
*
|
||||
* @param string $uri Database URI.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setUri(string $uri): self
|
||||
{
|
||||
$this->uri = $uri;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Settings;
|
||||
|
||||
use danog\MadelineProto\SettingsAbstract;
|
||||
|
||||
/**
|
||||
* Base class for storage backends.
|
||||
*/
|
||||
abstract class DatabaseAbstract extends SettingsAbstract
|
||||
{
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Settings;
|
||||
|
||||
use danog\MadelineProto\SettingsAbstract;
|
||||
|
||||
class Files extends SettingsAbstract
|
||||
{
|
||||
/**
|
||||
* Allow automatic upload of files from file paths present in constructors?
|
||||
*/
|
||||
protected bool $allowAutomaticUpload = true;
|
||||
/**
|
||||
* Upload parallel chunk count.
|
||||
*/
|
||||
protected int $uploadParallelChunks = 20;
|
||||
/**
|
||||
* Download parallel chunk count.
|
||||
*/
|
||||
protected int $downloadParallelChunks = 20;
|
||||
|
||||
/**
|
||||
* Whether to report undownloadable media to TSF.
|
||||
*/
|
||||
protected bool $reportBrokenMedia = true;
|
||||
|
||||
public function mergeArray(array $settings): void
|
||||
{
|
||||
if (isset($settings['upload']['allow_automatic_upload'])) {
|
||||
$this->setAllowAutomaticUpload($settings['upload']['allow_automatic_upload']);
|
||||
}
|
||||
if (isset($settings['download']['report_broken_media'])) {
|
||||
$this->setReportBrokenMedia($settings['download']['report_broken_media']);
|
||||
}
|
||||
if (isset($settings['upload']['parallel_chunks'])) {
|
||||
$this->setUploadParallelChunks($settings['upload']['parallel_chunks']);
|
||||
}
|
||||
if (isset($settings['download']['parallel_chunks'])) {
|
||||
$this->setDownloadParallelChunks($settings['download']['parallel_chunks']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allow automatic upload of files from file paths present in constructors?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getAllowAutomaticUpload(): bool
|
||||
{
|
||||
return $this->allowAutomaticUpload;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set allow automatic upload of files from file paths present in constructors?
|
||||
*
|
||||
* @param bool $allowAutomaticUpload Allow automatic upload of files from file paths present in constructors?
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setAllowAutomaticUpload(bool $allowAutomaticUpload): self
|
||||
{
|
||||
$this->allowAutomaticUpload = $allowAutomaticUpload;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get upload parallel chunk count.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getUploadParallelChunks(): int
|
||||
{
|
||||
return $this->uploadParallelChunks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set upload parallel chunk count.
|
||||
*
|
||||
* @param int $uploadParallelChunks Upload parallel chunk count
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setUploadParallelChunks(int $uploadParallelChunks): self
|
||||
{
|
||||
$this->uploadParallelChunks = $uploadParallelChunks;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get download parallel chunk count.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getDownloadParallelChunks(): int
|
||||
{
|
||||
return $this->downloadParallelChunks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set download parallel chunk count.
|
||||
*
|
||||
* @param int $downloadParallelChunks Download parallel chunk count
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setDownloadParallelChunks(int $downloadParallelChunks): self
|
||||
{
|
||||
$this->downloadParallelChunks = $downloadParallelChunks;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether to report undownloadable media to TSF.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getReportBrokenMedia(): bool
|
||||
{
|
||||
return $this->reportBrokenMedia;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to report undownloadable media to TSF.
|
||||
*
|
||||
* @param bool $reportBrokenMedia Whether to report undownloadable media to TSF
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setReportBrokenMedia(bool $reportBrokenMedia): self
|
||||
{
|
||||
$this->reportBrokenMedia = $reportBrokenMedia;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Settings;
|
||||
|
||||
use danog\MadelineProto\SettingsAbstract;
|
||||
|
||||
class Ipc extends SettingsAbstract
|
||||
{
|
||||
/**
|
||||
* Whether to force full deserialization of instance, without using the IPC server/client.
|
||||
*
|
||||
* WARNING: this will cause slow startup if enabled.
|
||||
*/
|
||||
protected bool $forceFull = false;
|
||||
|
||||
public function mergeArray(array $settings): void
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WARNING: this will cause slow startup if enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getForceFull(): bool
|
||||
{
|
||||
return $this->forceFull;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set WARNING: this will cause slow startup if enabled.
|
||||
*
|
||||
* @param bool $forceFull WARNING: this will cause slow startup if enabled.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setForceFull(bool $forceFull): self
|
||||
{
|
||||
$this->forceFull = $forceFull;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Settings;
|
||||
|
||||
use danog\MadelineProto\Logger as MadelineProtoLogger;
|
||||
use danog\MadelineProto\Magic;
|
||||
use danog\MadelineProto\SettingsAbstract;
|
||||
use danog\MadelineProto\Tools;
|
||||
|
||||
class Logger extends SettingsAbstract
|
||||
{
|
||||
/**
|
||||
* Logger type.
|
||||
*
|
||||
* @var MadelineProtoLogger::LOGGER_* $type Logger type.
|
||||
*/
|
||||
protected int $type;
|
||||
|
||||
/**
|
||||
* Extra parameter for logger.
|
||||
*
|
||||
* @var null|callable|string
|
||||
*/
|
||||
protected $extra;
|
||||
|
||||
/**
|
||||
* Logging level.
|
||||
*
|
||||
* @var MadelineProtoLogger::LEVEL_*
|
||||
*/
|
||||
protected int $level = MadelineProtoLogger::LEVEL_VERBOSE;
|
||||
|
||||
/**
|
||||
* Maximum filesize for logger, in case of file logging.
|
||||
*/
|
||||
protected int $maxSize = 1 * 1024 * 1024;
|
||||
|
||||
public function mergeArray(array $settings): void
|
||||
{
|
||||
if (!isset($settings['logger']['logger_param']) && isset($settings['logger']['param'])) {
|
||||
$settings['logger']['logger_param'] = $settings['logger']['param'];
|
||||
}
|
||||
if (PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' && isset($settings['logger']['logger_param']) && $settings['logger']['logger_param'] === 'MadelineProto.log') {
|
||||
$settings['logger']['logger_param'] = Magic::$script_cwd.'/MadelineProto.log';
|
||||
}
|
||||
switch ($settings['logger']['logger_level'] ?? null) {
|
||||
case 'ULTRA_VERBOSE':
|
||||
$settings['logger']['logger_level'] = 5;
|
||||
break;
|
||||
case 'VERBOSE':
|
||||
$settings['logger']['logger_level'] = 4;
|
||||
break;
|
||||
case 'NOTICE':
|
||||
$settings['logger']['logger_level'] = 3;
|
||||
break;
|
||||
case 'WARNING':
|
||||
$settings['logger']['logger_level'] = 2;
|
||||
break;
|
||||
case 'ERROR':
|
||||
$settings['logger']['logger_level'] = 1;
|
||||
break;
|
||||
case 'FATAL ERROR':
|
||||
$settings['logger']['logger_level'] = 0;
|
||||
break;
|
||||
}
|
||||
if (isset($settings['logger']['logger'])) {
|
||||
$this->setType($settings['logger']['logger']);
|
||||
}
|
||||
if (isset($settings['logger']['logger_param'])) {
|
||||
$this->setExtra($settings['logger']['logger_param']);
|
||||
}
|
||||
if (isset($settings['logger']['logger_level'])) {
|
||||
$this->setLevel($settings['logger']['logger_level']);
|
||||
}
|
||||
if (isset($settings['logger']['max_size'])) {
|
||||
$this->setMaxSize($settings['logger']['max_size'] ?? 1 * 1024 * 1024);
|
||||
}
|
||||
|
||||
$this->init();
|
||||
}
|
||||
public function __construct()
|
||||
{
|
||||
$this->type = (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg')
|
||||
? MadelineProtoLogger::ECHO_LOGGER
|
||||
: MadelineProtoLogger::FILE_LOGGER;
|
||||
$this->extra = Magic::$script_cwd.'/MadelineProto.log';
|
||||
}
|
||||
|
||||
public function __sleep()
|
||||
{
|
||||
return $this->extra instanceof \Closure
|
||||
? ['type', 'extra', 'level', 'maxSize']
|
||||
: ['type', 'level', 'maxSize'];
|
||||
}
|
||||
/**
|
||||
* Wakeup function.
|
||||
*/
|
||||
public function __wakeup()
|
||||
{
|
||||
$this->type = (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg')
|
||||
? MadelineProtoLogger::ECHO_LOGGER
|
||||
: MadelineProtoLogger::FILE_LOGGER;
|
||||
if (!$this->extra && $this->type === MadelineProtoLogger::FILE_LOGGER) {
|
||||
$this->extra = Magic::$script_cwd.'/MadelineProto.log';
|
||||
}
|
||||
|
||||
$this->init();
|
||||
}
|
||||
/**
|
||||
* Initialize global logging.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function init()
|
||||
{
|
||||
Magic::classExists(false);
|
||||
MadelineProtoLogger::constructorFromSettings($this);
|
||||
}
|
||||
/**
|
||||
* Get $type Logger type.
|
||||
*
|
||||
* @return MadelineProtoLogger::LOGGER_*
|
||||
*/
|
||||
public function getType(): int
|
||||
{
|
||||
return \defined(\MADELINE_WORKER::class) ? MadelineProtoLogger::FILE_LOGGER : $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set $type Logger type.
|
||||
*
|
||||
* @param MadelineProtoLogger::LOGGER_* $type $type Logger type.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setType(int $type): self
|
||||
{
|
||||
if ($type === MadelineProtoLogger::NO_LOGGER) {
|
||||
$type = (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg')
|
||||
? MadelineProtoLogger::ECHO_LOGGER
|
||||
: MadelineProtoLogger::FILE_LOGGER;
|
||||
}
|
||||
$this->type = $type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extra parameter for logger.
|
||||
*
|
||||
* @return null|callable|string
|
||||
*/
|
||||
public function getExtra()
|
||||
{
|
||||
return $this->type === MadelineProtoLogger::FILE_LOGGER
|
||||
? Tools::absolute($this->extra)
|
||||
: $this->extra;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set extra parameter for logger.
|
||||
*
|
||||
* @param null|callable|string $extra Extra parameter for logger.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setExtra($extra): self
|
||||
{
|
||||
$this->extra = $extra;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get logging level.
|
||||
*
|
||||
* @return MadelineProtoLogger::LEVEL_*
|
||||
*/
|
||||
public function getLevel(): int
|
||||
{
|
||||
return $this->level;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set logging level.
|
||||
*
|
||||
* @param MadelineProtoLogger::LEVEL_* $level Logging level.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setLevel(int $level): self
|
||||
{
|
||||
$this->level = \max($level, MadelineProtoLogger::NOTICE);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get maximum filesize for logger, in case of file logging.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getMaxSize(): int
|
||||
{
|
||||
return $this->maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set maximum filesize for logger, in case of file logging.
|
||||
*
|
||||
* @param int $maxSize Maximum filesize for logger, in case of file logging.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setMaxSize(int $maxSize): self
|
||||
{
|
||||
$this->maxSize = \max($maxSize, 100 * 1024);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Settings;
|
||||
|
||||
use danog\MadelineProto\SettingsAbstract;
|
||||
|
||||
class Peer extends SettingsAbstract
|
||||
{
|
||||
/**
|
||||
* Cache time for full peer information (seconds).
|
||||
*/
|
||||
protected int $fullInfoCacheTime = 60*60;
|
||||
/**
|
||||
* Should madeline fetch the full member list of every group it meets?
|
||||
*/
|
||||
protected bool $fullFetch = false;
|
||||
/**
|
||||
* Whether to cache all peers on startup for userbots.
|
||||
*/
|
||||
protected bool $cacheAllPeersOnStartup = false;
|
||||
|
||||
public function mergeArray(array $settings): void
|
||||
{
|
||||
foreach (self::toCamel([
|
||||
'full_info_cache_time',
|
||||
'full_fetch',
|
||||
'cache_all_peers_on_startup'
|
||||
]) as $object => $array) {
|
||||
if (isset($settings['peer'][$array])) {
|
||||
$this->{$object}($settings['peer'][$array]);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get cache time for full peer information (seconds).
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getFullInfoCacheTime(): int
|
||||
{
|
||||
return $this->fullInfoCacheTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cache time for full peer information (seconds).
|
||||
*
|
||||
* @param int $fullInfoCacheTime Cache time for full peer information (seconds).
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setFullInfoCacheTime(int $fullInfoCacheTime): self
|
||||
{
|
||||
$this->fullInfoCacheTime = $fullInfoCacheTime;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get should madeline fetch the full member list of every group it meets?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getFullFetch(): bool
|
||||
{
|
||||
return $this->fullFetch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set should madeline fetch the full member list of every group it meets?
|
||||
*
|
||||
* @param bool $fullFetch Should madeline fetch the full member list of every group it meets?
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setFullFetch(bool $fullFetch): self
|
||||
{
|
||||
$this->fullFetch = $fullFetch;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether to cache all peers on startup for userbots.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getCacheAllPeersOnStartup(): bool
|
||||
{
|
||||
return $this->cacheAllPeersOnStartup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to cache all peers on startup for userbots.
|
||||
*
|
||||
* @param bool $cacheAllPeersOnStartup Whether to cache all peers on startup for userbots.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setCacheAllPeersOnStartup(bool $cacheAllPeersOnStartup): self
|
||||
{
|
||||
$this->cacheAllPeersOnStartup = $cacheAllPeersOnStartup;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
|
||||
namespace danog\MadelineProto\Settings;
|
||||
|
||||
use danog\MadelineProto\SettingsAbstract;
|
||||
|
||||
class Pwr extends SettingsAbstract
|
||||
{
|
||||
/**
|
||||
* Whether to try resolving usernames using PWRTelegram DB.
|
||||
*/
|
||||
protected bool $requests = true;
|
||||
/**
|
||||
* DB token.
|
||||
*/
|
||||
protected string $dbToken = '';
|
||||
|
||||
public function mergeArray(array $settings): void
|
||||
{
|
||||
$this->requests = $settings['pwr']['requests'] ?? true;
|
||||
$this->dbToken = $settings['pwr']['db_token'] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether to try resolving usernames using PWRTelegram DB.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function getRequests(): bool
|
||||
{
|
||||
return $this->requests;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set whether to try resolving usernames using PWRTelegram DB.
|
||||
*
|
||||
* @param bool $requests Whether to try resolving usernames using PWRTelegram DB.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setRequests(bool $requests): self
|
||||
{
|
||||
$this->requests = $requests;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get DB token.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getDbToken(): string
|
||||
{
|
||||
return $this->dbToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set DB token.
|
||||
*
|
||||
* @param string $dbToken DB token.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setDbToken(string $dbToken): self
|
||||
{
|
||||
$this->dbToken = $dbToken;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue