1984 lines
79 KiB
PHP
1984 lines
79 KiB
PHP
<?php
|
||
|
||
/**
|
||
* MTProto module.
|
||
*
|
||
* This file is part of MadelineProto.
|
||
* MadelineProto is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||
* MadelineProto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||
* See the GNU Affero General Public License for more details.
|
||
* You should have received a copy of the GNU General Public License along with MadelineProto.
|
||
* If not, see <http://www.gnu.org/licenses/>.
|
||
*
|
||
* @author Daniil Gentili <daniil@daniil.it>
|
||
* @copyright 2016-2020 Daniil Gentili <daniil@daniil.it>
|
||
* @license https://opensource.org/licenses/AGPL-3.0 AGPLv3
|
||
*
|
||
* @link https://docs.madelineproto.xyz MadelineProto documentation
|
||
*/
|
||
|
||
namespace danog\MadelineProto;
|
||
|
||
use Amp\Dns\Resolver;
|
||
use Amp\File\StatCache;
|
||
use Amp\Http\Client\HttpClient;
|
||
use Amp\Promise;
|
||
use danog\MadelineProto\Async\AsyncConstruct;
|
||
use danog\MadelineProto\Db\DbArray;
|
||
use danog\MadelineProto\Db\DbPropertiesFabric;
|
||
use danog\MadelineProto\Db\DbPropertiesTrait;
|
||
use danog\MadelineProto\Db\Mysql;
|
||
use danog\MadelineProto\Ipc\Server;
|
||
use danog\MadelineProto\Loop\Generic\PeriodicLoopInternal;
|
||
use danog\MadelineProto\Loop\Update\FeedLoop;
|
||
use danog\MadelineProto\Loop\Update\SeqLoop;
|
||
use danog\MadelineProto\Loop\Update\UpdateLoop;
|
||
use danog\MadelineProto\MTProtoTools\CombinedUpdatesState;
|
||
use danog\MadelineProto\MTProtoTools\GarbageCollector;
|
||
use danog\MadelineProto\MTProtoTools\MinDatabase;
|
||
use danog\MadelineProto\MTProtoTools\ReferenceDatabase;
|
||
use danog\MadelineProto\MTProtoTools\UpdatesState;
|
||
use danog\MadelineProto\TL\TL;
|
||
use danog\MadelineProto\TL\TLCallback;
|
||
|
||
use function Amp\File\exists;
|
||
use function Amp\File\size;
|
||
|
||
/**
|
||
* Manages all of the mtproto stuff.
|
||
*/
|
||
class MTProto extends AsyncConstruct implements TLCallback
|
||
{
|
||
use \danog\Serializable;
|
||
use \danog\MadelineProto\MTProtoTools\AuthKeyHandler;
|
||
use \danog\MadelineProto\MTProtoTools\CallHandler;
|
||
use \danog\MadelineProto\MTProtoTools\PeerHandler;
|
||
use \danog\MadelineProto\MTProtoTools\UpdateHandler;
|
||
use \danog\MadelineProto\MTProtoTools\Files;
|
||
use \danog\MadelineProto\SecretChats\AuthKeyHandler;
|
||
use \danog\MadelineProto\SecretChats\MessageHandler;
|
||
use \danog\MadelineProto\SecretChats\ResponseHandler;
|
||
use \danog\MadelineProto\SecretChats\SeqNoHandler;
|
||
use \danog\MadelineProto\TL\Conversion\BotAPI;
|
||
use \danog\MadelineProto\TL\Conversion\BotAPIFiles;
|
||
use \danog\MadelineProto\TL\Conversion\Extension;
|
||
use \danog\MadelineProto\TL\Conversion\TD;
|
||
use \danog\MadelineProto\VoIP\AuthKeyHandler;
|
||
use \danog\MadelineProto\Wrappers\DialogHandler;
|
||
use \danog\MadelineProto\Wrappers\Events;
|
||
use \danog\MadelineProto\Wrappers\Webhook;
|
||
use \danog\MadelineProto\Wrappers\Callback;
|
||
use \danog\MadelineProto\Wrappers\Login;
|
||
use \danog\MadelineProto\Wrappers\Loop;
|
||
use \danog\MadelineProto\Wrappers\Noop;
|
||
use \danog\MadelineProto\Wrappers\Start;
|
||
use \danog\MadelineProto\Wrappers\Templates;
|
||
use \danog\MadelineProto\Wrappers\TOS;
|
||
use DbPropertiesTrait;
|
||
/**
|
||
* Old internal version of MadelineProto.
|
||
*
|
||
* DO NOT REMOVE THIS COMMENTED OUT CONSTANT
|
||
*
|
||
* @var int
|
||
*/
|
||
/*
|
||
const V = 71;
|
||
*/
|
||
/**
|
||
* Internal version of MadelineProto.
|
||
*
|
||
* Increased every time the default settings array or something big changes
|
||
*
|
||
* @var int
|
||
*/
|
||
const V = 143;
|
||
/**
|
||
* String release version.
|
||
*
|
||
* @var string
|
||
*/
|
||
const RELEASE = '5.0';
|
||
/**
|
||
* We're not logged in.
|
||
*
|
||
* @var int
|
||
*/
|
||
const NOT_LOGGED_IN = 0;
|
||
/**
|
||
* We're waiting for the login code.
|
||
*
|
||
* @var int
|
||
*/
|
||
const WAITING_CODE = 1;
|
||
/**
|
||
* We're waiting for parameters to sign up.
|
||
*
|
||
* @var int
|
||
*/
|
||
const WAITING_SIGNUP = -1;
|
||
/**
|
||
* We're waiting for the 2FA password.
|
||
*
|
||
* @var int
|
||
*/
|
||
const WAITING_PASSWORD = 2;
|
||
/**
|
||
* We're logged in.
|
||
*
|
||
* @var int
|
||
*/
|
||
const LOGGED_IN = 3;
|
||
/**
|
||
* Bad message error codes.
|
||
*
|
||
* @var array
|
||
*/
|
||
const BAD_MSG_ERROR_CODES = [16 => 'msg_id too low (most likely, client time is wrong; it would be worthwhile to synchronize it using msg_id notifications and re-send the original message with the “correct†msg_id or wrap it in a container with a new msg_id if the original message had waited too long on the client to be transmitted)', 17 => 'msg_id too high (similar to the previous case, the client time has to be synchronized, and the message re-sent with the correct msg_id)', 18 => 'incorrect two lower order msg_id bits (the server expects client message msg_id to be divisible by 4)', 19 => 'container msg_id is the same as msg_id of a previously received message (this must never happen)', 20 => 'message too old, and it cannot be verified whether the server has received a message with this msg_id or not', 32 => 'msg_seqno too low (the server has already received a message with a lower msg_id but with either a higher or an equal and odd seqno)', 33 => 'msg_seqno too high (similarly, there is a message with a higher msg_id but with either a lower or an equal and odd seqno)', 34 => 'an even msg_seqno expected (irrelevant message), but odd received', 35 => 'odd msg_seqno expected (relevant message), but even received', 48 => 'incorrect server salt (in this case, the bad_server_salt response is received with the correct salt, and the message is to be re-sent with it)', 64 => 'invalid container'];
|
||
/**
|
||
* Localized message info flags.
|
||
*
|
||
* @var array
|
||
*/
|
||
const MSGS_INFO_FLAGS = [1 => 'nothing is known about the message (msg_id too low, the other party may have forgotten it)', 2 => 'message not received (msg_id falls within the range of stored identifiers; however, the other party has certainly not received a message like that)', 3 => 'message not received (msg_id too high; however, the other party has certainly not received it yet)', 4 => 'message received (note that this response is also at the same time a receipt acknowledgment)', 8 => ' and message already acknowledged', 16 => ' and message not requiring acknowledgment', 32 => ' and RPC query contained in message being processed or processing already complete', 64 => ' and content-related response to message already generated', 128 => ' and other party knows for a fact that message is already received'];
|
||
/**
|
||
* Secret chat was not found.
|
||
*
|
||
* @var int
|
||
*/
|
||
const SECRET_EMPTY = 0;
|
||
/**
|
||
* Secret chat was requested.
|
||
*
|
||
* @var int
|
||
*/
|
||
const SECRET_REQUESTED = 1;
|
||
/**
|
||
* Secret chat was found.
|
||
*
|
||
* @var int
|
||
*/
|
||
const SECRET_READY = 2;
|
||
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'];
|
||
const BOTAPI_PARAMS_CONVERSION = ['disable_web_page_preview' => 'no_webpage', 'disable_notification' => 'silent', 'reply_to_message_id' => 'reply_to_msg_id', 'chat_id' => 'peer', 'text' => 'message'];
|
||
// Not content related constructors
|
||
const NOT_CONTENT_RELATED = [
|
||
//'rpc_result',
|
||
//'rpc_error',
|
||
'rpc_drop_answer',
|
||
'rpc_answer_unknown',
|
||
'rpc_answer_dropped_running',
|
||
'rpc_answer_dropped',
|
||
'get_future_salts',
|
||
'future_salt',
|
||
'future_salts',
|
||
'ping',
|
||
'pong',
|
||
'ping_delay_disconnect',
|
||
'destroy_session',
|
||
'destroy_session_ok',
|
||
'destroy_session_none',
|
||
//'new_session_created',
|
||
'msg_container',
|
||
'msg_copy',
|
||
'gzip_packed',
|
||
'http_wait',
|
||
'msgs_ack',
|
||
'bad_msg_notification',
|
||
'bad_server_salt',
|
||
'msgs_state_req',
|
||
'msgs_state_info',
|
||
'msgs_all_info',
|
||
'msg_detailed_info',
|
||
'msg_new_detailed_info',
|
||
'msg_resend_req',
|
||
'msg_resend_ans_req',
|
||
];
|
||
const DEFAULT_GETUPDATES_PARAMS = ['offset' => 0, 'limit' => null, 'timeout' => 0];
|
||
/**
|
||
* Instance of wrapper API.
|
||
*
|
||
* @var null|APIWrapper
|
||
*/
|
||
public $wrapper;
|
||
/**
|
||
* PWRTelegram webhook URL.
|
||
*
|
||
* @var boolean|string
|
||
*/
|
||
public $hook_url = false;
|
||
/**
|
||
* Settings array.
|
||
*
|
||
* @var array
|
||
*/
|
||
public $settings = [];
|
||
/**
|
||
* Config array.
|
||
*
|
||
* @var array
|
||
*/
|
||
private $config = ['expires' => -1];
|
||
/**
|
||
* TOS info.
|
||
*
|
||
* @var array
|
||
*/
|
||
private $tos = ['expires' => 0, 'accepted' => true];
|
||
/**
|
||
* Whether we're initing authorization.
|
||
*
|
||
* @var boolean
|
||
*/
|
||
private $initing_authorization = false;
|
||
/**
|
||
* Authorization info (User).
|
||
*
|
||
* @var array|null
|
||
*/
|
||
public $authorization = null;
|
||
/**
|
||
* Whether we're authorized.
|
||
*
|
||
* @var integer
|
||
*/
|
||
public $authorized = self::NOT_LOGGED_IN;
|
||
/**
|
||
* Main authorized DC ID.
|
||
*
|
||
* @var integer
|
||
*/
|
||
public $authorized_dc = -1;
|
||
/**
|
||
* RSA keys.
|
||
*
|
||
* @var array<RSA>
|
||
*/
|
||
private $rsa_keys = [];
|
||
/**
|
||
* CDN RSA keys.
|
||
*
|
||
* @var array
|
||
*/
|
||
private $cdn_rsa_keys = [];
|
||
/**
|
||
* Diffie-hellman config.
|
||
*
|
||
* @var array
|
||
*/
|
||
private $dh_config = ['version' => 0];
|
||
/**
|
||
* Internal peer database.
|
||
*
|
||
* @var DbArray
|
||
*/
|
||
public $chats;
|
||
|
||
/**
|
||
* Cache of usernames for chats.
|
||
*
|
||
* @var DbArray|Promise[]
|
||
*/
|
||
public $usernames;
|
||
/**
|
||
* Cached parameters for fetching channel participants.
|
||
*
|
||
* @var DbArray|Promise[]
|
||
*/
|
||
public $channel_participants;
|
||
/**
|
||
* When we last stored data in remote peer database (now doesn't exist anymore).
|
||
*
|
||
* @var integer
|
||
*/
|
||
public $last_stored = 0;
|
||
/**
|
||
* Temporary array of data to be sent to remote peer database.
|
||
*
|
||
* @var array
|
||
*/
|
||
public $qres = [];
|
||
/**
|
||
* Full chat info database.
|
||
*
|
||
* @var DbArray|Promise[]
|
||
*/
|
||
public $full_chats;
|
||
/**
|
||
* Latest chat message ID map for update handling.
|
||
*
|
||
* @var array
|
||
*/
|
||
private $msg_ids = [];
|
||
/**
|
||
* Version integer for upgrades.
|
||
*
|
||
* @var integer
|
||
*/
|
||
private $v = 0;
|
||
/**
|
||
* Cached getdialogs params.
|
||
*
|
||
* @var array
|
||
*/
|
||
private $dialog_params = ['limit' => 0, 'offset_date' => 0, 'offset_id' => 0, 'offset_peer' => ['_' => 'inputPeerEmpty'], 'count' => 0];
|
||
/**
|
||
* Support user ID.
|
||
*
|
||
* @var integer
|
||
*/
|
||
private $supportUser = 0;
|
||
/**
|
||
* File reference database.
|
||
*
|
||
* @var \danog\MadelineProto\MTProtoTools\ReferenceDatabase
|
||
*/
|
||
public $referenceDatabase;
|
||
/**
|
||
* min database.
|
||
*
|
||
* @var \danog\MadelineProto\MTProtoTools\MinDatabase
|
||
*/
|
||
public $minDatabase;
|
||
/**
|
||
* TOS check loop.
|
||
*/
|
||
public ?PeriodicLoopInternal $checkTosLoop = null;
|
||
/**
|
||
* Phone config loop.
|
||
*/
|
||
public ?PeriodicLoopInternal $phoneConfigLoop = null;
|
||
/**
|
||
* Config loop.
|
||
*/
|
||
public ?PeriodicLoopInternal $configLoop = null;
|
||
/**
|
||
* Call checker loop.
|
||
*/
|
||
private ?PeriodicLoopInternal $callCheckerLoop = null;
|
||
/**
|
||
* Autoserialization loop.
|
||
*/
|
||
private ?PeriodicLoopInternal $serializeLoop = null;
|
||
/**
|
||
* RPC reporting loop.
|
||
*/
|
||
private ?PeriodicLoopInternal $rpcLoop = null;
|
||
/**
|
||
* IPC server.
|
||
*/
|
||
private ?Server $ipcServer = null;
|
||
/**
|
||
* Feeder loops.
|
||
*
|
||
* @var array<\danog\MadelineProto\Loop\Update\FeedLoop>
|
||
*/
|
||
public $feeders = [];
|
||
/**
|
||
* Updater loops.
|
||
*
|
||
* @var array<\danog\MadelineProto\Loop\Update\UpdateLoop>
|
||
*/
|
||
public $updaters = [];
|
||
/**
|
||
* Boolean to avoid problems with exceptions thrown by forked strands, see tools.
|
||
*
|
||
* @var boolean
|
||
*/
|
||
public bool $destructing = false;
|
||
/**
|
||
* DataCenter instance.
|
||
*
|
||
* @var DataCenter
|
||
*/
|
||
public $datacenter;
|
||
/**
|
||
* Logger instance.
|
||
*
|
||
* @var Logger
|
||
*/
|
||
public $logger;
|
||
/**
|
||
* TL serializer.
|
||
*
|
||
* @var \danog\MadelineProto\TL\TL
|
||
*/
|
||
private $TL;
|
||
|
||
/**
|
||
* Snitch.
|
||
*/
|
||
private Snitch $snitch;
|
||
|
||
/**
|
||
* List of properties stored in database (memory or external).
|
||
* @see DbPropertiesFabric
|
||
* @var array
|
||
*/
|
||
protected array $dbProperies = [
|
||
'chats' => 'array',
|
||
'full_chats' => 'array',
|
||
'channel_participants' => 'array',
|
||
'usernames' => 'array',
|
||
];
|
||
|
||
/**
|
||
* Constructor function.
|
||
*
|
||
* @param array $settings Settings
|
||
*
|
||
* @return void
|
||
*/
|
||
public function __magic_construct($settings = [])
|
||
{
|
||
$this->setInitPromise($this->__construct_async($settings));
|
||
}
|
||
/**
|
||
* Async constructor function.
|
||
*
|
||
* @param array $settings Settings
|
||
*
|
||
* @return \Generator
|
||
*/
|
||
public function __construct_async($settings = []): \Generator
|
||
{
|
||
Magic::classExists();
|
||
// Parse and store settings
|
||
yield from $this->updateSettings($settings, false);
|
||
$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) {
|
||
$key = (yield from (new RSA())->load($this->TL, $key));
|
||
$this->rsa_keys[$key->fp] = $key;
|
||
}
|
||
// (re)-initialize TL
|
||
$this->logger->logger(Lang::$current_lang['TL_translation'], Logger::ULTRA_VERBOSE);
|
||
$callbacks = [$this, $this->referenceDatabase];
|
||
if (!($this->authorization['user']['bot'] ?? false)) {
|
||
$callbacks[] = $this->minDatabase;
|
||
}
|
||
$this->TL->init($this->settings['tl_schema']['src'], $callbacks);
|
||
yield from $this->connectToAllDcs();
|
||
$this->startLoops();
|
||
$this->datacenter->curdc = 2;
|
||
if ((!isset($this->authorization['user']['bot']) || !$this->authorization['user']['bot']) && $this->datacenter->getDataCenterConnection($this->datacenter->curdc)->hasTempAuthKey()) {
|
||
try {
|
||
$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'];
|
||
}
|
||
} catch (RPCErrorException $e) {
|
||
if ($e->rpc !== 'BOT_METHOD_INVALID') {
|
||
throw $e;
|
||
}
|
||
}
|
||
}
|
||
yield from $this->getConfig([], ['datacenter' => $this->datacenter->curdc]);
|
||
$this->startUpdateSystem(true);
|
||
$this->v = self::V;
|
||
|
||
GarbageCollector::start();
|
||
}
|
||
/**
|
||
* Sleep function.
|
||
*
|
||
* @return array
|
||
*/
|
||
public function __sleep(): array
|
||
{
|
||
if ($this->settings['serialization']['cleanup_before_serialization']) {
|
||
$this->cleanup();
|
||
}
|
||
return [
|
||
// Databases
|
||
'chats',
|
||
'full_chats',
|
||
'referenceDatabase',
|
||
'minDatabase',
|
||
'channel_participants',
|
||
'usernames',
|
||
|
||
// Misc caching
|
||
'dialog_params',
|
||
'last_stored',
|
||
'qres',
|
||
'supportUser',
|
||
'tos',
|
||
|
||
// Event handler
|
||
'event_handler',
|
||
'event_handler_instance',
|
||
'loop_callback',
|
||
'updates',
|
||
'updates_key',
|
||
'hook_url',
|
||
|
||
// Web login template
|
||
'web_template',
|
||
|
||
// Settings
|
||
'settings',
|
||
'config',
|
||
|
||
// Authorization keys
|
||
'datacenter',
|
||
|
||
// Authorization state
|
||
'authorization',
|
||
'authorized',
|
||
'authorized_dc',
|
||
|
||
// Authorization cache
|
||
'rsa_keys',
|
||
'dh_config',
|
||
|
||
// Update state
|
||
'got_state',
|
||
'channels_state',
|
||
'msg_ids',
|
||
|
||
// Version
|
||
'v',
|
||
|
||
// TL
|
||
'TL',
|
||
|
||
// Secret chats
|
||
'secret_chats',
|
||
'temp_requested_secret_chats',
|
||
'temp_rekeyed_secret_chats',
|
||
|
||
// Report URI
|
||
'reportDest',
|
||
];
|
||
}
|
||
|
||
/**
|
||
* Cleanup memory and session file.
|
||
*
|
||
* @return self
|
||
*/
|
||
public function cleanup(): self
|
||
{
|
||
$this->referenceDatabase = new ReferenceDatabase($this);
|
||
$callbacks = [$this, $this->referenceDatabase];
|
||
if (!($this->authorization['user']['bot'] ?? false)) {
|
||
$callbacks[] = $this->minDatabase;
|
||
}
|
||
$this->TL->updateCallbacks($callbacks);
|
||
return $this;
|
||
}
|
||
/**
|
||
* Logger.
|
||
*
|
||
* @param string $param Parameter
|
||
* @param int $level Logging level
|
||
* @param string $file File where the message originated
|
||
*
|
||
* @return void
|
||
*/
|
||
public function logger($param, int $level = Logger::NOTICE, string $file = ''): void
|
||
{
|
||
if ($file === null) {
|
||
$file = \basename(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0]['file'], '.php');
|
||
}
|
||
isset($this->logger) ? $this->logger->logger($param, $level, $file) : Logger::$default->logger($param, $level, $file);
|
||
}
|
||
/**
|
||
* Get TL namespaces.
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getMethodNamespaces(): array
|
||
{
|
||
return $this->TL->getMethodNamespaces();
|
||
}
|
||
/**
|
||
* Get namespaced methods (method => namespace).
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getMethodsNamespaced(): array
|
||
{
|
||
return $this->TL->getMethodsNamespaced();
|
||
}
|
||
/**
|
||
* Get TL serializer.
|
||
*
|
||
* @return TL
|
||
*/
|
||
public function getTL(): \danog\MadelineProto\TL\TL
|
||
{
|
||
return $this->TL;
|
||
}
|
||
/**
|
||
* Get logger.
|
||
*
|
||
* @return Logger
|
||
*/
|
||
public function getLogger(): Logger
|
||
{
|
||
return $this->logger;
|
||
}
|
||
/**
|
||
* Get async HTTP client.
|
||
*
|
||
* @return \Amp\Http\Client\HttpClient
|
||
*/
|
||
public function getHTTPClient(): HttpClient
|
||
{
|
||
return $this->datacenter->getHTTPClient();
|
||
}
|
||
/**
|
||
* Get async DNS client.
|
||
*
|
||
* @return \Amp\Dns\Resolver
|
||
*/
|
||
public function getDNSClient(): Resolver
|
||
{
|
||
return $this->datacenter->getDNSClient();
|
||
}
|
||
/**
|
||
* Get contents of remote file asynchronously.
|
||
*
|
||
* @param string $url URL
|
||
*
|
||
* @return \Generator<string>
|
||
*/
|
||
public function fileGetContents(string $url): \Generator
|
||
{
|
||
return $this->datacenter->fileGetContents($url);
|
||
}
|
||
/**
|
||
* Get all datacenter connections.
|
||
*
|
||
* @return array<DataCenterConnection>
|
||
*/
|
||
public function getDataCenterConnections(): array
|
||
{
|
||
return $this->datacenter->getDataCenterConnections();
|
||
}
|
||
/**
|
||
* Get main DC ID.
|
||
*
|
||
* @return int
|
||
*/
|
||
public function getDataCenterId(): int
|
||
{
|
||
return $this->datacenter->curdc;
|
||
}
|
||
/**
|
||
* Prompt serialization of instance.
|
||
*
|
||
* @internal
|
||
*
|
||
* @return void
|
||
*/
|
||
public function serialize()
|
||
{
|
||
if ($this->wrapper && $this->inited()) {
|
||
$this->wrapper->serialize();
|
||
}
|
||
}
|
||
/**
|
||
* Start all internal loops.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function startLoops()
|
||
{
|
||
if (!$this->callCheckerLoop) {
|
||
$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);
|
||
}
|
||
if (!$this->phoneConfigLoop) {
|
||
$this->phoneConfigLoop = new PeriodicLoopInternal($this, [$this, 'getPhoneConfig'], 'phone config', 24 * 3600 * 1000);
|
||
}
|
||
if (!$this->checkTosLoop) {
|
||
$this->checkTosLoop = new PeriodicLoopInternal($this, [$this, 'checkTos'], 'TOS', 24 * 3600 * 1000);
|
||
}
|
||
if (!$this->configLoop) {
|
||
$this->configLoop = new PeriodicLoopInternal($this, [$this, 'getConfig'], 'config', 24 * 3600 * 1000);
|
||
}
|
||
if (!$this->rpcLoop) {
|
||
$this->rpcLoop = new PeriodicLoopInternal($this, [$this, 'rpcReport'], 'config', 60 * 1000);
|
||
}
|
||
if (!$this->ipcServer) {
|
||
$this->ipcServer = new Server($this);
|
||
$this->ipcServer->setIpcPath($this->wrapper->getIpcPath());
|
||
}
|
||
$this->callCheckerLoop->start();
|
||
$this->serializeLoop->start();
|
||
$this->phoneConfigLoop->start();
|
||
$this->configLoop->start();
|
||
$this->checkTosLoop->start();
|
||
$this->rpcLoop->start();
|
||
$this->ipcServer->start();
|
||
}
|
||
/**
|
||
* Stop all internal loops.
|
||
*
|
||
* @return void
|
||
*/
|
||
private function stopLoops()
|
||
{
|
||
if ($this->callCheckerLoop) {
|
||
$this->callCheckerLoop->signal(true);
|
||
$this->callCheckerLoop = null;
|
||
}
|
||
if ($this->serializeLoop) {
|
||
$this->serializeLoop->signal(true);
|
||
$this->serializeLoop = null;
|
||
}
|
||
if ($this->phoneConfigLoop) {
|
||
$this->phoneConfigLoop->signal(true);
|
||
$this->phoneConfigLoop = null;
|
||
}
|
||
if ($this->configLoop) {
|
||
$this->configLoop->signal(true);
|
||
$this->configLoop = null;
|
||
}
|
||
if ($this->checkTosLoop) {
|
||
$this->checkTosLoop->signal(true);
|
||
$this->checkTosLoop = null;
|
||
}
|
||
if ($this->rpcLoop) {
|
||
$this->rpcLoop->signal(true);
|
||
$this->rpcLoop = null;
|
||
}
|
||
if ($this->ipcServer) {
|
||
$this->ipcServer->signal(null);
|
||
$this->ipcServer = null;
|
||
}
|
||
}
|
||
/**
|
||
* Report RPC errors.
|
||
*
|
||
* @internal
|
||
*
|
||
* @return \Generator
|
||
*/
|
||
public function rpcReport(): \Generator
|
||
{
|
||
$toReport = RPCErrorException::$toReport;
|
||
RPCErrorException::$toReport = [];
|
||
foreach ($toReport as [$method, $code, $error, $time]) {
|
||
try {
|
||
$res = \json_decode(yield from $this->fileGetContents('https://rpc.pwrtelegram.xyz/?method='.$method.'&code='.$code.'&error='.$error.'&t='.$time), true);
|
||
if (isset($res['ok']) && $res['ok'] && isset($res['result'])) {
|
||
$description = $res['result'];
|
||
RPCErrorException::$descriptions[$error] = $description;
|
||
RPCErrorException::$errorMethodMap[$code][$method][$error] = $error;
|
||
}
|
||
} catch (\Throwable $e) {
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Clean up properties from previous versions of MadelineProto.
|
||
*
|
||
* @internal
|
||
*
|
||
* @return void
|
||
*/
|
||
private function cleanupProperties()
|
||
{
|
||
if (!$this->channels_state instanceof CombinedUpdatesState) {
|
||
$this->channels_state = new CombinedUpdatesState($this->channels_state);
|
||
}
|
||
if (isset($this->updates_state)) {
|
||
if (!$this->updates_state instanceof UpdatesState) {
|
||
$this->updates_state = new UpdatesState($this->updates_state);
|
||
}
|
||
$this->channels_state->__construct([UpdateLoop::GENERIC => $this->updates_state]);
|
||
unset($this->updates_state);
|
||
}
|
||
if (!isset($this->datacenter)) {
|
||
$this->datacenter = new DataCenter($this, $this->settings['connection'], $this->settings['connection_settings']);
|
||
}
|
||
if (!isset($this->referenceDatabase)) {
|
||
$this->referenceDatabase = new ReferenceDatabase($this);
|
||
}
|
||
if (!isset($this->minDatabase)) {
|
||
$this->minDatabase = new MinDatabase($this);
|
||
}
|
||
if (!isset($this->TL)) {
|
||
$this->TL = new TL($this);
|
||
$this->logger->logger(Lang::$current_lang['TL_translation'], Logger::ULTRA_VERBOSE);
|
||
$callbacks = [$this, $this->referenceDatabase];
|
||
if (!($this->authorization['user']['bot'] ?? false)) {
|
||
$callbacks[] = $this->minDatabase;
|
||
}
|
||
$this->TL->init($this->settings['tl_schema']['src'], $callbacks);
|
||
}
|
||
|
||
yield from $this->initDb($this);
|
||
}
|
||
|
||
/**
|
||
* Upgrade MadelineProto instance.
|
||
*
|
||
* @return \Generator
|
||
* @throws Exception
|
||
* @throws RPCErrorException
|
||
* @throws \Throwable
|
||
*/
|
||
private function upgradeMadelineProto(): \Generator
|
||
{
|
||
if (!isset($this->snitch)) {
|
||
$this->snitch = new Snitch;
|
||
}
|
||
$this->logger->logger(Lang::$current_lang['serialization_ofd'], Logger::WARNING);
|
||
foreach ($this->datacenter->getDataCenterConnections() as $dc_id => $socket) {
|
||
if ($this->authorized === self::LOGGED_IN && \strpos($dc_id, '_') === false && $socket->hasPermAuthKey() && $socket->hasTempAuthKey()) {
|
||
$socket->bind();
|
||
$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']);
|
||
}
|
||
|
||
yield from $this->initDb($this);
|
||
|
||
if (!isset($this->secret_chats)) {
|
||
$this->secret_chats = [];
|
||
}
|
||
$iterator = $this->full_chats->getIterator();
|
||
while (yield $iterator->advance()) {
|
||
[$id, $full] = $iterator->getCurrent();
|
||
if (isset($full['full'], $full['last_update'])) {
|
||
$this->full_chats[$id] = ['full' => $full['full'], 'last_update' => $full['last_update']];
|
||
}
|
||
}
|
||
|
||
foreach ($this->secret_chats as $key => &$chat) {
|
||
if (!\is_array($chat)) {
|
||
unset($this->secret_chats[$key]);
|
||
continue;
|
||
}
|
||
if ($chat['layer'] >= 73) {
|
||
$chat['mtproto'] = 2;
|
||
} else {
|
||
$chat['mtproto'] = 1;
|
||
}
|
||
}
|
||
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);
|
||
foreach ($this->secret_chats as $chat => $data) {
|
||
try {
|
||
if (isset($this->secret_chats[$chat]) && $this->secret_chats[$chat]['InputEncryptedChat'] !== null) {
|
||
yield from $this->notifyLayer($chat);
|
||
}
|
||
} catch (\danog\MadelineProto\RPCErrorException $e) {
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Wakeup function.
|
||
*/
|
||
public function __wakeup()
|
||
{
|
||
$backtrace = \debug_backtrace(0, 4);
|
||
$backtrace = \end($backtrace);
|
||
$this->forceInit(false);
|
||
$this->setInitPromise($this->__wakeup_async($backtrace));
|
||
}
|
||
/**
|
||
* Async wakeup function.
|
||
*
|
||
* @param array $backtrace Stack trace
|
||
*
|
||
* @return \Generator
|
||
*/
|
||
public function __wakeup_async(array $backtrace): \Generator
|
||
{
|
||
// Setup one-time stuffs
|
||
Magic::classExists();
|
||
// 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']];
|
||
}
|
||
$this->settings['connection_settings']['all']['ipv6'] = Magic::$ipv6;
|
||
if ($this->authorized === true) {
|
||
$this->authorized = self::LOGGED_IN;
|
||
}
|
||
$force = false;
|
||
$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) {
|
||
yield from $this->upgradeMadelineProto();
|
||
$force = true;
|
||
}
|
||
// Cleanup old properties, init new stuffs
|
||
yield from $this->cleanupProperties();
|
||
// Update TL callbacks
|
||
$callbacks = [$this, $this->referenceDatabase];
|
||
if (!($this->authorization['user']['bot'] ?? false)) {
|
||
$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);
|
||
}
|
||
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;
|
||
$this->setupLogger();
|
||
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);
|
||
}
|
||
if ($this->authorized === self::LOGGED_IN && $this->settings['updates']['handle_updates']) {
|
||
$this->logger->logger(Lang::$current_lang['getupdates_deserialization'], Logger::NOTICE);
|
||
yield $this->updaters[UpdateLoop::GENERIC]->resume();
|
||
}
|
||
$this->updaters[UpdateLoop::GENERIC]->start();
|
||
|
||
GarbageCollector::start();
|
||
}
|
||
/**
|
||
* Unreference instance, allowing destruction.
|
||
*
|
||
* @internal
|
||
*
|
||
* @return void
|
||
*/
|
||
public function unreference(): void
|
||
{
|
||
$this->logger->logger("Will unreference instance");
|
||
$this->stopLoops();
|
||
if (isset($this->seqUpdater)) {
|
||
$this->seqUpdater->signal(true);
|
||
}
|
||
$channelIds = [];
|
||
foreach ($this->channels_state->get() as $state) {
|
||
$channelIds[] = $state->getChannel();
|
||
}
|
||
\sort($channelIds);
|
||
foreach ($channelIds as $channelId) {
|
||
if (isset($this->feeders[$channelId])) {
|
||
$this->feeders[$channelId]->signal(true);
|
||
}
|
||
if (isset($this->updaters[$channelId])) {
|
||
$this->updaters[$channelId]->signal(true);
|
||
}
|
||
}
|
||
foreach ($this->datacenter->getDataCenterConnections() as $datacenter) {
|
||
$datacenter->disconnect();
|
||
}
|
||
}
|
||
/**
|
||
* Destructor.
|
||
*/
|
||
public function __destruct()
|
||
{
|
||
$this->logger('Shutting down MadelineProto (MTProto)');
|
||
$this->unreference();
|
||
$this->logger("Successfully destroyed MadelineProto");
|
||
}
|
||
/**
|
||
* 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' => 113,
|
||
// layer version
|
||
'src' => [
|
||
// mtproto TL scheme
|
||
'mtproto' => __DIR__.'/TL_mtproto_v1.tl',
|
||
// telegram TL scheme
|
||
'telegram' => __DIR__.'/TL_telegram_v113.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' => false,
|
||
// 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' => true,
|
||
], '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
|
||
]
|
||
],
|
||
'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
|
||
*
|
||
* @return void
|
||
*/
|
||
public function updateSettings(array $settings, bool $reinit = true): \Generator
|
||
{
|
||
$settings = self::parseSettings($settings, $this->settings);
|
||
if ($settings['app_info'] === null) {
|
||
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();
|
||
|
||
if ($reinit) {
|
||
$this->__construct();
|
||
yield from $this->initAsynchronously();
|
||
}
|
||
}
|
||
/**
|
||
* Return current settings array.
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getSettings(): array
|
||
{
|
||
return $this->settings;
|
||
}
|
||
/**
|
||
* Setup logger.
|
||
*
|
||
* @return void
|
||
*/
|
||
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']) : '');
|
||
}
|
||
/**
|
||
* Reset all MTProto sessions.
|
||
*
|
||
* @param boolean $de Whether to reset the session ID
|
||
* @param boolean $auth_key Whether to reset the auth key
|
||
*
|
||
* @internal
|
||
*
|
||
* @return void
|
||
*/
|
||
public function resetMTProtoSession(bool $de = true, bool $auth_key = false): void
|
||
{
|
||
if (!\is_object($this->datacenter)) {
|
||
throw new Exception(Lang::$current_lang['session_corrupted']);
|
||
}
|
||
foreach ($this->datacenter->getDataCenterConnections() as $id => $socket) {
|
||
if ($de) {
|
||
$socket->resetSession();
|
||
}
|
||
if ($auth_key) {
|
||
$socket->setAuthKey(null);
|
||
}
|
||
}
|
||
}
|
||
/**
|
||
* Check if connected to datacenter using HTTP.
|
||
*
|
||
* @param string $datacenter DC ID
|
||
*
|
||
* @internal
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function isHttp(string $datacenter): bool
|
||
{
|
||
return $this->datacenter->isHttp($datacenter);
|
||
}
|
||
/**
|
||
* Checks whether all datacenters are authorized.
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function hasAllAuth(): bool
|
||
{
|
||
if ($this->isInitingAuthorization()) {
|
||
return false;
|
||
}
|
||
foreach ($this->datacenter->getDataCenterConnections() as $dc) {
|
||
if ((!$dc->isAuthorized() || !$dc->hasTempAuthKey()) && !$dc->isCDN()) {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
/**
|
||
* Whether we're initing authorization.
|
||
*
|
||
* @internal
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function isInitingAuthorization()
|
||
{
|
||
return $this->initing_authorization;
|
||
}
|
||
/**
|
||
* Connects to all datacenters and if necessary creates authorization keys, binds them and writes client info.
|
||
*
|
||
* @param boolean $reconnectAll Whether to reconnect to all DCs
|
||
*
|
||
* @return \Generator
|
||
*/
|
||
public function connectToAllDcs(bool $reconnectAll = true): \Generator
|
||
{
|
||
$this->channels_state->get(FeedLoop::GENERIC);
|
||
foreach ($this->channels_state->get() as $state) {
|
||
$channelId = $state->getChannel();
|
||
if (!isset($this->feeders[$channelId])) {
|
||
$this->feeders[$channelId] = new FeedLoop($this, $channelId);
|
||
}
|
||
if (!isset($this->updaters[$channelId])) {
|
||
$this->updaters[$channelId] = new UpdateLoop($this, $channelId);
|
||
}
|
||
}
|
||
if (!isset($this->seqUpdater)) {
|
||
$this->seqUpdater = new SeqLoop($this);
|
||
}
|
||
$this->datacenter->__construct($this, $this->settings['connection'], $this->settings['connection_settings'], $reconnectAll);
|
||
$dcs = [];
|
||
foreach ($this->datacenter->getDcs() as $new_dc) {
|
||
$dcs[] = $this->datacenter->dcConnect($new_dc);
|
||
}
|
||
yield \danog\MadelineProto\Tools::all($dcs);
|
||
yield from $this->initAuthorization();
|
||
yield from $this->parseConfig();
|
||
$dcs = [];
|
||
foreach ($this->datacenter->getDcs(false) as $new_dc) {
|
||
$dcs[] = $this->datacenter->dcConnect($new_dc);
|
||
}
|
||
yield \danog\MadelineProto\Tools::all($dcs);
|
||
yield from $this->initAuthorization();
|
||
yield from $this->parseConfig();
|
||
yield from $this->getPhoneConfig();
|
||
}
|
||
/**
|
||
* Clean up MadelineProto session after logout.
|
||
*
|
||
* @internal
|
||
*
|
||
* @return \Generator<void>
|
||
*/
|
||
public function resetSession(): \Generator
|
||
{
|
||
if (isset($this->seqUpdater)) {
|
||
$this->seqUpdater->signal(true);
|
||
unset($this->seqUpdater);
|
||
}
|
||
$channelIds = [];
|
||
foreach ($this->channels_state->get() as $state) {
|
||
$channelIds[] = $state->getChannel();
|
||
}
|
||
\sort($channelIds);
|
||
foreach ($channelIds as $channelId) {
|
||
if (isset($this->feeders[$channelId])) {
|
||
$this->feeders[$channelId]->signal(true);
|
||
unset($this->feeders[$channelId]);
|
||
}
|
||
if (isset($this->updaters[$channelId])) {
|
||
$this->updaters[$channelId]->signal(true);
|
||
unset($this->updaters[$channelId]);
|
||
}
|
||
}
|
||
foreach ($this->datacenter->getDataCenterConnections() as $socket) {
|
||
$socket->authorized(false);
|
||
}
|
||
$this->channels_state = new CombinedUpdatesState();
|
||
$this->got_state = false;
|
||
$this->msg_ids = [];
|
||
$this->authorized = self::NOT_LOGGED_IN;
|
||
$this->authorized_dc = -1;
|
||
$this->authorization = null;
|
||
$this->updates = [];
|
||
$this->secret_chats = [];
|
||
|
||
yield from $this->initDb($this, true);
|
||
|
||
$this->tos = ['expires' => 0, 'accepted' => true];
|
||
$this->referenceDatabase = new ReferenceDatabase($this);
|
||
$this->minDatabase = new MinDatabase($this);
|
||
$this->dialog_params = ['_' => 'MadelineProto.dialogParams', 'limit' => 0, 'offset_date' => 0, 'offset_id' => 0, 'offset_peer' => ['_' => 'inputPeerEmpty'], 'count' => 0];
|
||
}
|
||
/**
|
||
* Reset the update state and fetch all updates from the beginning.
|
||
*
|
||
* @return void
|
||
*/
|
||
public function resetUpdateState(): void
|
||
{
|
||
if (isset($this->seqUpdater)) {
|
||
$this->seqUpdater->signal(true);
|
||
}
|
||
$channelIds = [];
|
||
$newStates = [];
|
||
foreach ($this->channels_state->get() as $state) {
|
||
$channelIds[] = $state->getChannel();
|
||
$channelId = $state->getChannel();
|
||
$pts = $state->pts();
|
||
$pts = $channelId ? \max(1, $pts - 1000000) : ($pts > 4000000 ? $pts - 1000000 : \max(1, $pts - 1000000));
|
||
$newStates[$channelId] = new UpdatesState(['pts' => $pts], $channelId);
|
||
}
|
||
\sort($channelIds);
|
||
foreach ($channelIds as $channelId) {
|
||
if (isset($this->feeders[$channelId])) {
|
||
$this->feeders[$channelId]->signal(true);
|
||
}
|
||
if (isset($this->updaters[$channelId])) {
|
||
$this->updaters[$channelId]->signal(true);
|
||
}
|
||
}
|
||
$this->channels_state->__construct($newStates);
|
||
$this->startUpdateSystem();
|
||
}
|
||
/**
|
||
* Start the update system.
|
||
*
|
||
* @param boolean $anyway Force start update system?
|
||
*
|
||
* @internal
|
||
*
|
||
* @return void
|
||
*/
|
||
public function startUpdateSystem($anyway = false): void
|
||
{
|
||
if (!$this->inited() && !$anyway) {
|
||
$this->logger("Not starting update system");
|
||
return;
|
||
}
|
||
$this->logger("Starting update system");
|
||
if (!isset($this->seqUpdater)) {
|
||
$this->seqUpdater = new SeqLoop($this);
|
||
}
|
||
$this->channels_state->get(FeedLoop::GENERIC);
|
||
$channelIds = [];
|
||
foreach ($this->channels_state->get() as $state) {
|
||
$channelIds[] = $state->getChannel();
|
||
}
|
||
\sort($channelIds);
|
||
foreach ($channelIds as $channelId) {
|
||
if (!isset($this->feeders[$channelId])) {
|
||
$this->feeders[$channelId] = new FeedLoop($this, $channelId);
|
||
}
|
||
if (!isset($this->updaters[$channelId])) {
|
||
$this->updaters[$channelId] = new UpdateLoop($this, $channelId);
|
||
}
|
||
if ($this->feeders[$channelId]->start() && isset($this->feeders[$channelId])) {
|
||
$this->feeders[$channelId]->resume();
|
||
}
|
||
if ($this->updaters[$channelId]->start() && isset($this->updaters[$channelId])) {
|
||
$this->updaters[$channelId]->resume();
|
||
}
|
||
}
|
||
foreach ($this->datacenter->getDataCenterConnections() as $datacenter) {
|
||
$datacenter->flush();
|
||
}
|
||
if ($this->seqUpdater->start()) {
|
||
$this->seqUpdater->resume();
|
||
}
|
||
}
|
||
/**
|
||
* Store shared phone config.
|
||
*
|
||
* @param mixed $watcherId Watcher ID
|
||
*
|
||
* @internal
|
||
*
|
||
* @return \Generator<void>
|
||
*/
|
||
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()) {
|
||
$this->logger->logger('Fetching phone config...');
|
||
VoIPServerConfig::updateDefault(yield from $this->methodCallAsyncRead('phone.getCallConfig', [], ['datacenter' => $this->settings['connection_settings']['default_dc']]));
|
||
} else {
|
||
$this->logger->logger('Not fetching phone config');
|
||
}
|
||
}
|
||
/**
|
||
* Store RSA keys for CDN datacenters.
|
||
*
|
||
* @param string $datacenter DC ID
|
||
*
|
||
* @return \Generator
|
||
*/
|
||
public function getCdnConfig(string $datacenter): \Generator
|
||
{
|
||
try {
|
||
foreach ((yield from $this->methodCallAsyncRead('help.getCdnConfig', [], ['datacenter' => $datacenter]))['public_keys'] as $curkey) {
|
||
$curkey = (yield from (new RSA())->load($this->TL, $curkey['public_key']));
|
||
$this->cdn_rsa_keys[$curkey->fp] = $curkey;
|
||
}
|
||
} catch (\danog\MadelineProto\TL\Exception $e) {
|
||
$this->logger->logger($e->getMessage(), \danog\MadelineProto\Logger::FATAL_ERROR);
|
||
}
|
||
}
|
||
/**
|
||
* Get cached server-side config.
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getCachedConfig(): array
|
||
{
|
||
return $this->config;
|
||
}
|
||
/**
|
||
* Get cached (or eventually re-fetch) server-side config.
|
||
*
|
||
* @param array $config Current config
|
||
* @param array $options Options for method call
|
||
*
|
||
* @return \Generator
|
||
*/
|
||
public function getConfig(array $config = [], array $options = []): \Generator
|
||
{
|
||
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;
|
||
yield from $this->parseConfig();
|
||
$this->logger->logger(Lang::$current_lang['config_updated'], Logger::NOTICE);
|
||
$this->logger->logger($this->config, Logger::NOTICE);
|
||
return $this->config;
|
||
}
|
||
/**
|
||
* Parse cached config.
|
||
*
|
||
* @return \Generator
|
||
*/
|
||
private function parseConfig(): \Generator
|
||
{
|
||
if (isset($this->config['dc_options'])) {
|
||
$options = $this->config['dc_options'];
|
||
unset($this->config['dc_options']);
|
||
yield from $this->parseDcOptions($options);
|
||
}
|
||
}
|
||
/**
|
||
* Parse DC options from config.
|
||
*
|
||
* @param array $dc_options DC options
|
||
*
|
||
* @return \Generator
|
||
*/
|
||
private function parseDcOptions(array $dc_options): \Generator
|
||
{
|
||
$previous = $this->settings;
|
||
foreach ($dc_options as $dc) {
|
||
$test = $this->config['test_mode'] ? 'test' : 'main';
|
||
$id = $dc['id'];
|
||
if (isset($dc['static'])) {
|
||
//$id .= $dc['static'] ? '_static' : '';
|
||
}
|
||
if (isset($dc['cdn'])) {
|
||
$id .= $dc['cdn'] ? '_cdn' : '';
|
||
}
|
||
$id .= $dc['media_only'] ? '_media' : '';
|
||
$ipv6 = $dc['ipv6'] ? 'ipv6' : 'ipv4';
|
||
if (\is_numeric($id)) {
|
||
$id = (int) $id;
|
||
}
|
||
unset($dc['cdn'], $dc['media_only'], $dc['id'], $dc['ipv6']);
|
||
$this->settings['connection'][$test][$ipv6][$id] = $dc;
|
||
}
|
||
$curdc = $this->datacenter->curdc;
|
||
if ($previous !== $this->settings && (!$this->datacenter->has($curdc) || $this->datacenter->getDataCenterConnection($curdc)->byIPAddress())) {
|
||
$this->logger->logger('Got new DC options, reconnecting');
|
||
yield from $this->connectToAllDcs(false);
|
||
}
|
||
$this->datacenter->curdc = $curdc;
|
||
}
|
||
/**
|
||
* Get info about the logged-in user, cached.
|
||
*
|
||
* @return array|bool
|
||
*/
|
||
public function getSelf()
|
||
{
|
||
return $this->authorization['user'] ?? false;
|
||
}
|
||
/**
|
||
* Get info about the logged-in user, not cached.
|
||
*
|
||
* @return \Generator<array|bool>
|
||
*/
|
||
public function fullGetSelf(): \Generator
|
||
{
|
||
try {
|
||
$this->authorization = ['user' => (yield from $this->methodCallAsyncRead('users.getUsers', ['id' => [['_' => 'inputUserSelf']]], ['datacenter' => $this->datacenter->curdc]))[0]];
|
||
} catch (RPCErrorException $e) {
|
||
$this->logger->logger($e->getMessage());
|
||
return false;
|
||
}
|
||
return $this->authorization['user'];
|
||
}
|
||
/**
|
||
* Get authorization info.
|
||
*
|
||
* @return int
|
||
*/
|
||
public function getAuthorization(): int
|
||
{
|
||
return $this->authorized;
|
||
}
|
||
/**
|
||
* IDs of peers where to report errors.
|
||
*
|
||
* @var int[]
|
||
*/
|
||
private $reportDest = [];
|
||
/**
|
||
* Check if has report peers.
|
||
*
|
||
* @return boolean
|
||
*/
|
||
public function hasReportPeers(): bool
|
||
{
|
||
return (bool) $this->reportDest;
|
||
}
|
||
/**
|
||
* Set peer(s) where to send errors occurred in the event loop.
|
||
*
|
||
* @param int|string $userOrId Username(s) or peer ID(s)
|
||
*
|
||
* @return \Generator
|
||
*/
|
||
public function setReportPeers($userOrId): \Generator
|
||
{
|
||
if (!(\is_array($userOrId) && !isset($userOrId['_']) && !isset($userOrId['id']))) {
|
||
$userOrId = [$userOrId];
|
||
}
|
||
foreach ($userOrId as &$peer) {
|
||
$peer = (yield from $this->getInfo($peer))['bot_api_id'];
|
||
}
|
||
$this->reportDest = $userOrId;
|
||
}
|
||
/**
|
||
* Report an error to the previously set peer.
|
||
*
|
||
* @param string $message Error to report
|
||
*
|
||
* @return \Generator
|
||
*/
|
||
public function report(string $message): \Generator
|
||
{
|
||
if (!$this->reportDest) {
|
||
return;
|
||
}
|
||
$file = null;
|
||
if ($this->settings['logger']['logger'] === Logger::FILE_LOGGER && $path = $this->settings['logger']['logger_param']) {
|
||
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";
|
||
} elseif (!yield size($path)) {
|
||
$message = "!!! WARNING !!!\nThe logfile is empty, please DO NOT delete the logfile to avoid errors in MadelineProto!\n\n$message";
|
||
} else {
|
||
$file = yield from $this->methodCallAsyncRead(
|
||
'messages.uploadMedia',
|
||
[
|
||
'peer' => $this->reportDest[0],
|
||
'media' => [
|
||
'_' => 'inputMediaUploadedDocument',
|
||
'file' => $path,
|
||
'attributes' => [
|
||
['_' => 'documentAttributeFilename', 'file_name' => 'MadelineProto.log']
|
||
]
|
||
]
|
||
]
|
||
);
|
||
}
|
||
}
|
||
$sent = true;
|
||
foreach ($this->reportDest as $id) {
|
||
try {
|
||
yield from $this->methodCallAsyncRead('messages.sendMessage', ['peer' => $id, 'message' => $message]);
|
||
if ($file) {
|
||
yield from $this->methodCallAsyncRead('messages.sendMedia', ['peer' => $id, 'media' => $file]);
|
||
}
|
||
$sent &= true;
|
||
} catch (\Throwable $e) {
|
||
$sent &= false;
|
||
$this->logger("While reporting to $id: $e", Logger::FATAL_ERROR);
|
||
}
|
||
}
|
||
if ($sent && $file) {
|
||
\ftruncate($this->logger->stdout->getResource(), 0);
|
||
$this->logger->logger("Reported!");
|
||
}
|
||
}
|
||
/**
|
||
* Get full list of MTProto and API methods.
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getAllMethods(): array
|
||
{
|
||
$methods = [];
|
||
foreach ($this->getTL()->getMethods()->by_id as $method) {
|
||
$methods[] = $method['method'];
|
||
}
|
||
return \array_merge($methods, \get_class_methods(InternalDoc::class));
|
||
}
|
||
/**
|
||
* Called right before serialization of method starts.
|
||
*
|
||
* Pass the method name
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getMethodCallbacks(): array
|
||
{
|
||
return [];
|
||
}
|
||
/**
|
||
* Called right before serialization of method starts.
|
||
*
|
||
* Pass the method name
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getMethodBeforeCallbacks(): array
|
||
{
|
||
return [];
|
||
}
|
||
/**
|
||
* Called right after deserialization of object, passing the final object.
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getConstructorCallbacks(): array
|
||
{
|
||
return \array_merge(
|
||
\array_fill_keys(['chat', 'chatEmpty', 'chatForbidden', 'channel', 'channelEmpty', 'channelForbidden'], [[$this, 'addChat']]),
|
||
\array_fill_keys(['user', 'userEmpty'], [[$this, 'addUser']]),
|
||
['help.support' => [[$this, 'addSupport']]]
|
||
);
|
||
}
|
||
/**
|
||
* Called right before deserialization of object.
|
||
*
|
||
* Pass only the constructor name
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getConstructorBeforeCallbacks(): array
|
||
{
|
||
return [];
|
||
}
|
||
/**
|
||
* Called right before serialization of constructor.
|
||
*
|
||
* Passed the object, will return a modified version.
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getConstructorSerializeCallbacks(): array
|
||
{
|
||
return [];
|
||
}
|
||
/**
|
||
* Called if objects of the specified type cannot be serialized.
|
||
*
|
||
* Passed the unserializable object,
|
||
* will try to convert it to an object of the proper type.
|
||
*
|
||
* @return array
|
||
*/
|
||
public function getTypeMismatchCallbacks(): array
|
||
{
|
||
return \array_merge(\array_fill_keys(['User', 'InputUser', 'Chat', 'InputChannel', 'Peer', 'InputPeer', 'InputDialogPeer', 'InputNotifyPeer'], [$this, 'getInfo']), \array_fill_keys(['InputMedia', 'InputDocument', 'InputPhoto'], [$this, 'getFileInfo']), \array_fill_keys(['InputFileLocation'], [$this, 'getDownloadInfo']));
|
||
}
|
||
/**
|
||
* Get debug information for var_dump.
|
||
*
|
||
* @return array
|
||
*/
|
||
public function __debugInfo(): array
|
||
{
|
||
$vars = \get_object_vars($this);
|
||
unset($vars['full_chats'], $vars['chats'], $vars['referenceDatabase'], $vars['minDatabase'], $vars['TL']);
|
||
return $vars;
|
||
}
|
||
const ALL_MIMES = ['webp' => [0 => 'image/webp'], 'png' => [0 => 'image/png', 1 => 'image/x-png'], 'bmp' => [0 => 'image/bmp', 1 => 'image/x-bmp', 2 => 'image/x-bitmap', 3 => 'image/x-xbitmap', 4 => 'image/x-win-bitmap', 5 => 'image/x-windows-bmp', 6 => 'image/ms-bmp', 7 => 'image/x-ms-bmp', 8 => 'application/bmp', 9 => 'application/x-bmp', 10 => 'application/x-win-bitmap'], 'gif' => [0 => 'image/gif'], 'jpeg' => [0 => 'image/jpeg', 1 => 'image/pjpeg'], 'xspf' => [0 => 'application/xspf+xml'], 'vlc' => [0 => 'application/videolan'], 'wmv' => [0 => 'video/x-ms-wmv', 1 => 'video/x-ms-asf'], 'au' => [0 => 'audio/x-au'], 'ac3' => [0 => 'audio/ac3'], 'flac' => [0 => 'audio/x-flac'], 'ogg' => [0 => 'audio/ogg', 1 => 'video/ogg', 2 => 'application/ogg'], 'kmz' => [0 => 'application/vnd.google-earth.kmz'], 'kml' => [0 => 'application/vnd.google-earth.kml+xml'], 'rtx' => [0 => 'text/richtext'], 'rtf' => [0 => 'text/rtf'], 'jar' => [0 => 'application/java-archive', 1 => 'application/x-java-application', 2 => 'application/x-jar'], 'zip' => [0 => 'application/x-zip', 1 => 'application/zip', 2 => 'application/x-zip-compressed', 3 => 'application/s-compressed', 4 => 'multipart/x-zip'], '7zip' => [0 => 'application/x-compressed'], 'xml' => [0 => 'application/xml', 1 => 'text/xml'], 'svg' => [0 => 'image/svg+xml'], '3g2' => [0 => 'video/3gpp2'], '3gp' => [0 => 'video/3gp', 1 => 'video/3gpp'], 'mp4' => [0 => 'video/mp4'], 'm4a' => [0 => 'audio/x-m4a'], 'f4v' => [0 => 'video/x-f4v'], 'flv' => [0 => 'video/x-flv'], 'webm' => [0 => 'video/webm'], 'aac' => [0 => 'audio/x-acc'], 'm4u' => [0 => 'application/vnd.mpegurl'], 'pdf' => [0 => 'application/pdf', 1 => 'application/octet-stream'], 'pptx' => [0 => 'application/vnd.openxmlformats-officedocument.presentationml.presentation'], 'ppt' => [0 => 'application/powerpoint', 1 => 'application/vnd.ms-powerpoint', 2 => 'application/vnd.ms-office', 3 => 'application/msword'], 'docx' => [0 => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'], 'xlsx' => [0 => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 1 => 'application/vnd.ms-excel'], 'xl' => [0 => 'application/excel'], 'xls' => [0 => 'application/msexcel', 1 => 'application/x-msexcel', 2 => 'application/x-ms-excel', 3 => 'application/x-excel', 4 => 'application/x-dos_ms_excel', 5 => 'application/xls', 6 => 'application/x-xls'], 'xsl' => [0 => 'text/xsl'], 'mpeg' => [0 => 'video/mpeg'], 'mov' => [0 => 'video/quicktime'], 'avi' => [0 => 'video/x-msvideo', 1 => 'video/msvideo', 2 => 'video/avi', 3 => 'application/x-troff-msvideo'], 'movie' => [0 => 'video/x-sgi-movie'], 'log' => [0 => 'text/x-log'], 'txt' => [0 => 'text/plain'], 'css' => [0 => 'text/css'], 'html' => [0 => 'text/html'], 'wav' => [0 => 'audio/x-wav', 1 => 'audio/wave', 2 => 'audio/wav'], 'xhtml' => [0 => 'application/xhtml+xml'], 'tar' => [0 => 'application/x-tar'], 'tgz' => [0 => 'application/x-gzip-compressed'], 'psd' => [0 => 'application/x-photoshop', 1 => 'image/vnd.adobe.photoshop'], 'exe' => [0 => 'application/x-msdownload'], 'js' => [0 => 'application/x-javascript'], 'mp3' => [0 => 'audio/mpeg', 1 => 'audio/mpg', 2 => 'audio/mpeg3', 3 => 'audio/mp3'], 'rar' => [0 => 'application/x-rar', 1 => 'application/rar', 2 => 'application/x-rar-compressed'], 'gzip' => [0 => 'application/x-gzip'], 'hqx' => [0 => 'application/mac-binhex40', 1 => 'application/mac-binhex', 2 => 'application/x-binhex40', 3 => 'application/x-mac-binhex40'], 'cpt' => [0 => 'application/mac-compactpro'], 'bin' => [0 => 'application/macbinary', 1 => 'application/mac-binary', 2 => 'application/x-binary', 3 => 'application/x-macbinary'], 'oda' => [0 => 'application/oda'], 'ai' => [0 => 'application/postscript'], 'smil' => [0 => 'application/smil'], 'mif' => [0 => 'application/vnd.mif'], 'wbxml' => [0 => 'application/wbxml'], 'wmlc' => [0 => 'application/wmlc'], 'dcr' => [0 => 'application/x-director'], 'dvi' => [0 => 'application/x-dvi'], 'gtar' => [0 => 'application/x-gtar'], 'php' => [0 => 'application/x-httpd-php', 1 => 'application/php', 2 => 'application/x-php', 3 => 'text/php', 4 => 'text/x-php', 5 => 'application/x-httpd-php-source'], 'swf' => [0 => 'application/x-shockwave-flash'], 'sit' => [0 => 'application/x-stuffit'], 'z' => [0 => 'application/x-compress'], 'mid' => [0 => 'audio/midi'], 'aif' => [0 => 'audio/x-aiff', 1 => 'audio/aiff'], 'ram' => [0 => 'audio/x-pn-realaudio'], 'rpm' => [0 => 'audio/x-pn-realaudio-plugin'], 'ra' => [0 => 'audio/x-realaudio'], 'rv' => [0 => 'video/vnd.rn-realvideo'], 'jp2' => [0 => 'image/jp2', 1 => 'video/mj2', 2 => 'image/jpx', 3 => 'image/jpm'], 'tiff' => [0 => 'image/tiff'], 'eml' => [0 => 'message/rfc822'], 'pem' => [0 => 'application/x-x509-user-cert', 1 => 'application/x-pem-file'], 'p10' => [0 => 'application/x-pkcs10', 1 => 'application/pkcs10'], 'p12' => [0 => 'application/x-pkcs12'], 'p7a' => [0 => 'application/x-pkcs7-signature'], 'p7c' => [0 => 'application/pkcs7-mime', 1 => 'application/x-pkcs7-mime'], 'p7r' => [0 => 'application/x-pkcs7-certreqresp'], 'p7s' => [0 => 'application/pkcs7-signature'], 'crt' => [0 => 'application/x-x509-ca-cert', 1 => 'application/pkix-cert'], 'crl' => [0 => 'application/pkix-crl', 1 => 'application/pkcs-crl'], 'pgp' => [0 => 'application/pgp'], 'gpg' => [0 => 'application/gpg-keys'], 'rsa' => [0 => 'application/x-pkcs7'], 'ics' => [0 => 'text/calendar'], 'zsh' => [0 => 'text/x-scriptzsh'], 'cdr' => [0 => 'application/cdr', 1 => 'application/coreldraw', 2 => 'application/x-cdr', 3 => 'application/x-coreldraw', 4 => 'image/cdr', 5 => 'image/x-cdr', 6 => 'zz-application/zz-winassoc-cdr'], 'wma' => [0 => 'audio/x-ms-wma'], 'vcf' => [0 => 'text/x-vcard'], 'srt' => [0 => 'text/srt'], 'vtt' => [0 => 'text/vtt'], 'ico' => [0 => 'image/x-icon', 1 => 'image/x-ico', 2 => 'image/vnd.microsoft.icon'], 'csv' => [0 => 'text/x-comma-separated-values', 1 => 'text/comma-separated-values', 2 => 'application/vnd.msexcel'], 'json' => [0 => 'application/json', 1 => 'text/json']];
|
||
}
|