From 0001a45cd233701e9b67ef4e443fe8087cb4d6ee Mon Sep 17 00:00:00 2001 From: Daniil Gentili Date: Tue, 22 Sep 2020 11:48:12 +0200 Subject: [PATCH] Completely refactor settings, remove old (internal and external) APIs, generic code cleanup --- composer.json | 2 +- src/danog/MadelineProto/API.php | 51 +- src/danog/MadelineProto/APIWrapper.php | 14 - .../MadelineProto/AbstractAPIFactory.php | 4 +- src/danog/MadelineProto/ApiWrappers/Start.php | 7 +- src/danog/MadelineProto/CombinedAPI.php | 6 +- src/danog/MadelineProto/Connection.php | 4 +- src/danog/MadelineProto/DataCenter.php | 223 +++--- .../MadelineProto/DataCenterConnection.php | 27 +- .../MadelineProto/Db/ArrayCacheTrait.php | 16 +- .../MadelineProto/Db/DbPropertiesFactory.php | 25 +- .../MadelineProto/Db/DbPropertiesTrait.php | 2 +- src/danog/MadelineProto/Db/DbType.php | 5 +- src/danog/MadelineProto/Db/Driver/Mysql.php | 25 +- .../MadelineProto/Db/Driver/Postgres.php | 25 +- src/danog/MadelineProto/Db/Driver/Redis.php | 17 +- src/danog/MadelineProto/Db/DriverArray.php | 14 +- src/danog/MadelineProto/Db/MemoryArray.php | 16 +- src/danog/MadelineProto/Db/MysqlArray.php | 27 +- src/danog/MadelineProto/Db/PostgresArray.php | 27 +- src/danog/MadelineProto/Db/RedisArray.php | 24 +- src/danog/MadelineProto/Db/SqlArray.php | 11 +- src/danog/MadelineProto/DocsBuilder.php | 5 +- src/danog/MadelineProto/EventHandler.php | 15 +- src/danog/MadelineProto/InternalDoc.php | 4 +- src/danog/MadelineProto/Ipc/Client.php | 15 +- src/danog/MadelineProto/Logger.php | 187 ++--- .../Loop/Connection/CheckLoop.php | 2 +- .../Loop/Connection/PingLoop.php | 2 +- .../Loop/Connection/WriteLoop.php | 6 +- .../MadelineProto/Loop/Update/FeedLoop.php | 10 +- .../MadelineProto/Loop/Update/SeqLoop.php | 10 +- .../MadelineProto/Loop/Update/UpdateLoop.php | 8 +- src/danog/MadelineProto/Lua.php | 5 +- src/danog/MadelineProto/MTProto.php | 678 +++++------------- .../MTProtoSession/AckHandler.php | 16 +- .../MsgIdHandler/MsgIdHandler32.php | 7 +- .../MsgIdHandler/MsgIdHandler64.php | 4 +- .../MTProtoSession/ResponseHandler.php | 7 +- .../MadelineProto/MTProtoSession/Session.php | 2 + .../MTProtoTools/AuthKeyHandler.php | 20 +- .../MTProtoTools/CallHandler.php | 4 + .../MadelineProto/MTProtoTools/Files.php | 24 +- .../MTProtoTools/PeerHandler.php | 20 +- .../MTProtoTools/ReferenceDatabase.php | 18 +- .../MTProtoTools/UpdateHandler.php | 116 +-- src/danog/MadelineProto/Magic.php | 3 +- .../MadelineProto/MyTelegramOrgWrapper.php | 23 +- src/danog/MadelineProto/Settings.php | 498 +++++++++++++ src/danog/MadelineProto/Settings/AppInfo.php | 298 ++++++++ src/danog/MadelineProto/Settings/Auth.php | 148 ++++ .../MadelineProto/Settings/Connection.php | 620 ++++++++++++++++ .../Settings/Database/DatabaseAbstract.php | 125 ++++ .../Settings/Database/Memory.php | 43 ++ .../MadelineProto/Settings/Database/Mysql.php | 15 + .../Settings/Database/Postgres.php | 15 + .../MadelineProto/Settings/Database/Redis.php | 72 ++ .../Settings/Database/SqlAbstract.php | 167 +++++ .../Settings/DatabaseAbstract.php | 12 + src/danog/MadelineProto/Settings/Files.php | 138 ++++ src/danog/MadelineProto/Settings/Logger.php | 217 ++++++ src/danog/MadelineProto/Settings/Peer.php | 105 +++ src/danog/MadelineProto/Settings/Pwr.php | 71 ++ src/danog/MadelineProto/Settings/RPC.php | 202 ++++++ .../MadelineProto/Settings/SecretChats.php | 66 ++ .../MadelineProto/Settings/Serialization.php | 43 ++ src/danog/MadelineProto/Settings/TLSchema.php | 207 ++++++ src/danog/MadelineProto/SettingsAbstract.php | 57 ++ src/danog/MadelineProto/SettingsEmpty.php | 10 + src/danog/MadelineProto/Snitch.php | 1 + .../Stream/Common/UdpBufferedStream.php | 3 +- .../Stream/ConnectionContext.php | 4 +- src/danog/MadelineProto/TL/TL.php | 21 +- .../MadelineProto/TON/ADNLConnection.php | 2 +- .../MadelineProto/VoIP/AuthKeyHandler.php | 6 +- src/danog/MadelineProto/Wrappers/Callback.php | 4 +- .../MadelineProto/Wrappers/DialogHandler.php | 7 + src/danog/MadelineProto/Wrappers/Events.php | 56 +- src/danog/MadelineProto/Wrappers/Login.php | 80 ++- src/danog/MadelineProto/Wrappers/Loop.php | 20 +- src/danog/MadelineProto/Wrappers/Noop.php | 4 +- src/danog/MadelineProto/Wrappers/Start.php | 4 + src/danog/MadelineProto/Wrappers/Webhook.php | 40 +- 83 files changed, 4042 insertions(+), 1122 deletions(-) create mode 100644 src/danog/MadelineProto/Settings.php create mode 100644 src/danog/MadelineProto/Settings/AppInfo.php create mode 100644 src/danog/MadelineProto/Settings/Auth.php create mode 100644 src/danog/MadelineProto/Settings/Connection.php create mode 100644 src/danog/MadelineProto/Settings/Database/DatabaseAbstract.php create mode 100644 src/danog/MadelineProto/Settings/Database/Memory.php create mode 100644 src/danog/MadelineProto/Settings/Database/Mysql.php create mode 100644 src/danog/MadelineProto/Settings/Database/Postgres.php create mode 100644 src/danog/MadelineProto/Settings/Database/Redis.php create mode 100644 src/danog/MadelineProto/Settings/Database/SqlAbstract.php create mode 100644 src/danog/MadelineProto/Settings/DatabaseAbstract.php create mode 100644 src/danog/MadelineProto/Settings/Files.php create mode 100644 src/danog/MadelineProto/Settings/Logger.php create mode 100644 src/danog/MadelineProto/Settings/Peer.php create mode 100644 src/danog/MadelineProto/Settings/Pwr.php create mode 100644 src/danog/MadelineProto/Settings/RPC.php create mode 100644 src/danog/MadelineProto/Settings/SecretChats.php create mode 100644 src/danog/MadelineProto/Settings/Serialization.php create mode 100644 src/danog/MadelineProto/Settings/TLSchema.php create mode 100644 src/danog/MadelineProto/SettingsAbstract.php create mode 100644 src/danog/MadelineProto/SettingsEmpty.php diff --git a/composer.json b/composer.json index 79c419f0..f07700cf 100644 --- a/composer.json +++ b/composer.json @@ -12,6 +12,7 @@ "php": ">=7.4.0", "danog/primemodule": "^1", "erusev/parsedown": "^1.7", + "symfony/polyfill-mbstring": "*", "ext-mbstring": "*", "ext-json": "*", "ext-xml": "*", @@ -34,7 +35,6 @@ "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", diff --git a/src/danog/MadelineProto/API.php b/src/danog/MadelineProto/API.php index 10590f70..36f8afe0 100644 --- a/src/danog/MadelineProto/API.php +++ b/src/danog/MadelineProto/API.php @@ -20,6 +20,7 @@ namespace danog\MadelineProto; use Amp\Promise; +use danog\MadelineProto\Settings\Logger as SettingsLogger; /** * Main API wrapper for MadelineProto. @@ -94,7 +95,7 @@ class API extends InternalDoc /** * Global session unlock callback. * - * @var callback + * @var callable */ private $unlock; @@ -102,13 +103,14 @@ class API extends InternalDoc /** * 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 { + $settings = Settings::parseFromLegacy($settings); $this->wrapper = new APIWrapper($this, $this->exportNamespace()); Magic::classExists(); @@ -125,14 +127,16 @@ class API extends InternalDoc /** * Async constructor function. * - * @param string $session Session name - * @param array $settings Settings + * @param string $session Session name + * @param Settings|SettingsEmpty $settings Settings * * @return \Generator */ - public function __construct_async(string $session, array $settings = []): \Generator + public function __construct_async(string $session, SettingsAbstract $settings): \Generator { - Logger::constructorFromSettings($settings); + Logger::constructorFromSettings($settings instanceof SettingsEmpty + ? new SettingsLogger + : $settings->getLogger()); $this->session = $session = Tools::absolute($session); [$unserialized, $this->unlock] = yield from Serialization::legacyUnserialize($session); if ($unserialized) { @@ -146,25 +150,28 @@ class API extends InternalDoc unset($unserialized); - $this->API->wrapper = $this->wrapper; - yield from $this->API->initAsynchronously(); + yield from $this->API->wakeup($settings, $this->wrapper); $this->APIFactory(); $this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE); return; } } - if (!isset($settings['app_info']['api_id']) || !$settings['app_info']['api_id']) { - $app = (yield from $this->APIStart($settings)); + if ($settings instanceof SettingsEmpty) { + $settings = new Settings; + } + + $appInfo = $settings->getAppInfo(); + if (!$appInfo->hasApiInfo()) { + $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']; + $appInfo->setApiId($app['api_id']); + $appInfo->setApiHash($app['api_hash']); } - $this->API = new MTProto($settings); - $this->API->wrapper = $this->wrapper; + $this->API = new MTProto($settings, $this->wrapper); yield from $this->API->initAsynchronously(); $this->APIFactory(); $this->logger->logger(Lang::$current_lang['madelineproto_ready'], Logger::NOTICE); @@ -218,10 +225,6 @@ class API extends InternalDoc } } $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); - } } } @@ -288,6 +291,7 @@ class API extends InternalDoc */ public function startAndLoopAsync(string $eventHandler): \Generator { + $errors = []; $this->async(true); while (true) { try { @@ -295,6 +299,13 @@ class API extends InternalDoc 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()) { + $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"); } diff --git a/src/danog/MadelineProto/APIWrapper.php b/src/danog/MadelineProto/APIWrapper.php index 85f23d68..d23c5fc1 100644 --- a/src/danog/MadelineProto/APIWrapper.php +++ b/src/danog/MadelineProto/APIWrapper.php @@ -204,23 +204,9 @@ final class APIWrapper $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(); } Logger::log('Done serializing'); diff --git a/src/danog/MadelineProto/AbstractAPIFactory.php b/src/danog/MadelineProto/AbstractAPIFactory.php index c52540d8..3669689b 100644 --- a/src/danog/MadelineProto/AbstractAPIFactory.php +++ b/src/danog/MadelineProto/AbstractAPIFactory.php @@ -177,8 +177,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 $value Value + * @param string $class Custom class name * * @return array */ diff --git a/src/danog/MadelineProto/ApiWrappers/Start.php b/src/danog/MadelineProto/ApiWrappers/Start.php index 7c32c520..e5b50788 100644 --- a/src/danog/MadelineProto/ApiWrappers/Start.php +++ b/src/danog/MadelineProto/ApiWrappers/Start.php @@ -20,6 +20,7 @@ namespace danog\MadelineProto\ApiWrappers; use danog\MadelineProto\MyTelegramOrgWrapper; +use danog\MadelineProto\Settings; use danog\MadelineProto\Tools; use function Amp\ByteStream\getStdout; @@ -31,11 +32,11 @@ 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!'); @@ -111,7 +112,7 @@ Note that you can also provide the API parameters directly in the code using the } return null; } - private function webAPIPhoneLogin(array $settings): \Generator + private function webAPIPhoneLogin(Settings $settings): \Generator { try { $this->myTelegramOrgWrapper = new MyTelegramOrgWrapper($settings); diff --git a/src/danog/MadelineProto/CombinedAPI.php b/src/danog/MadelineProto/CombinedAPI.php index 9adab900..ba95959e 100644 --- a/src/danog/MadelineProto/CombinedAPI.php +++ b/src/danog/MadelineProto/CombinedAPI.php @@ -78,7 +78,7 @@ class CombinedAPI } return; } - \danog\MadelineProto\Logger::constructor(3); + //\danog\MadelineProto\Logger::constructor(3); \danog\MadelineProto\Logger::log("INSTANTIATING {$path}..."); $instance = new \danog\MadelineProto\API($path, $settings); $this->instance_paths[$path] = $path; @@ -201,10 +201,6 @@ class CombinedAPI 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]); diff --git a/src/danog/MadelineProto/Connection.php b/src/danog/MadelineProto/Connection.php index 22a6bd77..6095b681 100644 --- a/src/danog/MadelineProto/Connection.php +++ b/src/danog/MadelineProto/Connection.php @@ -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) { diff --git a/src/danog/MadelineProto/DataCenter.php b/src/danog/MadelineProto/DataCenter.php index f7a2ee8b..ff1dc58d 100644 --- a/src/danog/MadelineProto/DataCenter.php +++ b/src/danog/MadelineProto/DataCenter.php @@ -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->haveChanged(); if (!$reconnectAll) { $changed = []; $test = $API->getCachedConfig()['test_mode'] ?? false ? 'test' : 'main'; @@ -238,6 +241,7 @@ class DataCenter $this->webSocketConnnector = new Rfc6455Connector($this->HTTPClient); } } + $this->settings->applyChanges(); } /** * Set VoIP endpoints. @@ -304,147 +308,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 +439,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 +447,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 +648,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); } } diff --git a/src/danog/MadelineProto/DataCenterConnection.php b/src/danog/MadelineProto/DataCenterConnection.php index d2e386b1..990c0356 100644 --- a/src/danog/MadelineProto/DataCenterConnection.php +++ b/src/danog/MadelineProto/DataCenterConnection.php @@ -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. diff --git a/src/danog/MadelineProto/Db/ArrayCacheTrait.php b/src/danog/MadelineProto/Db/ArrayCacheTrait.php index da4ba3e9..5649a929 100644 --- a/src/danog/MadelineProto/Db/ArrayCacheTrait.php +++ b/src/danog/MadelineProto/Db/ArrayCacheTrait.php @@ -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 { diff --git a/src/danog/MadelineProto/Db/DbPropertiesFactory.php b/src/danog/MadelineProto/Db/DbPropertiesFactory.php index 3a7cf1fa..d12456d5 100644 --- a/src/danog/MadelineProto/Db/DbPropertiesFactory.php +++ b/src/danog/MadelineProto/Db/DbPropertiesFactory.php @@ -3,11 +3,16 @@ 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 $name @@ -16,29 +21,29 @@ class DbPropertiesFactory * @return Promise * * @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, string $propertyType, string $name, $value = null): Promise { $class = __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 +56,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); } } diff --git a/src/danog/MadelineProto/Db/DbPropertiesTrait.php b/src/danog/MadelineProto/Db/DbPropertiesTrait.php index a23bcec4..809d5f4a 100644 --- a/src/danog/MadelineProto/Db/DbPropertiesTrait.php +++ b/src/danog/MadelineProto/Db/DbPropertiesTrait.php @@ -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) { diff --git a/src/danog/MadelineProto/Db/DbType.php b/src/danog/MadelineProto/Db/DbType.php index 9c8b3050..af39e339 100644 --- a/src/danog/MadelineProto/Db/DbType.php +++ b/src/danog/MadelineProto/Db/DbType.php @@ -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 */ - 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; } diff --git a/src/danog/MadelineProto/Db/Driver/Mysql.php b/src/danog/MadelineProto/Db/Driver/Mysql.php index 8d216b6e..c15856ba 100644 --- a/src/danog/MadelineProto/Db/Driver/Mysql.php +++ b/src/danog/MadelineProto/Db/Driver/Mysql.php @@ -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 */ - 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]; diff --git a/src/danog/MadelineProto/Db/Driver/Postgres.php b/src/danog/MadelineProto/Db/Driver/Postgres.php index 01f82711..b32f7bd3 100644 --- a/src/danog/MadelineProto/Db/Driver/Postgres.php +++ b/src/danog/MadelineProto/Db/Driver/Postgres.php @@ -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 */ - 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]; diff --git a/src/danog/MadelineProto/Db/Driver/Redis.php b/src/danog/MadelineProto/Db/Driver/Redis.php index 48dae48c..406bd9c9 100644 --- a/src/danog/MadelineProto/Db/Driver/Redis.php +++ b/src/danog/MadelineProto/Db/Driver/Redis.php @@ -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 */ - 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(); diff --git a/src/danog/MadelineProto/Db/DriverArray.php b/src/danog/MadelineProto/Db/DriverArray.php index 60119156..c308198a 100644 --- a/src/danog/MadelineProto/Db/DriverArray.php +++ b/src/danog/MadelineProto/Db/DriverArray.php @@ -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 protected function initConnection($settings): \Generator; /** * @param self $new diff --git a/src/danog/MadelineProto/Db/MemoryArray.php b/src/danog/MadelineProto/Db/MemoryArray.php index ff4e00e1..ab15956d 100644 --- a/src/danog/MadelineProto/Db/MemoryArray.php +++ b/src/danog/MadelineProto/Db/MemoryArray.php @@ -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); diff --git a/src/danog/MadelineProto/Db/MysqlArray.php b/src/danog/MadelineProto/Db/MysqlArray.php index 3f2ec726..83a5d3c4 100644 --- a/src/danog/MadelineProto/Db/MysqlArray.php +++ b/src/danog/MadelineProto/Db/MysqlArray.php @@ -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 + */ + protected 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); } } diff --git a/src/danog/MadelineProto/Db/PostgresArray.php b/src/danog/MadelineProto/Db/PostgresArray.php index 5a9e1a2e..f7689176 100644 --- a/src/danog/MadelineProto/Db/PostgresArray.php +++ b/src/danog/MadelineProto/Db/PostgresArray.php @@ -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 + */ + protected 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']; } /** diff --git a/src/danog/MadelineProto/Db/RedisArray.php b/src/danog/MadelineProto/Db/RedisArray.php index 0edf4a64..c296c930 100644 --- a/src/danog/MadelineProto/Db/RedisArray.php +++ b/src/danog/MadelineProto/Db/RedisArray.php @@ -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 + */ + protected 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. * diff --git a/src/danog/MadelineProto/Db/SqlArray.php b/src/danog/MadelineProto/Db/SqlArray.php index ed72484a..0b191b4d 100644 --- a/src/danog/MadelineProto/Db/SqlArray.php +++ b/src/danog/MadelineProto/Db/SqlArray.php @@ -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); diff --git a/src/danog/MadelineProto/DocsBuilder.php b/src/danog/MadelineProto/DocsBuilder.php index d74a4b38..ec7ece32 100644 --- a/src/danog/MadelineProto/DocsBuilder.php +++ b/src/danog/MadelineProto/DocsBuilder.php @@ -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['tl_schema']); + $this->TL->init($new); if (isset($settings['tl_schema']['td']) && !isset($settings['tl_schema']['telegram'])) { $this->td = true; } diff --git a/src/danog/MadelineProto/EventHandler.php b/src/danog/MadelineProto/EventHandler.php index e6473478..7f220831 100644 --- a/src/danog/MadelineProto/EventHandler.php +++ b/src/danog/MadelineProto/EventHandler.php @@ -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) { diff --git a/src/danog/MadelineProto/InternalDoc.php b/src/danog/MadelineProto/InternalDoc.php index d9c58f3f..ba49f597 100644 --- a/src/danog/MadelineProto/InternalDoc.php +++ b/src/danog/MadelineProto/InternalDoc.php @@ -5249,9 +5249,9 @@ class InternalDoc extends APIFactory /** * Return current settings array. * - * @return array + * @return Settings */ - public function getSettings(): array + public function getSettings(): Settings { return $this->API->getSettings(); } diff --git a/src/danog/MadelineProto/Ipc/Client.php b/src/danog/MadelineProto/Ipc/Client.php index 5d8821a8..d257097c 100644 --- a/src/danog/MadelineProto/Ipc/Client.php +++ b/src/danog/MadelineProto/Ipc/Client.php @@ -55,7 +55,7 @@ class Client { $this->logger = $logger; $this->server = $server; - Tools::callFork($this->loop()); + Tools::callFork($this->loopInternal()); } /** * Logger. @@ -78,7 +78,7 @@ class Client * * @return \Generator */ - private function loop(): \Generator + private function loopInternal(): \Generator { while ($payload = yield $this->server->receive()) { [$id, $payload] = $payload; @@ -96,6 +96,17 @@ class Client } } } + /** + * Run the provided async callable. + * + * @param callable $callback Async callable to run + * + * @return mixed + */ + public function loop(callable $callback): \Generator + { + return yield $callback(); + } /** * Unreference. * diff --git a/src/danog/MadelineProto/Logger.php b/src/danog/MadelineProto/Logger.php index 6713ce24..806cade6 100644 --- a/src/danog/MadelineProto/Logger.php +++ b/src/danog/MadelineProto/Logger.php @@ -22,6 +22,7 @@ namespace danog\MadelineProto; use Amp\ByteStream\ResourceOutputStream; use Amp\Failure; use Amp\Loop; +use danog\MadelineProto\Settings\Logger as SettingsLogger; use function Amp\ByteStream\getStderr; use function Amp\ByteStream\getStdout; @@ -44,7 +45,7 @@ class Logger /** * Optional logger parameter. * - * @var mixed + * @var null|string|callable */ public $optional = null; /** @@ -80,7 +81,7 @@ class Logger /** * Default logger instance. * - * @var self + * @var ?self */ public static $default; /** @@ -93,116 +94,104 @@ class Logger * Log rotation loop ID. */ private string $rotateId = ''; + /** + * 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 + * @param SettingsLogger $settings Settings instance * * @return void */ - public static function constructorFromSettings(array $settings) + public static function constructorFromSettings(SettingsLogger $settings): void { 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)); + self::$default = new self($settings); } } - /** - * Get logger from MadelineProto settings. - * - * @param array $settings Settings array - * @param string $prefix Optional prefix for log messages - * - * @return self - */ - public static function getLoggerFromSettings(array $settings, string $prefix = ''): 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; - } - /** - * 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->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 +208,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 +233,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. diff --git a/src/danog/MadelineProto/Loop/Connection/CheckLoop.php b/src/danog/MadelineProto/Loop/Connection/CheckLoop.php index b04a1c39..5a86b5f6 100644 --- a/src/danog/MadelineProto/Loop/Connection/CheckLoop.php +++ b/src/danog/MadelineProto/Loop/Connection/CheckLoop.php @@ -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 diff --git a/src/danog/MadelineProto/Loop/Connection/PingLoop.php b/src/danog/MadelineProto/Loop/Connection/PingLoop.php index 03d8720e..81729dec 100644 --- a/src/danog/MadelineProto/Loop/Connection/PingLoop.php +++ b/src/danog/MadelineProto/Loop/Connection/PingLoop.php @@ -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()) { diff --git a/src/danog/MadelineProto/Loop/Connection/WriteLoop.php b/src/danog/MadelineProto/Loop/Connection/WriteLoop.php index 18e2ad0b..5bca7562 100644 --- a/src/danog/MadelineProto/Loop/Connection/WriteLoop.php +++ b/src/danog/MadelineProto/Loop/Connection/WriteLoop.php @@ -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]); diff --git a/src/danog/MadelineProto/Loop/Update/FeedLoop.php b/src/danog/MadelineProto/Loop/Update/FeedLoop.php index f57302da..ea7992b9 100644 --- a/src/danog/MadelineProto/Loop/Update/FeedLoop.php +++ b/src/danog/MadelineProto/Loop/Update/FeedLoop.php @@ -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; diff --git a/src/danog/MadelineProto/Loop/Update/SeqLoop.php b/src/danog/MadelineProto/Loop/Update/SeqLoop.php index 61e0b7e5..518150ef 100644 --- a/src/danog/MadelineProto/Loop/Update/SeqLoop.php +++ b/src/danog/MadelineProto/Loop/Update/SeqLoop.php @@ -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 = []; diff --git a/src/danog/MadelineProto/Loop/Update/UpdateLoop.php b/src/danog/MadelineProto/Loop/Update/UpdateLoop.php index 214ed14b..d1fc05a4 100644 --- a/src/danog/MadelineProto/Loop/Update/UpdateLoop.php +++ b/src/danog/MadelineProto/Loop/Update/UpdateLoop.php @@ -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': diff --git a/src/danog/MadelineProto/Lua.php b/src/danog/MadelineProto/Lua.php index 5f8def07..c6d86ae1 100644 --- a/src/danog/MadelineProto/Lua.php +++ b/src/danog/MadelineProto/Lua.php @@ -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(); } diff --git a/src/danog/MadelineProto/MTProto.php b/src/danog/MadelineProto/MTProto.php index 777c76a5..abf9c7fb 100644 --- a/src/danog/MadelineProto/MTProto.php +++ b/src/danog/MadelineProto/MTProto.php @@ -23,12 +23,11 @@ use Amp\Dns\Resolver; use Amp\File\StatCache; use Amp\Http\Client\HttpClient; use Amp\Promise; +use Closure; use danog\MadelineProto\Async\AsyncConstruct; use danog\MadelineProto\Db\DbArray; use danog\MadelineProto\Db\DbPropertiesFactory; use danog\MadelineProto\Db\DbPropertiesTrait; -use danog\MadelineProto\Db\Driver\Redis; -use danog\MadelineProto\Db\Mysql; use danog\MadelineProto\Ipc\Server; use danog\MadelineProto\Loop\Generic\PeriodicLoopInternal; use danog\MadelineProto\Loop\Update\FeedLoop; @@ -39,6 +38,8 @@ use danog\MadelineProto\MTProtoTools\GarbageCollector; use danog\MadelineProto\MTProtoTools\MinDatabase; use danog\MadelineProto\MTProtoTools\ReferenceDatabase; use danog\MadelineProto\MTProtoTools\UpdatesState; +use danog\MadelineProto\Settings\Database\Memory; +use danog\MadelineProto\Settings\TLSchema; use danog\MadelineProto\TL\TL; use danog\MadelineProto\TL\TLCallback; @@ -160,6 +161,7 @@ class MTProto extends AsyncConstruct implements TLCallback * @var int */ const SECRET_READY = 2; + const GETUPDATES_HANDLER = 'getUpdates'; const TD_PARAMS_CONVERSION = ['updateNewMessage' => ['_' => 'updateNewMessage', 'disable_notification' => ['message', 'silent'], 'message' => ['message']], 'message' => ['_' => 'message', 'id' => ['id'], 'sender_user_id' => ['from_id'], 'chat_id' => ['to_id', 'choose_chat_id_from_botapi'], 'send_state' => ['choose_incoming_or_sent'], 'can_be_edited' => ['choose_can_edit'], 'can_be_deleted' => ['choose_can_delete'], 'is_post' => ['post'], 'date' => ['date'], 'edit_date' => ['edit_date'], 'forward_info' => ['fwd_info', 'choose_forward_info'], 'reply_to_message_id' => ['reply_to_msg_id'], 'ttl' => ['choose_ttl'], 'ttl_expires_in' => ['choose_ttl_expires_in'], 'via_bot_user_id' => ['via_bot_id'], 'views' => ['views'], 'content' => ['choose_message_content'], 'reply_markup' => ['reply_markup']], 'messages.sendMessage' => ['chat_id' => ['peer'], 'reply_to_message_id' => ['reply_to_msg_id'], 'disable_notification' => ['silent'], 'from_background' => ['background'], 'input_message_content' => ['choose_message_content'], 'reply_markup' => ['reply_markup']]]; const TD_REVERSE = ['sendMessage' => 'messages.sendMessage']; const TD_IGNORE = ['updateMessageID']; @@ -213,9 +215,9 @@ class MTProto extends AsyncConstruct implements TLCallback /** * Settings array. * - * @var array + * @var Settings */ - public $settings = []; + public $settings; /** * Config array. * @@ -413,6 +415,58 @@ class MTProto extends AsyncConstruct implements TLCallback */ private Snitch $snitch; + /** + * DC list. + */ + protected array $dcList = [ + 'test' => [ + // Test datacenters + 'ipv4' => [ + // ipv4 addresses + 2 => [ + // The rest will be fetched using help.getConfig + 'ip_address' => '149.154.167.40', + 'port' => 443, + 'media_only' => false, + 'tcpo_only' => false, + ], + ], + 'ipv6' => [ + // ipv6 addresses + 2 => [ + // The rest will be fetched using help.getConfig + 'ip_address' => '2001:067c:04e8:f002:0000:0000:0000:000e', + 'port' => 443, + 'media_only' => false, + 'tcpo_only' => false, + ], + ], + ], + 'main' => [ + // Main datacenters + 'ipv4' => [ + // ipv4 addresses + 2 => [ + // The rest will be fetched using help.getConfig + 'ip_address' => '149.154.167.51', + 'port' => 443, + 'media_only' => false, + 'tcpo_only' => false, + ], + ], + 'ipv6' => [ + // ipv6 addresses + 2 => [ + // The rest will be fetched using help.getConfig + 'ip_address' => '2001:067c:04e8:f002:0000:0000:0000:000a', + 'port' => 443, + 'media_only' => false, + 'tcpo_only' => false, + ], + ], + ] + ]; + /** * List of properties stored in database (memory or external). * @see DbPropertiesFactory @@ -428,32 +482,36 @@ class MTProto extends AsyncConstruct implements TLCallback /** * Constructor function. * - * @param array $settings Settings + * @param Settings|SettingsEmpty $settings Settings + * @param APIWrapper $wrapper API wrapper * * @return void */ - public function __magic_construct($settings = []) + public function __magic_construct(SettingsAbstract $settings, APIWrapper $wrapper) { + $this->wrapper = $wrapper; $this->setInitPromise($this->__construct_async($settings)); } /** * Async constructor function. * - * @param array $settings Settings + * @param Settings|SettingsEmpty $settings Settings * * @return \Generator */ - public function __construct_async($settings = []): \Generator + public function __construct_async(SettingsAbstract $settings): \Generator { + // Initialize needed stuffs Magic::classExists(); // Parse and store settings yield from $this->updateSettings($settings, false); + // Actually instantiate needed classes like a boss $this->logger->logger(Lang::$current_lang['inst_dc'], Logger::ULTRA_VERBOSE); yield from $this->cleanupProperties(); // Load rsa keys $this->logger->logger(Lang::$current_lang['load_rsa'], Logger::ULTRA_VERBOSE); $this->rsa_keys = []; - foreach ($this->settings['authorization']['rsa_keys'] as $key) { + foreach ($this->settings->getAuth()->getRsaKeys() as $key) { $key = (yield from (new RSA())->load($this->TL, $key)); $this->rsa_keys[$key->fp] = $key; } @@ -463,7 +521,7 @@ class MTProto extends AsyncConstruct implements TLCallback if (!($this->authorization['user']['bot'] ?? false)) { $callbacks[] = $this->minDatabase; } - $this->TL->init($this->settings['tl_schema']['src'], $callbacks); + $this->TL->init($this->settings->getSchema(), $callbacks); yield from $this->connectToAllDcs(); $this->startLoops(); $this->datacenter->curdc = 2; @@ -472,7 +530,7 @@ class MTProto extends AsyncConstruct implements TLCallback $nearest_dc = yield from $this->methodCallAsyncRead('help.getNearestDc', [], ['datacenter' => $this->datacenter->curdc]); $this->logger->logger(\sprintf(Lang::$current_lang['nearest_dc'], $nearest_dc['country'], $nearest_dc['nearest_dc']), Logger::NOTICE); if ($nearest_dc['nearest_dc'] != $nearest_dc['this_dc']) { - $this->settings['connection_settings']['default_dc'] = $this->datacenter->curdc = (int) $nearest_dc['nearest_dc']; + $this->settings->setDefaultDc($this->datacenter->curdc = (int) $nearest_dc['nearest_dc']); } } catch (RPCErrorException $e) { if ($e->rpc !== 'BOT_METHOD_INVALID') { @@ -486,6 +544,13 @@ class MTProto extends AsyncConstruct implements TLCallback GarbageCollector::start(); } + /** + * Set API wrapper needed for triggering serialization functions. + */ + public function setWrapper(APIWrapper $wrapper): void + { + $this->wrapper = $wrapper; + } /** * Sleep function. * @@ -493,13 +558,11 @@ class MTProto extends AsyncConstruct implements TLCallback */ public function __sleep(): array { - if ( - $this->settings['serialization']['cleanup_before_serialization'] - && $this->settings['db']['type'] === 'memory' - ) { + $db = $this->settings->getDb(); + if ($db instanceof Memory && $db->getCleanup()) { $this->cleanup(); } - return [ + $res = [ // Databases 'chats', 'full_chats', @@ -529,6 +592,7 @@ class MTProto extends AsyncConstruct implements TLCallback // Settings 'settings', 'config', + 'dcList', // Authorization keys 'datacenter', @@ -561,6 +625,10 @@ class MTProto extends AsyncConstruct implements TLCallback // Report URI 'reportDest', ]; + if (!$this->updateHandler instanceof Closure) { + $res[] = 'updateHandler'; + } + return $res; } /** @@ -716,7 +784,7 @@ class MTProto extends AsyncConstruct implements TLCallback $this->callCheckerLoop = new PeriodicLoopInternal($this, [$this, 'checkCalls'], 'call check', 10 * 1000); } if (!$this->serializeLoop) { - $this->serializeLoop = new PeriodicLoopInternal($this, [$this, 'serialize'], 'serialize', $this->settings['serialization']['serialization_interval'] * 1000); + $this->serializeLoop = new PeriodicLoopInternal($this, [$this, 'serialize'], 'serialize', $this->settings->getSerialization()->getInterval() * 1000); } if (!$this->phoneConfigLoop) { $this->phoneConfigLoop = new PeriodicLoopInternal($this, [$this, 'getPhoneConfig'], 'phone config', 24 * 3600 * 1000); @@ -821,7 +889,7 @@ class MTProto extends AsyncConstruct implements TLCallback unset($this->updates_state); } if (!isset($this->datacenter)) { - $this->datacenter = new DataCenter($this, $this->settings['connection'], $this->settings['connection_settings']); + $this->datacenter ??= new DataCenter($this, $this->dcList, $this->settings->getConnection()); } if (!isset($this->referenceDatabase)) { $this->referenceDatabase = new ReferenceDatabase($this); @@ -842,7 +910,7 @@ class MTProto extends AsyncConstruct implements TLCallback if (!($this->authorization['user']['bot'] ?? false)) { $callbacks[] = $this->minDatabase; } - $this->TL->init($this->settings['tl_schema']['src'], $callbacks); + $this->TL->init($this->settings->getSchema(), $callbacks); } yield from $this->initDb($this); @@ -869,17 +937,7 @@ class MTProto extends AsyncConstruct implements TLCallback $socket->authorized(true); } } - $settings = $this->settings; - if (isset($settings['updates']['callback'][0]) && $settings['updates']['callback'][0] === $this) { - $settings['updates']['callback'] = 'getUpdatesUpdateHandler'; - } - if (isset($settings['updates']['getdifference_interval']) && $settings['updates']['getdifference_interval'] === -1) { - unset($settings['updates']['getdifference_interval']); - } - unset($settings['tl_schema']); - if (isset($settings['authorization']['rsa_key'])) { - unset($settings['authorization']['rsa_key']); - } + $this->settings->setSchema(new TLSchema); yield from $this->initDb($this); @@ -907,35 +965,10 @@ class MTProto extends AsyncConstruct implements TLCallback } unset($chat); - foreach ($settings['connection_settings'] as $key => &$connection) { - if (\in_array($key, ['default_dc', 'media_socket_count', 'robin_period'])) { - continue; - } - if (!\is_array($connection)) { - unset($settings['connection_settings'][$key]); - continue; - } - if (!isset($connection['proxy'])) { - $connection['proxy'] = '\\Socket'; - } - if (!isset($connection['proxy_extra'])) { - $connection['proxy_extra'] = []; - } - if (!isset($connection['pfs'])) { - $connection['pfs'] = \extension_loaded('gmp'); - } - if ($connection['protocol'] === 'obfuscated2') { - $connection['protocol'] = 'tcp_intermediate_padded'; - $connection['obfuscated'] = true; - } - } - unset($connection); - $this->resetMTProtoSession(true, true); $this->config = ['expires' => -1]; $this->dh_config = ['version' => 0]; - $this->settings = $settings; - yield from $this->__construct_async($settings); + yield from $this->__construct_async($this->settings); foreach ($this->secret_chats as $chat => $data) { try { if (isset($this->secret_chats[$chat]) && $this->secret_chats[$chat]['InputEncryptedChat'] !== null) { @@ -946,53 +979,82 @@ class MTProto extends AsyncConstruct implements TLCallback } } /** - * Wakeup function. + * Post-deserialization initialization function. + * + * @param Settings|SettingsEmpty $settings New settings + * @param APIWrapper $wrapper API wrapper + * + * @internal + * + * @return \Generator */ - public function __wakeup() + public function wakeup(SettingsAbstract $settings, APIWrapper $wrapper): \Generator { - $backtrace = \debug_backtrace(0, 4); - $backtrace = \end($backtrace); + // Set API wrapper + $this->wrapper = $wrapper; + // BC stuff + if ($this->authorized === true) { + $this->authorized = self::LOGGED_IN; + } + // Convert old array settings to new settings object + if (\is_array($this->settings)) { + if (($this->settings['updates']['callback'] ?? '') === 'getUpdatesUpdateHandler') { + $this->settings['updates']['callback'] = [$this, 'getUpdatesUpdateHandler']; + } + if (\is_callable($this->settings['updates']['callback'] ?? null)) { + $this->updateHandler = $this->settings['updates']['callback']; + } + + $this->dcList = $this->settings['connection'] ?? $this->dcList; + } + $this->settings = Settings::parseFromLegacy($this->settings); + // Clean up phone call array + foreach ($this->calls as $id => $controller) { + if (!\is_object($controller)) { + unset($this->calls[$id]); + } elseif ($controller->getCallState() === VoIP::CALL_STATE_ENDED) { + $controller->setMadeline($this); + $controller->discard(); + } else { + $controller->setMadeline($this); + } + } + $this->forceInit(false); - $this->setInitPromise($this->__wakeup_async($backtrace)); + $this->setInitPromise($this->wakeupAsync($settings)); + + return $this->initAsynchronously(); } /** * Async wakeup function. * - * @param array $backtrace Stack trace + * @param Settings|SettingsEmpty $settings New settings * * @return \Generator */ - public function __wakeup_async(array $backtrace): \Generator + private function wakeupAsync(SettingsAbstract $settings): \Generator { // Setup one-time stuffs Magic::classExists(); + $this->settings->getConnection()->init(); // Setup logger $this->setupLogger(); // Setup language Lang::$current_lang =& Lang::$lang['en']; - if (Lang::$lang[$this->settings['app_info']['lang_code'] ?? 'en'] ?? false) { - Lang::$current_lang =& Lang::$lang[$this->settings['app_info']['lang_code']]; + if (Lang::$lang[$this->settings->getAppInfo()->getLangCode()] ?? false) { + Lang::$current_lang =& Lang::$lang[$this->settings->getAppInfo()->getLangCode()]; } - $this->settings['connection_settings']['all']['ipv6'] = Magic::$ipv6; - if ($this->authorized === true) { - $this->authorized = self::LOGGED_IN; - } - $force = false; + // Reset MTProto session (not related to user session) $this->resetMTProtoSession(); - if (isset($backtrace['function'], $backtrace['class'], $backtrace['args']) && $backtrace['class'] === 'danog\\MadelineProto\\API' && $backtrace['function'] === '__construct_async') { - if (\count($backtrace['args']) >= 2) { - yield from $this->updateSettings($backtrace['args'][1], false); - } - } - if (($this->settings['tl_schema']['src']['botAPI'] ?? '') !== __DIR__.'/TL_botAPI.tl') { - unset($this->v); - } - if (!\file_exists($this->settings['tl_schema']['src']['telegram'])) { - unset($this->v); - } - if (!isset($this->v) || $this->v !== self::V) { + // Update settings from constructor + yield from $this->updateSettings($settings, false); + // Session update process for BC + $forceDialogs = false; + if (!isset($this->v) + || $this->v !== self::V + || $this->settings->getSchema()->needsUpgrade()) { yield from $this->upgradeMadelineProto(); - $force = true; + $forceDialogs = true; } // Cleanup old properties, init new stuffs yield from $this->cleanupProperties(); @@ -1002,20 +1064,8 @@ class MTProto extends AsyncConstruct implements TLCallback $callbacks[] = $this->minDatabase; } $this->TL->updateCallbacks($callbacks); - if ($this->event_handler && \class_exists($this->event_handler) && \is_subclass_of($this->event_handler, EventHandler::class)) { - $this->setEventHandler($this->event_handler); - } + // Connect to all DCs, start internal loops yield from $this->connectToAllDcs(); - foreach ($this->calls as $id => $controller) { - if (!\is_object($controller)) { - unset($this->calls[$id]); - } elseif ($controller->getCallState() === \danog\MadelineProto\VoIP::CALL_STATE_ENDED) { - $controller->setMadeline($this); - $controller->discard(); - } else { - $controller->setMadeline($this); - } - } $this->startLoops(); if (yield from $this->fullGetSelf()) { $this->authorized = self::LOGGED_IN; @@ -1023,11 +1073,15 @@ class MTProto extends AsyncConstruct implements TLCallback yield from $this->getCdnConfig($this->datacenter->curdc); yield from $this->initAuthorization(); } - $this->startUpdateSystem(true); - if ($this->authorized === self::LOGGED_IN && !$this->authorization['user']['bot'] && $this->settings['peer']['cache_all_peers_on_startup']) { - yield from $this->getDialogs($force); + // onStart event handler + if ($this->event_handler && \class_exists($this->event_handler) && \is_subclass_of($this->event_handler, EventHandler::class)) { + yield from $this->setEventHandler($this->event_handler); } - if ($this->authorized === self::LOGGED_IN && $this->settings['updates']['handle_updates']) { + $this->startUpdateSystem(true); + if ($this->authorized === self::LOGGED_IN && !$this->authorization['user']['bot'] && $this->settings->getPeer()->getCacheAllPeersOnStartup()) { + yield from $this->getDialogs($forceDialogs); + } + if ($this->authorized === self::LOGGED_IN) { $this->logger->logger(Lang::$current_lang['getupdates_deserialization'], Logger::NOTICE); yield $this->updaters[UpdateLoop::GENERIC]->resume(); } @@ -1075,379 +1129,33 @@ class MTProto extends AsyncConstruct implements TLCallback $this->unreference(); $this->logger("Successfully destroyed MadelineProto"); } - /** - * Get correct settings array for the latest version. - * - * @param array $settings Current settings array - * @param array $previousSettings Previous settings array - * - * @internal - * - * @return array - */ - public static function parseSettings(array $settings, array $previousSettings = []): array - { - //Magic::classExists(); - $settings = \array_replace_recursive($previousSettings, $settings); - if (isset($previousSettings['connection_settings']['default_dc'])) { - $settings['connection_settings']['default_dc'] = $previousSettings['connection_settings']['default_dc']; - } - if (!isset($settings['app_info']['api_id']) || !$settings['app_info']['api_id']) { - if (isset($previousSettings['app_info']['api_id']) && $previousSettings['app_info']['api_id']) { - $settings['app_info']['api_id'] = $previousSettings['app_info']['api_id']; - $settings['app_info']['api_hash'] = $previousSettings['app_info']['api_hash']; - } else { - $settings['app_info'] = []; - } - } - // Detect device model - try { - $device_model = \php_uname('s'); - } catch (\danog\MadelineProto\Exception $e) { - $device_model = 'Web server'; - } - if (($settings['app_info']['api_id'] ?? 0) === 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) - $device_model = 'LGENexus 5'; - } - // Detect system version - try { - $system_version = \php_uname('r'); - } catch (\danog\MadelineProto\Exception $e) { - $system_version = PHP_VERSION; - } - if (($settings['app_info']['api_id'] ?? 0) === 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, - // ...and in particular 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) - $system_version = 'SDK 28'; - } - // Detect language - $lang_code = 'en'; - Lang::$current_lang =& Lang::$lang[$lang_code]; - if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - $lang_code = \substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2); - } elseif (isset($_SERVER['LANG'])) { - $lang_code = \explode('_', $_SERVER['LANG'])[0]; - } - if (isset(Lang::$lang[$lang_code])) { - Lang::$current_lang =& Lang::$lang[$lang_code]; - } - // Detect language pack - $lang_pack = ''; - if (($settings['app_info']['api_id'] ?? 0) === 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, - // ...and in particular 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) - $lang_pack = 'android'; - } - // Detect app version - $app_version = self::RELEASE.' ('.self::V.', '.\str_replace(' (AN UPDATE IS REQUIRED)', '', Magic::$revision).')'; - if (($settings['app_info']['api_id'] ?? 0) === 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, - // ...and in particular 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) - $app_version = '4.9.1 (13613)'; - } - // Set default settings - $default_settings = ['authorization' => [ - // Authorization settings - 'default_temp_auth_key_expires_in' => 1 * 24 * 60 * 60, - // validity of temporary keys and the binding of the temporary and permanent keys - 'rsa_keys' => ["-----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-----"], - ], 'connection' => [ - // List of datacenters/subdomains where to connect - 'ssl_subdomains' => [ - // Subdomains of web.telegram.org for https protocol - 1 => 'pluto', - 2 => 'venus', - 3 => 'aurora', - 4 => 'vesta', - 5 => 'flora', - ], - 'test' => [ - // Test datacenters - 'ipv4' => [ - // ipv4 addresses - 2 => [ - // The rest will be fetched using help.getConfig - 'ip_address' => '149.154.167.40', - 'port' => 443, - 'media_only' => false, - 'tcpo_only' => false, - ], - ], - 'ipv6' => [ - // ipv6 addresses - 2 => [ - // The rest will be fetched using help.getConfig - 'ip_address' => '2001:067c:04e8:f002:0000:0000:0000:000e', - 'port' => 443, - 'media_only' => false, - 'tcpo_only' => false, - ], - ], - ], - 'main' => [ - // Main datacenters - 'ipv4' => [ - // ipv4 addresses - 2 => [ - // The rest will be fetched using help.getConfig - 'ip_address' => '149.154.167.51', - 'port' => 443, - 'media_only' => false, - 'tcpo_only' => false, - ], - ], - 'ipv6' => [ - // ipv6 addresses - 2 => [ - // The rest will be fetched using help.getConfig - 'ip_address' => '2001:067c:04e8:f002:0000:0000:0000:000a', - 'port' => 443, - 'media_only' => false, - 'tcpo_only' => false, - ], - ], - ], - ], 'connection_settings' => [ - // connection settings - 'all' => [ - // These settings will be applied on every datacenter that hasn't a custom settings subarray... - 'protocol' => 'tcp_abridged', - // can be tcp_full, tcp_abridged, tcp_intermediate, http, https, obfuscated2, udp (unsupported) - 'test_mode' => false, - // decides whether to connect to the main telegram servers or to the testing servers (deep telegram) - 'ipv6' => Magic::$ipv6, - // decides whether to use ipv6, ipv6 attribute of API attribute of API class contains autodetected boolean - 'timeout' => 2, - // RPC timeout - 'drop_timeout' => 5*60, - // timeout for sockets - 'proxy' => Magic::$altervista ? '\\HttpProxy' : '\\Socket', - // The proxy class to use - 'proxy_extra' => Magic::$altervista ? ['address' => 'localhost', 'port' => 80] : [], - // Extra parameters to pass to the proxy class using setExtra - 'obfuscated' => false, - 'transport' => 'tcp', - 'pfs' => false, - ], - 'media_socket_count' => ['min' => 5, 'max' => 10], - 'robin_period' => 10, - 'default_dc' => 2, - ], 'app_info' => [ - // obtained in https://my.telegram.org - //'api_id' => you should put an API id in the settings array you provide - //'api_hash' => you should put an API hash in the settings array you provide - 'device_model' => $device_model, - 'system_version' => $system_version, - 'app_version' => $app_version, - // 🌚 - // 'app_version' => self::V, - 'lang_code' => $lang_code, - 'lang_pack' => $lang_pack, - ], 'tl_schema' => [ - // TL scheme files - 'layer' => 117, - // layer version - 'src' => [ - // mtproto TL scheme - 'mtproto' => __DIR__.'/TL_mtproto_v1.tl', - // telegram TL scheme - 'telegram' => __DIR__.'/TL_telegram_v117.tl', - // secret chats TL scheme - 'secret' => __DIR__.'/TL_secret.tl', - // bot API TL scheme - 'botAPI' => __DIR__.'/TL_botAPI.tl', - ], - ], 'logger' => [ - // Logger settings - /* - * logger modes: - * 0 - No logger - * 1 - Log to the default logger destination - * 2 - Log to file defined in second parameter - * 3 - Echo logs - * 4 - Call callable provided in logger_param. logger_param must accept two parameters: array $message, int $level - * $message is an array containing the messages the log, $level, is the logging level - */ - // write to - 'logger_param' => Magic::$script_cwd.'/MadelineProto.log', - 'logger' => (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') ? Logger::ECHO_LOGGER : Logger::FILE_LOGGER, - // overwrite previous setting and echo logs - 'logger_level' => Logger::VERBOSE, - 'max_size' => 1 * 1024 * 1024, - // Logging level, available logging levels are: ULTRA_VERBOSE, VERBOSE, NOTICE, WARNING, ERROR, FATAL_ERROR. Can be provided as last parameter to the logging function. - ], 'max_tries' => [ - 'query' => 5, - // How many times should I try to call a method or send an object before throwing an exception - 'authorization' => 5, - // How many times should I try to generate an authorization key before throwing an exception - 'response' => 5, - ], 'flood_timeout' => ['wait_if_lt' => 10 * 60], 'msg_array_limit' => [ - // How big should be the arrays containing the incoming and outgoing messages? - 'incoming' => 100, - 'outgoing' => 100, - 'call_queue' => 200, - ], 'peer' => [ - 'full_info_cache_time' => 3600, - // Full peer info cache validity - 'full_fetch' => false, - // Should madeline fetch the full member list of every group it meets? - 'cache_all_peers_on_startup' => false, - ], 'requests' => ['gzip_encode_if_gt' => 1024 * 1024], 'updates' => [ - 'handle_updates' => true, - // Should I handle updates? - 'handle_old_updates' => true, - // Should I handle old updates on startup? - 'getdifference_interval' => 10, - // Getdifference manual polling interval - 'callback' => 'getUpdatesUpdateHandler', - // Update callback - 'run_callback' => false, - ], 'secret_chats' => ['accept_chats' => true], - 'serialization' => ['serialization_interval' => 30, 'cleanup_before_serialization' => false], - /** - * Where internal database will be stored? - * memory - session file - * mysql - mysql database. - */ - 'db' => [ - 'type' => 'memory', - /** @see Mysql */ - 'mysql' => [ - 'host' => '127.0.0.1', - 'port' => 3306, - 'user' => 'root', - 'password' => '', - 'database' => 'MadelineProto', //will be created automatically - 'max_connections' => 10, - 'idle_timeout' => 60, - 'cache_ttl' => '+5 minutes', //keep records in memory after last read - ], - /** @see Postgres */ - 'postgres' => [ - 'host' => '127.0.0.1', - 'port' => 5432, - 'user' => 'root', - 'password' => '', - 'database' => 'MadelineProto', //will be created automatically - 'max_connections' => 10, - 'idle_timeout' => 60, - 'cache_ttl' => '+5 minutes', //keep records in memory after last read - ], - /** @see Redis */ - 'redis' => [ - 'host' => 'redis://127.0.0.1', - 'port' => 6379, - 'password' => '', - 'database' => 0, //will be created automatically - 'cache_ttl' => '+5 minutes', //keep records in memory after last read - ], - ], - 'upload' => ['allow_automatic_upload' => true, 'part_size' => 512 * 1024, 'parallel_chunks' => 20], 'download' => ['report_broken_media' => true, 'part_size' => 1024 * 1024, 'parallel_chunks' => 20], 'pwr' => [ - 'pwr' => false, - // Need info ? - 'db_token' => false, - // Need info ? - 'strict' => false, - // Need info ? - 'requests' => true, - ]]; - $settings = \array_replace_recursive($default_settings, $settings); - if (isset(Lang::$lang[$settings['app_info']['lang_code']])) { - Lang::$current_lang =& Lang::$lang[$settings['app_info']['lang_code']]; - } - /*if ($settings['app_info']['api_id'] < 20) { - $settings['connection_settings']['all']['protocol'] = 'obfuscated2'; - }*/ - switch ($settings['logger']['logger_level']) { - 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; - } - return $settings; - } /** * Parse, update and store settings. * - * @param array $settings Settings - * @param bool $reinit Whether to reinit the instance + * @param Settings|SettingsEmpty $settings Settings + * @param bool $reinit Whether to reinit the instance * - * @return void + * @return \Generator */ - public function updateSettings(array $settings, bool $reinit = true): \Generator + public function updateSettings(SettingsAbstract $settings, bool $reinit = true): \Generator { - $settings = self::parseSettings($settings, $this->settings); - if ($settings['app_info'] === null) { + if ($settings instanceof SettingsEmpty) { + if (!isset($this->settings)) { + $this->settings = new Settings; + } else { + return; + } + } else { + if (!isset($this->settings)) { + $this->settings = $settings; + } else { + $this->settings->merge($settings); + } + } + if (!$this->settings->getAppInfo()->hasApiInfo()) { throw new \danog\MadelineProto\Exception(Lang::$current_lang['api_not_set'], 0, null, 'MadelineProto', 1); } - $this->settings = $settings; - if (!$this->settings['updates']['handle_updates']) { - $this->updates = []; - } + // Setup logger $this->setupLogger(); @@ -1457,11 +1165,11 @@ class MTProto extends AsyncConstruct implements TLCallback } } /** - * Return current settings array. + * Return current settings. * - * @return array + * @return Settings */ - public function getSettings(): array + public function getSettings(): Settings { return $this->settings; } @@ -1472,7 +1180,10 @@ class MTProto extends AsyncConstruct implements TLCallback */ public function setupLogger(): void { - $this->logger = Logger::getLoggerFromSettings($this->settings, isset($this->authorization['user']) ? (isset($this->authorization['user']['username']) ? $this->authorization['user']['username'] : $this->authorization['user']['id']) : ''); + $this->logger = new Logger( + $this->settings->getLogger(), + $this->authorization['user']['username'] ?? $this->authorization['user']['id'] ?? '' + ); } /** * Reset all MTProto sessions. @@ -1561,7 +1272,7 @@ class MTProto extends AsyncConstruct implements TLCallback if (!isset($this->seqUpdater)) { $this->seqUpdater = new SeqLoop($this); } - $this->datacenter->__construct($this, $this->settings['connection'], $this->settings['connection_settings'], $reconnectAll); + $this->datacenter->__construct($this, $this->dcList, $this->settings->getConnection(), $reconnectAll); $dcs = []; foreach ($this->datacenter->getDcs() as $new_dc) { $dcs[] = $this->datacenter->dcConnect($new_dc); @@ -1717,9 +1428,12 @@ class MTProto extends AsyncConstruct implements TLCallback */ public function getPhoneConfig($watcherId = null): \Generator { - if ($this->authorized === self::LOGGED_IN && \class_exists(VoIPServerConfigInternal::class) && !$this->authorization['user']['bot'] && $this->datacenter->getDataCenterConnection($this->settings['connection_settings']['default_dc'])->hasTempAuthKey()) { + if ($this->authorized === self::LOGGED_IN + && \class_exists(VoIPServerConfigInternal::class) + && !$this->authorization['user']['bot'] + && $this->datacenter->getDataCenterConnection($this->settings->getDefaultDc())->hasTempAuthKey()) { $this->logger->logger('Fetching phone config...'); - VoIPServerConfig::updateDefault(yield from $this->methodCallAsyncRead('phone.getCallConfig', [], ['datacenter' => $this->settings['connection_settings']['default_dc']])); + VoIPServerConfig::updateDefault(yield from $this->methodCallAsyncRead('phone.getCallConfig', [], $this->settings->getDefaultDcParams())); } else { $this->logger->logger('Not fetching phone config'); } @@ -1764,7 +1478,7 @@ class MTProto extends AsyncConstruct implements TLCallback if ($this->config['expires'] > \time()) { return $this->config; } - $this->config = empty($config) ? yield from $this->methodCallAsyncRead('help.getConfig', $config, $options ?: ['datacenter' => $this->settings['connection_settings']['default_dc']]) : $config; + $this->config = empty($config) ? yield from $this->methodCallAsyncRead('help.getConfig', $config, $options ?: $this->settings->getDefaultDcParams()) : $config; yield from $this->parseConfig(); $this->logger->logger(Lang::$current_lang['config_updated'], Logger::NOTICE); $this->logger->logger($this->config, Logger::NOTICE); @@ -1792,7 +1506,7 @@ class MTProto extends AsyncConstruct implements TLCallback */ private function parseDcOptions(array $dc_options): \Generator { - $previous = $this->settings; + $previous = $this->dcList; foreach ($dc_options as $dc) { $test = $this->config['test_mode'] ? 'test' : 'main'; $id = $dc['id']; @@ -1808,10 +1522,10 @@ class MTProto extends AsyncConstruct implements TLCallback $id = (int) $id; } unset($dc['cdn'], $dc['media_only'], $dc['id'], $dc['ipv6']); - $this->settings['connection'][$test][$ipv6][$id] = $dc; + $this->dcList[$test][$ipv6][$id] = $dc; } $curdc = $this->datacenter->curdc; - if ($previous !== $this->settings && (!$this->datacenter->has($curdc) || $this->datacenter->getDataCenterConnection($curdc)->byIPAddress())) { + if ($previous !== $this->dcList && (!$this->datacenter->has($curdc) || $this->datacenter->getDataCenterConnection($curdc)->byIPAddress())) { $this->logger->logger('Got new DC options, reconnecting'); yield from $this->connectToAllDcs(false); } @@ -1877,8 +1591,13 @@ class MTProto extends AsyncConstruct implements TLCallback if (!(\is_array($userOrId) && !isset($userOrId['_']) && !isset($userOrId['id']))) { $userOrId = [$userOrId]; } - foreach ($userOrId as &$peer) { - $peer = (yield from $this->getInfo($peer))['bot_api_id']; + foreach ($userOrId as $k => &$peer) { + try { + $peer = (yield from $this->getInfo($peer))['bot_api_id']; + } catch (\Throwable $e) { + unset($userOrId[$k]); + $this->logger("Could not obtain info about report peer $peer: $e", Logger::FATAL_ERROR); + } } $this->reportDest = $userOrId; } @@ -1895,7 +1614,8 @@ class MTProto extends AsyncConstruct implements TLCallback return; } $file = null; - if ($this->settings['logger']['logger'] === Logger::FILE_LOGGER && $path = $this->settings['logger']['logger_param']) { + if ($this->settings->getLogger()->getType() === Logger::FILE_LOGGER + && $path = $this->settings->getLogger()->getExtra()) { StatCache::clear($path); if (!yield exists($path)) { $message = "!!! WARNING !!!\nThe logfile does not exist, please DO NOT delete the logfile to avoid errors in MadelineProto!\n\n$message"; diff --git a/src/danog/MadelineProto/MTProtoSession/AckHandler.php b/src/danog/MadelineProto/MTProtoSession/AckHandler.php index 88905b5e..088459b5 100644 --- a/src/danog/MadelineProto/MTProtoSession/AckHandler.php +++ b/src/danog/MadelineProto/MTProtoSession/AckHandler.php @@ -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; diff --git a/src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler32.php b/src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler32.php index 5931396d..4b51c415 100644 --- a/src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler32.php +++ b/src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler32.php @@ -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'])) { diff --git a/src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler64.php b/src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler64.php index b57c0509..bfd1ed41 100644 --- a/src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler64.php +++ b/src/danog/MadelineProto/MTProtoSession/MsgIdHandler/MsgIdHandler64.php @@ -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'])) { diff --git a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php index 5f1d178c..8951219d 100644 --- a/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php +++ b/src/danog/MadelineProto/MTProtoSession/ResponseHandler.php @@ -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(); diff --git a/src/danog/MadelineProto/MTProtoSession/Session.php b/src/danog/MadelineProto/MTProtoSession/Session.php index f7c0fcff..431e04a9 100644 --- a/src/danog/MadelineProto/MTProtoSession/Session.php +++ b/src/danog/MadelineProto/MTProtoSession/Session.php @@ -21,6 +21,8 @@ namespace danog\MadelineProto\MTProtoSession; /** * Manages MTProto session-specific data. + * + * @property MTProto $API */ abstract class Session { diff --git a/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php b/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php index 08c32b0b..17ef3038 100644 --- a/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/AuthKeyHandler.php @@ -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,7 +67,7 @@ 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); /** @@ -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) { diff --git a/src/danog/MadelineProto/MTProtoTools/CallHandler.php b/src/danog/MadelineProto/MTProtoTools/CallHandler.php index a1c23fd0..c91b6b51 100644 --- a/src/danog/MadelineProto/MTProtoTools/CallHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/CallHandler.php @@ -19,8 +19,12 @@ namespace danog\MadelineProto\MTProtoTools; +use danog\MadelineProto\Settings; + /** * Manages method and object calls. + * + * @property Settings $settings Settings */ trait CallHandler { diff --git a/src/danog/MadelineProto/MTProtoTools/Files.php b/src/danog/MadelineProto/MTProtoTools/Files.php index b9f05c3c..18584e34 100644 --- a/src/danog/MadelineProto/MTProtoTools/Files.php +++ b/src/danog/MadelineProto/MTProtoTools/Files.php @@ -38,22 +38,26 @@ use Amp\Promise; use Amp\Success; use danog\MadelineProto\Exception; use danog\MadelineProto\FileCallbackInterface; +use danog\MadelineProto\Settings; 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 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 { @@ -83,7 +87,7 @@ trait Files 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']) { + if (!$this->settings->getFiles()->getAllowAutomaticUpload()) { return yield from $this->uploadFromUrl($file, 0, $fileName, $cb, $encrypted); } $file = Tools::absolute($file); @@ -194,7 +198,7 @@ trait Files }; } else { if (!$stream instanceof BufferedRawStream) { - $ctx = (new ConnectionContext())->addStream(PremadeStream::getName(), $stream)->addStream(SimpleBufferedRawStream::getName()); + $ctx = (new ConnectionContext())->addStream(PremadeStream::class, $stream)->addStream(SimpleBufferedRawStream::class); $stream = (yield from $ctx->getStream()); $created = true; } @@ -263,12 +267,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'; @@ -395,7 +399,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. @@ -1134,9 +1138,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'; } diff --git a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php index 68c1b8c2..ef781ef6 100644 --- a/src/danog/MadelineProto/MTProtoTools/PeerHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/PeerHandler.php @@ -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 { @@ -157,7 +160,7 @@ trait PeerHandler 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); + $this->cachePwrChat(-$chat['id'], $this->getSettings()->getPeer()->getFullFetch(), true); } $this->cacheChatUsername(-$chat['id'], $chat); break; @@ -196,8 +199,8 @@ trait PeerHandler } $this->chats[$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(); diff --git a/src/danog/MadelineProto/MTProtoTools/ReferenceDatabase.php b/src/danog/MadelineProto/MTProtoTools/ReferenceDatabase.php index 9f8bd28e..651e8a33 100644 --- a/src/danog/MadelineProto/MTProtoTools/ReferenceDatabase.php +++ b/src/danog/MadelineProto/MTProtoTools/ReferenceDatabase.php @@ -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}"); diff --git a/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php b/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php index aa4b0c05..0167f4f8 100644 --- a/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php +++ b/src/danog/MadelineProto/MTProtoTools/UpdateHandler.php @@ -20,59 +20,33 @@ 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; + /** * 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 +58,17 @@ 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 = \array_merge(MTProto::DEFAULT_GETUPDATES_PARAMS, $params); if (empty($this->updates)) { $this->update_deferred = new Deferred(); if (!$params['timeout']) { $params['timeout'] = 0.001; } - yield $this->waitUpdate(); + yield from $this->waitUpdate(); } 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 +197,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 +213,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 +360,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 +373,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 +404,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 +413,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; } } diff --git a/src/danog/MadelineProto/Magic.php b/src/danog/MadelineProto/Magic.php index b0760ff5..7e9ff5cc 100644 --- a/src/danog/MadelineProto/Magic.php +++ b/src/danog/MadelineProto/Magic.php @@ -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 @@ -247,7 +248,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 diff --git a/src/danog/MadelineProto/MyTelegramOrgWrapper.php b/src/danog/MadelineProto/MyTelegramOrgWrapper.php index f4c349aa..f2117519 100644 --- a/src/danog/MadelineProto/MyTelegramOrgWrapper.php +++ b/src/danog/MadelineProto/MyTelegramOrgWrapper.php @@ -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. diff --git a/src/danog/MadelineProto/Settings.php b/src/danog/MadelineProto/Settings.php new file mode 100644 index 00000000..86b1387e --- /dev/null +++ b/src/danog/MadelineProto/Settings.php @@ -0,0 +1,498 @@ +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; + } + /** + * 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) { + 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); + + 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; + } +} diff --git a/src/danog/MadelineProto/Settings/AppInfo.php b/src/danog/MadelineProto/Settings/AppInfo.php new file mode 100644 index 00000000..fce218f0 --- /dev/null +++ b/src/danog/MadelineProto/Settings/AppInfo.php @@ -0,0 +1,298 @@ +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 + $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; + } +} diff --git a/src/danog/MadelineProto/Settings/Auth.php b/src/danog/MadelineProto/Settings/Auth.php new file mode 100644 index 00000000..3511f552 --- /dev/null +++ b/src/danog/MadelineProto/Settings/Auth.php @@ -0,0 +1,148 @@ +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; + } +} diff --git a/src/danog/MadelineProto/Settings/Connection.php b/src/danog/MadelineProto/Settings/Connection.php new file mode 100644 index 00000000..8a989f2b --- /dev/null +++ b/src/danog/MadelineProto/Settings/Connection.php @@ -0,0 +1,620 @@ + 2]; + /** + * Protocol identifier. + * + * @var class-string + */ + protected string $protocol = AbridgedStream::class; + /** + * Transport identifier. + * + * @var class-string + */ + protected string $transport = DefaultStream::class; + /** + * Proxy identifiers. + * + * @var array, 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; + + /** + * Whether the connection settings changed. + */ + private bool $changed = 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]); + } + } + /** + * Whether the settings have changed. + * + * @return boolean + */ + public function haveChanged(): bool + { + return $this->changed; + } + /** + * Signal that changes have been applied. + * + * @return void + */ + public function applyChanges(): void + { + $this->changed = false; + } + /** + * Get protocol identifier. + * + * @return string + */ + public function getProtocol(): string + { + return $this->protocol; + } + + /** + * Set protocol identifier. + * + * @param class-string $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->changed = true; + $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->changed = true; + $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->changed = true; + $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->changed = true; + $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->changed = true; + $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->changed = true; + $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->changed = true; + $this->defaultDc = $defaultDc; + $this->defaultDcParams = ['datacenter' => $defaultDc]; + + return $this; + } + + /** + * Get proxy identifiers. + * + * @return array, array> + */ + public function getProxies(): array + { + return $this->proxy; + } + + /** + * Add proxy identifier to list. + * + * @param class-string $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->changed = true; + $this->proxy[$proxy][] = $extra; + + 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; + } + $this->changed = true; + 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->changed = true; + $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->changed = true; + $this->testMode = $testMode; + + return $this; + } + + /** + * Get transport identifier. + * + * @return class-string + */ + public function getTransport(): string + { + return $this->transport; + } + + /** + * Set transport identifier. + * + * @param class-string $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->changed = true; + $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->changed = true; + $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->changed = true; + $this->timeout = $timeout; + + return $this; + } +} diff --git a/src/danog/MadelineProto/Settings/Database/DatabaseAbstract.php b/src/danog/MadelineProto/Settings/Database/DatabaseAbstract.php new file mode 100644 index 00000000..03854ac3 --- /dev/null +++ b/src/danog/MadelineProto/Settings/Database/DatabaseAbstract.php @@ -0,0 +1,125 @@ + $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; +} diff --git a/src/danog/MadelineProto/Settings/Database/Memory.php b/src/danog/MadelineProto/Settings/Database/Memory.php new file mode 100644 index 00000000..8b99a6ad --- /dev/null +++ b/src/danog/MadelineProto/Settings/Database/Memory.php @@ -0,0 +1,43 @@ +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; + } +} diff --git a/src/danog/MadelineProto/Settings/Database/Mysql.php b/src/danog/MadelineProto/Settings/Database/Mysql.php new file mode 100644 index 00000000..a1a1fdc6 --- /dev/null +++ b/src/danog/MadelineProto/Settings/Database/Mysql.php @@ -0,0 +1,15 @@ +setUri("tcp://".($settings['host']).(isset($settings['port']) ? ':'.($settings['port']) : '')); + } + parent::mergeArray($settings); + } +} diff --git a/src/danog/MadelineProto/Settings/Database/Postgres.php b/src/danog/MadelineProto/Settings/Database/Postgres.php new file mode 100644 index 00000000..7b797faf --- /dev/null +++ b/src/danog/MadelineProto/Settings/Database/Postgres.php @@ -0,0 +1,15 @@ +setUri("tcp://".($settings['host']).(isset($settings['port']) ? ':'.($settings['port']) : '')); + } + parent::mergeArray($settings); + } +} diff --git a/src/danog/MadelineProto/Settings/Database/Redis.php b/src/danog/MadelineProto/Settings/Database/Redis.php new file mode 100644 index 00000000..3e08b1ab --- /dev/null +++ b/src/danog/MadelineProto/Settings/Database/Redis.php @@ -0,0 +1,72 @@ +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; + } +} diff --git a/src/danog/MadelineProto/Settings/Database/SqlAbstract.php b/src/danog/MadelineProto/Settings/Database/SqlAbstract.php new file mode 100644 index 00000000..c7e075ee --- /dev/null +++ b/src/danog/MadelineProto/Settings/Database/SqlAbstract.php @@ -0,0 +1,167 @@ + $array) { + if (isset($settings[$array])) { + $this->{$object}($settings[$array]); + } + } + if (isset($settings['user'])) { + $this->setUsername($settings['user']); + } + } + + /** + * 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; + } +} diff --git a/src/danog/MadelineProto/Settings/DatabaseAbstract.php b/src/danog/MadelineProto/Settings/DatabaseAbstract.php new file mode 100644 index 00000000..e94c9657 --- /dev/null +++ b/src/danog/MadelineProto/Settings/DatabaseAbstract.php @@ -0,0 +1,12 @@ +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; + } +} diff --git a/src/danog/MadelineProto/Settings/Logger.php b/src/danog/MadelineProto/Settings/Logger.php new file mode 100644 index 00000000..ef8d97e8 --- /dev/null +++ b/src/danog/MadelineProto/Settings/Logger.php @@ -0,0 +1,217 @@ +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; + $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; + } +} diff --git a/src/danog/MadelineProto/Settings/Peer.php b/src/danog/MadelineProto/Settings/Peer.php new file mode 100644 index 00000000..33ece872 --- /dev/null +++ b/src/danog/MadelineProto/Settings/Peer.php @@ -0,0 +1,105 @@ + $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; + } +} diff --git a/src/danog/MadelineProto/Settings/Pwr.php b/src/danog/MadelineProto/Settings/Pwr.php new file mode 100644 index 00000000..185f5766 --- /dev/null +++ b/src/danog/MadelineProto/Settings/Pwr.php @@ -0,0 +1,71 @@ +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; + } +} diff --git a/src/danog/MadelineProto/Settings/RPC.php b/src/danog/MadelineProto/Settings/RPC.php new file mode 100644 index 00000000..b862933d --- /dev/null +++ b/src/danog/MadelineProto/Settings/RPC.php @@ -0,0 +1,202 @@ +setRpcTimeout($settings['connection_settings']['all']['drop_timeout']); + } + if (isset($settings['flood_timeout']['wait_if_lt'])) { + $this->setFloodTimeout($settings['flood_timeout']['wait_if_lt']); + } + if (isset($settings['msg_array_limit']['incoming'])) { + $this->setLimitIncoming($settings['msg_array_limit']['incoming']); + } + if (isset($settings['msg_array_limit']['outgoing'])) { + $this->setLimitOutgoing($settings['msg_array_limit']['outgoing']); + } + if (isset($settings['msg_array_limit']['call_queue'])) { + $this->setLimitCallQueue($settings['msg_array_limit']['call_queue']); + } + if (isset($settings['requests']['gzip_encode_if_gt'])) { + $this->setLimitCallQueue($settings['requests']['gzip_encode_if_gt']); + } + } + + /** + * Get RPC timeout. + * + * @return int + */ + public function getRpcTimeout(): int + { + return $this->rpcTimeout; + } + + /** + * Set RPC timeout. + * + * @param int $rpcTimeout RPC timeout. + * + * @return self + */ + public function setRpcTimeout(int $rpcTimeout): self + { + $this->rpcTimeout = $rpcTimeout; + + return $this; + } + + /** + * Get flood timeout: if FLOOD_WAIT_ time is bigger than this, throw exception instead of waiting asynchronously. + * + * @return int + */ + public function getFloodTimeout(): int + { + return $this->floodTimeout; + } + + /** + * Set flood timeout: if FLOOD_WAIT_ time is bigger than this, throw exception instead of waiting asynchronously. + * + * @param int $floodTimeout Flood timeout: if FLOOD_WAIT_ time is bigger than this, throw exception instead of waiting asynchronously + * + * @return self + */ + public function setFloodTimeout(int $floodTimeout): self + { + $this->floodTimeout = $floodTimeout; + + return $this; + } + + /** + * Get maximum number of messages to be stored in the incoming queue. + * + * @return int + */ + public function getLimitIncoming(): int + { + return $this->limitIncoming; + } + + /** + * Set maximum number of messages to be stored in the incoming queue. + * + * @param int $limitIncoming Maximum number of messages to be stored in the incoming queue + * + * @return self + */ + public function setLimitIncoming(int $limitIncoming): self + { + $this->limitIncoming = $limitIncoming; + + return $this; + } + + /** + * Get maximum number of messages to be stored in the outgoing queue. + * + * @return int + */ + public function getLimitOutgoing(): int + { + return $this->limitOutgoing; + } + + /** + * Set maximum number of messages to be stored in the outgoing queue. + * + * @param int $limitOutgoing Maximum number of messages to be stored in the outgoing queue + * + * @return self + */ + public function setLimitOutgoing(int $limitOutgoing): self + { + $this->limitOutgoing = $limitOutgoing; + + return $this; + } + + /** + * Get maximum number of messages to consider when using call queues. + * + * @return int + */ + public function getLimitCallQueue(): int + { + return $this->limitCallQueue; + } + + /** + * Set maximum number of messages to consider when using call queues. + * + * @param int $limitCallQueue Maximum number of messages to consider when using call queues + * + * @return self + */ + public function setLimitCallQueue(int $limitCallQueue): self + { + $this->limitCallQueue = $limitCallQueue; + + return $this; + } + + /** + * Get encode payload with GZIP if bigger than. + * + * @return int + */ + public function getGzipEncodeIfGt(): int + { + return $this->gzipEncodeIfGt; + } + + /** + * Set encode payload with GZIP if bigger than. + * + * @param int $gzipEncodeIfGt Encode payload with GZIP if bigger than + * + * @return self + */ + public function setGzipEncodeIfGt(int $gzipEncodeIfGt): self + { + $this->gzipEncodeIfGt = $gzipEncodeIfGt; + + return $this; + } +} diff --git a/src/danog/MadelineProto/Settings/SecretChats.php b/src/danog/MadelineProto/Settings/SecretChats.php new file mode 100644 index 00000000..6b5451f5 --- /dev/null +++ b/src/danog/MadelineProto/Settings/SecretChats.php @@ -0,0 +1,66 @@ + + */ + protected $accept = true; + + public function mergeArray(array $settings): void + { + if (isset($settings['secret_chats']['accept_chats'])) { + $this->setAccept($settings['secret_chats']['accept_chats']); + } + } + /** + * Get boolean or array of IDs. + * + * @return bool|array + */ + public function getAccept() + { + return $this->accept; + } + + /** + * Set boolean or array of IDs. + * + * @param bool|array $accept Boolean or array of IDs + * + * @return self + */ + public function setAccept($accept): self + { + $this->accept = $accept; + + return $this; + } + + /** + * Can we accept this chat. + * + * @internal + * + * @param integer $id + * @return boolean + */ + public function canAccept(int $id): bool + { + if ($this->accept) { + return false; + } + if ($this->accept === true) { + return true; + } + return \in_array($id, $this->accept); + } +} diff --git a/src/danog/MadelineProto/Settings/Serialization.php b/src/danog/MadelineProto/Settings/Serialization.php new file mode 100644 index 00000000..8da90224 --- /dev/null +++ b/src/danog/MadelineProto/Settings/Serialization.php @@ -0,0 +1,43 @@ +setInterval($settings['serialization']['serialization_interval']); + } + } + /** + * Get serialization interval, in seconds. + * + * @return int + */ + public function getInterval(): int + { + return $this->interval; + } + + /** + * Set serialization interval, in seconds. + * + * @param int $interval Serialization interval, in seconds. + * + * @return self + */ + public function setInterval(int $interval): self + { + $this->interval = $interval; + + return $this; + } +} diff --git a/src/danog/MadelineProto/Settings/TLSchema.php b/src/danog/MadelineProto/Settings/TLSchema.php new file mode 100644 index 00000000..513ee926 --- /dev/null +++ b/src/danog/MadelineProto/Settings/TLSchema.php @@ -0,0 +1,207 @@ +setLayer($settings['layer']); + } + if (isset($settings['src'])) { + $src = $settings['src']; + if (isset($src['mtproto'])) { + $this->setMTProtoSchema($src['mtproto']); + } + if (isset($src['telegram'])) { + $this->setAPISchema($src['telegram']); + } + if (isset($src['secret'])) { + $this->setSecretSchema($src['secret']); + } + } + } + + /** + * Upgrade scheme autonomously. + */ + public function __wakeup() + { + if (!\file_exists($this->APISchema) // Scheme was upgraded + && $this->APISchema !== __DIR__.'/../TL_mtproto_v117.tl' // Session path has changed + ) { + $new = new self; + $this->setAPISchema($new->getAPISchema()); + $this->setMTProtoSchema($new->getMTProtoSchema()); + $this->setSecretSchema($new->getSecretSchema()); + $this->setLayer($this->getLayer()); + $this->wasUpgraded = true; + } + } + /** + * Returns whether the TL parser should re-parse the TL schemes. + * + * @return boolean + */ + public function needsUpgrade(): bool + { + return $this->wasUpgraded; + } + /** + * Signal that scheme was re-parsed. + * + * @return void + */ + public function upgrade(): void + { + $this->wasUpgraded = false; + } + /** + * Get TL layer version. + * + * @return int + */ + public function getLayer(): int + { + return $this->layer; + } + + /** + * Set TL layer version. + * + * @param int $layer TL layer version. + * + * @return self + */ + public function setLayer(int $layer): self + { + $this->layer = $layer; + + return $this; + } + + /** + * Get MTProto schema path. + * + * @return string + */ + public function getMTProtoSchema(): string + { + return $this->MTProtoSchema; + } + + /** + * Set MTProto schema path. + * + * @param string $MTProtoSchema MTProto schema path. + * + * @return self + */ + public function setMTProtoSchema(string $MTProtoSchema): self + { + $this->MTProtoSchema = $MTProtoSchema; + + return $this; + } + + /** + * Get API schema path. + * + * @return string + */ + public function getAPISchema(): string + { + return $this->APISchema; + } + + /** + * Set API schema path. + * + * @param string $APISchema API schema path. + * + * @return self + */ + public function setAPISchema(string $APISchema): self + { + $this->APISchema = $APISchema; + + return $this; + } + + + /** + * Get secret schema path. + * + * @return string + */ + public function getSecretSchema(): string + { + return $this->secretSchema; + } + + /** + * Set secret schema path. + * + * @param string $secretSchema Secret schema path. + * + * @return self + */ + public function setSecretSchema(string $secretSchema): self + { + $this->secretSchema = $secretSchema; + + return $this; + } + + /** + * Get the value of other. + * + * @return array + */ + public function getOther(): array + { + return $this->other; + } + + /** + * Set the value of other. + * + * @param array $other + * + * @return self + */ + public function setOther(array $other): self + { + $this->other = $other; + + return $this; + } +} diff --git a/src/danog/MadelineProto/SettingsAbstract.php b/src/danog/MadelineProto/SettingsAbstract.php new file mode 100644 index 00000000..27e105ce --- /dev/null +++ b/src/danog/MadelineProto/SettingsAbstract.php @@ -0,0 +1,57 @@ +getDefaultProperties(); + foreach ($class->getProperties(ReflectionProperty::IS_PROTECTED|ReflectionProperty::IS_PUBLIC) as $property) { + $name = $property->getName(); + if (isset($other->{$name}) + && ( + !isset($defaults[$name]) + || $other->{$name} !== $defaults[$name] // Isn't equal to the default value + ) + ) { + $this->{'set'.\ucfirst($name)}($other->{$name}); + } + } + } + /** + * Convert array of legacy array property names to new camel case names. + * + * @param array $properties Properties + * + * @return array + */ + protected static function toCamel(array $properties): array + { + $result = []; + foreach ($properties as $prop) { + $result['set'.\ucfirst(Tools::toCamelCase($prop))] = $prop; + } + return $result; + } +} diff --git a/src/danog/MadelineProto/SettingsEmpty.php b/src/danog/MadelineProto/SettingsEmpty.php new file mode 100644 index 00000000..b4756880 --- /dev/null +++ b/src/danog/MadelineProto/SettingsEmpty.php @@ -0,0 +1,10 @@ + */ -class UdpBufferedStream extends DefaultStream implements BufferedStreamInterface +class UdpBufferedStream extends DefaultStream implements BufferedStreamInterface, MTProtoBufferInterface { use BufferedStream; private RawStreamInterface $stream; diff --git a/src/danog/MadelineProto/Stream/ConnectionContext.php b/src/danog/MadelineProto/Stream/ConnectionContext.php index bbbe6663..5c50870b 100644 --- a/src/danog/MadelineProto/Stream/ConnectionContext.php +++ b/src/danog/MadelineProto/Stream/ConnectionContext.php @@ -432,7 +432,7 @@ class ConnectionContext { foreach ($this->nextStreams as $couple) { list($streamName, $extra) = $couple; - if ($streamName === ObfuscatedStream::getName() && isset($extra['address'])) { + if ($streamName === ObfuscatedStream::class && isset($extra['address'])) { $extra['_'] = 'inputClientProxy'; return $extra; } @@ -461,7 +461,7 @@ class ConnectionContext $string .= ' => '; } $string .= \preg_replace('/.*\\\\/', '', $stream[0]); - if ($stream[1] && $stream[0] !== DefaultStream::getName()) { + if ($stream[1] && $stream[0] !== DefaultStream::class) { $string .= ' ('.\json_encode($stream[1]).')'; } } diff --git a/src/danog/MadelineProto/TL/TL.php b/src/danog/MadelineProto/TL/TL.php index ad5e3438..a8ad19f9 100644 --- a/src/danog/MadelineProto/TL/TL.php +++ b/src/danog/MadelineProto/TL/TL.php @@ -21,6 +21,7 @@ namespace danog\MadelineProto\TL; use Amp\Promise; use danog\MadelineProto\MTProto; +use danog\MadelineProto\Settings\TLSchema; use danog\MadelineProto\Tools; /** @@ -128,12 +129,12 @@ class TL /** * Initialize TL parser. * - * @param array $files Scheme files + * @param TLSchema $files Scheme files * @param TLCallback[] $objects TL Callback objects * * @return void */ - public function init(array $files, array $objects = []) + public function init(TLSchema $files, array $objects = []) { $this->API->logger->logger(\danog\MadelineProto\Lang::$current_lang['TL_loading'], \danog\MadelineProto\Logger::VERBOSE); $this->updateCallbacks($objects); @@ -142,7 +143,12 @@ class TL $this->tdConstructors = new TLConstructors(); $this->tdMethods = new TLMethods(); $this->tdDescriptions = ['types' => [], 'constructors' => [], 'methods' => []]; - foreach ($files as $scheme_type => $file) { + foreach ([ + 'api' => $files->getAPISchema(), + 'mtproto' => $files->getMTProtoSchema(), + 'secret' => $files->getSecretSchema(), + ...$files->getOther() + ] as $scheme_type => $file) { $this->API->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['file_parsing'], \basename($file)), \danog\MadelineProto\Logger::VERBOSE); $filec = \file_get_contents(Tools::absolute($file)); $TL_dict = \json_decode($filec, true); @@ -156,7 +162,7 @@ class TL $class = null; $dparams = []; $lineBuf = ''; - foreach ($tl_file as $line_number => $line) { + foreach ($tl_file as $line) { $line = \rtrim($line); if (\preg_match('|^//@|', $line)) { $list = \explode(' @', \str_replace('//', ' ', $line)); @@ -232,7 +238,7 @@ class TL $id = \hash('crc32b', $clean); if (\preg_match('/^[^\\s]+#([a-f0-9]*)/i', $line, $matches)) { $nid = \str_pad($matches[1], 8, '0', \STR_PAD_LEFT); - if ($id !== $nid && $scheme_type !== 'botAPI') { + if ($id !== $nid) { $this->API->logger->logger(\sprintf(\danog\MadelineProto\Lang::$current_lang['crc32_mismatch'], $id, $nid, $line), \danog\MadelineProto\Logger::ERROR); } $id = $nid; @@ -293,7 +299,7 @@ class TL } } } - if (isset($files['td']) && isset($files['telegram'])) { + if (isset($files->getOther()['td'])) { foreach ($this->tdConstructors->by_id as $id => $data) { $name = $data['predicate']; if ($this->constructors->findById($id) === false) { @@ -318,6 +324,7 @@ class TL } } } + $files->upgrade(); } /** * Get TL namespaces. @@ -606,7 +613,7 @@ class TL } } elseif ($method === 'messages.sendEncryptedFile') { if (isset($arguments['file'])) { - if ((!\is_array($arguments['file']) || !(isset($arguments['file']['_']) && $this->constructors->findByPredicate($arguments['file']['_']) === 'InputEncryptedFile')) && $this->API->settings['upload']['allow_automatic_upload']) { + if ((!\is_array($arguments['file']) || !(isset($arguments['file']['_']) && $this->constructors->findByPredicate($arguments['file']['_']) === 'InputEncryptedFile')) && $this->API->getSettings()->getFiles()->getAllowAutomaticUpload()) { $arguments['file'] = (yield from $this->API->uploadEncrypted($arguments['file'])); } if (isset($arguments['file']['key'])) { diff --git a/src/danog/MadelineProto/TON/ADNLConnection.php b/src/danog/MadelineProto/TON/ADNLConnection.php index e234e0b5..b5d07652 100644 --- a/src/danog/MadelineProto/TON/ADNLConnection.php +++ b/src/danog/MadelineProto/TON/ADNLConnection.php @@ -124,7 +124,7 @@ class ADNLConnection $payload .= $encryptedRandom; $ip = \long2ip(\unpack('V', Tools::packSignedInt($endpoint['ip']))[1]); $port = $endpoint['port']; - $ctx = (new ConnectionContext())->setSocketContext(new ConnectContext())->setUri("tcp://{$ip}:{$port}")->addStream(DefaultStream::getName())->addStream(BufferedRawStream::getName())->addStream(CtrStream::getName(), $obf)->addStream(HashedBufferedStream::getName(), 'sha256')->addStream(ADNLStream::getName()); + $ctx = (new ConnectionContext())->setSocketContext(new ConnectContext())->setUri("tcp://{$ip}:{$port}")->addStream(DefaultStream::class)->addStream(BufferedRawStream::class)->addStream(CtrStream::class, $obf)->addStream(HashedBufferedStream::class, 'sha256')->addStream(ADNLStream::class); $this->stream = (yield from $ctx->getStream($payload)); Tools::callFork((function (): \Generator { //yield Tools::sleep(1); diff --git a/src/danog/MadelineProto/VoIP/AuthKeyHandler.php b/src/danog/MadelineProto/VoIP/AuthKeyHandler.php index ed9f58c1..8cc805cd 100644 --- a/src/danog/MadelineProto/VoIP/AuthKeyHandler.php +++ b/src/danog/MadelineProto/VoIP/AuthKeyHandler.php @@ -307,11 +307,7 @@ trait AuthKeyHandler yield from $this->methodCallAsyncRead('phone.saveCallDebug', ['peer' => $call, 'debug' => $this->calls[$call['id']]->getDebugLog()], ['datacenter' => $this->datacenter->curdc]); } $update = ['_' => 'updatePhoneCall', 'phone_call' => $this->calls[$call['id']]]; - if (isset($this->settings['pwr']['strict']) && $this->settings['pwr']['strict']) { - $this->pwrUpdateHandler($update); - } else { - \in_array($this->settings['updates']['callback'], [['danog\\MadelineProto\\API', 'getUpdatesUpdateHandler'], 'getUpdatesUpdateHandler']) ? $this->getUpdatesUpdateHandler($update) : $this->settings['updates']['callback']($update); - } + $this->updates[$this->updates_key++] = $update; unset($this->calls[$call['id']]); } /** diff --git a/src/danog/MadelineProto/Wrappers/Callback.php b/src/danog/MadelineProto/Wrappers/Callback.php index 56d7b3db..1c03649f 100644 --- a/src/danog/MadelineProto/Wrappers/Callback.php +++ b/src/danog/MadelineProto/Wrappers/Callback.php @@ -33,9 +33,7 @@ trait Callback */ public function setCallback($callback): void { - $this->settings['updates']['callback'] = $callback; - $this->settings['updates']['run_callback'] = true; - $this->settings['updates']['handle_updates'] = true; + $this->updateHandler = $callback; $this->startUpdateSystem(); } } diff --git a/src/danog/MadelineProto/Wrappers/DialogHandler.php b/src/danog/MadelineProto/Wrappers/DialogHandler.php index 26f0eab1..82c76e5a 100644 --- a/src/danog/MadelineProto/Wrappers/DialogHandler.php +++ b/src/danog/MadelineProto/Wrappers/DialogHandler.php @@ -19,6 +19,13 @@ namespace danog\MadelineProto\Wrappers; +use danog\MadelineProto\Settings; + +/** + * Dialog handler. + * + * @property Settings $settings Settings + */ trait DialogHandler { /** diff --git a/src/danog/MadelineProto/Wrappers/Events.php b/src/danog/MadelineProto/Wrappers/Events.php index a5b2df8b..c9f31264 100644 --- a/src/danog/MadelineProto/Wrappers/Events.php +++ b/src/danog/MadelineProto/Wrappers/Events.php @@ -20,17 +20,21 @@ namespace danog\MadelineProto\Wrappers; use danog\MadelineProto\EventHandler; +use danog\MadelineProto\Settings; + use danog\MadelineProto\Tools; /** * Event handler. + * + * @property Settings $settings Settings */ trait Events { /** * Event handler class name. * - * @var string + * @var class-string */ public $event_handler; /** @@ -46,30 +50,53 @@ trait Events */ private $eventHandlerMethods = []; /** - * Set event handler. + * Initialize existing event handler. * - * @param string|EventHandler $event_handler Event handler + * @internal * - * @return \Generator + * @return void */ - public function setEventHandler($event_handler): \Generator + public function initExistingEventHandler(): void { - if (!\class_exists($event_handler) || !\is_subclass_of($event_handler, '\\danog\\MadelineProto\\EventHandler')) { - throw new \danog\MadelineProto\Exception('Wrong event handler was defined'); + if ($this->event_handler && \class_exists($this->event_handler) && \is_subclass_of($this->API->event_handler, EventHandler::class)) { + $this->initEventHandler($this->API->event_handler); } - $this->event_handler = $event_handler; + } + /** + * Initialize event handler. + * + * @param class-string $eventHandler + * + * @return void + */ + private function initEventHandler(string $eventHandler): void + { + $this->event_handler = $eventHandler; if (!$this->event_handler_instance instanceof $this->event_handler) { $class_name = $this->event_handler; $this->event_handler_instance = new $class_name($this->wrapper); - } elseif ($this->wrapper) { - $this->event_handler_instance->__construct($this->wrapper); } + $this->event_handler_instance->initInternal($this->wrapper); + } + /** + * Set event handler. + * + * @param class-string $eventHandler Event handler + * + * @return \Generator + */ + public function setEventHandler(string $eventHandler): \Generator + { + if (!\is_subclass_of($eventHandler, EventHandler::class)) { + throw new \danog\MadelineProto\Exception('Wrong event handler was defined'); + } + $this->initEventHandler($eventHandler); $this->eventHandlerMethods = []; foreach (\get_class_methods($this->event_handler) as $method) { if ($method === 'onLoop') { $this->loop_callback = [$this->event_handler_instance, 'onLoop']; } elseif ($method === 'onAny') { - foreach ($this->getTL()->getConstructors()->by_id as $id => $constructor) { + foreach ($this->getTL()->getConstructors()->by_id as $constructor) { if ($constructor['type'] === 'Update' && !isset($this->eventHandlerMethods[$constructor['predicate']])) { $this->eventHandlerMethods[$constructor['predicate']] = [$this->event_handler_instance, 'onAny']; } @@ -82,12 +109,10 @@ trait Events } } yield from $this->setReportPeers($this->event_handler_instance->getReportPeers()); - $this->settings['updates']['callback'] = [$this, 'eventUpdateHandler']; - $this->settings['updates']['handle_updates'] = true; - $this->settings['updates']['run_callback'] = true; if (\method_exists($this->event_handler_instance, 'onStart')) { Tools::callFork($this->event_handler_instance->onStart()); } + $this->updateHandler = [$this, 'eventUpdateHandler']; if ($this->inited()) { $this->startUpdateSystem(); } @@ -105,9 +130,6 @@ trait Events $this->event_handler_instance = null; $this->eventHandlerMethods = []; $this->setNoop(); - if ($disableUpdateHandling) { - $this->settings['updates']['handle_updates'] = false; - } } /** * Get event handler. diff --git a/src/danog/MadelineProto/Wrappers/Login.php b/src/danog/MadelineProto/Wrappers/Login.php index 82536ea9..8ccd8b4f 100644 --- a/src/danog/MadelineProto/Wrappers/Login.php +++ b/src/danog/MadelineProto/Wrappers/Login.php @@ -19,11 +19,16 @@ namespace danog\MadelineProto\Wrappers; +use danog\MadelineProto\MTProto; use danog\MadelineProto\MTProto\PermAuthKey; use danog\MadelineProto\MTProtoTools\PasswordCalculator; +use danog\MadelineProto\Settings; + /** * Manages logging in and out. + * + * @property Settings $settings Settings */ trait Login { @@ -34,7 +39,7 @@ trait Login */ public function logout(): \Generator { - yield from $this->methodCallAsyncRead('auth.logOut', [], ['datacenter' => $this->datacenter->curdc]); + yield from $this->methodCallAsyncRead('auth.logOut', []); yield from $this->resetSession(); $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['logout_ok'], \danog\MadelineProto\Logger::NOTICE); $this->startUpdateSystem(); @@ -49,16 +54,21 @@ trait Login */ public function botLogin(string $token): \Generator { - if ($this->authorized === self::LOGGED_IN) { + if ($this->authorized === MTProto::LOGGED_IN) { return; - $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['already_loggedIn'], \danog\MadelineProto\Logger::NOTICE); - yield from $this->logout(); } $callbacks = [$this, $this->referenceDatabase]; $this->TL->updateCallbacks($callbacks); $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['login_bot'], \danog\MadelineProto\Logger::NOTICE); - $this->authorization = yield from $this->methodCallAsyncRead('auth.importBotAuthorization', ['bot_auth_token' => $token, 'api_id' => $this->settings['app_info']['api_id'], 'api_hash' => $this->settings['app_info']['api_hash']], ['datacenter' => $this->datacenter->curdc]); - $this->authorized = self::LOGGED_IN; + $this->authorization = yield from $this->methodCallAsyncRead( + 'auth.importBotAuthorization', + [ + 'bot_auth_token' => $token, + 'api_id' => $this->settings->getAppInfo()->getApiId(), + 'api_hash' => $this->settings->getAppInfo()->getApiHash(), + ] + ); + $this->authorized = MTProto::LOGGED_IN; $this->authorized_dc = $this->datacenter->curdc; $this->datacenter->getDataCenterConnection($this->datacenter->curdc)->authorized(true); $this->updates = []; @@ -78,16 +88,26 @@ trait Login */ public function phoneLogin($number, $sms_type = 5): \Generator { - if ($this->authorized === self::LOGGED_IN) { + if ($this->authorized === MTProto::LOGGED_IN) { $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['already_loggedIn'], \danog\MadelineProto\Logger::NOTICE); yield from $this->logout(); } $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['login_code_sending'], \danog\MadelineProto\Logger::NOTICE); - $this->authorization = yield from $this->methodCallAsyncRead('auth.sendCode', ['settings' => ['_' => 'codeSettings'], 'phone_number' => $number, 'sms_type' => $sms_type, 'api_id' => $this->settings['app_info']['api_id'], 'api_hash' => $this->settings['app_info']['api_hash'], 'lang_code' => $this->settings['app_info']['lang_code']], ['datacenter' => $this->datacenter->curdc]); + $this->authorization = yield from $this->methodCallAsyncRead( + 'auth.sendCode', + [ + 'settings' => ['_' => 'codeSettings'], + 'phone_number' => $number, + 'sms_type' => $sms_type, + 'api_id' => $this->settings->getAppInfo()->getApiId(), + 'api_hash' => $this->settings->getAppInfo()->getApiHash(), + 'lang_code' => $this->settings->getAppInfo()->getLangCode() + ] + ); $this->authorized_dc = $this->datacenter->curdc; $this->authorization['phone_number'] = $number; //$this->authorization['_'] .= 'MP'; - $this->authorized = self::WAITING_CODE; + $this->authorized = MTProto::WAITING_CODE; $this->updates = []; $this->updates_key = 0; $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['login_code_sent'], \danog\MadelineProto\Logger::NOTICE); @@ -102,26 +122,26 @@ trait Login */ public function completePhoneLogin($code): \Generator { - if ($this->authorized !== self::WAITING_CODE) { + if ($this->authorized !== MTProto::WAITING_CODE) { throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['login_code_uncalled']); } - $this->authorized = self::NOT_LOGGED_IN; + $this->authorized = MTProto::NOT_LOGGED_IN; $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['login_user'], \danog\MadelineProto\Logger::NOTICE); try { - $authorization = yield from $this->methodCallAsyncRead('auth.signIn', ['phone_number' => $this->authorization['phone_number'], 'phone_code_hash' => $this->authorization['phone_code_hash'], 'phone_code' => (string) $code], ['datacenter' => $this->datacenter->curdc]); + $authorization = yield from $this->methodCallAsyncRead('auth.signIn', ['phone_number' => $this->authorization['phone_number'], 'phone_code_hash' => $this->authorization['phone_code_hash'], 'phone_code' => (string) $code]); } catch (\danog\MadelineProto\RPCErrorException $e) { if ($e->rpc === 'SESSION_PASSWORD_NEEDED') { $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['login_2fa_enabled'], \danog\MadelineProto\Logger::NOTICE); - $this->authorization = yield from $this->methodCallAsyncRead('account.getPassword', [], ['datacenter' => $this->datacenter->curdc]); + $this->authorization = yield from $this->methodCallAsyncRead('account.getPassword', []); if (!isset($this->authorization['hint'])) { $this->authorization['hint'] = ''; } - $this->authorized = self::WAITING_PASSWORD; + $this->authorized = MTProto::WAITING_PASSWORD; return $this->authorization; } if ($e->rpc === 'PHONE_NUMBER_UNOCCUPIED') { $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['login_need_signup'], \danog\MadelineProto\Logger::NOTICE); - $this->authorized = self::WAITING_SIGNUP; + $this->authorized = MTProto::WAITING_SIGNUP; $this->authorization['phone_code'] = $code; return ['_' => 'account.needSignup']; } @@ -129,12 +149,12 @@ trait Login } if ($authorization['_'] === 'auth.authorizationSignUpRequired') { $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['login_need_signup'], \danog\MadelineProto\Logger::NOTICE); - $this->authorized = self::WAITING_SIGNUP; + $this->authorized = MTProto::WAITING_SIGNUP; $this->authorization['phone_code'] = $code; $authorization['_'] = 'account.needSignup'; return $authorization; } - $this->authorized = self::LOGGED_IN; + $this->authorized = MTProto::LOGGED_IN; $this->authorization = $authorization; $this->datacenter->getDataCenterConnection($this->datacenter->curdc)->authorized(true); yield from $this->initAuthorization(); @@ -152,7 +172,7 @@ trait Login */ public function importAuthorization($authorization): \Generator { - if ($this->authorized === self::LOGGED_IN) { + if ($this->authorized === MTProto::LOGGED_IN) { $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['already_loggedIn'], \danog\MadelineProto\Logger::NOTICE); yield from $this->logout(); } @@ -167,7 +187,7 @@ trait Login $dataCenterConnection->resetSession(); $dataCenterConnection->setPermAuthKey($auth_key); $dataCenterConnection->authorized(true); - $this->authorized = self::LOGGED_IN; + $this->authorized = MTProto::LOGGED_IN; yield from $this->initAuthorization(); yield from $this->getPhoneConfig(); $res = (yield from $this->fullGetSelf()); @@ -186,7 +206,7 @@ trait Login */ public function exportAuthorization(): \Generator { - if ($this->authorized !== self::LOGGED_IN) { + if ($this->authorized !== MTProto::LOGGED_IN) { throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['not_loggedIn']); } yield from $this->fullGetSelf(); @@ -203,13 +223,13 @@ trait Login */ public function completeSignup(string $first_name, string $last_name = ''): \Generator { - if ($this->authorized !== self::WAITING_SIGNUP) { + if ($this->authorized !== MTProto::WAITING_SIGNUP) { throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['signup_uncalled']); } - $this->authorized = self::NOT_LOGGED_IN; + $this->authorized = MTProto::NOT_LOGGED_IN; $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['signing_up'], \danog\MadelineProto\Logger::NOTICE); - $this->authorization = yield from $this->methodCallAsyncRead('auth.signUp', ['phone_number' => $this->authorization['phone_number'], 'phone_code_hash' => $this->authorization['phone_code_hash'], 'phone_code' => $this->authorization['phone_code'], 'first_name' => $first_name, 'last_name' => $last_name], ['datacenter' => $this->datacenter->curdc]); - $this->authorized = self::LOGGED_IN; + $this->authorization = yield from $this->methodCallAsyncRead('auth.signUp', ['phone_number' => $this->authorization['phone_number'], 'phone_code_hash' => $this->authorization['phone_code_hash'], 'phone_code' => $this->authorization['phone_code'], 'first_name' => $first_name, 'last_name' => $last_name]); + $this->authorized = MTProto::LOGGED_IN; $this->datacenter->getDataCenterConnection($this->datacenter->curdc)->authorized(true); yield from $this->initAuthorization(); yield from $this->getPhoneConfig(); @@ -226,15 +246,15 @@ trait Login */ public function complete2faLogin(string $password): \Generator { - if ($this->authorized !== self::WAITING_PASSWORD) { + if ($this->authorized !== MTProto::WAITING_PASSWORD) { throw new \danog\MadelineProto\Exception(\danog\MadelineProto\Lang::$current_lang['2fa_uncalled']); } - $this->authorized = self::NOT_LOGGED_IN; + $this->authorized = MTProto::NOT_LOGGED_IN; $hasher = new PasswordCalculator($this->logger); $hasher->addInfo($this->authorization); $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['login_user'], \danog\MadelineProto\Logger::NOTICE); - $this->authorization = yield from $this->methodCallAsyncRead('auth.checkPassword', ['password' => $hasher->getCheckPassword($password)], ['datacenter' => $this->datacenter->curdc]); - $this->authorized = self::LOGGED_IN; + $this->authorization = yield from $this->methodCallAsyncRead('auth.checkPassword', ['password' => $hasher->getCheckPassword($password)]); + $this->authorized = MTProto::LOGGED_IN; $this->datacenter->getDataCenterConnection($this->datacenter->curdc)->authorized(true); yield from $this->initAuthorization(); $this->logger->logger(\danog\MadelineProto\Lang::$current_lang['login_ok'], \danog\MadelineProto\Logger::NOTICE); @@ -254,7 +274,7 @@ trait Login public function update2fa(array $params): \Generator { $hasher = new PasswordCalculator($this->logger); - $hasher->addInfo(yield from $this->methodCallAsyncRead('account.getPassword', [], ['datacenter' => $this->datacenter->curdc])); - return yield from $this->methodCallAsyncRead('account.updatePasswordSettings', $hasher->getPassword($params), ['datacenter' => $this->datacenter->curdc]); + $hasher->addInfo(yield from $this->methodCallAsyncRead('account.getPassword', [])); + return yield from $this->methodCallAsyncRead('account.updatePasswordSettings', $hasher->getPassword($params)); } } diff --git a/src/danog/MadelineProto/Wrappers/Loop.php b/src/danog/MadelineProto/Wrappers/Loop.php index 45ee5508..0c89d966 100644 --- a/src/danog/MadelineProto/Wrappers/Loop.php +++ b/src/danog/MadelineProto/Wrappers/Loop.php @@ -20,11 +20,15 @@ namespace danog\MadelineProto\Wrappers; use Amp\Promise; +use danog\MadelineProto\Settings; use danog\MadelineProto\Shutdown; + use danog\MadelineProto\Tools; /** * Manages logging in and out. + * + * @property Settings $settings Settings */ trait Loop { @@ -87,7 +91,7 @@ trait Loop /** * 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 */ @@ -105,7 +109,7 @@ trait Loop $this->logger->logger('Not authorized, not starting event loop', \danog\MadelineProto\Logger::FATAL_ERROR); return false; } - if (\in_array($this->settings['updates']['callback'], [['danog\\MadelineProto\\API', 'getUpdatesUpdateHandler'], 'getUpdatesUpdateHandler'])) { + if ($this->updateHandler === self::GETUPDATES_HANDLER) { $this->logger->logger('Getupdates event handler is enabled, exiting from loop', \danog\MadelineProto\Logger::FATAL_ERROR); return false; } @@ -113,21 +117,19 @@ trait Loop if (!\is_callable($this->loop_callback)) { $this->loop_callback = null; } - if (!$this->settings['updates']['handle_updates']) { - $this->settings['updates']['handle_updates'] = true; - } - if (!$this->settings['updates']['run_callback']) { - $this->settings['updates']['run_callback'] = true; - } $this->initSelfRestart(); $this->startUpdateSystem(); $this->logger->logger('Started update loop', \danog\MadelineProto\Logger::NOTICE); $this->stopLoop = false; do { + if (!$this->updateHandler) { + yield from $this->waitUpdate(); + continue; + } $updates = $this->updates; $this->updates = []; foreach ($updates as $update) { - $r = $this->settings['updates']['callback']($update); + $r = ($this->updateHandler)($update); if (\is_object($r)) { \danog\MadelineProto\Tools::callFork($r); } diff --git a/src/danog/MadelineProto/Wrappers/Noop.php b/src/danog/MadelineProto/Wrappers/Noop.php index c068fded..6fe2106e 100644 --- a/src/danog/MadelineProto/Wrappers/Noop.php +++ b/src/danog/MadelineProto/Wrappers/Noop.php @@ -28,9 +28,7 @@ trait Noop */ public function setNoop(): void { - $this->settings['updates']['callback'] = [$this, 'noop']; - $this->settings['updates']['run_callback'] = false; - $this->settings['updates']['handle_updates'] = true; + $this->updateHandler = null; $this->updates = []; $this->startUpdateSystem(); } diff --git a/src/danog/MadelineProto/Wrappers/Start.php b/src/danog/MadelineProto/Wrappers/Start.php index dd5cf468..0df1afcc 100644 --- a/src/danog/MadelineProto/Wrappers/Start.php +++ b/src/danog/MadelineProto/Wrappers/Start.php @@ -20,10 +20,14 @@ namespace danog\MadelineProto\Wrappers; use danog\MadelineProto\MTProto; +use danog\MadelineProto\Settings; + use danog\MadelineProto\Tools; /** * Manages simple logging in and out. + * + * @property Settings $settings Settings */ trait Start { diff --git a/src/danog/MadelineProto/Wrappers/Webhook.php b/src/danog/MadelineProto/Wrappers/Webhook.php index b7604057..df576fe6 100644 --- a/src/danog/MadelineProto/Wrappers/Webhook.php +++ b/src/danog/MadelineProto/Wrappers/Webhook.php @@ -19,8 +19,13 @@ namespace danog\MadelineProto\Wrappers; +use Amp\Http\Client\Request; +use danog\MadelineProto\Settings; + /** * Manages logging in and out. + * + * @property Settings $settings */ trait Webhook { @@ -36,9 +41,38 @@ trait Webhook { $this->pem_path = $pem_path; $this->hook_url = $hook_url; - $this->settings['updates']['callback'] = [$this, 'pwrWebhook']; - $this->settings['updates']['run_callback'] = true; - $this->settings['updates']['handle_updates'] = true; + $this->updateHandler = [$this, 'pwrWebhook']; $this->startUpdateSystem(); } + /** + * Send update to webhook. + * + * @param array $update Update + * + * @return void + */ + private function pwrWebhook(array $update): void + { + $payload = \json_encode($update); + if ($payload === '') { + $this->logger->logger($update, $payload, \json_last_error_msg()); + $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}"); + } + } + })()); + } }